-
Notifications
You must be signed in to change notification settings - Fork 13
Switch context by test #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Makes 'scripts' a Python package. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
set -Eeou pipefail | ||
|
||
test "${MDB_BASH_DEBUG:-0}" -eq 1 && set -x | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a small comment for the non bash users? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to put comment to each that line, because that line should be present in all of our bash scripts. |
||
|
||
# The script launches e2e test. Note, that the Operator and necessary resources are deployed | ||
# inside the test | ||
|
@@ -49,5 +50,3 @@ else | |
fi | ||
|
||
title "E2e test ${test} is finished" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eof new line? |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -Eeou pipefail | ||
test "${MDB_BASH_DEBUG:-0}" -eq 1 && set -x | ||
|
||
# shellcheck disable=1091 | ||
source scripts/funcs/errors | ||
|
||
script_name=$(readlink -f "${BASH_SOURCE[0]}") | ||
script_dir=$(dirname "${script_name}") | ||
context_file="${script_dir}/../../.generated/context.export.env" | ||
context_file="$(realpath "${script_dir}/../../.generated/context.export.env")" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this resolves to the physical path and avoids huge paths with multiple /../../ when debugging with -x |
||
|
||
if [[ ! -f ${context_file} ]]; then | ||
fatal "File ${context_file} not found! Make sure to follow this guide to get started: https://wiki.corp.mongodb.com/display/MMS/Setting+up+local+development+and+E2E+testing#SettinguplocaldevelopmentandE2Etesting-GettingStartedGuide(VariantSwitching)" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -Eeou pipefail | ||
test "${MDB_BASH_DEBUG:-0}" -eq 1 && set -x | ||
|
||
usage() { | ||
echo "Switch context by passing the test (evergreen task name or full evergreen task URL)." | ||
echo "If there is more than one variant running given test, then fzf picker is used." | ||
echo "Usage: $0 <test>" | ||
echo " <test> is a task name from .evergreen.yml (e.g. 'e2e_search_community_basic') or a full Evergreen task URL." | ||
} | ||
|
||
source scripts/funcs/errors | ||
|
||
list_pytest_marks() { | ||
rg -g '*.py' -o --no-line-number --no-heading --replace '$1' -m 1 \ | ||
'@(?:pytest\.)?mark\.(e2e_[a-zA-Z0-9_]+)' \ | ||
docker/mongodb-kubernetes-tests | ||
} | ||
|
||
pick_test_by_file_mark_or_task_url() { | ||
if ! test_list="$(list_pytest_marks | sort -u)"; then | ||
echo "Couldn't list pytest marks." | ||
echo "${test_list}" | ||
return 1 | ||
fi | ||
|
||
test=$(fzf --print-query --header-first --with-nth '{2}: {1}' -d ':' --accept-nth 2 \ | ||
--header "Select file or task to find contexts where its used. You can paste full task's evergreen url here" <<< "${test_list}") || true | ||
if [[ -z ${test} ]]; then | ||
echo "Aborted selecting test" | ||
return 1 | ||
fi | ||
|
||
# test may contain one or two lines (file:mark or just mark/url) | ||
number_of_selected_lines=$(wc -l <<< "${test}") | ||
if [[ ${number_of_selected_lines} -eq 2 ]]; then | ||
test="$(tail -n 1 <<< "${test}")" | ||
elif [[ ${number_of_selected_lines} -gt 2 ]]; then | ||
echo "Too many lines selected: ${test}" | ||
return 1 | ||
fi | ||
|
||
echo "${test}" | ||
} | ||
|
||
main() { | ||
test="${1:-}" | ||
|
||
if [[ -z ${test} ]]; then | ||
test=$(pick_test_by_file_mark_or_task_url) | ||
echo "Selected test: ${test}" | ||
fi | ||
|
||
if [[ "${test}" = *spruce.mongodb.com* ]]; then | ||
find_variant_arg="--task-url" | ||
else | ||
find_variant_arg="--task-name" | ||
fi | ||
|
||
if ! contexts=$(scripts/evergreen/run_python.sh scripts/python/find_test_variants.py "${find_variant_arg}" "${test}"); then | ||
echo "Couldn't find any test contexts running test: ${test}" | ||
echo "${contexts}" | ||
exit 1 | ||
fi | ||
|
||
echo "Found contexts that are running test: ${test}" | ||
echo "${contexts}" | ||
|
||
selected_context="${contexts}" | ||
if [[ $(wc -l <<< "${contexts}") -gt 1 ]]; then | ||
if ! selected_context=$(fzf --header "${test} runs in multiple variants/contexts. Select one to switch context into." --header-first --layout=reverse <<< "${contexts}"); then | ||
echo "Aborted selecting context" | ||
exit 1 | ||
fi | ||
fi | ||
|
||
scripts/dev/switch_context.sh "${selected_context}" | ||
} | ||
|
||
main "$@" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Makes 'scripts.python' a Python package. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import os | ||
|
||
import requests | ||
|
||
EVERGREEN_API = "https://evergreen.mongodb.com/api" | ||
|
||
|
||
def get_evergreen_auth_headers() -> dict: | ||
""" | ||
Returns Evergreen API authentication headers using EVERGREEN_USER and EVERGREEN_API_KEY environment variables. | ||
Raises RuntimeError if either variable is missing. | ||
""" | ||
evg_user = os.environ.get("EVERGREEN_USER", "") | ||
api_key = os.environ.get("EVERGREEN_API_KEY", "") | ||
if evg_user == "" or api_key == "": | ||
raise RuntimeError("EVERGREEN_USER and EVERGREEN_API_KEY must be set") | ||
return {"Api-User": evg_user, "Api-Key": api_key} | ||
|
||
|
||
def get_task_details(task_id: str) -> dict: | ||
""" | ||
Fetch task details from Evergreen API for a given task_id. | ||
Returns the JSON response as a dict. | ||
Raises requests.HTTPError if the request fails. | ||
""" | ||
url = f"{EVERGREEN_API}/rest/v2/tasks/{task_id}" | ||
headers = get_evergreen_auth_headers() | ||
response = requests.get(url, headers=headers) | ||
response.raise_for_status() | ||
return response.json() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import argparse | ||
import re | ||
import sys | ||
|
||
import yaml | ||
|
||
from scripts.python.evergreen_api import get_task_details | ||
|
||
|
||
def find_task_variants(evergreen_yml_path: str, task_name: str) -> list[str]: | ||
with open(evergreen_yml_path, "r") as file: | ||
evergreen_data = yaml.safe_load(file) | ||
|
||
task_groups = evergreen_data.get("task_groups", []) | ||
build_variants = evergreen_data.get("buildvariants", []) | ||
|
||
matching_task_groups = [group["name"] for group in task_groups if task_name in group.get("tasks", [])] | ||
|
||
matching_variants: list[str] = [] | ||
for variant in build_variants: | ||
variant_tasks = variant.get("tasks", []) | ||
for task_entry in variant_tasks: | ||
task_name = task_entry.get("name") if isinstance(task_entry, dict) else task_entry | ||
if task_name in matching_task_groups: | ||
matching_variants.append(variant["name"]) | ||
break | ||
|
||
return matching_variants | ||
|
||
|
||
def extract_task_name_from_url(task_url: str) -> str: | ||
match = re.search(r"/task/([^/]+)/", task_url) | ||
if not match: | ||
raise Exception("Could not extract task name from URL") | ||
return match.group(1) | ||
|
||
|
||
def find_task_variant_by_url(task_url: str) -> list[str]: | ||
task_name = extract_task_name_from_url(task_url) | ||
details = get_task_details(task_name) | ||
if "build_variant" not in details: | ||
raise Exception(f'"build_variant" not found in task details: {details}') | ||
|
||
return details["build_variant"] | ||
|
||
|
||
def main() -> None: | ||
parser = argparse.ArgumentParser(description="Find Evergreen build variants for a given task.") | ||
parser.add_argument( | ||
"--evergreen-file", default=".evergreen.yml", help="Path to evergreen.yml (default: evergreen.yml)" | ||
) | ||
parser.add_argument("--task-name", required=False, help="Task name to search for") | ||
parser.add_argument( | ||
"--task-url", | ||
required=False, | ||
help="Full evergreen url to a task, e.g. https://spruce.mongodb.com/task/mongodb_kubernetes_unit_5e913/logs?execution=0", | ||
) | ||
args = parser.parse_args() | ||
|
||
# Ensure exactly one of --task-name or --task-url is provided | ||
if bool(args.task_name) == bool(args.task_url): | ||
parser.error("Exactly one of --task-name or --task-url must be provided.") | ||
|
||
try: | ||
if args.task_name: | ||
variants = find_task_variants(args.evergreen_file, args.task_name) | ||
else: | ||
variants = [find_task_variant_by_url(args.task_url)] | ||
except Exception as e: | ||
print(f"Error: {e}", file=sys.stderr) | ||
sys.exit(1) | ||
|
||
if not variants: | ||
sys.exit(1) | ||
|
||
for variant_name in variants: | ||
print(variant_name) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import io | ||
import os | ||
import sys | ||
|
||
import pytest | ||
|
||
from scripts.python.find_test_variants import ( | ||
extract_task_name_from_url, | ||
find_task_variants, | ||
main, | ||
) | ||
|
||
# This test file uses our real .evergreen.yml, so it might require adjustments if we change the test structure | ||
|
||
|
||
def test_find_task_variants(): | ||
project_dir = os.environ.get("PROJECT_DIR", ".") | ||
evergreen_file = os.path.join(project_dir, ".evergreen.yml") | ||
result = find_task_variants(evergreen_file, "e2e_feature_controls_authentication") | ||
assert sorted(result) == ["e2e_mdb_kind_ubi_cloudqa", "e2e_static_mdb_kind_ubi_cloudqa"] | ||
|
||
result = find_task_variants(evergreen_file, "e2e_sharded_cluster") | ||
assert sorted(result) == [ | ||
"e2e_mdb_kind_ubi_cloudqa", | ||
"e2e_multi_cluster_kind", | ||
"e2e_static_mdb_kind_ubi_cloudqa", | ||
"e2e_static_multi_cluster_kind", | ||
] | ||
|
||
result = find_task_variants(evergreen_file, "") | ||
assert sorted(result) == [] | ||
|
||
result = find_task_variants(evergreen_file, "invalid!") | ||
assert sorted(result) == [] | ||
|
||
|
||
def test_main_output(monkeypatch): | ||
project_dir = os.environ.get("PROJECT_DIR", ".") | ||
evergreen_file = os.path.join(project_dir, ".evergreen.yml") | ||
args = [ | ||
"find_task_variants.py", | ||
"--evergreen-file", | ||
evergreen_file, | ||
"--task-name", | ||
"e2e_feature_controls_authentication", | ||
] | ||
monkeypatch.setattr(sys, "argv", args) | ||
captured = io.StringIO() | ||
monkeypatch.setattr("sys.stdout", captured) | ||
main() | ||
output = captured.getvalue().strip().splitlines() | ||
assert sorted(output) == ["e2e_mdb_kind_ubi_cloudqa", "e2e_static_mdb_kind_ubi_cloudqa"] | ||
|
||
|
||
def test_main_output_no_matches(monkeypatch): | ||
""" | ||
Test that main() exits with code 1 when there are no matching variants. | ||
""" | ||
project_dir = os.environ.get("PROJECT_DIR", ".") | ||
evergreen_file = os.path.join(project_dir, ".evergreen.yml") | ||
args = ["find_task_variants.py", "--evergreen-file", evergreen_file, "--task-name", "nonexistent_task_name"] | ||
monkeypatch.setattr(sys, "argv", args) | ||
captured = io.StringIO() | ||
monkeypatch.setattr("sys.stdout", captured) | ||
with pytest.raises(SystemExit) as e: | ||
main() | ||
assert e.value.code == 1 | ||
assert captured.getvalue().strip() == "" | ||
|
||
|
||
def test_extract_task_name(): | ||
url = "https://spruce.mongodb.com/task/mongodb_kubernetes_e2e_custom_domain_mdb_kind_ubi_cloudqa_e2e_replica_set_patch_ca24d93d7a931f7853a679b4576674cace37bb16_6851672289288f00073de47a_25_06_17_13_01_24/logs?execution=0" | ||
expected = "mongodb_kubernetes_e2e_custom_domain_mdb_kind_ubi_cloudqa_e2e_replica_set_patch_ca24d93d7a931f7853a679b4576674cace37bb16_6851672289288f00073de47a_25_06_17_13_01_24" | ||
assert extract_task_name_from_url(url) == expected | ||
|
||
|
||
def test_extract_task_name_invalid_url(): | ||
invalid_url = ( | ||
"https://spruce.mongodb.com/version/6851672289288f00073de47a/tasks?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC" | ||
) | ||
with pytest.raises(Exception): | ||
extract_task_name_from_url(invalid_url) | ||
|
||
|
||
def test_extract_task_name_empty(): | ||
with pytest.raises(Exception): | ||
extract_task_name_from_url("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a small comment? I actually have no clue what this does :D