Fix Cloud Hypervisor live migration nextest filtering#4563
Conversation
🤖 AI Test SelectionNo test cases were selected for this PR. |
There was a problem hiding this comment.
Pull request overview
This PR updates the Cloud Hypervisor test harness to run live migration tests through the CLI path and to build a shell-safe nextest filter expression while preserving the base filter behavior.
Changes:
- Route live migration execution through the integration CLI test type and apply a nextest
--filterset-based filter. - Add helper logic to construct a combined nextest filterset (base substring + include + exclude).
- Adjust subtest listing and run orchestration to support
cli_test_type/cli_test_filteroverrides.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py | Switch live migration run to the CLI integration path and pass only/skip plus CLI filtering parameters. |
| lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py | Add nextest filterset construction and CLI-path execution for filtered runs, plus supporting subtest prep parameters. |
Run Cloud Hypervisor live migration tests through the CLI path and construct the nextest filter as a shell-safe expression. Preserve the base test filter while excluding the serial-only live migration test from the parallel run.
🤖 AI Test SelectionNo test cases were selected for this PR. |
Replace the custom bash argument quoting helper with shlex.quote and quote the generated bash script as a whole before passing it to bash -lc. Remove the redundant cli_test_type override by running live migration through the integration test type and selecting live migration cases with the nextest filter.
🤖 AI Test SelectionNo test cases were selected for this PR. |
Derive the expected subtest set when running Cloud Hypervisor tests with a nextest CLI filter instead of passing an empty set into result processing. Fail fast if the filter and include/exclude lists select no subtests so a zero-test run cannot pass silently.
🤖 AI Test SelectionNo test cases were selected for this PR. |
| ref: str = "", | ||
| only: Optional[List[str]] = None, | ||
| skip: Optional[List[str]] = None, | ||
| only_test_prefixes: Optional[List[str]] = None, |
There was a problem hiding this comment.
Is this unused? I don't see any caller passing this.
There was a problem hiding this comment.
Good catch. This was leftover from an earlier live-migration filtering approach. The current [cli_test_filter] path now derives the filtered subtest set directly, and no caller passes only_test_prefixes, so I removed it.
Drop the only_test_prefixes parameter from the Cloud Hypervisor test runner because no caller supplies it. Remove the corresponding private prefix-filter branches so the run_tests signature only exposes supported include, exclude, and CLI substring filters.
🤖 AI Test SelectionNo test cases were selected for this PR. |
| test_script_args = f"--test-filter {shlex.quote(nextest_filter)}" | ||
| cmd_args = ( | ||
| f"tests --hypervisor {hypervisor} --{test_type} -- " | ||
| f"{test_script_args}" | ||
| ) |
CH integration tests run under cargo-nextest, which rejects the libtest '-- -- --list' flag ('failed to parse test binary arguments --list: arguments are unsupported', exit 96). _list_subtests therefore returned an empty list and _prepare_filtered_subtests failed with 'No Cloud Hypervisor integration subtests matched filter', even though the tests exist and the nextest run path works.
List subtests with 'cargo nextest list --message-format json' instead, run in the dev container via 'dev_cli.sh shell' and mirroring the integration build env (mshv feature, devcli_testenv cfg). The return value stays the same List[str] of '<module>::<test>' names, so the runbook-driven include/exclude (only/skip/cli_test_filter), the no-match validation, and per-subtest result tracking are unchanged.
🤖 AI Test SelectionNo test cases were selected for this PR. |
| @staticmethod | ||
| def _parse_nextest_subtest_list(stdout: str) -> List[str]: | ||
| # "cargo nextest list --message-format json" prints a JSON document with | ||
| # a "rust-suites" map; each suite has a "testcases" map keyed by the test | ||
| # name (e.g. "live_migration::test_live_migration_local"). Build progress | ||
| # is written to stderr, so stdout starts at the first '{'. Return the | ||
| # test names to preserve the previous "<module>::<test>" list contract. | ||
| brace = stdout.find("{") | ||
| if brace == -1: | ||
| return [] | ||
| try: | ||
| data = json.loads(stdout[brace:]) | ||
| except json.JSONDecodeError: | ||
| return [] | ||
| subtests: List[str] = [] | ||
| for suite in data.get("rust-suites", {}).values(): | ||
| subtests.extend(suite.get("testcases", {}).keys()) | ||
| return subtests |
The previous fix listed subtests via 'dev_cli.sh shell -- RUSTFLAGS=..(--cfg devcli_testenv).. cargo nextest list'. dev_cli.sh's shell command runs its arguments as 'bash -c \$*', which re-joins them on spaces and drops quoting, so the space inside the RUSTFLAGS value was split: bash set RUSTFLAGS=--cfg and then tried to run 'devcli_testenv' as a command (exit 127, 'devcli_testenv: command not found'). cargo never ran, the subtest list came back empty, and _prepare_filtered_subtests still failed with 'No subtests matched filter'.
Use the space-free 'RUSTFLAGS=--cfg=devcli_testenv' form so it survives the bash -c hand-off, and make the JSON parser scan each '{' for the nextest object with a 'rust-suites' map, since dev_cli.sh interleaves docker pull and cargo build output on the same stream.
🤖 AI Test SelectionNo test cases were selected for this PR. |
| def _list_subtests(self, hypervisor: str, test_type: str) -> List[str]: | ||
| cmd_args = f"tests --hypervisor {hypervisor} --{test_type} -- -- --list" | ||
| # Cloud Hypervisor's integration tests run under cargo-nextest, which | ||
| # rejects the libtest "-- -- --list" flag ("failed to parse test binary | ||
| # arguments `--list`: arguments are unsupported"). Enumerate the subtests | ||
| # with "cargo nextest list" instead. The return value is still the same | ||
| # List[str] of "<module>::<test>" names, so the runbook-driven | ||
| # include/exclude (only/skip/cli_test_filter) and per-subtest result |
… of failing Cloud Hypervisor gates every live migration integration test behind cfg(not(feature = "mshv")). The mshv integration build (cargo nextest ... --features mshv) compiles them out, so _list_subtests correctly returns no live_migration tests and the "live_migration" substring filter matches nothing. _prepare_filtered_subtests previously called fail() in that case, turning a legitimately-empty selection into a hard AssertionError. This made verify_cloud_hypervisor_live_migration_tests fail on mshv even though the tests do not exist in that build (they had been silently passing before the subtest-list assertion was added). Raise SkippedException instead so an unavailable test family (live migration on mshv, or an include/exclude filter that removes all matches) is reported as a clean skip per LISA conventions. KVM builds (no mshv feature) still compile these tests, so the filter matches and they run normally.
🤖 AI Test SelectionNo test cases were selected for this PR. |
| filterset = f"test(~{self._escape_nextest_filter_matcher(base_filter)})" | ||
|
|
||
| if only: | ||
| included_tests = "|".join( | ||
| f"test(={self._escape_nextest_filter_matcher(test_name)})" | ||
| for test_name in only | ||
| ) | ||
| filterset = f"({filterset}&({included_tests}))" | ||
|
|
||
| if skip: | ||
| for skipped_test in skip: | ||
| skipped_filter = self._escape_nextest_filter_matcher(skipped_test) | ||
| filterset = f"{filterset}-test(={skipped_filter})" | ||
|
|
| filtered_subtests = [ | ||
| subtest for subtest in subtests if cli_test_filter in subtest | ||
| ] | ||
| if only is not None: | ||
| filtered_subtests = [ | ||
| subtest for subtest in filtered_subtests if subtest in only | ||
| ] | ||
| if skip is not None: | ||
| filtered_subtests = [ | ||
| subtest for subtest in filtered_subtests if subtest not in skip | ||
| ] |
| @@ -1380,7 +1480,27 @@ def _cache_vmm_version(self) -> None: | |||
| self._log.debug(f"Could not cache VMM version: {e}") | |||
|
|
|||
| def _list_subtests(self, hypervisor: str, test_type: str) -> List[str]: | |||
Run Cloud Hypervisor live migration tests through the CLI path and construct the nextest filter as a shell-safe expression. Preserve the base test filter while excluding the serial-only live migration test from the parallel run.
Description
Related Issue
Type of Change
Checklist
Test Validation
Key Test Cases:
Impacted LISA Features:
Tested Azure Marketplace Images:
Test Results