From cf529de415002d2f3e0a09157cfb182cfa3ef4b7 Mon Sep 17 00:00:00 2001 From: Naoto Ono Date: Wed, 22 Apr 2026 12:48:50 +0900 Subject: [PATCH 1/2] Fix misleading error for empty adaptive subsets --- launchable/commands/subset.py | 7 +++- tests/commands/test_subset.py | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/launchable/commands/subset.py b/launchable/commands/subset.py index 26202ed04..46377960b 100644 --- a/launchable/commands/subset.py +++ b/launchable/commands/subset.py @@ -626,8 +626,11 @@ def run(self): subset_result = self.request_subset() if len(subset_result.subset) == 0: - warn_and_exit_if_fail_fast_mode("Error: no tests found matching the path.") - return + if len(subset_result.rest) == 0: + warn_and_exit_if_fail_fast_mode("Error: no tests found matching the path.") + return + else: + click.echo(click.style("No tests were selected for this code change.", fg="yellow"), err=True) if split: click.echo("subset/{}".format(subset_result.subset_id)) diff --git a/tests/commands/test_subset.py b/tests/commands/test_subset.py index 7781ba71c..adc9c3ebf 100644 --- a/tests/commands/test_subset.py +++ b/tests/commands/test_subset.py @@ -208,6 +208,85 @@ def test_subset_targetless(self): payload = json.loads(gzip.decompress(responses.calls[1].request.body).decode()) self.assertTrue(payload.get('useServerSideOptimizationTarget')) + @responses.activate + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_empty_valid_subset(self): + pipe = "test_aaa.py\ntest_bbb.py\ntest_ccc.py" + responses.replace( + responses.POST, + "{}/intake/organizations/{}/workspaces/{}/subset".format( + get_base_url(), + self.organization, + self.workspace), + json={ + "testPaths": [], + "testRunner": "file", + "rest": [ + [{"type": "file", "name": "test_aaa.py"}], + [{"type": "file", "name": "test_bbb.py"}], + [{"type": "file", "name": "test_ccc.py"}], + ], + "subsettingId": 123, + "summary": { + "subset": {"duration": 0, "candidates": 99, "rate": 0}, + "rest": {"duration": 30, "candidates": 0, "rate": 100} + }, + "isObservation": False, + }, + status=200) + + result = self.cli( + "subset", + "--target", + "30%", + "--session", + self.session, + "file", + input=pipe, + mix_stderr=False) + self.assert_success(result) + self.assertEqual(result.stdout, "") + self.assertIn("No tests were selected for this code change.", result.stderr) + self.assertIn("Launchable created subset 123", result.stderr) + self.assertNotIn("Error: no tests found matching the path.", result.stderr) + + @responses.activate + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_empty_subset_and_rest_is_error_even_if_summary_has_candidates(self): + pipe = "test_aaa.py\ntest_bbb.py\ntest_ccc.py" + responses.replace( + responses.POST, + "{}/intake/organizations/{}/workspaces/{}/subset".format( + get_base_url(), + self.organization, + self.workspace), + json={ + "testPaths": [], + "testRunner": "file", + "rest": [], + "subsettingId": 123, + "summary": { + "subset": {"duration": 0, "candidates": 99, "rate": 0}, + "rest": {"duration": 30, "candidates": 99, "rate": 100} + }, + "isObservation": False, + }, + status=200) + + result = self.cli( + "subset", + "--target", + "30%", + "--session", + self.session, + "file", + input=pipe, + mix_stderr=False) + self.assert_success(result) + self.assertEqual(result.stdout, "") + self.assertIn("Error: no tests found matching the path.", result.stderr) + self.assertNotIn("No tests were selected for this code change.", result.stderr) + @responses.activate @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) def test_subset_goalspec(self): From e499c9155ab80feba3e6afa82207e83b10a477e1 Mon Sep 17 00:00:00 2001 From: Naoto Ono Date: Tue, 28 Apr 2026 20:45:11 +0900 Subject: [PATCH 2/2] Do not throw an error only if adaptive dynamic subset is enabled --- launchable/commands/subset.py | 8 +++++--- tests/commands/test_subset.py | 28 ++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/launchable/commands/subset.py b/launchable/commands/subset.py index 46377960b..595a9c426 100644 --- a/launchable/commands/subset.py +++ b/launchable/commands/subset.py @@ -626,11 +626,13 @@ def run(self): subset_result = self.request_subset() if len(subset_result.subset) == 0: - if len(subset_result.rest) == 0: + if len(subset_result.rest) > 0 and client.is_pts_v2_enabled() and confidence is not None: + # Adaptive Dynamic Subset can return an empty subset when the model + # determines no tests in that suite are relevant to the code change. + click.echo(click.style("No tests were selected for this code change.", fg="yellow"), err=True) + else: warn_and_exit_if_fail_fast_mode("Error: no tests found matching the path.") return - else: - click.echo(click.style("No tests were selected for this code change.", fg="yellow"), err=True) if split: click.echo("subset/{}".format(subset_result.subset_id)) diff --git a/tests/commands/test_subset.py b/tests/commands/test_subset.py index adc9c3ebf..ef4933a58 100644 --- a/tests/commands/test_subset.py +++ b/tests/commands/test_subset.py @@ -210,8 +210,16 @@ def test_subset_targetless(self): @responses.activate @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) - def test_subset_with_empty_valid_subset(self): + def test_confidence_subset_with_empty_valid_subset_when_pts_v2_enabled(self): pipe = "test_aaa.py\ntest_bbb.py\ntest_ccc.py" + responses.replace( + responses.GET, + "{}/intake/organizations/{}/workspaces/{}/state".format( + get_base_url(), + self.organization, + self.workspace), + json={"isFailFastMode": False, "isPtsV2Enabled": True}, + status=200) responses.replace( responses.POST, "{}/intake/organizations/{}/workspaces/{}/subset".format( @@ -237,8 +245,8 @@ def test_subset_with_empty_valid_subset(self): result = self.cli( "subset", - "--target", - "30%", + "--confidence", + "90%", "--session", self.session, "file", @@ -252,8 +260,16 @@ def test_subset_with_empty_valid_subset(self): @responses.activate @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) - def test_subset_with_empty_subset_and_rest_is_error_even_if_summary_has_candidates(self): + def test_confidence_subset_with_empty_subset_and_rest_is_error_even_if_summary_has_candidates(self): pipe = "test_aaa.py\ntest_bbb.py\ntest_ccc.py" + responses.replace( + responses.GET, + "{}/intake/organizations/{}/workspaces/{}/state".format( + get_base_url(), + self.organization, + self.workspace), + json={"isFailFastMode": False, "isPtsV2Enabled": True}, + status=200) responses.replace( responses.POST, "{}/intake/organizations/{}/workspaces/{}/subset".format( @@ -275,8 +291,8 @@ def test_subset_with_empty_subset_and_rest_is_error_even_if_summary_has_candidat result = self.cli( "subset", - "--target", - "30%", + "--confidence", + "90%", "--session", self.session, "file",