From 24a16f0648de8ec8df37093d1ba4a8e146e74697 Mon Sep 17 00:00:00 2001 From: Vivek Yadav Date: Fri, 26 Jun 2026 13:26:17 -0700 Subject: [PATCH 1/4] Fix Cloud Hypervisor live migration nextest filtering 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. --- .../testsuites/cloud_hypervisor/ch_tests.py | 7 +- .../cloud_hypervisor/ch_tests_tool.py | 101 +++++++++++++++++- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py index f621c6bfa5..3c8cded93e 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py @@ -126,14 +126,17 @@ def verify_cloud_hypervisor_live_migration_tests( "ch_live_migration_tests_excluded", ) ch_tests: CloudHypervisorTests = node.tools[CloudHypervisorTests] + # Cloud Hypervisor exposes live migration as integration subtests. ch_tests.run_tests( result, "integration-live-migration", hypervisor, log_path, ref, - include_list, - exclude_list, + only=include_list, + skip=exclude_list, + cli_test_type="integration", + cli_test_filter="live_migration", ) @TestCaseMetadata( diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py index 39efcbcea7..35cfd93fc6 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py @@ -144,18 +144,79 @@ def _sanitize_name(self, s: str) -> str: """Sanitize names for filenames: keep alphanumeric, dot, dash, underscore.""" return re.sub(r"[^A-Za-z0-9_.-]", "_", s) + @staticmethod + def _escape_nextest_filter_matcher(matcher: str) -> str: + return matcher.replace("\\", "\\\\").replace(")", "\\)") + + @staticmethod + def _quote_bash_arg(arg: str) -> str: + if "'" in arg: + fail( + "Unsupported character (') in argument passed to bash. " + "Remove single quotes from filter values before running " + "Cloud Hypervisor tests." + ) + escaped = ( + arg.replace("\\", "\\\\") + .replace('"', '\\"') + .replace("$", "\\$") + .replace("`", "\\`") + ) + return f'"{escaped}"' + + def _build_nextest_filterset( + self, + base_filter: str, + only: Optional[List[str]], + skip: Optional[List[str]], + ) -> str: + 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})" + + return f"--filterset={filterset}" + def _prepare_subtests( self, test_type: str, hypervisor: str, only: Optional[List[str]], skip: Optional[List[str]], + cli_test_type: Optional[str] = None, + only_test_prefixes: Optional[List[str]] = None, ) -> Dict[str, Any]: """Prepare subtests and skip arguments.""" - subtests = self._list_subtests(hypervisor, test_type) + subtests = self._list_subtests(hypervisor, cli_test_type or test_type) # Store the ordered list for diagnostic purposes self._ordered_subtests = subtests.copy() + if only_test_prefixes is not None: + prefixed_subtests = [ + subtest + for subtest in subtests + if any(subtest.startswith(prefix) for prefix in only_test_prefixes) + ] + if not prefixed_subtests: + fail( + f"No Cloud Hypervisor {test_type} subtests matched prefixes " + f"{only_test_prefixes}. Verify the selected Cloud Hypervisor " + "ref contains those integration tests." + ) + if only is None: + only = prefixed_subtests + else: + only = [subtest for subtest in only if subtest in prefixed_subtests] + if only is not None: if not skip: skip = [] @@ -409,16 +470,46 @@ def run_tests( ref: str = "", only: Optional[List[str]] = None, skip: Optional[List[str]] = None, + cli_test_type: Optional[str] = None, + only_test_prefixes: Optional[List[str]] = None, + cli_test_filter: Optional[str] = None, ) -> None: + cli_test_type = cli_test_type or test_type + if ref: self.node.tools[Git].checkout(ref, self.repo_root) - subtests = self._prepare_subtests(test_type, hypervisor, only, skip) + if cli_test_filter: + subtests: Dict[str, Any] = {"subtest_set": set(), "skip_args": ""} + else: + subtests = self._prepare_subtests( + test_type, + hypervisor, + only, + skip, + cli_test_type, + only_test_prefixes, + ) self._configure_environment_if_needed(hypervisor) # Use enhanced diagnostics for better debugging and monitoring skip_args = subtests["skip_args"] - cmd_args = f"tests --hypervisor {hypervisor} --{test_type} -- -- {skip_args}" + if cli_test_filter: + nextest_filter = self._build_nextest_filterset( + cli_test_filter, + only, + skip, + ) + test_script_args = f"--test-filter {self._quote_bash_arg(nextest_filter)}" + cmd_args = ( + f"tests --hypervisor {hypervisor} --{cli_test_type} -- " + f"{test_script_args}" + ) + else: + cmd_args = ( + f"tests --hypervisor {hypervisor} --{cli_test_type} -- -- " + f"{skip_args}" + ) # normalize name so artifacts are predictable (no spaces/colons/slashes) safe_test_type = self._sanitize_name(test_type.replace("-", "_")) test_name = self._sanitize_name(f"ch_{safe_test_type}_{hypervisor}") @@ -1379,8 +1470,8 @@ def _cache_vmm_version(self) -> None: except Exception as e: self._log.debug(f"Could not cache VMM version: {e}") - def _list_subtests(self, hypervisor: str, test_type: str) -> List[str]: - cmd_args = f"tests --hypervisor {hypervisor} --{test_type} -- -- --list" + def _list_subtests(self, hypervisor: str, cli_test_type: str) -> List[str]: + cmd_args = f"tests --hypervisor {hypervisor} --{cli_test_type} -- -- --list" # Use enhanced environment variables for consistency enhanced_env_vars = self.env_vars.copy() enhanced_env_vars.update( From deefd9a3574e39837be65c104880c1eb6431df78 Mon Sep 17 00:00:00 2001 From: Vivek Yadav Date: Mon, 29 Jun 2026 12:44:25 -0700 Subject: [PATCH 2/4] Address Cloud Hypervisor review comments 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. --- .../testsuites/cloud_hypervisor/ch_tests.py | 3 +- .../cloud_hypervisor/ch_tests_tool.py | 39 +++++-------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py index 3c8cded93e..1304bb6be1 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests.py @@ -129,13 +129,12 @@ def verify_cloud_hypervisor_live_migration_tests( # Cloud Hypervisor exposes live migration as integration subtests. ch_tests.run_tests( result, - "integration-live-migration", + "integration", hypervisor, log_path, ref, only=include_list, skip=exclude_list, - cli_test_type="integration", cli_test_filter="live_migration", ) diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py index 35cfd93fc6..515431772b 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py @@ -148,22 +148,6 @@ def _sanitize_name(self, s: str) -> str: def _escape_nextest_filter_matcher(matcher: str) -> str: return matcher.replace("\\", "\\\\").replace(")", "\\)") - @staticmethod - def _quote_bash_arg(arg: str) -> str: - if "'" in arg: - fail( - "Unsupported character (') in argument passed to bash. " - "Remove single quotes from filter values before running " - "Cloud Hypervisor tests." - ) - escaped = ( - arg.replace("\\", "\\\\") - .replace('"', '\\"') - .replace("$", "\\$") - .replace("`", "\\`") - ) - return f'"{escaped}"' - def _build_nextest_filterset( self, base_filter: str, @@ -192,11 +176,10 @@ def _prepare_subtests( hypervisor: str, only: Optional[List[str]], skip: Optional[List[str]], - cli_test_type: Optional[str] = None, only_test_prefixes: Optional[List[str]] = None, ) -> Dict[str, Any]: """Prepare subtests and skip arguments.""" - subtests = self._list_subtests(hypervisor, cli_test_type or test_type) + subtests = self._list_subtests(hypervisor, test_type) # Store the ordered list for diagnostic purposes self._ordered_subtests = subtests.copy() @@ -470,12 +453,9 @@ def run_tests( ref: str = "", only: Optional[List[str]] = None, skip: Optional[List[str]] = None, - cli_test_type: Optional[str] = None, only_test_prefixes: Optional[List[str]] = None, cli_test_filter: Optional[str] = None, ) -> None: - cli_test_type = cli_test_type or test_type - if ref: self.node.tools[Git].checkout(ref, self.repo_root) @@ -487,7 +467,6 @@ def run_tests( hypervisor, only, skip, - cli_test_type, only_test_prefixes, ) self._configure_environment_if_needed(hypervisor) @@ -500,15 +479,14 @@ def run_tests( only, skip, ) - test_script_args = f"--test-filter {self._quote_bash_arg(nextest_filter)}" + test_script_args = f"--test-filter {shlex.quote(nextest_filter)}" cmd_args = ( - f"tests --hypervisor {hypervisor} --{cli_test_type} -- " + f"tests --hypervisor {hypervisor} --{test_type} -- " f"{test_script_args}" ) else: cmd_args = ( - f"tests --hypervisor {hypervisor} --{cli_test_type} -- -- " - f"{skip_args}" + f"tests --hypervisor {hypervisor} --{test_type} -- -- " f"{skip_args}" ) # normalize name so artifacts are predictable (no spaces/colons/slashes) safe_test_type = self._sanitize_name(test_type.replace("-", "_")) @@ -1073,7 +1051,7 @@ def _run_with_enhanced_diagnostics( # Create a single command that runs everything on the remote VM # with proper bash handling - full_cmd = f"""bash -lc ' + script = f""" set -o pipefail # enable core dumps (best-effort) @@ -1297,7 +1275,8 @@ def _run_with_enhanced_diagnostics( fi exit $ec -'""" +""" + full_cmd = f"bash -lc {shlex.quote(script)}" # Best-effort install gdb if not available try: @@ -1470,8 +1449,8 @@ def _cache_vmm_version(self) -> None: except Exception as e: self._log.debug(f"Could not cache VMM version: {e}") - def _list_subtests(self, hypervisor: str, cli_test_type: str) -> List[str]: - cmd_args = f"tests --hypervisor {hypervisor} --{cli_test_type} -- -- --list" + def _list_subtests(self, hypervisor: str, test_type: str) -> List[str]: + cmd_args = f"tests --hypervisor {hypervisor} --{test_type} -- -- --list" # Use enhanced environment variables for consistency enhanced_env_vars = self.env_vars.copy() enhanced_env_vars.update( From b16555455f6c6b589d45cd2f7f392caaf20da272 Mon Sep 17 00:00:00 2001 From: Vivek Yadav Date: Mon, 29 Jun 2026 13:51:04 -0700 Subject: [PATCH 3/4] Track filtered Cloud Hypervisor subtests 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. --- .../cloud_hypervisor/ch_tests_tool.py | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py index 515431772b..0c4450b80d 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py @@ -214,6 +214,47 @@ def _prepare_subtests( return {"subtest_set": set(subtests), "skip_args": skip_args} + def _prepare_filtered_subtests( + self, + test_type: str, + hypervisor: str, + cli_test_filter: str, + only: Optional[List[str]], + skip: Optional[List[str]], + only_test_prefixes: Optional[List[str]] = None, + ) -> Dict[str, Any]: + subtests = self._list_subtests(hypervisor, test_type) + self._ordered_subtests = subtests.copy() + + if only_test_prefixes is not None: + subtests = [ + subtest + for subtest in subtests + if any(subtest.startswith(prefix) for prefix in only_test_prefixes) + ] + + 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 + ] + + if not filtered_subtests: + fail( + f"No Cloud Hypervisor {test_type} subtests matched filter " + f"'{cli_test_filter}'. Verify the selected Cloud Hypervisor ref " + "contains matching tests and review include/exclude filters." + ) + + self._log.debug(f"Final Subtests list to run: {filtered_subtests}") + return {"subtest_set": set(filtered_subtests), "skip_args": ""} + def _configure_environment_if_needed(self, hypervisor: str) -> None: """Configure environment specific settings if needed.""" if isinstance(self.node.os, CBLMariner) and hypervisor == "mshv": @@ -460,7 +501,14 @@ def run_tests( self.node.tools[Git].checkout(ref, self.repo_root) if cli_test_filter: - subtests: Dict[str, Any] = {"subtest_set": set(), "skip_args": ""} + subtests = self._prepare_filtered_subtests( + test_type, + hypervisor, + cli_test_filter, + only, + skip, + only_test_prefixes, + ) else: subtests = self._prepare_subtests( test_type, From 07117d85bedf043b2aca93a9cb8426f008afb86d Mon Sep 17 00:00:00 2001 From: Vivek Yadav Date: Wed, 1 Jul 2026 10:31:55 -0700 Subject: [PATCH 4/4] Remove unused CH test prefix filter 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. --- .../cloud_hypervisor/ch_tests_tool.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py index 0c4450b80d..8f2598e6c5 100644 --- a/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py +++ b/lisa/microsoft/testsuites/cloud_hypervisor/ch_tests_tool.py @@ -176,30 +176,12 @@ def _prepare_subtests( hypervisor: str, only: Optional[List[str]], skip: Optional[List[str]], - only_test_prefixes: Optional[List[str]] = None, ) -> Dict[str, Any]: """Prepare subtests and skip arguments.""" subtests = self._list_subtests(hypervisor, test_type) # Store the ordered list for diagnostic purposes self._ordered_subtests = subtests.copy() - if only_test_prefixes is not None: - prefixed_subtests = [ - subtest - for subtest in subtests - if any(subtest.startswith(prefix) for prefix in only_test_prefixes) - ] - if not prefixed_subtests: - fail( - f"No Cloud Hypervisor {test_type} subtests matched prefixes " - f"{only_test_prefixes}. Verify the selected Cloud Hypervisor " - "ref contains those integration tests." - ) - if only is None: - only = prefixed_subtests - else: - only = [subtest for subtest in only if subtest in prefixed_subtests] - if only is not None: if not skip: skip = [] @@ -221,18 +203,10 @@ def _prepare_filtered_subtests( cli_test_filter: str, only: Optional[List[str]], skip: Optional[List[str]], - only_test_prefixes: Optional[List[str]] = None, ) -> Dict[str, Any]: subtests = self._list_subtests(hypervisor, test_type) self._ordered_subtests = subtests.copy() - if only_test_prefixes is not None: - subtests = [ - subtest - for subtest in subtests - if any(subtest.startswith(prefix) for prefix in only_test_prefixes) - ] - filtered_subtests = [ subtest for subtest in subtests if cli_test_filter in subtest ] @@ -494,7 +468,6 @@ def run_tests( ref: str = "", only: Optional[List[str]] = None, skip: Optional[List[str]] = None, - only_test_prefixes: Optional[List[str]] = None, cli_test_filter: Optional[str] = None, ) -> None: if ref: @@ -507,7 +480,6 @@ def run_tests( cli_test_filter, only, skip, - only_test_prefixes, ) else: subtests = self._prepare_subtests( @@ -515,7 +487,6 @@ def run_tests( hypervisor, only, skip, - only_test_prefixes, ) self._configure_environment_if_needed(hypervisor)