diff --git a/docs/results.md b/docs/results.md index 0959b63..6c6420b 100644 --- a/docs/results.md +++ b/docs/results.md @@ -58,6 +58,16 @@ Example: kci-dev results tests --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 ``` +### test + +Obtains a single test result. + +Example: + +```sh +kci-dev results test --id 'maestro:67d3e293f378f0c5986d3309' --download-logs --json +``` + ## Common parameters ### --origin diff --git a/kcidev/libs/dashboard.py b/kcidev/libs/dashboard.py index 90fe98d..6128997 100644 --- a/kcidev/libs/dashboard.py +++ b/kcidev/libs/dashboard.py @@ -91,6 +91,11 @@ def dashboard_fetch_tests(origin, giturl, branch, commit, arch): return dashboard_api_fetch(endpoint, params) +def dashboard_fetch_test(test_id): + endpoint = f"test/{test_id}" + return dashboard_api_fetch(endpoint, {}) + + def dashboard_fetch_tree_list(origin): params = { "origin": origin, diff --git a/kcidev/libs/files.py b/kcidev/libs/files.py new file mode 100644 index 0000000..a6c2ed7 --- /dev/null +++ b/kcidev/libs/files.py @@ -0,0 +1,25 @@ +import gzip +import os +import re + +import requests +from libs.common import kci_err + +INVALID_FILE_CHARS = re.compile(r'[\\/:"*?<>|]+') + + +def to_valid_filename(filename): + return INVALID_FILE_CHARS.sub("", filename) + + +def download_logs_to_file(log_url, log_file): + try: + log_gz = requests.get(log_url) + log = gzip.decompress(log_gz.content) + log_file = to_valid_filename(log_file) + with open(log_file, mode="wb") as file: + file.write(log) + log_path = "file://" + os.path.join(os.getcwd(), log_file) + return log_path + except: + kci_err(f"Failed to fetch log {log_url}.") diff --git a/kcidev/subcommands/results/__init__.py b/kcidev/subcommands/results/__init__.py index 17b03b6..fce7019 100644 --- a/kcidev/subcommands/results/__init__.py +++ b/kcidev/subcommands/results/__init__.py @@ -7,12 +7,14 @@ dashboard_fetch_boots, dashboard_fetch_builds, dashboard_fetch_summary, + dashboard_fetch_test, dashboard_fetch_tests, ) from libs.git_repo import set_giturl_branch_commit from subcommands.results.parser import ( cmd_builds, cmd_list_trees, + cmd_single_test, cmd_summary, cmd_tests, ) @@ -63,7 +65,7 @@ def wrapper(*args, **kwargs): return wrapper -def build_and_test_options(func): +def builds_and_tests_options(func): @click.option( "--download-logs", is_flag=True, @@ -90,6 +92,25 @@ def wrapper(*args, **kwargs): return wrapper +def single_build_and_test_options(func): + @click.option( + "--id", + "op_id", + required=True, + help="Pass an id filter to get specific results", + ) + @click.option( + "--download-logs", + is_flag=True, + help="Select desired results action", + ) + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper + + @click.group(help="[Experimental] Get results from the dashboard") def results(): """Commands related to results.""" @@ -121,7 +142,7 @@ def trees(origin, use_json): @results.command() @common_options -@build_and_test_options +@builds_and_tests_options def builds( origin, git_folder, @@ -146,7 +167,7 @@ def builds( @results.command() @common_options -@build_and_test_options +@builds_and_tests_options def boots( origin, git_folder, @@ -171,7 +192,7 @@ def boots( @results.command() @common_options -@build_and_test_options +@builds_and_tests_options def tests( origin, git_folder, @@ -194,5 +215,13 @@ def tests( cmd_tests(data["tests"], commit, download_logs, status, filter, count, use_json) +@results.command() +@single_build_and_test_options +@results_display_options +def test(op_id, download_logs, use_json): + data = dashboard_fetch_test(op_id) + cmd_single_test(data, download_logs, use_json) + + if __name__ == "__main__": main_kcidev() diff --git a/kcidev/subcommands/results/parser.py b/kcidev/subcommands/results/parser.py index 3db83b6..3acbf33 100644 --- a/kcidev/subcommands/results/parser.py +++ b/kcidev/subcommands/results/parser.py @@ -4,6 +4,7 @@ import requests import yaml from libs.dashboard import dashboard_fetch_tree_list +from libs.files import download_logs_to_file from kcidev.libs.common import * @@ -69,11 +70,16 @@ def create_test_json(test, log_path): test_status = "FAIL" else: test_status = f"INCONCLUSIVE (status: {test['status']})" + config_name = "No config available" + if "config" in test: + config_name = test["config"] + elif "config_name" in test: + config_name = test["config_name"] return { "test_path": test["path"], "hardware": test["environment_misc"]["platform"], "compatibles": test.get("environment_compatible", []), - "config": test["config"], + "config": config_name, "arch": test["architecture"], "status": test_status, "start_time": test["start_time"], @@ -251,59 +257,71 @@ def cmd_tests(data, commit, download_logs, status_filter, filter, count, use_jso log_path = test["log_url"] if download_logs: - try: - log_gz = requests.get(test["log_url"]) - log = gzip.decompress(log_gz.content) - log_file = f"{test['misc']['platform']}__{test['path']}__{test['config']}-{test['architecture']}-{test['compiler']}-{commit}.log" - with open(log_file, mode="wb") as file: - file.write(log) - log_path = "file://" + os.path.join(os.getcwd(), log_file) - except: - kci_err(f"Failed to fetch log {test['log_url']}.") - pass + log_file = f"{test['misc']['platform']}__{test['path']}__{test['config']}-{test['architecture']}-{test['compiler']}-{commit}.log" + log_path = download_logs_to_file(test["log_url"], log_file) if count: filtered_tests += 1 elif use_json: tests.append(create_test_json(test, log_path)) else: - kci_msg_nonl("- test path: ") - kci_msg_cyan_nonl(test["path"]) - kci_msg("") - - kci_msg_nonl(" hardware: ") - kci_msg_cyan_nonl(test["environment_misc"]["platform"]) - kci_msg("") - - if test["environment_compatible"]: - kci_msg_nonl(" compatibles: ") - kci_msg_cyan_nonl(" | ".join(test["environment_compatible"])) - kci_msg("") - - kci_msg_nonl(" config: ") - kci_msg_cyan_nonl(test["config"]) - kci_msg_nonl(" arch: ") - kci_msg_cyan_nonl(test["architecture"]) - kci_msg_nonl(" compiler: ") - kci_msg_cyan_nonl(test["compiler"]) - kci_msg("") - - kci_msg_nonl(" status:") - if test["status"] == "PASS": - kci_msg_green_nonl("PASS") - elif test["status"] == "FAIL": - kci_msg_red_nonl("FAIL") - else: - kci_msg_yellow_nonl(f"INCONCLUSIVE (status: {test['status']})") - kci_msg("") - - kci_msg(f" log: {log_path}") - kci_msg(f" start time: {test['start_time']}") - kci_msg(f" id: {test['id']}") - kci_msg(f" dashboard: https://dashboard.kernelci.org/test/{test['id']}") - kci_msg("") + print_test(test, log_path) if count and use_json: kci_msg(f'{{"count":{filtered_tests}}}') elif count: kci_msg(filtered_tests) elif use_json: kci_msg(json.dumps(tests)) + + +def print_test(test, log_path): + kci_msg_nonl("- test path: ") + kci_msg_cyan_nonl(test["path"]) + kci_msg("") + + kci_msg_nonl(" hardware: ") + kci_msg_cyan_nonl(test["environment_misc"]["platform"]) + kci_msg("") + + if test["environment_compatible"]: + kci_msg_nonl(" compatibles: ") + kci_msg_cyan_nonl(" | ".join(test["environment_compatible"])) + kci_msg("") + + kci_msg_nonl(" config: ") + if "config" in test: + kci_msg_cyan_nonl(test["config"]) + elif "config_name" in test: + kci_msg_cyan_nonl(test["config_name"]) + else: + kci_msg_cyan_nonl("No config available") + kci_msg_nonl(" arch: ") + kci_msg_cyan_nonl(test["architecture"]) + kci_msg_nonl(" compiler: ") + kci_msg_cyan_nonl(test["compiler"]) + kci_msg("") + + kci_msg_nonl(" status:") + if test["status"] == "PASS": + kci_msg_green_nonl("PASS") + elif test["status"] == "FAIL": + kci_msg_red_nonl("FAIL") + else: + kci_msg_yellow_nonl(f"INCONCLUSIVE (status: {test['status']})") + kci_msg("") + + kci_msg(f" log: {log_path}") + kci_msg(f" start time: {test['start_time']}") + kci_msg(f" id: {test['id']}") + kci_msg(f" dashboard: https://dashboard.kernelci.org/test/{test['id']}") + kci_msg("") + + +def cmd_single_test(test, download_logs, use_json): + log_path = test["log_url"] + if download_logs: + log_file = f"{test['environment_misc']['platform']}__{test['path']}__{test['config_name']}-{test['architecture']}-{test['compiler']}-{test['id']}.log" + log_path = download_logs_to_file(test["log_url"], log_file) + if use_json: + kci_msg(create_test_json(test, log_path)) + else: + print_test(test, log_path)