diff --git a/docs/results.md b/docs/results.md index 4906840..a02605a 100644 --- a/docs/results.md +++ b/docs/results.md @@ -30,7 +30,7 @@ kci-dev results summary --giturl 'https://git.kernel.org/pub/scm/linux/kernel/gi ### builds -List builds. +List builds results. Example: @@ -38,6 +38,26 @@ Example: kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 ``` +### boots + +List boot results. + +Example: + +```sh +kci-dev results boots --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --latest +``` + +### tests + +List test results. + +Example: + +```sh +kci-dev results tests --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 +``` + ## Common parameters ### --origin @@ -74,7 +94,7 @@ kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git ### --status Filter results by the status: "all", "pass", "fail" or "inconclusive". -(available for subcommand `build`) +(available for subcommands `build`, `boots` and `tests`) Example: ```sh @@ -84,13 +104,30 @@ kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git ## --download-logs Automatically download logs for results listed. -(available for subcommand `build`) +(available for subcommands `build`, `boots` and `tests`) Example: ```sh -kci-dev results build --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 --download-logs +kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 --download-logs +``` + +## --filter + +Pass a YAML filter file to customize results. Only supports hardware filtering at the moment. +See filter yaml example below: +(available for subcommands `boots` and `tests`) + +```yaml +hardware: + - radxa,rock2-square + - fsl,imx6q + - dell-latitude-3445-7520c-skyrim ``` +Example: +```sh +kci-dev results boots --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --latest --filter=filter.yaml +``` ### without arguments diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 4ebc46c..2d1a1a4 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -12,13 +12,14 @@ import click import requests +import yaml from kcidev.libs.common import * DASHBOARD_API = "https://dashboard.kernelci.org/api/" -def fetch_from_api(endpoint, params): +def dashboard_api_fetch(endpoint, params): base_url = urllib.parse.urljoin(DASHBOARD_API, endpoint) try: url = "{}?{}".format(base_url, urllib.parse.urlencode(params)) @@ -30,16 +31,48 @@ def fetch_from_api(endpoint, params): return r.json() -def fetch_full_results(origin, giturl, branch, commit): - endpoint = f"tree/{commit}/full" +def dashboard_fetch_summary(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/summary" params = { "origin": origin, "git_url": giturl, "git_branch": branch, - "commit": commit, } - return fetch_from_api(endpoint, params) + return dashboard_api_fetch(endpoint, params) + + +def dashboard_fetch_builds(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/builds" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) + + +def dashboard_fetch_boots(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/boots" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) + + +def dashboard_fetch_tests(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/tests" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) def repository_url_cleaner(url): @@ -56,11 +89,11 @@ def repository_url_cleaner(url): return url_cleaned -def fetch_tree_fast(origin): +def dashboard_fetch_tree_list(origin): params = { "origin": origin, } - return fetch_from_api("tree-fast", params) + return dashboard_api_fetch("tree-fast", params) def is_inside_work_tree(git_folder): @@ -131,7 +164,7 @@ def get_folder_repository(git_folder, branch): def get_latest_commit(origin, giturl, branch): - trees = fetch_tree_fast(origin) + trees = dashboard_fetch_tree_list(origin) for t in trees: if t["git_repository_url"] == giturl and t["git_repository_branch"] == branch: return t["git_commit_hash"] @@ -165,17 +198,17 @@ def sum_inconclusive_results(results): def cmd_summary(data): kci_msg("pass/fail/inconclusive") - - builds = data["buildsSummary"]["builds"] + summary = data["summary"] + builds = summary["builds"]["status"] print_summary("builds", builds["valid"], builds["invalid"], builds["null"]) - boots = data["bootStatusSummary"] + boots = summary["boots"]["status"] inconclusive_boots = sum_inconclusive_results(boots) pass_boots = boots["PASS"] if "PASS" in boots.keys() else 0 fail_boots = boots["FAIL"] if "FAIL" in boots.keys() else 0 print_summary("boots", pass_boots, fail_boots, inconclusive_boots) - tests = data["testStatusSummary"] + tests = summary["tests"]["status"] pass_tests = tests["PASS"] if "PASS" in tests.keys() else 0 fail_tests = tests["FAIL"] if "FAIL" in tests.keys() else 0 inconclusive_tests = sum_inconclusive_results(tests) @@ -183,7 +216,7 @@ def cmd_summary(data): def cmd_list_trees(origin): - trees = fetch_tree_fast(origin) + trees = dashboard_fetch_tree_list(origin) for t in trees: kci_msg_green_nonl(f"- {t['tree_name']}/{t['git_repository_branch']}:\n") kci_msg(f" giturl: {t['git_repository_url']}") @@ -238,6 +271,100 @@ def cmd_builds(data, commit, download_logs, status): kci_msg(f" config_url: {build['config_url']}") kci_msg(f" log: {log_path}") kci_msg(f" id: {build['id']}") + kci_msg(f" dashboard: https://dashboard.kernelci.org/build/{build['id']}") + kci_msg("") + + +def filter_out_by_status(status, filter): + if filter == "all": + return False + + if filter == status.lower(): + return False + + elif filter == "inconclusive" and status in [ + "ERROR", + "SKIP", + "MISS", + "DONE", + "NULL", + ]: + return False + + return True + + +def filter_out_by_hardware(test, filter_data): + # Check if the hardware name is in the list + hardware_list = filter_data["hardware"] + if test["misc"]["platform"] in hardware_list: + return False + + if test["environment_compatible"]: + for compatible in test["environment_compatible"]: + if compatible in hardware_list: + return False + + return True + + +def cmd_tests(data, commit, download_logs, status_filter, filter): + filter_data = yaml.safe_load(filter) if filter else None + + for test in data: + if filter_out_by_status(test["status"], status_filter): + continue + + if filter_data and filter_out_by_hardware(test, filter_data): + continue + + 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 {log_file}).") + pass + + kci_msg_nonl("- test path: ") + kci_msg_cyan_nonl(test["path"]) + kci_msg("") + + kci_msg_nonl(" hardware: ") + kci_msg_cyan_nonl(test["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("") @@ -283,7 +410,7 @@ def wrapper(*args, **kwargs): return wrapper -def build_options(func): +def build_and_test_options(func): @click.option( "--download-logs", is_flag=True, @@ -295,6 +422,11 @@ def build_options(func): help="Status of test result", default="all", ) + @click.option( + "--filter", + type=click.File("r"), + help="Pass filter file for builds, boot and tests results.", + ) @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) @@ -325,7 +457,7 @@ def summary( giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) - data = fetch_full_results(origin, giturl, branch, commit) + data = dashboard_fetch_summary(origin, giturl, branch, commit) cmd_summary(data) @@ -343,18 +475,75 @@ def trees(ctx, origin): @results.command() @common_options -@build_options +@build_and_test_options @click.pass_context def builds( - ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, ): """Display build results.""" giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) - data = fetch_full_results(origin, giturl, branch, commit) + data = dashboard_fetch_builds(origin, giturl, branch, commit) cmd_builds(data, commit, download_logs, status) +@results.command() +@common_options +@build_and_test_options +@click.pass_context +def boots( + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, +): + """Display boot results.""" + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, latest, git_folder + ) + data = dashboard_fetch_boots(origin, giturl, branch, commit) + cmd_tests(data["boots"], commit, download_logs, status, filter) + + +@results.command() +@common_options +@build_and_test_options +@click.pass_context +def tests( + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, +): + """Display test results.""" + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, latest, git_folder + ) + data = dashboard_fetch_tests(origin, giturl, branch, commit) + cmd_tests(data["tests"], commit, download_logs, status, filter) + + if __name__ == "__main__": main_kcidev() diff --git a/kcidev/subcommands/sub.py b/kcidev/subcommands/sub.py deleted file mode 100644 index 7a043d8..0000000 --- a/kcidev/subcommands/sub.py +++ /dev/null @@ -1,53 +0,0 @@ -import click - - -# Main CLI group -@click.group() -def cli(): - """kci-dev: A tool for KernelCI development.""" - pass - - -# Subgroup for 'maestro' -@cli.group() -def maestro(): - """Commands related to maestro.""" - pass - - -# Subcommands under 'maestro' -@maestro.command() -def test_checkout(): - """Test the checkout process.""" - click.echo("Running maestro test-checkout...") - - -@maestro.command() -def test_patch(): - """Test a patch.""" - click.echo("Running maestro test-patch...") - - -# Subgroup for 'results' -@cli.group() -def results(): - """Commands related to results.""" - pass - - -# Subcommands under 'results' -@results.command() -def summary(): - """Display a summary of results.""" - click.echo("Displaying results summary...") - - -@results.command() -def details(): - """Display detailed results.""" - click.echo("Displaying detailed results...") - - -# Entry point -if __name__ == "__main__": - cli()