#!/usr/bin/env python3 # Copyright (c) 2023 Intel Corporation # # SPDX-License-Identifier: Apache-2.0 """ Tests for scl.py functions """ import logging import mock import os import pytest import sys ZEPHYR_BASE = os.getenv("ZEPHYR_BASE") sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister")) import scl from contextlib import nullcontext from importlib import reload from pykwalify.errors import SchemaError from yaml.scanner import ScannerError TESTDATA_1 = [ (False), (True), ] @pytest.mark.parametrize( 'fail_c', TESTDATA_1, ids=['C YAML', 'non-C YAML'] ) def test_yaml_imports(fail_c): class ImportRaiser: def find_spec(self, fullname, path, target=None): if fullname == 'yaml.CLoader' and fail_c: raise ImportError() if fullname == 'yaml.CSafeLoader' and fail_c: raise ImportError() if fullname == 'yaml.CDumper' and fail_c: raise ImportError() modules_mock = sys.modules.copy() if hasattr(modules_mock['yaml'], 'CLoader'): del modules_mock['yaml'].CLoader del modules_mock['yaml'].CSafeLoader del modules_mock['yaml'].CDumper cloader_mock = mock.Mock() loader_mock = mock.Mock() csafeloader_mock = mock.Mock() safeloader_mock = mock.Mock() cdumper_mock = mock.Mock() dumper_mock = mock.Mock() if not fail_c: modules_mock['yaml'].CLoader = cloader_mock modules_mock['yaml'].CSafeLoader = csafeloader_mock modules_mock['yaml'].CDumper = cdumper_mock modules_mock['yaml'].Loader = loader_mock modules_mock['yaml'].SafeLoader = safeloader_mock modules_mock['yaml'].Dumper = dumper_mock meta_path_mock = sys.meta_path[:] meta_path_mock.insert(0, ImportRaiser()) with mock.patch.dict('sys.modules', modules_mock, clear=True), \ mock.patch('sys.meta_path', meta_path_mock): reload(scl) assert sys.modules['scl'].Loader == loader_mock if fail_c else \ cloader_mock assert sys.modules['scl'].SafeLoader == safeloader_mock if fail_c else \ csafeloader_mock assert sys.modules['scl'].Dumper == dumper_mock if fail_c else \ cdumper_mock import yaml reload(yaml) TESTDATA_2 = [ (False, logging.CRITICAL, []), (True, None, ['can\'t import pykwalify; won\'t validate YAML']), ] @pytest.mark.parametrize( 'fail_pykwalify, log_level, expected_logs', TESTDATA_2, ids=['pykwalify OK', 'no pykwalify'] ) def test_pykwalify_import(caplog, fail_pykwalify, log_level, expected_logs): class ImportRaiser: def find_spec(self, fullname, path, target=None): if fullname == 'pykwalify.core' and fail_pykwalify: raise ImportError() modules_mock = sys.modules.copy() modules_mock['pykwalify'] = None if fail_pykwalify else \ modules_mock['pykwalify'] meta_path_mock = sys.meta_path[:] meta_path_mock.insert(0, ImportRaiser()) with mock.patch.dict('sys.modules', modules_mock, clear=True), \ mock.patch('sys.meta_path', meta_path_mock): reload(scl) if log_level: assert logging.getLogger('pykwalify.core').level == log_level assert all([log in caplog.text for log in expected_logs]) if fail_pykwalify: assert scl._yaml_validate(None, None) is None assert scl._yaml_validate(mock.Mock(), mock.Mock()) is None reload(scl) TESTDATA_3 = [ (False), (True), ] @pytest.mark.parametrize( 'fail_parsing', TESTDATA_3, ids=['ok', 'parsing error'] ) def test_yaml_load(caplog, fail_parsing): result_mock = mock.Mock() def mock_load(*args, **kwargs): if fail_parsing: context_mark = mock.Mock() problem_mark = mock.Mock() type(context_mark).args = mock.PropertyMock(return_value=[]) type(context_mark).name = 'dummy context mark' type(context_mark).line = 0 type(context_mark).column = 0 type(problem_mark).args = mock.PropertyMock(return_value=[]) type(problem_mark).name = 'dummy problem mark' type(problem_mark).line = 0 type(problem_mark).column = 0 raise ScannerError(context='dummy context', context_mark=context_mark, problem='dummy problem', problem_mark=problem_mark, note='Dummy note') return result_mock filename = 'dummy/file.yaml' with mock.patch('yaml.load', side_effect=mock_load), \ mock.patch('builtins.open', mock.mock_open()) as mock_file: with pytest.raises(ScannerError) if fail_parsing else nullcontext(): result = scl.yaml_load(filename) mock_file.assert_called_with('dummy/file.yaml', 'r', encoding='utf-8') if not fail_parsing: assert result == result_mock else: assert 'dummy problem mark:0:0: error: dummy problem' \ ' (note Dummy note context @dummy context mark:0:0' \ ' dummy context)' in caplog.text TESTDATA_4 = [ (True, False, None), (False, False, SchemaError), (False, True, ScannerError), ] @pytest.mark.parametrize( 'validate, fail_load, expected_error', TESTDATA_4, ids=['successful validation', 'failed validation', 'failed load'] ) def test_yaml_load_verify(validate, fail_load, expected_error): filename = 'dummy/file.yaml' schema_mock = mock.Mock() data_mock = mock.Mock() def mock_load(file_name, *args, **kwargs): assert file_name == filename if fail_load: raise ScannerError return data_mock def mock_validate(data, schema, *args, **kwargs): assert data == data_mock assert schema == schema_mock if validate: return True raise SchemaError(u'Schema validation failed.') with mock.patch('scl.yaml_load', side_effect=mock_load), \ mock.patch('scl._yaml_validate', side_effect=mock_validate), \ pytest.raises(expected_error) if expected_error else nullcontext(): res = scl.yaml_load_verify(filename, schema_mock) if validate: assert res == data_mock TESTDATA_5 = [ (True, True, None), (True, False, SchemaError), (False, None, None), ] @pytest.mark.parametrize( 'schema_exists, validate, expected_error', TESTDATA_5, ids=['successful validation', 'failed validation', 'no schema'] ) def test_yaml_validate(schema_exists, validate, expected_error): data_mock = mock.Mock() schema_mock = mock.Mock() if schema_exists else None def mock_validate(raise_exception, *args, **kwargs): assert raise_exception if validate: return True raise SchemaError(u'Schema validation failed.') def mock_core(source_data, schema_data, *args, **kwargs): assert source_data == data_mock assert schema_data == schema_mock return mock.Mock(validate=mock_validate) core_mock = mock.Mock(side_effect=mock_core) with mock.patch('pykwalify.core.Core', core_mock), \ pytest.raises(expected_error) if expected_error else nullcontext(): scl._yaml_validate(data_mock, schema_mock) if schema_exists: core_mock.assert_called_once() else: core_mock.assert_not_called() def test_yaml_load_empty_file(tmp_path): quarantine_file = tmp_path / 'empty_quarantine.yml' quarantine_file.write_text("# yaml file without data") with pytest.raises(scl.EmptyYamlFileException): scl.yaml_load_verify(quarantine_file, None)