diff --git a/launchable/commands/subset.py b/launchable/commands/subset.py index 26202ed04..595a9c426 100644 --- a/launchable/commands/subset.py +++ b/launchable/commands/subset.py @@ -626,8 +626,13 @@ 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 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 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..ef4933a58 100644 --- a/tests/commands/test_subset.py +++ b/tests/commands/test_subset.py @@ -208,6 +208,101 @@ 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_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( + 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", + "--confidence", + "90%", + "--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_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( + 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", + "--confidence", + "90%", + "--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):