#!/usr/bin/env python3
#
# Copyright 2019, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Unittests for native_module_info."""

import logging
import os
import unittest
from unittest import mock

from aidegen import constant
from aidegen.lib import common_util
from aidegen.lib import module_info
from aidegen.lib import native_module_info

import atest

_PATH_TO_MULT_MODULES_WITH_MULTI_ARCH = 'shared/path/to/be/used2'
_TESTABLE_MODULES_WITH_SHARED_PATH = [
    'multiarch', 'multiarch1', 'multiarch2', 'multiarch3', 'multiarch3_32'
]
_REBUILD_TARGET1 = 'android.frameworks.bufferhub@1.0'
_NATIVE_INCLUDES1 = [
    'frameworks/native/include',
    'frameworks/native/libs/ui',
    'out/soong/.intermediates/' + _REBUILD_TARGET1 + '_genc++_headers/gen'
]
_CC_NAME_TO_MODULE_INFO = {
    'multiarch': {
        'path': [
            'shared/path/to/be/used2'
        ],
        'srcs': [
            'shared/path/to/be/used2/multiarch.cpp',
            'out/soong/.intermediates/shared/path/to/be/used2/gen/Iarch.cpp'
        ],
        'local_common_flags': {
            constant.KEY_HEADER: _NATIVE_INCLUDES1
        },
        'module_name': 'multiarch'
    },
    'multiarch1': {
        'path': [
            'shared/path/to/be/used2/arch1'
        ],
        'module_name': 'multiarch1'
    },
    'multiarch2': {
        'path': [
            'shared/path/to/be/used2/arch2'
        ],
        'module_name': 'multiarch2'
    },
    'multiarch3': {
        'path': [
            'shared/path/to/be/used2/arch3'
        ],
        'module_name': 'multiarch3'
    },
    'multiarch3_32': {
        'path': [
            'shared/path/to/be/used2/arch3_32'
        ],
        'module_name': 'multiarch3_32'
    },
    _REBUILD_TARGET1: {
        'path': [
            '/path/to/rebuild'
        ],
        'module_name': _REBUILD_TARGET1
    },
    'multiarch-eng': {
        'path': [
            'shared/path/to/be/used2-eng'
        ],
        'srcs': [
            'shared/path/to/be/used2/multiarch-eng.cpp',
            'out/soong/.intermediates/shared/path/to/be/used2/gen/Iarch-eng.cpp'
        ],
        'module_name': 'multiarch-eng'
    }
}
_CC_MODULE_INFO = {
    'clang': '${ANDROID_ROOT}/prebuilts/clang/host/linux-x86/bin/clang',
    'clang++': '${ANDROID_ROOT}/prebuilts/clang/host/linux-x86/bin/clang++',
    'modules': _CC_NAME_TO_MODULE_INFO
}


