2017-07-05 16:27:40 +02:00
|
|
|
# kas - setup tool for bitbake based projects
|
|
|
|
#
|
|
|
|
# Copyright (c) Siemens AG, 2017
|
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
|
|
# in the Software without restriction, including without limitation the rights
|
|
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be
|
|
|
|
# included in all copies or substantial portions of the Software.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
# SOFTWARE.
|
|
|
|
|
|
|
|
# pylint: disable=missing-docstring,no-self-use
|
|
|
|
|
|
|
|
import os
|
|
|
|
import io
|
|
|
|
import textwrap
|
|
|
|
import contextlib
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from kas import includehandler
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
def fixed_version(monkeypatch):
|
2017-07-17 22:38:05 +02:00
|
|
|
monkeypatch.setattr(includehandler, '__file_version__', 5)
|
|
|
|
monkeypatch.setattr(includehandler, '__compatible_file_version__', 4)
|
2017-07-05 16:27:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
class MockFileIO(io.StringIO):
|
|
|
|
def close(self):
|
|
|
|
self.seek(0)
|
|
|
|
|
|
|
|
|
|
|
|
def mock_file(indented_content):
|
|
|
|
return MockFileIO(textwrap.dedent(indented_content))
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def patch_open(component, string='', dictionary=None):
|
|
|
|
dictionary = dictionary or {}
|
|
|
|
old_attr = getattr(component, 'open', None)
|
|
|
|
component.open = lambda f, *a, **k: mock_file(dictionary.get(f, string))
|
|
|
|
yield
|
|
|
|
if old_attr:
|
|
|
|
component.open = old_attr
|
|
|
|
else:
|
|
|
|
del component.open
|
|
|
|
|
|
|
|
|
|
|
|
class TestLoadConfig(object):
|
|
|
|
def test_err_invalid_ext(self):
|
|
|
|
# Test for invalid file extension:
|
|
|
|
exception = includehandler.LoadConfigException
|
|
|
|
with pytest.raises(exception):
|
|
|
|
includehandler.load_config('x.xyz')
|
|
|
|
|
|
|
|
def util_exception_content(self, testvector):
|
|
|
|
for string, exception in testvector:
|
|
|
|
with patch_open(includehandler, string=string):
|
|
|
|
with pytest.raises(exception):
|
|
|
|
includehandler.load_config('x.yml')
|
|
|
|
|
|
|
|
def test_err_header_missing(self):
|
|
|
|
exception = includehandler.LoadConfigException
|
|
|
|
testvector = [
|
|
|
|
('', exception),
|
|
|
|
('a', exception),
|
|
|
|
('1', exception),
|
|
|
|
('a:', exception)
|
|
|
|
]
|
|
|
|
|
|
|
|
self.util_exception_content(testvector)
|
|
|
|
|
|
|
|
def test_err_header_invalid_type(self):
|
|
|
|
exception = includehandler.LoadConfigException
|
|
|
|
testvector = [
|
|
|
|
('header:', exception),
|
|
|
|
('header: 1', exception),
|
|
|
|
('header: a', exception),
|
|
|
|
('header: []', exception),
|
|
|
|
]
|
|
|
|
|
|
|
|
self.util_exception_content(testvector)
|
|
|
|
|
|
|
|
def test_err_version_missing(self):
|
|
|
|
exception = includehandler.LoadConfigException
|
|
|
|
testvector = [
|
|
|
|
('header: {}', exception),
|
|
|
|
('header: {a: 1}', exception),
|
|
|
|
]
|
|
|
|
|
|
|
|
self.util_exception_content(testvector)
|
|
|
|
|
|
|
|
def test_err_version_invalid_format(self):
|
|
|
|
exception = includehandler.LoadConfigException
|
|
|
|
testvector = [
|
2017-07-17 22:38:05 +02:00
|
|
|
('header: {version: "0.5"}', exception),
|
|
|
|
('header: {version: "x"}', exception),
|
|
|
|
('header: {version: 3}', exception),
|
|
|
|
('header: {version: 6}', exception),
|
2017-07-05 16:27:40 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
self.util_exception_content(testvector)
|
|
|
|
|
|
|
|
def test_header_valid(self):
|
|
|
|
testvector = [
|
2017-07-17 22:38:05 +02:00
|
|
|
'header: {version: 4}',
|
|
|
|
'header: {version: 5}',
|
2017-07-05 16:27:40 +02:00
|
|
|
]
|
|
|
|
for string in testvector:
|
|
|
|
with patch_open(includehandler, string=string):
|
|
|
|
includehandler.load_config('x.yml')
|
|
|
|
|
2017-07-17 22:38:05 +02:00
|
|
|
def test_compat_version(self, monkeypatch):
|
|
|
|
monkeypatch.setattr(includehandler, '__compatible_file_version__', 1)
|
|
|
|
with patch_open(includehandler, string='header: {version: "0.10"}'):
|
|
|
|
includehandler.load_config('x.yml')
|
|
|
|
|
2017-07-05 16:27:40 +02:00
|
|
|
|
|
|
|
class TestGlobalIncludes(object):
|
|
|
|
header = '''
|
|
|
|
header:
|
2017-07-17 22:38:05 +02:00
|
|
|
version: 5
|
2017-07-05 16:27:40 +02:00
|
|
|
{}'''
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def util_include_content(self, testvector, monkeypatch):
|
|
|
|
# disable schema validation for these tests:
|
|
|
|
monkeypatch.setattr(includehandler, 'CONFIGSCHEMA', {})
|
2017-07-05 16:27:40 +02:00
|
|
|
for test in testvector:
|
|
|
|
with patch_open(includehandler, dictionary=test['fdict']):
|
|
|
|
ginc = includehandler.GlobalIncludes('x.yml')
|
|
|
|
config, missing = ginc.get_config(repos=test['rdict'])
|
|
|
|
|
|
|
|
# Remove header, because we dont want to compare it:
|
|
|
|
config.pop('header')
|
|
|
|
|
|
|
|
assert test['conf'] == config
|
|
|
|
assert test['rmiss'] == missing
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def test_valid_includes_none(self, monkeypatch):
|
2017-07-05 16:27:40 +02:00
|
|
|
header = self.__class__.header
|
|
|
|
testvector = [
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format('')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
self.util_include_content(testvector, monkeypatch)
|
2017-07-05 16:27:40 +02:00
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def test_valid_includes_some(self, monkeypatch):
|
2017-07-05 16:27:40 +02:00
|
|
|
header = self.__class__.header
|
|
|
|
testvector = [
|
|
|
|
# Include one file from the same repo:
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(' includes: ["y.yml"]'),
|
|
|
|
os.path.abspath('y.yml'): header.format('\nv:')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v': None
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
# Include one file from another not available repo:
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(
|
|
|
|
' includes: [{repo: rep, file: y.yml}]'),
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
'rep',
|
|
|
|
]
|
|
|
|
},
|
|
|
|
# Include one file from the same repo and one from another
|
|
|
|
# not available repo:
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(' includes: ["y.yml", '
|
|
|
|
'{repo: rep, file: y.yml}]'),
|
|
|
|
os.path.abspath('y.yml'): header.format('\nv:')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v': None
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
'rep',
|
|
|
|
]
|
|
|
|
},
|
|
|
|
# Include one file from another available repo:
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(
|
|
|
|
' includes: [{repo: rep, file: y.yml}]'),
|
|
|
|
'/rep/y.yml': header.format('\nv:')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
'rep': '/rep'
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v': None
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
self.util_include_content(testvector, monkeypatch)
|
2017-07-05 16:27:40 +02:00
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def test_valid_overwriting(self, monkeypatch):
|
2017-07-05 16:27:40 +02:00
|
|
|
header = self.__class__.header
|
|
|
|
testvector = [
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(''' includes: ["y.yml"]
|
|
|
|
v: x'''),
|
|
|
|
os.path.abspath('y.yml'): header.format('''
|
|
|
|
v: y''')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v': 'x'
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(''' includes: ["y.yml"]
|
|
|
|
v: {v: x}'''),
|
|
|
|
os.path.abspath('y.yml'): header.format('''
|
|
|
|
v: {v: y}''')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v': {'v': 'x'}
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(''' includes: ["y.yml"]
|
|
|
|
v1:
|
|
|
|
v2: []
|
|
|
|
v3:
|
|
|
|
- a: c'''),
|
|
|
|
os.path.abspath('y.yml'): header.format('''
|
|
|
|
v1: a
|
|
|
|
v2: [a]
|
|
|
|
v3:
|
|
|
|
- a: b
|
|
|
|
- d: c}]''')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v1': None,
|
|
|
|
'v2': [],
|
|
|
|
'v3': [{'a': 'c'}]
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
self.util_include_content(testvector, monkeypatch)
|
2017-07-05 16:27:40 +02:00
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def test_valid_merging(self, monkeypatch):
|
2017-07-05 16:27:40 +02:00
|
|
|
header = self.__class__.header
|
|
|
|
testvector = [
|
|
|
|
{
|
|
|
|
'fdict': {
|
|
|
|
'x.yml': header.format(''' includes: ["y.yml"]
|
|
|
|
v1: x
|
|
|
|
v3:
|
|
|
|
a: b
|
|
|
|
b:
|
|
|
|
e:
|
|
|
|
c: d'''),
|
|
|
|
os.path.abspath('y.yml'): header.format('''
|
|
|
|
v2: y
|
|
|
|
v3:
|
|
|
|
d: e
|
|
|
|
b:
|
|
|
|
c:
|
|
|
|
e: f''')
|
|
|
|
},
|
|
|
|
'rdict': {
|
|
|
|
},
|
|
|
|
'conf': {
|
|
|
|
'v1': 'x',
|
|
|
|
'v2': 'y',
|
|
|
|
'v3': {
|
|
|
|
'a': 'b',
|
|
|
|
'b': {'c': None, 'e': None},
|
|
|
|
'c': 'd',
|
|
|
|
'd': 'e',
|
|
|
|
'e': 'f'}
|
|
|
|
},
|
|
|
|
'rmiss': [
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
self.util_include_content(testvector, monkeypatch)
|
2017-07-05 16:27:40 +02:00
|
|
|
|
2017-10-13 12:20:05 +02:00
|
|
|
def test_valid_ordering(self, monkeypatch):
|
|
|
|
# disable schema validation for this test:
|
|
|
|
monkeypatch.setattr(includehandler, 'CONFIGSCHEMA', {})
|
2017-07-05 16:27:40 +02:00
|
|
|
header = self.__class__.header
|
|
|
|
with patch_open(includehandler, dictionary={
|
|
|
|
'x.yml': header.format(''' includes: ["y.yml", "z.yml"]
|
|
|
|
v: {v1: x, v2: x}'''),
|
|
|
|
os.path.abspath('y.yml'): header.format(''' includes: ["z.yml"]
|
|
|
|
v: {v2: y, v3: y, v5: y}'''),
|
|
|
|
os.path.abspath('z.yml'): header.format('''
|
|
|
|
v: {v3: z, v4: z}''')}):
|
|
|
|
ginc = includehandler.GlobalIncludes('x.yml')
|
|
|
|
config, _ = ginc.get_config()
|
|
|
|
keys = list(config['v'].keys())
|
|
|
|
index = {keys[i]: i for i in range(len(keys))}
|
|
|
|
|
|
|
|
# Check for vars in z.yml:
|
|
|
|
assert index['v3'] < index['v1']
|
|
|
|
assert index['v3'] < index['v2']
|
|
|
|
assert index['v3'] < index['v5']
|
|
|
|
assert index['v4'] < index['v1']
|
|
|
|
assert index['v4'] < index['v2']
|
|
|
|
assert index['v4'] < index['v5']
|
|
|
|
|
|
|
|
# Check for vars in y.yml:
|
|
|
|
assert index['v2'] < index['v1']
|
|
|
|
assert index['v3'] < index['v1']
|
|
|
|
assert index['v5'] < index['v1']
|