From 1317070e48ad6038dc0509d33a13580fc5205bd3 Mon Sep 17 00:00:00 2001 From: Konboi Date: Fri, 21 Nov 2025 15:33:59 +0900 Subject: [PATCH 1/7] [AIENG-279] define a new data class and use it --- launchable/commands/compare/subsets.py | 68 +++++++++++++++++++------- setup.cfg | 1 + 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index c6162f4e0..6c192b537 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -1,8 +1,42 @@ -from typing import List, Tuple, Union +from pathlib import Path +from typing import List, Optional, Sequence, Tuple, Union import click from tabulate import tabulate +try: + # for from 3.7 + from dataclasses import dataclass +except ImportError: + # for 3.6 + from dataclasses import dataclass + + +@dataclass(frozen=True) +class SubsetResultBase: + order: int + name: str + + +class SubsetResults: + def __init__(self, results: Sequence[SubsetResultBase]): + self._results: List[SubsetResultBase] = list(results) + self._index_map = {r.name: r.order for r in self._results} + + @property + def results(self) -> List[SubsetResultBase]: + return self._results + + def get_order(self, name: str) -> Optional[int]: + return self._index_map.get(name) + + @classmethod + def from_file(cls, file_path: Path) -> "SubsetResults": + with open(file_path, "r", encoding="utf-8") as subset_file: + results = subset_file.read().splitlines() + entries = [SubsetResultBase(order=order, name=result) for order, result in enumerate(results, start=1)] + return cls(entries) + @click.command() @click.argument('file_before', type=click.Path(exists=True)) @@ -12,31 +46,29 @@ def subsets(file_before, file_after): Compare two subset files and display changes in test order positions """ - # Read files and map test paths to their indices - with open(file_before, 'r') as f: - before_tests = f.read().splitlines() - before_index_map = {test: idx for idx, test in enumerate(before_tests)} - - with open(file_after, 'r') as f: - after_tests = f.read().splitlines() - after_index_map = {test: idx for idx, test in enumerate(after_tests)} + before_subset = SubsetResults.from_file(file_before) + after_subset = SubsetResults.from_file(file_after) # List of tuples representing test order changes (before, after, diff, test) rows: List[Tuple[Union[int, str], Union[int, str], Union[int, str], str]] = [] # Calculate order difference and add each test in file_after to changes - for after_idx, test in enumerate(after_tests): - if test in before_index_map: - before_idx = before_index_map[test] - diff = after_idx - before_idx - rows.append((before_idx + 1, after_idx + 1, diff, test)) + for result in after_subset.results: + test_name = result.name + after_order = result.order + before_order = before_subset.get_order(test_name) + if before_order is not None: + diff = after_order - before_order + rows.append((before_order, after_order, diff, test_name)) else: - rows.append(('-', after_idx + 1, 'NEW', test)) + rows.append(('-', after_order, 'NEW', test_name)) # Add all deleted tests to changes - for before_idx, test in enumerate(before_tests): - if test not in after_index_map: - rows.append((before_idx + 1, '-', 'DELETED', test)) + for result in before_subset.results: + test_name = result.name + before_order = result.order + if after_subset.get_order(test_name) is None: + rows.append((before_order, '-', 'DELETED', test_name)) # Sort changes by the order diff rows.sort(key=lambda x: (0 if isinstance(x[2], str) else 1, x[2])) diff --git a/setup.cfg b/setup.cfg index d86cc5b35..6c018ecf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ packages = find: install_requires = click>=8.0,<8.1;python_version=='3.6' click>=8.1,<8.2;python_version>'3.6' + dataclass requests>=2.25;python_version>='3.6' urllib3>=1.26 junitparser>=4.0.0 From 9f58efc3dee72c041ac03c4d1c72ec4b5576de76 Mon Sep 17 00:00:00 2001 From: Konboi Date: Fri, 21 Nov 2025 16:05:13 +0900 Subject: [PATCH 2/7] [AIENG-279] add options to get results from the server and rename classes --- launchable/commands/compare/subsets.py | 81 ++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index 6c192b537..50b117e53 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -1,9 +1,12 @@ +from http import HTTPStatus from pathlib import Path -from typing import List, Optional, Sequence, Tuple, Union +from typing import Any, List, Optional, Sequence, Tuple, Union import click from tabulate import tabulate +from launchable.utils.launchable_client import LaunchableClient + try: # for from 3.7 from dataclasses import dataclass @@ -18,7 +21,22 @@ class SubsetResultBase: name: str -class SubsetResults: +@dataclass(frozen=True) +class SubsetResult(SubsetResultBase): + density: float + reason: str + duration_sec: float + + @classmethod + def from_inspect_apigxsn(cls, result: dict[str, Any], order: int) -> "SubsetResult": + name = result.get("testPath", []) or [] + density = float(result.get("density") or 0.0) + reason = result.get("reason", "") + duration_sec = float(result.get("duration") or 0.0) / 1000.0 # convert to sec from msec + return cls(order=order, name=name, density=density, reason=reason, duration_sec=duration_sec) + + +class SubsetResultBases: def __init__(self, results: Sequence[SubsetResultBase]): self._results: List[SubsetResultBase] = list(results) self._index_map = {r.name: r.order for r in self._results} @@ -31,23 +49,74 @@ def get_order(self, name: str) -> Optional[int]: return self._index_map.get(name) @classmethod - def from_file(cls, file_path: Path) -> "SubsetResults": + def from_file(cls, file_path: Path) -> "SubsetResultBases": with open(file_path, "r", encoding="utf-8") as subset_file: results = subset_file.read().splitlines() entries = [SubsetResultBase(order=order, name=result) for order, result in enumerate(results, start=1)] return cls(entries) +class SubsetResults(SubsetResultBases): + def __init__(self, results: Sequence[SubsetResult]): + super().__init__(results) + + @classmethod + def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": + try: + response = client.request("get", f"subset/{subset_id}") + if response.status_code == HTTPStatus.NOT_FOUND: + raise click.ClickException( + f"Subset {subset_id} not found. Check subset ID and try again." + ) + response.raise_for_status() + except Exception as exc: + client.print_exception_and_recover(exc, "Warning: failed to load subset results") + raise click.ClickException("Failed to load subset results") from exc + + payload = response.json() + order = 1 + results: List[SubsetResult] = [] + entries = (payload.get("testPaths", []) or []) + (payload.get("rest", []) or []) + for entry in entries: + results.append(SubsetResult.from_inspect_api(entry, order)) + order += 1 + return cls(results) + + @click.command() @click.argument('file_before', type=click.Path(exists=True)) @click.argument('file_after', type=click.Path(exists=True)) -def subsets(file_before, file_after): +@click.option( + '--subset-id-before', + 'subset_id_before', + type=int, + help='Subset ID for the first subset to compare', + metavar="SUBSET_ID") +@click.option( + '--subset-id-after', + 'subset_id_after', + type=int, + help='Subset ID for the second subset to compare', + metavar="SUBSET_ID") +def subsets(file_before, file_after, subset_id_before, subset_id_after): """ Compare two subset files and display changes in test order positions """ + from_file = file_before is not None and file_after is not None + from_subset_id = subset_id_before is not None and subset_id_after is not None + + if from_file and from_subset_id: + raise click.ClickException("Specify either both subset files or both subset IDs, not both.") + if not from_file and not from_subset_id: + raise click.ClickException("You must specify either both subset files or both subset IDs.") + + if from_file: + _from_file(file_before=file_before, file_after=file_after) + - before_subset = SubsetResults.from_file(file_before) - after_subset = SubsetResults.from_file(file_after) +def _from_file(file_before: Path, file_after: Path): + before_subset = SubsetResultBases.from_file(file_before) + after_subset = SubsetResultBases.from_file(file_after) # List of tuples representing test order changes (before, after, diff, test) rows: List[Tuple[Union[int, str], Union[int, str], Union[int, str], str]] = [] From 8f1bd1783533c01f644669f0eb277d261dcb706e Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 25 Nov 2025 08:15:26 +0900 Subject: [PATCH 3/7] [AIENG-279] implement from subset ids --- launchable/commands/compare/subsets.py | 73 ++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index 50b117e53..c88519f9e 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -5,6 +5,7 @@ import click from tabulate import tabulate +from build.lib.launchable.testpath import unparse_test_path from launchable.utils.launchable_client import LaunchableClient try: @@ -28,8 +29,9 @@ class SubsetResult(SubsetResultBase): duration_sec: float @classmethod - def from_inspect_apigxsn(cls, result: dict[str, Any], order: int) -> "SubsetResult": - name = result.get("testPath", []) or [] + def from_inspect_api(cls, result: dict[str, Any], order: int) -> "SubsetResult": + test_path = result.get("testPath", []) or [] + name = unparse_test_path(test_path) density = float(result.get("density") or 0.0) reason = result.get("reason", "") duration_sec = float(result.get("duration") or 0.0) / 1000.0 # convert to sec from msec @@ -60,6 +62,10 @@ class SubsetResults(SubsetResultBases): def __init__(self, results: Sequence[SubsetResult]): super().__init__(results) + @property + def results(self) -> List[SubsetResult]: + return self._results + @classmethod def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": try: @@ -98,10 +104,14 @@ def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": type=int, help='Subset ID for the second subset to compare', metavar="SUBSET_ID") -def subsets(file_before, file_after, subset_id_before, subset_id_after): - """ - Compare two subset files and display changes in test order positions - """ +def subsets(context: click.core.Context, file_before, file_after, subset_id_before, subset_id_after): + """Compare subsets sourced from files or remote subset IDs.""" + + if (file_before is not None) ^ (file_after is not None): + raise click.ClickException("Provide both subset files when using file arguments.") + if (subset_id_before is not None) ^ (subset_id_after is not None): + raise click.ClickException("Provide both subset IDs when using --subset-id options.") + from_file = file_before is not None and file_after is not None from_subset_id = subset_id_before is not None and subset_id_after is not None @@ -110,11 +120,56 @@ def subsets(file_before, file_after, subset_id_before, subset_id_after): if not from_file and not from_subset_id: raise click.ClickException("You must specify either both subset files or both subset IDs.") - if from_file: - _from_file(file_before=file_before, file_after=file_after) + if from_subset_id: + client = LaunchableClient(app=context) + _from_subset_ids(client=client, subset_id_before=subset_id_before, subset_id_after=subset_id_after) + return + + _from_files(file_before=file_before, file_after=file_after) + + +def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_after: int): + before_subset = SubsetResults.load(client, subset_id_before) + after_subset = SubsetResults.load(client, subset_id_after) + + # List of tuples representing test order changes + # (Rank, Subset Rank, Test Path, Reason, Density) + rows: List[str, int, str, float] = [] + + # Calculate order difference and add each test in file_after to changes + for result in after_subset.results: + test_name = result.name + after_order = result.order + before_order = before_subset.get_order(test_name) + if before_order is None: + rows.append(('NEW', after_order, test_name, result.reason, result.density)) + else: + diff = after_order - before_order + rank = "±0" + if diff > 0: + rank = "↓" + str(diff) + elif diff < 0: + rank = "↑" + str(-diff) + + rows.append((rank, after_order, test_name, result.reason, result.density)) + + # Add all deleted tests to changes + for result in before_subset.results: + test_name = result.name + before_order = result.order + if after_subset.get_order(test_name) is None: + rows.append(("DELETED", '-', test_name, "", "")) + + # Display results in a tabular format + headers = ["Rank", "Subset Rank", "Test Name", "Reason", "Density"] + tabular_data = [ + (rank, after, test_name, reason, density) + for rank, after, test_name, reason, density in rows + ] + click.echo_via_pager(tabulate(tabular_data, headers=headers, tablefmt="github")) -def _from_file(file_before: Path, file_after: Path): +def _from_files(file_before: Path, file_after: Path): before_subset = SubsetResultBases.from_file(file_before) after_subset = SubsetResultBases.from_file(file_after) From 7b8f0d3543ef1b056e736a233ae9d2d0a8786a46 Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 25 Nov 2025 12:09:56 +0900 Subject: [PATCH 4/7] [AIENG-279] add tests --- tests/commands/compare/test_subsets.py | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/commands/compare/test_subsets.py b/tests/commands/compare/test_subsets.py index df0676bbc..b3c177fd2 100644 --- a/tests/commands/compare/test_subsets.py +++ b/tests/commands/compare/test_subsets.py @@ -1,6 +1,9 @@ import os from unittest import mock +import responses + +from smart_tests.utils.http_client import get_base_url from tests.cli_test_case import CliTestCase @@ -153,3 +156,56 @@ def tearDown(self): os.remove("subset-before.txt") if os.path.exists("subset-after.txt"): os.remove("subset-after.txt") + + @mock.patch.dict(os.environ, {"SMART_TESTS_TOKEN": CliTestCase.smart_tests_token}) + @responses.activate + def test_subsets_subset_ids(self): + responses.add( + responses.GET, + f"{get_base_url()}/intake/organizations/{self.organization}/workspaces/{self.workspace}/subset/100", + json={ + "subsetting": { + "id": 100, + }, + "testPaths": [ + {"testPath": [{"type": "file", "name": "aaa.py"}], "duration": 10, "density": 0.9, "reason": "Changed file: aaa.py"}, # noqa: E501 + {"testPath": [{"type": "file", "name": "bbb.py"}], "duration": 10, "density": 0.8, "reason": "Changed file: bbb.py"} # noqa: E501 + ], + "rest": [ + {"testPath": [{"type": "file", "name": "ccc.py"}], "duration": 10, "density": 0.7, "reason": "Changed file: ccc.py"} # noqa: E501 + ] + }, + status=200 + ) + responses.add( + responses.GET, + f"{get_base_url()}/intake/organizations/{self.organization}/workspaces/{self.workspace}/subset/101", + json={ + "subsetting": { + "id": 101, + }, + "testPaths": [ + {"testPath": [{"type": "file", "name": "ddd.py"}], "duration": 10, "density": 0.9, "reason": "Changed file: ddd.py"}, # noqa: E501 + {"testPath": [{"type": "file", "name": "ccc.py"}], "duration": 10, "density": 0.7, "reason": "Changed file: ccc.py"} # noqa: E501 + ], + "rest": [ + {"testPath": [{"type": "file", "name": "bbb.py"}], "duration": 10, "density": 0.5, "reason": "Changed file: bbb.py"} # noqa: E501 + ] + }, + status=200 + ) + + result = self.cli('compare', 'subsets', + '--subset-id-before', '100', + '--subset-id-after', '101', + mix_stderr=False) + + self.assert_success(result) + expect = """| Rank | Subset Rank | Test Name | Reason | Density | +|---------|---------------|-------------|----------------------|-----------| +| NEW | 1 | file=ddd.py | Changed file: ddd.py | 0.9 | +| ↑1 | 2 | file=ccc.py | Changed file: ccc.py | 0.7 | +| ↓1 | 3 | file=bbb.py | Changed file: bbb.py | 0.5 | +| DELETED | - | file=aaa.py | | | +""" + self.assertEqual(result.stdout, expect) From 7be6d06db96f3569d41f875fd4083b6c2756e944 Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 25 Nov 2025 12:24:52 +0900 Subject: [PATCH 5/7] fix type check --- launchable/commands/compare/subsets.py | 30 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index c88519f9e..6710bc1fa 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -1,6 +1,6 @@ from http import HTTPStatus from pathlib import Path -from typing import Any, List, Optional, Sequence, Tuple, Union +from typing import Any, Generic, List, Optional, Sequence, Tuple, TypeVar, Union import click from tabulate import tabulate @@ -38,33 +38,36 @@ def from_inspect_api(cls, result: dict[str, Any], order: int) -> "SubsetResult": return cls(order=order, name=name, density=density, reason=reason, duration_sec=duration_sec) -class SubsetResultBases: - def __init__(self, results: Sequence[SubsetResultBase]): - self._results: List[SubsetResultBase] = list(results) +TSubsetResult = TypeVar("TSubsetResult", bound="SubsetResultBase") + + +class SubsetResultBases(Generic[TSubsetResult]): + def __init__(self, results: Sequence[TSubsetResult]): + self._results: List[TSubsetResult] = list(results) self._index_map = {r.name: r.order for r in self._results} @property - def results(self) -> List[SubsetResultBase]: + def results(self) -> List[TSubsetResult]: return self._results def get_order(self, name: str) -> Optional[int]: return self._index_map.get(name) - @classmethod - def from_file(cls, file_path: Path) -> "SubsetResultBases": + @staticmethod + def from_file(file_path: Path) -> "SubsetResultBases[SubsetResultBase]": with open(file_path, "r", encoding="utf-8") as subset_file: results = subset_file.read().splitlines() entries = [SubsetResultBase(order=order, name=result) for order, result in enumerate(results, start=1)] - return cls(entries) + return SubsetResultBases(entries) -class SubsetResults(SubsetResultBases): +class SubsetResults(SubsetResultBases[SubsetResult]): def __init__(self, results: Sequence[SubsetResult]): super().__init__(results) @property def results(self) -> List[SubsetResult]: - return self._results + return super().results @classmethod def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": @@ -121,10 +124,15 @@ def subsets(context: click.core.Context, file_before, file_after, subset_id_befo raise click.ClickException("You must specify either both subset files or both subset IDs.") if from_subset_id: + client = LaunchableClient(app=context) + # for type check + assert subset_id_before is not None and subset_id_after is not None _from_subset_ids(client=client, subset_id_before=subset_id_before, subset_id_after=subset_id_after) return + # for type check + assert file_before is not None and file_after is not None _from_files(file_before=file_before, file_after=file_after) @@ -134,7 +142,7 @@ def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_ # List of tuples representing test order changes # (Rank, Subset Rank, Test Path, Reason, Density) - rows: List[str, int, str, float] = [] + rows: List[Tuple[str, Union[int, str], str, str, Union[float, str]]] = [] # Calculate order difference and add each test in file_after to changes for result in after_subset.results: From 0824a30313c9c0ad5dab7f28b82e454fcb7e838a Mon Sep 17 00:00:00 2001 From: Konboi Date: Tue, 25 Nov 2025 14:21:29 +0900 Subject: [PATCH 6/7] fix design --- launchable/commands/compare/subsets.py | 21 +++++++++++++++++++-- tests/commands/compare/test_subsets.py | 18 ++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index 6710bc1fa..476a8ea0b 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -140,12 +140,20 @@ def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_ before_subset = SubsetResults.load(client, subset_id_before) after_subset = SubsetResults.load(client, subset_id_after) + total = 0 + promoted = 0 + demoted = 0 + affected = set() # List of tuples representing test order changes # (Rank, Subset Rank, Test Path, Reason, Density) rows: List[Tuple[str, Union[int, str], str, str, Union[float, str]]] = [] # Calculate order difference and add each test in file_after to changes for result in after_subset.results: + total += 1 + if result.reason.startswith("Changed file: "): + affected.add(result.reason.removeprefix("Changed file: ")) + test_name = result.name after_order = result.order before_order = before_subset.get_order(test_name) @@ -156,8 +164,10 @@ def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_ rank = "±0" if diff > 0: rank = "↓" + str(diff) + demoted += 1 elif diff < 0: rank = "↑" + str(-diff) + promoted += 1 rows.append((rank, after_order, test_name, result.reason, result.density)) @@ -168,13 +178,20 @@ def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_ if after_subset.get_order(test_name) is None: rows.append(("DELETED", '-', test_name, "", "")) + summary = f"""PTS subset change summary: +──────────────────────────────── +-> {total} tests analyzed | {promoted} ↑ promoted | {demoted} ↓ demoted +-> Code files affected: {', '.join(sorted(affected)) if len(affected) < 10 else str(len(affected)) + ' files'} +──────────────────────────────── +""" + # Display results in a tabular format - headers = ["Rank", "Subset Rank", "Test Name", "Reason", "Density"] + headers = ["Δ Rank", "Subset Rank", "Test Name", "Reason", "Density"] tabular_data = [ (rank, after, test_name, reason, density) for rank, after, test_name, reason, density in rows ] - click.echo_via_pager(tabulate(tabular_data, headers=headers, tablefmt="github")) + click.echo_via_pager(summary + "\n" + tabulate(tabular_data, headers=headers, tablefmt="simple")) def _from_files(file_before: Path, file_after: Path): diff --git a/tests/commands/compare/test_subsets.py b/tests/commands/compare/test_subsets.py index b3c177fd2..22c7a200b 100644 --- a/tests/commands/compare/test_subsets.py +++ b/tests/commands/compare/test_subsets.py @@ -201,11 +201,17 @@ def test_subsets_subset_ids(self): mix_stderr=False) self.assert_success(result) - expect = """| Rank | Subset Rank | Test Name | Reason | Density | -|---------|---------------|-------------|----------------------|-----------| -| NEW | 1 | file=ddd.py | Changed file: ddd.py | 0.9 | -| ↑1 | 2 | file=ccc.py | Changed file: ccc.py | 0.7 | -| ↓1 | 3 | file=bbb.py | Changed file: bbb.py | 0.5 | -| DELETED | - | file=aaa.py | | | + expect = """PTS subset change summary: +──────────────────────────────── +-> 3 tests analyzed | 1 ↑ promoted | 1 ↓ demoted +-> Code files affected: bbb.py, ccc.py, ddd.py +──────────────────────────────── + +Δ Rank Subset Rank Test Name Reason Density +-------- ------------- ----------- -------------------- --------- +NEW 1 file=ddd.py Changed file: ddd.py 0.9 +↑1 2 file=ccc.py Changed file: ccc.py 0.7 +↓1 3 file=bbb.py Changed file: bbb.py 0.5 +DELETED - file=aaa.py """ self.assertEqual(result.stdout, expect) From cda6e991a0d244fe4a4a94e01c740a229b3bd771 Mon Sep 17 00:00:00 2001 From: Konboi Date: Mon, 1 Dec 2025 15:57:19 +0900 Subject: [PATCH 7/7] [chore] fix CI errors --- Pipfile | 1 + launchable/commands/compare/subsets.py | 23 +++++++++-------------- setup.cfg | 2 +- tests/commands/compare/test_subsets.py | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index 1490547d6..8d78d0609 100644 --- a/Pipfile +++ b/Pipfile @@ -22,6 +22,7 @@ mypy = "<1.16.0" pre-commit = "*" responses = "*" types-click = "*" +types-dataclasses = {markers = "python_version < '3.7'"} types-pkg_resources = "0.1.3" types-python-dateutil = "*" types-requests = "*" diff --git a/launchable/commands/compare/subsets.py b/launchable/commands/compare/subsets.py index 476a8ea0b..d43e547a1 100644 --- a/launchable/commands/compare/subsets.py +++ b/launchable/commands/compare/subsets.py @@ -1,20 +1,14 @@ +from dataclasses import dataclass from http import HTTPStatus from pathlib import Path -from typing import Any, Generic, List, Optional, Sequence, Tuple, TypeVar, Union +from typing import Any, Dict, Generic, List, Optional, Sequence, Tuple, TypeVar, Union import click from tabulate import tabulate -from build.lib.launchable.testpath import unparse_test_path +from launchable.testpath import unparse_test_path from launchable.utils.launchable_client import LaunchableClient -try: - # for from 3.7 - from dataclasses import dataclass -except ImportError: - # for 3.6 - from dataclasses import dataclass - @dataclass(frozen=True) class SubsetResultBase: @@ -29,7 +23,7 @@ class SubsetResult(SubsetResultBase): duration_sec: float @classmethod - def from_inspect_api(cls, result: dict[str, Any], order: int) -> "SubsetResult": + def from_inspect_api(cls, result: Dict[str, Any], order: int) -> "SubsetResult": test_path = result.get("testPath", []) or [] name = unparse_test_path(test_path) density = float(result.get("density") or 0.0) @@ -93,8 +87,8 @@ def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": @click.command() -@click.argument('file_before', type=click.Path(exists=True)) -@click.argument('file_after', type=click.Path(exists=True)) +@click.argument('file_before', type=click.Path(exists=True), required=False) +@click.argument('file_after', type=click.Path(exists=True), required=False) @click.option( '--subset-id-before', 'subset_id_before', @@ -107,6 +101,7 @@ def load(cls, client: LaunchableClient, subset_id: int) -> "SubsetResults": type=int, help='Subset ID for the second subset to compare', metavar="SUBSET_ID") +@click.pass_context def subsets(context: click.core.Context, file_before, file_after, subset_id_before, subset_id_after): """Compare subsets sourced from files or remote subset IDs.""" @@ -125,7 +120,7 @@ def subsets(context: click.core.Context, file_before, file_after, subset_id_befo if from_subset_id: - client = LaunchableClient(app=context) + client = LaunchableClient(app=context.obj) # for type check assert subset_id_before is not None and subset_id_after is not None _from_subset_ids(client=client, subset_id_before=subset_id_before, subset_id_after=subset_id_after) @@ -152,7 +147,7 @@ def _from_subset_ids(client: LaunchableClient, subset_id_before: int, subset_id_ for result in after_subset.results: total += 1 if result.reason.startswith("Changed file: "): - affected.add(result.reason.removeprefix("Changed file: ")) + affected.add(result.reason[len("Changed file: "):]) test_name = result.name after_order = result.order diff --git a/setup.cfg b/setup.cfg index 6c018ecf1..1c339b8ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ packages = find: install_requires = click>=8.0,<8.1;python_version=='3.6' click>=8.1,<8.2;python_version>'3.6' - dataclass + dataclasses;python_version=='3.6' requests>=2.25;python_version>='3.6' urllib3>=1.26 junitparser>=4.0.0 diff --git a/tests/commands/compare/test_subsets.py b/tests/commands/compare/test_subsets.py index 22c7a200b..4b4723ad3 100644 --- a/tests/commands/compare/test_subsets.py +++ b/tests/commands/compare/test_subsets.py @@ -3,7 +3,7 @@ import responses -from smart_tests.utils.http_client import get_base_url +from launchable.utils.http_client import get_base_url from tests.cli_test_case import CliTestCase @@ -157,7 +157,7 @@ def tearDown(self): if os.path.exists("subset-after.txt"): os.remove("subset-after.txt") - @mock.patch.dict(os.environ, {"SMART_TESTS_TOKEN": CliTestCase.smart_tests_token}) + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) @responses.activate def test_subsets_subset_ids(self): responses.add(