xref: /aosp_15_r20/tools/asuite/atest/integration_tests/atest_dry_run_diff_tests.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1#!/usr/bin/env python3
2#
3# Copyright 2024, The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A collection of integration test cases for atest."""
18
19import concurrent.futures
20import csv
21import dataclasses
22import functools
23import multiprocessing
24import pathlib
25from typing import Any, Optional
26import atest_integration_test
27
28
29@dataclasses.dataclass
30class _AtestCommandUsage:
31  """A class to hold the atest command and its usage frequency."""
32
33  command: str
34  usage_count: int
35  user_count: int
36
37  @staticmethod
38  def to_json(usage: '_AtestCommandUsage') -> dict[str, Any]:
39    """Converts an _AtestCommandUsage object to a JSON dictionary."""
40    return {
41        'command': usage.command,
42        'usage_count': usage.usage_count,
43        'user_count': usage.user_count,
44    }
45
46  @staticmethod
47  def from_json(json_dict: dict[str, Any]) -> '_AtestCommandUsage':
48    """Creates an _AtestCommandUsage object from a JSON dictionary."""
49    return _AtestCommandUsage(
50        json_dict['command'],
51        json_dict['usage_count'],
52        json_dict['user_count'],
53    )
54
55
56class AtestDryRunDiffTests(atest_integration_test.AtestTestCase):
57  """Tests to compare the atest dry run output between atest prod binary and dev binary."""
58
59  def setUp(self):
60    super().setUp()
61    self.maxDiff = None
62
63  def test_dry_run_output_diff(self):
64    """Tests to compare the atest dry run output between atest prod binary and dev binary."""
65    script = self.create_atest_script()
66    script.add_build_step(self._build_step)
67    script.add_test_step(self._test_step)
68    script.run()
69
70  def _get_atest_command_usages(
71      self, repo_root: str, dry_run_diff_test_cmd_input_file: Optional[str]
72  ) -> list[_AtestCommandUsage]:
73    """Returns the atest command usages for the dry run diff test.
74
75    Returns:
76      A list of _AtestCommandUsage objects.
77    """
78    if not dry_run_diff_test_cmd_input_file:
79      return [
80          _AtestCommandUsage(cmd, -1, -1) for cmd in _default_input_commands
81      ]
82    with (
83        pathlib.Path(repo_root)
84        .joinpath(dry_run_diff_test_cmd_input_file)
85        .open()
86    ) as input_file:
87      reader = csv.reader(input_file)
88      return [_AtestCommandUsage(*row) for row in reader if row and row[0]]
89
90  def _build_step(
91      self,
92      step_in: atest_integration_test.StepInput,
93  ) -> atest_integration_test.StepOutput:
94
95    run_command = lambda use_prod, command_usage: self.run_atest_command(
96        '--dry-run -it ' + command_usage.command,
97        step_in,
98        include_device_serial=False,
99        use_prebuilt_atest_binary=use_prod,
100        pipe_to_stdin='n',
101    )
102    get_prod_result = functools.partial(run_command, True)
103    get_dev_result = functools.partial(run_command, False)
104
105    command_usages = self._get_atest_command_usages(
106        step_in.get_repo_root(),
107        step_in.get_config().dry_run_diff_test_cmd_input_file,
108    )
109
110    with concurrent.futures.ThreadPoolExecutor(
111        max_workers=multiprocessing.cpu_count()
112    ) as executor:
113      # Run the version command with -c to clear the cache by the prod binary.
114      self.run_atest_command(
115          '--version -c',
116          step_in,
117          include_device_serial=False,
118          use_prebuilt_atest_binary=True,
119      )
120      cmd_results_prod = list(executor.map(get_prod_result, command_usages))
121      # Run the version command with -c to clear the cache by the dev binary.
122      self.run_atest_command(
123          '--version -c',
124          step_in,
125          include_device_serial=False,
126          use_prebuilt_atest_binary=False,
127      )
128      cmd_results_dev = list(executor.map(get_dev_result, command_usages))
129
130    step_out = self.create_step_output()
131    step_out.set_snapshot_include_paths([])
132    step_out.add_snapshot_obj(
133        'usages', list(map(_AtestCommandUsage.to_json, command_usages))
134    )
135    step_out.add_snapshot_obj(
136        'returncode_prod',
137        list(map(lambda result: result.get_returncode(), cmd_results_prod)),
138    )
139    step_out.add_snapshot_obj(
140        'returncode_dev',
141        list(map(lambda result: result.get_returncode(), cmd_results_dev)),
142    )
143    step_out.add_snapshot_obj(
144        'elapsed_time_prod',
145        list(map(lambda result: result.get_elapsed_time(), cmd_results_prod)),
146    )
147    step_out.add_snapshot_obj(
148        'elapsed_time_dev',
149        list(map(lambda result: result.get_elapsed_time(), cmd_results_dev)),
150    )
151    step_out.add_snapshot_obj(
152        'runner_cmd_prod',
153        list(
154            map(
155                lambda result: result.get_atest_log_values_from_prefix(
156                    atest_integration_test.DRY_RUN_COMMAND_LOG_PREFIX
157                ),
158                cmd_results_prod,
159            )
160        ),
161    )
162    step_out.add_snapshot_obj(
163        'runner_cmd_dev',
164        list(
165            map(
166                lambda result: result.get_atest_log_values_from_prefix(
167                    atest_integration_test.DRY_RUN_COMMAND_LOG_PREFIX
168                ),
169                cmd_results_dev,
170            )
171        ),
172    )
173
174    return step_out
175
176  def _test_step(self, step_in: atest_integration_test.StepInput) -> None:
177    usages = list(map(_AtestCommandUsage.from_json, step_in.get_obj('usages')))
178    returncode_prod = step_in.get_obj('returncode_prod')
179    returncode_dev = step_in.get_obj('returncode_dev')
180    elapsed_time_prod = step_in.get_obj('elapsed_time_prod')
181    elapsed_time_dev = step_in.get_obj('elapsed_time_dev')
182    runner_cmd_prod = step_in.get_obj('runner_cmd_prod')
183    runner_cmd_dev = step_in.get_obj('runner_cmd_dev')
184
185    for idx in range(len(usages)):
186      impact_str = (
187          'Potential'
188          f' impacted number of users: {usages[idx].user_count}, number of'
189          f' invocations: {usages[idx].usage_count}.'
190      )
191      with self.subTest(name=f'{usages[idx].command}_returncode'):
192        self.assertEqual(
193            returncode_prod[idx],
194            returncode_dev[idx],
195            f'Return code mismatch for command: {usages[idx].command}. Prod:'
196            f' {returncode_prod[idx]} Dev: {returncode_dev[idx]}. {impact_str}',
197        )
198      with self.subTest(name=f'{usages[idx].command}_elapsed_time'):
199        self.assertAlmostEqual(
200            elapsed_time_prod[idx],
201            elapsed_time_dev[idx],
202            delta=12,
203            msg=(
204                f'Elapsed time mismatch for command: {usages[idx].command}.'
205                f' Prod: {elapsed_time_prod[idx]} Dev:'
206                f' {elapsed_time_dev[idx]} {impact_str}'
207            ),
208        )
209      with self.subTest(
210          name=f'{usages[idx].command}_runner_cmd_has_same_elements'
211      ):
212        self.assertEqual(
213            len(runner_cmd_prod[idx]),
214            len(runner_cmd_dev[idx]),
215            'Nummber of runner commands mismatch for command:'
216            ' {usages[idx].command}.',
217        )
218
219        for cmd_idx in range(len(runner_cmd_prod[idx])):
220          sanitized_runner_cmd_prod = (
221              atest_integration_test.sanitize_runner_command(runner_cmd_prod[idx][cmd_idx])
222          )
223          sanitized_runner_cmd_dev = (
224              atest_integration_test.sanitize_runner_command(runner_cmd_dev[idx][cmd_idx])
225          )
226          self.assertEqual(
227              set(sanitized_runner_cmd_prod.split(' ')),
228              set(sanitized_runner_cmd_dev.split(' ')),
229              'Runner command mismatch for command:'
230              f' {usages[idx].command}.\nProd:\n'
231              f' {sanitized_runner_cmd_prod}\nDev:\n{sanitized_runner_cmd_dev}\n'
232              f' {impact_str}',
233          )
234
235
236# A copy of the list of atest commands tested in the command verification tests.
237_default_input_commands = [
238    'AnimatorTest',
239    'CtsAnimationTestCases:AnimatorTest',
240    'CtsSampleDeviceTestCases:android.sample.cts',
241    'CtsAnimationTestCases CtsSampleDeviceTestCases',
242    'HelloWorldTests',
243    'android.animation.cts',
244    'android.sample.cts.SampleDeviceReportLogTest',
245    'android.sample.cts.SampleDeviceTest#testSharedPreferences',
246    'hello_world_test',
247    'native-benchmark',
248    'platform_testing/tests/example/native',
249    'platform_testing/tests/example/native/Android.bp',
250    'tools/tradefederation/core/res/config/native-benchmark.xml',
251    'QuickAccessWalletRoboTests',
252    'QuickAccessWalletRoboTests --host',
253    'CtsWifiAwareTestCases',
254    'pts-bot:PAN/GN/MISC/UUID/BV-01-C',
255    'TeeUIUtilsTest',
256    'android.security.cts.PermissionMemoryFootprintTest',
257    'CtsSampleDeviceTestCases:SampleDeviceTest#testSharedPreferences',
258    'CtsSampleDeviceTestCases:android.sample.cts.SampleDeviceReportLogTest',
259    (
260        'PerInstance/CameraHidlTest#'
261        'configureInjectionStreamsAvailableOutputs/0_internal_0'
262    ),
263    (
264        'VtsHalCameraProviderV2_4TargetTest:PerInstance/'
265        'CameraHidlTest#configureInjectionStreamsAvailableOutputs/'
266        '0_internal_0'
267    ),
268    (
269        'TeeUIUtilsTest#intersectTest,ConvexObjectConstruction,'
270        'ConvexObjectLineIntersection'
271    ),
272    (
273        'CtsSecurityTestCases:android.security.cts.'
274        'ActivityManagerTest#testActivityManager_'
275        'registerUidChangeObserver_allPermission'
276    ),
277    (
278        'cts/tests/tests/security/src/android/security/cts/'
279        'ActivityManagerTest.java#testActivityManager_'
280        'registerUidChangeObserver_allPermission'
281    ),
282    (
283        'cts/tests/tests/security/src/android/security/cts/'
284        'PermissionMemoryFootprintTest.kt#'
285        'checkAppsCantIncreasePermissionSizeAfterCreating'
286    ),
287    (
288        'android.security.cts.PermissionMemoryFootprintTest#'
289        'checkAppsCantIncreasePermissionSizeAfterCreating'
290    ),
291]
292
293if __name__ == '__main__':
294  atest_integration_test.main()
295