diff --git a/docs/results.md b/docs/results.md index 9c0ee0c..8f69816 100644 --- a/docs/results.md +++ b/docs/results.md @@ -28,6 +28,29 @@ Example: kci-dev results summary --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 ``` +#### --history + +Show commit history with test results for multiple recent commits in a table format. This displays historical data showing pass/fail/inconclusive counts for builds, boots, and tests across recent commits. + +Example: + +```sh +kci-dev results summary --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git' --branch master --history +``` + +Output: +``` ++--------------+-------------------------------------+----------+-----------+-----------------+ +| Commit | Name | Builds | Boots | Tests | ++==============+=====================================+==========+===========+=================+ +| c4dce0c094a8 | spi-fix-v6.16-rc3-58-gc4dce0c094a89 | 47/1/0 | 100/0/47 | 8853/1677/3016 | ++--------------+-------------------------------------+----------+-----------+-----------------+ +| 92ca6c498a5e | v6.16-rc3-57-g92ca6c498a5e6 | 48/0/0 | 130/1/53 | 8856/1456/3111 | ++--------------+-------------------------------------+----------+-----------+-----------------+ +``` + +The format shows pass/fail/inconclusive counts with color coding (green for pass, red for fail, yellow for inconclusive). + ### builds List builds results. diff --git a/kcidev/libs/dashboard.py b/kcidev/libs/dashboard.py index a563301..624ae6a 100644 --- a/kcidev/libs/dashboard.py +++ b/kcidev/libs/dashboard.py @@ -100,6 +100,20 @@ def dashboard_fetch_summary(origin, giturl, branch, commit, arch, use_json): return dashboard_api_fetch(endpoint, params, use_json) +def dashboard_fetch_commits_history(origin, giturl, branch, commit, use_json): + """Fetch commit history data from /commits endpoint""" + endpoint = f"tree/{commit}/commits" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + logging.info(f"Fetching commit history for commit {commit} on {branch} branch") + logging.debug(f"Parameters: origin={origin}, git_url={giturl}") + return dashboard_api_fetch(endpoint, params, use_json) + + def dashboard_fetch_builds( origin, giturl, branch, commit, arch, tree, start_date, end_date, use_json ): diff --git a/kcidev/subcommands/results/__init__.py b/kcidev/subcommands/results/__init__.py index ffc47b6..4415eca 100644 --- a/kcidev/subcommands/results/__init__.py +++ b/kcidev/subcommands/results/__init__.py @@ -9,6 +9,7 @@ dashboard_fetch_boots, dashboard_fetch_build, dashboard_fetch_builds, + dashboard_fetch_commits_history, dashboard_fetch_summary, dashboard_fetch_test, dashboard_fetch_tests, @@ -23,6 +24,7 @@ ) from kcidev.subcommands.results.parser import ( cmd_builds, + cmd_commits_history, cmd_list_trees, cmd_single_build, cmd_single_test, @@ -77,13 +79,24 @@ def results(ctx): @results.command() @common_options -def summary(origin, git_folder, giturl, branch, commit, latest, arch, tree, use_json): +@click.option("--history", is_flag=True, help="Show commit history data") +def summary( + origin, git_folder, giturl, branch, commit, latest, arch, tree, history, use_json +): """Display a summary of results.""" - giturl, branch, commit = set_giturl_branch_commit( - origin, giturl, branch, commit, latest, git_folder - ) - data = dashboard_fetch_summary(origin, giturl, branch, commit, arch, use_json) - cmd_summary(data, use_json) + if history: + # For history, always use latest commit + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, True, git_folder + ) + data = dashboard_fetch_commits_history(origin, giturl, branch, commit, use_json) + cmd_commits_history(data, use_json) + else: + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, latest, git_folder + ) + data = dashboard_fetch_summary(origin, giturl, branch, commit, arch, use_json) + cmd_summary(data, use_json) @results.command() diff --git a/kcidev/subcommands/results/parser.py b/kcidev/subcommands/results/parser.py index f02e03d..17a9f8e 100644 --- a/kcidev/subcommands/results/parser.py +++ b/kcidev/subcommands/results/parser.py @@ -488,3 +488,99 @@ def cmd_hardware_list(data, use_json): kci_msg_cyan(hardware["platform"], nl=False) kci_msg("") kci_msg("") + + +def format_colored_summary(pass_count, fail_count, inconclusive_count): + """Format pass/fail/inconclusive with colors like print_summary""" + import click + + # Format with colors using click.style directly for table display + pass_str = ( + click.style(str(pass_count), fg="green") if pass_count else str(pass_count) + ) + fail_str = click.style(str(fail_count), fg="red") if fail_count else str(fail_count) + inconclusive_str = ( + click.style(str(inconclusive_count), fg="yellow") + if inconclusive_count + else str(inconclusive_count) + ) + + return f"{pass_str}/{fail_str}/{inconclusive_str}" + + +def cmd_commits_history(data, use_json): + """Display commit history data from /commits endpoint""" + logging.info("Displaying commit history") + + if use_json: + kci_msg(json.dumps(data)) + else: + # Handle both list and dict responses + if isinstance(data, list): + commits = data + else: + commits = data.get("commits", []) + + if not commits: + kci_msg("No commits found") + return + + # Format as table with shortened pass/fail/inconclusive format + from tabulate import tabulate + + table_data = [] + + for commit in commits: + # Get summaries + builds = commit.get("builds", {}) + boots = commit.get("boots", {}) + tests = commit.get("tests", {}) + + # Calculate totals in same format as regular summary + builds_pass = builds.get("PASS", 0) + builds_fail = builds.get("FAIL", 0) + builds_inconclusive = ( + builds.get("ERROR", 0) + + builds.get("SKIP", 0) + + builds.get("MISS", 0) + + builds.get("DONE", 0) + + builds.get("NULL", 0) + ) + + boots_pass = boots.get("pass", 0) + boots_fail = boots.get("fail", 0) + boots_inconclusive = ( + boots.get("miss", 0) + + boots.get("error", 0) + + boots.get("null", 0) + + boots.get("skip", 0) + + boots.get("done", 0) + ) + + tests_pass = tests.get("pass", 0) + tests_fail = tests.get("fail", 0) + tests_inconclusive = ( + tests.get("error", 0) + + tests.get("skip", 0) + + tests.get("miss", 0) + + tests.get("done", 0) + + tests.get("null", 0) + ) + + table_data.append( + [ + commit.get("git_commit_hash", "unknown")[:12], + commit.get("git_commit_name", "unknown"), + format_colored_summary( + builds_pass, builds_fail, builds_inconclusive + ), + format_colored_summary(boots_pass, boots_fail, boots_inconclusive), + format_colored_summary(tests_pass, tests_fail, tests_inconclusive), + ] + ) + + headers = ["Commit", "Name", "Builds", "Boots", "Tests"] + # Use click.secho to preserve colors in the table + import click + + click.secho(tabulate(table_data, headers=headers, tablefmt="grid"), color=True) diff --git a/tests/test_kcidev.py b/tests/test_kcidev.py index d5248b0..325610e 100644 --- a/tests/test_kcidev.py +++ b/tests/test_kcidev.py @@ -177,6 +177,39 @@ def test_main(): pass +def test_kcidev_results_summary_history_help(): + """Test the --history flag appears in results summary help""" + command = ["poetry", "run", "kci-dev", "results", "summary", "--help"] + result = run(command, stdout=PIPE, stderr=PIPE, universal_newlines=True) + print("returncode: " + str(result.returncode)) + print("#### stdout ####") + print(result.stdout) + print("#### stderr ####") + print(result.stderr) + assert result.returncode == 0 + assert "--history" in result.stdout + assert "Show commit history data" in result.stdout + + +def test_kcidev_results_summary_history_import(): + """Test that history functionality can be imported without errors""" + from kcidev.libs.dashboard import dashboard_fetch_commits_history + from kcidev.subcommands.results.parser import ( + cmd_commits_history, + format_colored_summary, + ) + + # Test that functions exist and are callable + assert callable(cmd_commits_history) + assert callable(format_colored_summary) + assert callable(dashboard_fetch_commits_history) + + # Test format_colored_summary with sample data + result = format_colored_summary(10, 5, 2) + assert isinstance(result, str) + assert "/" in result # Should contain separators + + def test_clean(): # clean enviroment shutil.rmtree("my-new-repo/")