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