# pylint: disable=protected-access
class NativeModuleInfoUnittests(unittest.TestCase):
    """Unit tests for module_info.py"""

    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    @mock.patch.object(common_util, 'get_related_paths')
    def test_get_module_names_in_targets_paths(self, mock_relpath, mock_load):
        """Test get_module_names_in_targets_paths handling."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mock_relpath.return_value = (_PATH_TO_MULT_MODULES_WITH_MULTI_ARCH, '')
        result = mod_info.get_module_names_in_targets_paths(['multiarch'])
        self.assertEqual(_TESTABLE_MODULES_WITH_SHARED_PATH, result)

    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_get_module_includes_empty(self, mock_load):
        """Test get_module_includes without include paths."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        result = mod_info.get_module_includes('multiarch1')
        self.assertEqual(set(), result)

    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_get_module_includes_not_empty(self, mock_load):
        """Test get_module_includes with include paths."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mod_info.name_to_module_info = _CC_NAME_TO_MODULE_INFO
        result = mod_info.get_module_includes('multiarch')
        self.assertEqual(set(_NATIVE_INCLUDES1), result)

    @mock.patch.object(logging, 'warning')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_module_need_build_without_mod_info(self, mock_load, mock_warn):
        """Test get_module_includes without module info."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        self.assertFalse(mod_info.is_module_need_build('test_multiarch'))
        self.assertTrue(mock_warn.called)

    @mock.patch.object(logging, 'warning')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_module_need_build_with_mod_info(self, mock_load, mock_warn):
        """Test get_module_includes with module info."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        self.assertFalse(mod_info.is_module_need_build('multiarch1'))
        self.assertFalse(mock_warn.called)

    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_include_need_build')
    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_source_need_build')
    @mock.patch.object(logging, 'warning')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_module_need_build_with_src_needs(
            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
        """Test get_module_includes with needed build source modules."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mock_src_need.return_value = True
        mock_inc_need.return_value = False
        self.assertTrue(mod_info.is_module_need_build('multiarch'))
        self.assertFalse(mock_warn.called)

    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_include_need_build')
    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_source_need_build')
    @mock.patch.object(logging, 'warning')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_module_need_build_with_inc_needs(
            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
        """Test get_module_includes with needed build include modules."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mock_src_need.return_value = False
        mock_inc_need.return_value = True
        self.assertTrue(mod_info.is_module_need_build('multiarch'))
        self.assertFalse(mock_warn.called)

    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_include_need_build')
    @mock.patch.object(native_module_info.NativeModuleInfo,
                       '_is_source_need_build')
    @mock.patch.object(logging, 'warning')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_module_need_build_without_needs(
            self, mock_load, mock_warn, mock_src_need, mock_inc_need):
        """Test get_module_includes without needs."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mock_src_need.return_value = False
        mock_inc_need.return_value = False
        self.assertFalse(mod_info.is_module_need_build('multiarch1'))
        self.assertFalse(mock_warn.called)

    @mock.patch.object(os.path, 'isfile')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_source_need_build_return_true(self, mock_load, mock_isfile):
        """Test _is_source_need_build with source paths or not existing."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        mock_isfile.return_value = False
        self.assertTrue(mod_info._is_source_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch']))

    @mock.patch.object(os.path, 'isfile')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_source_need_build_return_false(self, mock_load, mock_isfile):
        """Test _is_source_need_build without source paths or paths exist."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        self.assertFalse(mod_info._is_source_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch1']))
        mock_isfile.return_value = True
        self.assertFalse(mod_info._is_source_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch']))

    @mock.patch.object(os.path, 'isdir')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_include_need_build_return_true(self, mock_load, mock_isdir):
        """Test _is_include_need_build with include paths and not existing."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mock_isdir.return_value = False
        mod_info = native_module_info.NativeModuleInfo()
        self.assertTrue(mod_info._is_include_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch']))

    @mock.patch.object(os.path, 'isdir')
    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_is_include_need_build_return_false(self, mock_load, mock_isdir):
        """Test _is_include_need_build without include paths or paths exist."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        self.assertFalse(mod_info._is_include_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch1']))
        mock_isdir.return_value = True
        self.assertFalse(mod_info._is_include_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch']))
        mock_isdir.return_value = True
        self.assertFalse(mod_info._is_include_need_build(
            _CC_NAME_TO_MODULE_INFO['multiarch']))

    def test_not_implemented_methods(self):
        """Test not implemented methods."""
        mod_info = native_module_info.NativeModuleInfo()
        target = 'test'
        with self.assertRaises(NotImplementedError):
            mod_info.get_testable_modules()
        with self.assertRaises(NotImplementedError):
            mod_info.is_testable_module(mock.Mock())
        with self.assertRaises(NotImplementedError):
            mod_info.has_test_config(mock.Mock())
        with self.assertRaises(NotImplementedError):
            mod_info.get_robolectric_test_name(target)
        with self.assertRaises(NotImplementedError):
            mod_info.is_robolectric_test(target)
        with self.assertRaises(NotImplementedError):
            mod_info.is_auto_gen_test_config(target)
        with self.assertRaises(NotImplementedError):
            mod_info.is_robolectric_module(mock.Mock())
        with self.assertRaises(NotImplementedError):
            mod_info.is_native_test(target)

    @mock.patch.object(
        native_module_info.NativeModuleInfo, '_load_module_info_file')
    def test_not_implement_methods_check(self, mock_load):
        """Test for all not implemented methods which should raise error."""
        mock_load.return_value = None, _CC_NAME_TO_MODULE_INFO
        mod_info = native_module_info.NativeModuleInfo()
        with self.assertRaises(NotImplementedError):
            suite_name = 'test'
            test_data = {'a': 'test'}
            mod_info.is_suite_in_compatibility_suites(suite_name, test_data)

        with self.assertRaises(NotImplementedError):
            mod_info.get_testable_modules()

        with self.assertRaises(NotImplementedError):
            test_data = {'a': 'test'}
            mod_info.is_testable_module(test_data)

        with self.assertRaises(NotImplementedError):
            test_data = {'a': 'test'}
            mod_info.has_test_config(test_data)

        with self.assertRaises(NotImplementedError):
            test_mod = 'mod_a'
            mod_info.get_robolectric_test_name(test_mod)

        with self.assertRaises(NotImplementedError):
            test_mod = 'mod_a'
            mod_info.is_robolectric_test(test_mod)

        with self.assertRaises(NotImplementedError):
            test_mod = 'a'
            mod_info.is_auto_gen_test_config(test_mod)

        with self.assertRaises(NotImplementedError):
            test_data = {'a': 'test'}
            mod_info.is_robolectric_module(test_data)

        with self.assertRaises(NotImplementedError):
            test_mod = 'a'
            mod_info.is_native_test(test_mod)

    @mock.patch.object(atest.module_info.ModuleInfo, '_load_module_info_file')
    @mock.patch.object(os.path, 'isfile')
    @mock.patch.object(common_util, 'get_json_dict')
    @mock.patch.object(module_info.AidegenModuleInfo,
                       '_discover_mod_file_and_target')
    @mock.patch.object(common_util, 'get_blueprint_json_path')
    def test_load_module_info_file(self, mock_get_json, mock_load, mock_dict,
                                   mock_isfile, mock_base_load):
        """Test _load_module_info_file."""
        force_build = True
        mod_file = '/test/path'
        mock_get_json.return_value = mod_file
        native_module_info.NativeModuleInfo(force_build)
        self.assertTrue(mock_get_json.called_with(
            constant.BLUEPRINT_CC_JSONFILE_NAME))
        mock_load.return_value = mod_file, mod_file
        self.assertTrue(mock_load.called_with(True))
        mock_dict.return_value = _CC_MODULE_INFO
        self.assertTrue(mock_dict.called_with(mod_file))
        self.assertFalse(mock_base_load.called)
        mock_base_load.reset_mock()
        mock_get_json.reset_mock()
        mock_load.reset_mock()
        mock_dict.reset_mock()

        native_module_info.NativeModuleInfo(force_build, mod_file)
        self.assertFalse(mock_get_json.called)
        mock_load.return_value = None, mod_file
        self.assertTrue(mock_load.called_with(True))
        self.assertTrue(mock_dict.called_with(mod_file))
        self.assertFalse(mock_base_load.called)
        mock_base_load.reset_mock()
        mock_get_json.reset_mock()
        mock_load.reset_mock()
        mock_dict.reset_mock()

        force_build = False
        mock_get_json.return_value = mod_file
        native_module_info.NativeModuleInfo(force_build, mod_file)
        self.assertFalse(mock_get_json.called)
        mock_isfile.return_value = True
        self.assertFalse(mock_load.called)
        mock_dict.return_value = _CC_MODULE_INFO
        self.assertTrue(mock_dict.called_with(mod_file))
        self.assertFalse(mock_base_load.called)
        mock_base_load.reset_mock()
        mock_get_json.reset_mock()
        mock_load.reset_mock()
        mock_dict.reset_mock()


if __name__ == '__main__':
    unittest.main()
