1#!/usr/bin/env python3
2#
3# Copyright 2019, 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"""Unittests for native_module_info."""
18
19import logging
20import os
21import unittest
22from unittest import mock
23
24from aidegen import constant
25from aidegen.lib import common_util
26from aidegen.lib import module_info
27from aidegen.lib import native_module_info
28
29import atest
30
31_PATH_TO_MULT_MODULES_WITH_MULTI_ARCH = 'shared/path/to/be/used2'
32_TESTABLE_MODULES_WITH_SHARED_PATH = [
33    'multiarch', 'multiarch1', 'multiarch2', 'multiarch3', 'multiarch3_32'
34]
35_REBUILD_TARGET1 = '[email protected]'
36_NATIVE_INCLUDES1 = [
37    'frameworks/native/include',
38    'frameworks/native/libs/ui',
39    'out/soong/.intermediates/' + _REBUILD_TARGET1 + '_genc++_headers/gen'
40]
41_CC_NAME_TO_MODULE_INFO = {
42    'multiarch': {
43        'path': [
44            'shared/path/to/be/used2'
45        ],
46        'srcs': [
47            'shared/path/to/be/used2/multiarch.cpp',
48            'out/soong/.intermediates/shared/path/to/be/used2/gen/Iarch.cpp'
49        ],
50        'local_common_flags': {
51            constant.KEY_HEADER: _NATIVE_INCLUDES1
52        },
53        'module_name': 'multiarch'
54    },
55    'multiarch1': {
56        'path': [
57            'shared/path/to/be/used2/arch1'
58        ],
59        'module_name': 'multiarch1'
60    },
61    'multiarch2': {
62        'path': [
63            'shared/path/to/be/used2/arch2'
64        ],
65        'module_name': 'multiarch2'
66    },
67    'multiarch3': {
68        'path': [
69            'shared/path/to/be/used2/arch3'
70        ],
71        'module_name': 'multiarch3'
72    },
73    'multiarch3_32': {
74        'path': [
75            'shared/path/to/be/used2/arch3_32'
76        ],
77        'module_name': 'multiarch3_32'
78    },
79    _REBUILD_TARGET1: {
80        'path': [
81            '/path/to/rebuild'
82        ],
83        'module_name': _REBUILD_TARGET1
84    },
85    'multiarch-eng': {
86        'path': [
87            'shared/path/to/be/used2-eng'
88        ],
89        'srcs': [
90            'shared/path/to/be/used2/multiarch-eng.cpp',
91            'out/soong/.intermediates/shared/path/to/be/used2/gen/Iarch-eng.cpp'
92        ],
93        'module_name': 'multiarch-eng'
94    }
95}
96_CC_MODULE_INFO = {
97    'clang': '${ANDROID_ROOT}/prebuilts/clang/host/linux-x86/bin/clang',
98    'clang++': '${ANDROID_ROOT}/prebuilts/clang/host/linux-x86/bin/clang++',
99    'modules': _CC_NAME_TO_MODULE_INFO
100}
101
102
103# pylint: disable=protected-access
104class NativeModuleInfoUnittests(unittest.TestCase):
105    """Unit tests for module_info.py"""
106
107    @mock.patch.object(
108        native_module_info.NativeModuleInfo, '_load_module_info_file')
109    @mock.patch.object(common_util, 'get_related_paths')
110    def test_get_module_names_in_targets_paths(self, mock_relpath, mock_load):
111        """Test get_module_names_in_targets_paths handling."""
112        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
113        mod_info = native_module_info.NativeModuleInfo()
114        mock_relpath.return_value = (_PATH_TO_MULT_MODULES_WITH_MULTI_ARCH, '')
115        result = mod_info.get_module_names_in_targets_paths(['multiarch'])
116        self.assertEqual(_TESTABLE_MODULES_WITH_SHARED_PATH, result)
117
118    @mock.patch.object(
119        native_module_info.NativeModuleInfo, '_load_module_info_file')
120    def test_get_module_includes_empty(self, mock_load):
121        """Test get_module_includes without include paths."""
122        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
123        mod_info = native_module_info.NativeModuleInfo()
124        result = mod_info.get_module_includes('multiarch1')
125        self.assertEqual(set(), result)
126
127    @mock.patch.object(
128        native_module_info.NativeModuleInfo, '_load_module_info_file')
129    def test_get_module_includes_not_empty(self, mock_load):
130        """Test get_module_includes with include paths."""
131        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
132        mod_info = native_module_info.NativeModuleInfo()
133        result = mod_info.get_module_includes('multiarch')
134        self.assertEqual(set(_NATIVE_INCLUDES1), result)
135
136    @mock.patch.object(logging, 'warning')
137    @mock.patch.object(
138        native_module_info.NativeModuleInfo, '_load_module_info_file')
139    def test_is_module_need_build_without_mod_info(self, mock_load, mock_warn):
140        """Test get_module_includes without module info."""
141        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
142        mod_info = native_module_info.NativeModuleInfo()
143        self.assertFalse(mod_info.is_module_need_build('test_multiarch'))
144        self.assertTrue(mock_warn.called)
145
146    @mock.patch.object(logging, 'warning')
147    @mock.patch.object(
148        native_module_info.NativeModuleInfo, '_load_module_info_file')
149    def test_is_module_need_build_with_mod_info(self, mock_load, mock_warn):
150        """Test get_module_includes with module info."""
151        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
152        mod_info = native_module_info.NativeModuleInfo()
153        self.assertFalse(mod_info.is_module_need_build('multiarch1'))
154        self.assertFalse(mock_warn.called)
155
156    @mock.patch.object(native_module_info.NativeModuleInfo,
157                       '_is_include_need_build')
158    @mock.patch.object(native_module_info.NativeModuleInfo,
159                       '_is_source_need_build')
160    @mock.patch.object(logging, 'warning')
161    @mock.patch.object(
162        native_module_info.NativeModuleInfo, '_load_module_info_file')
163    def test_is_module_need_build_with_src_needs(
164            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
165        """Test get_module_includes with needed build source modules."""
166        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
167        mod_info = native_module_info.NativeModuleInfo()
168        mock_src_need.return_value = True
169        mock_inc_need.return_value = False
170        self.assertTrue(mod_info.is_module_need_build('multiarch'))
171        self.assertFalse(mock_warn.called)
172
173    @mock.patch.object(native_module_info.NativeModuleInfo,
174                       '_is_include_need_build')
175    @mock.patch.object(native_module_info.NativeModuleInfo,
176                       '_is_source_need_build')
177    @mock.patch.object(logging, 'warning')
178    @mock.patch.object(
179        native_module_info.NativeModuleInfo, '_load_module_info_file')
180    def test_is_module_need_build_with_inc_needs(
181            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
182        """Test get_module_includes with needed build include modules."""
183        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
184        mod_info = native_module_info.NativeModuleInfo()
185        mock_src_need.return_value = False
186        mock_inc_need.return_value = True
187        self.assertTrue(mod_info.is_module_need_build('multiarch'))
188        self.assertFalse(mock_warn.called)
189
190    @mock.patch.object(native_module_info.NativeModuleInfo,
191                       '_is_include_need_build')
192    @mock.patch.object(native_module_info.NativeModuleInfo,
193                       '_is_source_need_build')
194    @mock.patch.object(logging, 'warning')
195    @mock.patch.object(
196        native_module_info.NativeModuleInfo, '_load_module_info_file')
197    def test_is_module_need_build_without_needs(
198            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
199        """Test get_module_includes without needs."""
200        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
201        mod_info = native_module_info.NativeModuleInfo()
202        mock_src_need.return_value = False
203        mock_inc_need.return_value = False
204        self.assertFalse(mod_info.is_module_need_build('multiarch1'))
205        self.assertFalse(mock_warn.called)
206
207    @mock.patch.object(os.path, 'isfile')
208    @mock.patch.object(
209        native_module_info.NativeModuleInfo, '_load_module_info_file')
210    def test_is_source_need_build_return_true(self, mock_load, mock_isfile):
211        """Test _is_source_need_build with source paths or not existing."""
212        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
213        mod_info = native_module_info.NativeModuleInfo()
214        mock_isfile.return_value = False
215        self.assertTrue(mod_info._is_source_need_build(
216            _CC_NAME_TO_MODULE_INFO['multiarch']))
217
218    @mock.patch.object(os.path, 'isfile')
219    @mock.patch.object(
220        native_module_info.NativeModuleInfo, '_load_module_info_file')
221    def test_is_source_need_build_return_false(self, mock_load, mock_isfile):
222        """Test _is_source_need_build without source paths or paths exist."""
223        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
224        mod_info = native_module_info.NativeModuleInfo()
225        self.assertFalse(mod_info._is_source_need_build(
226            _CC_NAME_TO_MODULE_INFO['multiarch1']))
227        mock_isfile.return_value = True
228        self.assertFalse(mod_info._is_source_need_build(
229            _CC_NAME_TO_MODULE_INFO['multiarch']))
230
231    @mock.patch.object(os.path, 'isdir')
232    @mock.patch.object(
233        native_module_info.NativeModuleInfo, '_load_module_info_file')
234    def test_is_include_need_build_return_true(self, mock_load, mock_isdir):
235        """Test _is_include_need_build with include paths and not existing."""
236        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
237        mock_isdir.return_value = False
238        mod_info = native_module_info.NativeModuleInfo()
239        self.assertTrue(mod_info._is_include_need_build(
240            _CC_NAME_TO_MODULE_INFO['multiarch']))
241
242    @mock.patch.object(os.path, 'isdir')
243    @mock.patch.object(
244        native_module_info.NativeModuleInfo, '_load_module_info_file')
245    def test_is_include_need_build_return_false(self, mock_load, mock_isdir):
246        """Test _is_include_need_build without include paths or paths exist."""
247        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
248        mod_info = native_module_info.NativeModuleInfo()
249        self.assertFalse(mod_info._is_include_need_build(
250            _CC_NAME_TO_MODULE_INFO['multiarch1']))
251        mock_isdir.return_value = True
252        self.assertFalse(mod_info._is_include_need_build(
253            _CC_NAME_TO_MODULE_INFO['multiarch']))
254        mock_isdir.return_value = True
255        self.assertFalse(mod_info._is_include_need_build(
256            _CC_NAME_TO_MODULE_INFO['multiarch']))
257
258    def test_not_implemented_methods(self):
259        """Test not implemented methods."""
260        mod_info = native_module_info.NativeModuleInfo()
261        target = 'test'
262        with self.assertRaises(NotImplementedError):
263            mod_info.get_testable_modules()
264        with self.assertRaises(NotImplementedError):
265            mod_info.is_testable_module(mock.Mock())
266        with self.assertRaises(NotImplementedError):
267            mod_info.has_test_config(mock.Mock())
268        with self.assertRaises(NotImplementedError):
269            mod_info.get_robolectric_test_name(target)
270        with self.assertRaises(NotImplementedError):
271            mod_info.is_robolectric_test(target)
272        with self.assertRaises(NotImplementedError):
273            mod_info.is_auto_gen_test_config(target)
274        with self.assertRaises(NotImplementedError):
275            mod_info.is_robolectric_module(mock.Mock())
276        with self.assertRaises(NotImplementedError):
277            mod_info.is_native_test(target)
278
279    @mock.patch.object(
280        native_module_info.NativeModuleInfo, '_load_module_info_file')
281    def test_not_implement_methods_check(self, mock_load):
282        """Test for all not implemented methods which should raise error."""
283        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
284        mod_info = native_module_info.NativeModuleInfo()
285        with self.assertRaises(NotImplementedError):
286            suite_name = 'test'
287            test_data = {'a': 'test'}
288            mod_info.is_suite_in_compatibility_suites(suite_name, test_data)
289
290        with self.assertRaises(NotImplementedError):
291            mod_info.get_testable_modules()
292
293        with self.assertRaises(NotImplementedError):
294            test_data = {'a': 'test'}
295            mod_info.is_testable_module(test_data)
296
297        with self.assertRaises(NotImplementedError):
298            test_data = {'a': 'test'}
299            mod_info.has_test_config(test_data)
300
301        with self.assertRaises(NotImplementedError):
302            test_mod = 'mod_a'
303            mod_info.get_robolectric_test_name(test_mod)
304
305        with self.assertRaises(NotImplementedError):
306            test_mod = 'mod_a'
307            mod_info.is_robolectric_test(test_mod)
308
309        with self.assertRaises(NotImplementedError):
310            test_mod = 'a'
311            mod_info.is_auto_gen_test_config(test_mod)
312
313        with self.assertRaises(NotImplementedError):
314            test_data = {'a': 'test'}
315            mod_info.is_robolectric_module(test_data)
316
317        with self.assertRaises(NotImplementedError):
318            test_mod = 'a'
319            mod_info.is_native_test(test_mod)
320
321    @mock.patch.object(atest.module_info.ModuleInfo, '_load_module_info_file')
322    @mock.patch.object(os.path, 'isfile')
323    @mock.patch.object(common_util, 'get_json_dict')
324    @mock.patch.object(module_info.AidegenModuleInfo,
325                       '_discover_mod_file_and_target')
326    @mock.patch.object(common_util, 'get_blueprint_json_path')
327    def test_load_module_info_file(self, mock_get_json, mock_load, mock_dict,
328                                   mock_isfile, mock_base_load):
329        """Test _load_module_info_file."""
330        force_build = True
331        mod_file = '/test/path'
332        mock_get_json.return_value = mod_file
333        native_module_info.NativeModuleInfo(force_build)
334        self.assertTrue(mock_get_json.called_with(
335            constant.BLUEPRINT_CC_JSONFILE_NAME))
336        mock_load.return_value = mod_file, mod_file
337        self.assertTrue(mock_load.called_with(True))
338        mock_dict.return_value = _CC_MODULE_INFO
339        self.assertTrue(mock_dict.called_with(mod_file))
340        self.assertFalse(mock_base_load.called)
341        mock_base_load.reset_mock()
342        mock_get_json.reset_mock()
343        mock_load.reset_mock()
344        mock_dict.reset_mock()
345
346        native_module_info.NativeModuleInfo(force_build, mod_file)
347        self.assertFalse(mock_get_json.called)
348        mock_load.return_value = None, mod_file
349        self.assertTrue(mock_load.called_with(True))
350        self.assertTrue(mock_dict.called_with(mod_file))
351        self.assertFalse(mock_base_load.called)
352        mock_base_load.reset_mock()
353        mock_get_json.reset_mock()
354        mock_load.reset_mock()
355        mock_dict.reset_mock()
356
357        force_build = False
358        mock_get_json.return_value = mod_file
359        native_module_info.NativeModuleInfo(force_build, mod_file)
360        self.assertFalse(mock_get_json.called)
361        mock_isfile.return_value = True
362        self.assertFalse(mock_load.called)
363        mock_dict.return_value = _CC_MODULE_INFO
364        self.assertTrue(mock_dict.called_with(mod_file))
365        self.assertFalse(mock_base_load.called)
366        mock_base_load.reset_mock()
367        mock_get_json.reset_mock()
368        mock_load.reset_mock()
369        mock_dict.reset_mock()
370
371
372if __name__ == '__main__':
373    unittest.main()
374