xref: /aosp_15_r20/tools/asuite/atest/coverage/coverage_unittest.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"""Unit tests for coverage."""
18
19# pylint: disable=invalid-name
20
21from pathlib import PosixPath
22import unittest
23from unittest import mock
24from atest import atest_utils
25from atest import constants
26from atest import module_info
27from atest.coverage import coverage
28from atest.test_finders import test_info
29
30
31class DeduceCodeUnderTestUnittests(unittest.TestCase):
32  """Tests for _deduce_code_under_test."""
33
34  def test_code_under_test_is_defined_return_modules_in_code_under_test(self):
35    mod_info = create_module_info([
36        module(
37            name='test1',
38            dependencies=['dep1', 'dep2'],
39            code_under_test=['dep1'],
40        ),
41        module(name='dep1', dependencies=['dep1dep1', 'dep1dep2']),
42        module(name='dep1dep1'),
43        module(name='dep1dep2', dependencies=['dep1dep2dep']),
44        module(name='dep1dep2dep'),
45        module(name='dep2'),
46    ])
47
48    self.assertEqual(
49        coverage._deduce_code_under_test([create_test_info('test1')], mod_info),
50        {'dep1'},
51    )
52
53  def test_code_under_test_not_defined_return_all_modules_from_one_test(self):
54    mod_info = create_module_info([
55        module(name='test1', dependencies=['dep1', 'dep2']),
56        module(name='dep1', dependencies=['dep1dep1', 'dep1dep2']),
57        module(name='dep1dep1'),
58        module(name='dep1dep2', dependencies=['dep1dep2dep']),
59        module(name='dep1dep2dep'),
60        module(name='dep2'),
61        module(name='shouldnotappear'),
62    ])
63
64    self.assertEqual(
65        coverage._deduce_code_under_test([create_test_info('test1')], mod_info),
66        {
67            'test1',
68            'dep1',
69            'dep2',
70            'dep1dep1',
71            'dep1dep2',
72            'dep1dep2dep',
73        },
74    )
75
76  def test_code_under_test_not_defined_return_all_modules_from_all_tests(self):
77    mod_info = create_module_info([
78        module(name='test1', dependencies=['testlib', 'test1dep']),
79        module(name='test2', dependencies=['testlib', 'test2dep']),
80        module(name='testlib', dependencies=['testlibdep']),
81        module(name='testlibdep'),
82        module(name='test1dep'),
83        module(name='test2dep'),
84        module(name='shouldnotappear'),
85    ])
86
87    self.assertEqual(
88        coverage._deduce_code_under_test(
89            [create_test_info('test1'), create_test_info('test2')], mod_info
90        ),
91        {'test1', 'test2', 'testlib', 'testlibdep', 'test1dep', 'test2dep'},
92    )
93
94
95class CollectJavaReportJarsUnittests(unittest.TestCase):
96  """Test cases for _collect_java_report_jars."""
97
98  @mock.patch.object(
99      atest_utils,
100      'get_build_out_dir',
101      return_value=PosixPath('/out/soong/.intermediates'),
102  )
103  @mock.patch.object(
104      PosixPath,
105      'rglob',
106      return_value=[
107          '/out/soong/.intermediates/path/to/java_lib/variant-name/jacoco-report-classes/java_lib.jar'
108      ],
109  )
110  def test_java_lib(self, _rglob, _get_build_out_dir):
111    code_under_test = {'java_lib'}
112    mod_info = create_module_info([
113        module(name='java_lib', path='path/to'),
114    ])
115
116    self.assertEqual(
117        coverage._collect_java_report_jars(code_under_test, mod_info, False),
118        {
119            'java_lib': [
120                '/out/soong/.intermediates/path/to/java_lib/variant-name/jacoco-report-classes/java_lib.jar'
121            ]
122        },
123    )
124
125  def test_host_test_includes_installed(self):
126    code_under_test = {'java_host_test'}
127    mod_info = create_module_info([
128        module(
129            name='java_host_test',
130            installed=[
131                '/path/to/out/host/java_host_test.jar',
132                '/path/to/out/host/java_host_test.config',
133            ],
134        ),
135    ])
136
137    self.assertEqual(
138        coverage._collect_java_report_jars(code_under_test, mod_info, True),
139        {'java_host_test': ['/path/to/out/host/java_host_test.jar']},
140    )
141
142
143class CollectNativeReportBinariesUnittests(unittest.TestCase):
144  """Test cases for _collect_native_report_binaries."""
145
146  @mock.patch.object(
147      atest_utils,
148      'get_build_out_dir',
149      return_value=PosixPath('/out/soong/.intermediates'),
150  )
151  @mock.patch.object(PosixPath, 'glob')
152  @mock.patch.object(
153      coverage,
154      '_strip_irrelevant_objects',
155      return_value={
156          '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
157      },
158  )
159  def test_native_binary(self, _strip_irrelevant_objects, _glob, _get_build_out_dir):
160    _glob.return_value = [
161        PosixPath(
162            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
163        )
164    ]
165    code_under_test = {'native_bin'}
166    mod_info = create_module_info([
167        module(name='native_bin', path='path/to'),
168    ])
169
170    self.assertEqual(
171        coverage._collect_native_report_binaries(
172            code_under_test, mod_info, False
173        ),
174        {
175            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
176        },
177    )
178
179  @mock.patch.object(
180      atest_utils,
181      'get_build_out_dir',
182      return_value=PosixPath('/out/soong/.intermediates'),
183  )
184  @mock.patch.object(PosixPath, 'glob')
185  @mock.patch.object(
186      coverage,
187      '_strip_irrelevant_objects',
188      return_value={
189          '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
190      },
191  )
192  def test_skip_rsp_and_d_and_toc_files(self, _strip_irrelevant_objects, _glob, _get_build_out_dir):
193    _glob.return_value = [
194        PosixPath(
195            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
196        ),
197        PosixPath(
198            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin.rsp'
199        ),
200        PosixPath(
201            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin.d'
202        ),
203        PosixPath(
204            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin.toc'
205        ),
206    ]
207    code_under_test = {'native_bin'}
208    mod_info = create_module_info([
209        module(name='native_bin', path='path/to'),
210    ])
211
212    self.assertEqual(
213        coverage._collect_native_report_binaries(
214            code_under_test, mod_info, False
215        ),
216        {
217            '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
218        },
219    )
220
221  @mock.patch.object(
222      coverage,
223      '_strip_irrelevant_objects',
224      return_value={
225          '/out/host/nativetests/native_host_test'
226      },
227  )
228  def test_host_test_includes_installed(self, _strip_irrelevant_objects):
229    code_under_test = {'native_host_test'}
230    mod_info = create_module_info([
231        module(
232            name='native_host_test',
233            installed=['/out/host/nativetests/native_host_test'],
234            classes=[constants.MODULE_CLASS_NATIVE_TESTS],
235        ),
236    ])
237
238    self.assertEqual(
239        coverage._collect_native_report_binaries(
240            code_under_test, mod_info, True
241        ),
242        {'/out/host/nativetests/native_host_test'},
243    )
244
245
246class GenerateCoverageReportUnittests(unittest.TestCase):
247  """Tests for the code-under-test feature."""
248
249  @mock.patch.object(coverage, '_collect_java_report_jars', return_value={})
250  @mock.patch.object(
251      coverage, '_collect_native_report_binaries', return_value=set()
252  )
253  def test_generate_report_for_code_under_test_passed_in_from_atest(
254      self, _collect_native, _collect_java
255  ):
256    test_infos = [create_test_info('test')]
257    mod_info = create_module_info([
258        module(name='test', dependencies=['lib1', 'lib2']),
259        module(name='lib1'),
260        module(name='lib2', dependencies=['lib2dep']),
261        module(name='lib2dep'),
262    ])
263    code_under_test = ['lib1', 'lib2']
264
265    coverage.generate_coverage_report(
266        '/tmp/results_dir', test_infos, mod_info, True, code_under_test
267    )
268
269    _collect_java.assert_called_with(code_under_test, mod_info, True)
270    _collect_native.assert_called_with(code_under_test, mod_info, True)
271
272  @mock.patch.object(coverage, '_collect_java_report_jars', return_value={})
273  @mock.patch.object(
274      coverage, '_collect_native_report_binaries', return_value=set()
275  )
276  def test_generate_report_for_modules_get_from_deduce_code_under_test(
277      self, _collect_native, _collect_java
278  ):
279    test_infos = [create_test_info('test')]
280    mod_info = create_module_info([
281        module(name='test', dependencies=['lib1', 'lib2']),
282        module(name='lib1'),
283        module(name='lib2', dependencies=['lib2dep']),
284        module(name='lib2dep'),
285        module(name='not_a_dep'),
286    ])
287
288    coverage.generate_coverage_report(
289        '/tmp/results_dir', test_infos, mod_info, False, []
290    )
291
292    expected_code_under_test = {'test', 'lib1', 'lib2', 'lib2dep'}
293    _collect_java.assert_called_with(expected_code_under_test, mod_info, False)
294    _collect_native.assert_called_with(
295        expected_code_under_test, mod_info, False
296    )
297
298
299def create_module_info(modules=None):
300  """Wrapper function for creating module_info.ModuleInfo."""
301  name_to_module_info = {}
302  modules = modules or []
303
304  for m in modules:
305    name_to_module_info[m['module_name']] = m
306
307  return module_info.load_from_dict(name_to_module_info)
308
309
310# pylint: disable=too-many-arguments
311def module(
312    name=None,
313    path=None,
314    installed=None,
315    classes=None,
316    auto_test_config=None,
317    test_config=None,
318    shared_libs=None,
319    dependencies=None,
320    runtime_dependencies=None,
321    data=None,
322    data_dependencies=None,
323    compatibility_suites=None,
324    host_dependencies=None,
325    srcs=None,
326    supported_variants=None,
327    code_under_test=None,
328):
329  name = name or 'libhello'
330
331  m = {}
332
333  m['module_name'] = name
334  m['class'] = classes or []
335  m['path'] = [path or '']
336  m['installed'] = installed or []
337  m['is_unit_test'] = 'false'
338  m['auto_test_config'] = auto_test_config or []
339  m['test_config'] = test_config or []
340  m['shared_libs'] = shared_libs or []
341  m['runtime_dependencies'] = runtime_dependencies or []
342  m['dependencies'] = dependencies or []
343  m['data'] = data or []
344  m['data_dependencies'] = data_dependencies or []
345  m['compatibility_suites'] = compatibility_suites or []
346  m['host_dependencies'] = host_dependencies or []
347  m['srcs'] = srcs or []
348  m['supported_variants'] = supported_variants or []
349  m['code_under_test'] = code_under_test or []
350  return m
351
352
353def create_test_info(name='HelloWorldTest'):
354  """Helper function for creating test_info.TestInfo."""
355  return test_info.TestInfo(name, 'AtestTradefedRunner', set())
356
357
358if __name__ == '__main__':
359  unittest.main()
360