clm5/manage_externals/test/test_unit_externals_description.py
2024-05-09 15:14:01 +08:00

479 lines
18 KiB
Python

#!/usr/bin/env python3
"""Unit test driver for checkout_externals
Note: this script assume the path to the checkout_externals.py module is
already in the python path.
"""
from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function
import os
import os.path
import shutil
import unittest
try:
# python2
from ConfigParser import SafeConfigParser as config_parser
def config_string_cleaner(text):
"""convert strings into unicode
"""
return text.decode('utf-8')
except ImportError:
# python3
from configparser import ConfigParser as config_parser
def config_string_cleaner(text):
"""Python3 already uses unicode strings, so just return the string
without modification.
"""
return text
from manic.externals_description import DESCRIPTION_SECTION, VERSION_ITEM
from manic.externals_description import ExternalsDescription
from manic.externals_description import ExternalsDescriptionDict
from manic.externals_description import ExternalsDescriptionConfigV1
from manic.externals_description import get_cfg_schema_version
from manic.externals_description import read_externals_description_file
from manic.externals_description import create_externals_description
from manic.global_constants import EMPTY_STR
class TestCfgSchemaVersion(unittest.TestCase):
"""Test that schema identification for the externals description
returns the correct results.
"""
def setUp(self):
"""Reusable config object
"""
self._config = config_parser()
self._config.add_section('section1')
self._config.set('section1', 'keword', 'value')
self._config.add_section(DESCRIPTION_SECTION)
def test_schema_version_valid(self):
"""Test that schema identification returns the correct version for a
valid tag.
"""
version_str = '2.1.3'
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, version_str)
major, minor, patch = get_cfg_schema_version(self._config)
expected_major = 2
expected_minor = 1
expected_patch = 3
self.assertEqual(expected_major, major)
self.assertEqual(expected_minor, minor)
self.assertEqual(expected_patch, patch)
def test_schema_section_missing(self):
"""Test that an error is returned if the schema section is missing
from the input file.
"""
self._config.remove_section(DESCRIPTION_SECTION)
with self.assertRaises(RuntimeError):
get_cfg_schema_version(self._config)
def test_schema_version_missing(self):
"""Test that a externals description file without a version raises a
runtime error.
"""
# Note: the default setup method shouldn't include a version
# keyword, but remove it just to be future proof....
self._config.remove_option(DESCRIPTION_SECTION, VERSION_ITEM)
with self.assertRaises(RuntimeError):
get_cfg_schema_version(self._config)
def test_schema_version_not_int(self):
"""Test that a externals description file a version that doesn't
decompose to integer major, minor and patch versions raises
runtime error.
"""
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, 'unknown')
with self.assertRaises(RuntimeError):
get_cfg_schema_version(self._config)
class TestModelDescritionConfigV1(unittest.TestCase):
"""Test that parsing config/ini fileproduces a correct dictionary
for the externals description.
"""
# pylint: disable=R0902
def setUp(self):
"""Boiler plate construction of string containing xml for multiple components.
"""
self._comp1_name = 'comp1'
self._comp1_path = 'path/to/comp1'
self._comp1_protocol = 'svn'
self._comp1_url = 'https://svn.somewhere.com/path/of/comp1'
self._comp1_tag = 'a_nice_tag_v1'
self._comp1_is_required = 'True'
self._comp1_externals = ''
self._comp2_name = 'comp2'
self._comp2_path = 'path/to/comp2'
self._comp2_protocol = 'git'
self._comp2_url = '/local/clone/of/comp2'
self._comp2_branch = 'a_very_nice_branch'
self._comp2_is_required = 'False'
self._comp2_externals = 'path/to/comp2.cfg'
def _setup_comp1(self, config):
"""Boiler plate construction of xml string for componet 1
"""
config.add_section(self._comp1_name)
config.set(self._comp1_name, 'local_path', self._comp1_path)
config.set(self._comp1_name, 'protocol', self._comp1_protocol)
config.set(self._comp1_name, 'repo_url', self._comp1_url)
config.set(self._comp1_name, 'tag', self._comp1_tag)
config.set(self._comp1_name, 'required', self._comp1_is_required)
def _setup_comp2(self, config):
"""Boiler plate construction of xml string for componet 2
"""
config.add_section(self._comp2_name)
config.set(self._comp2_name, 'local_path', self._comp2_path)
config.set(self._comp2_name, 'protocol', self._comp2_protocol)
config.set(self._comp2_name, 'repo_url', self._comp2_url)
config.set(self._comp2_name, 'branch', self._comp2_branch)
config.set(self._comp2_name, 'required', self._comp2_is_required)
config.set(self._comp2_name, 'externals', self._comp2_externals)
@staticmethod
def _setup_externals_description(config):
"""Add the required exernals description section
"""
config.add_section(DESCRIPTION_SECTION)
config.set(DESCRIPTION_SECTION, VERSION_ITEM, '1.0.1')
def _check_comp1(self, model):
"""Test that component one was constructed correctly.
"""
self.assertTrue(self._comp1_name in model)
comp1 = model[self._comp1_name]
self.assertEqual(comp1[ExternalsDescription.PATH], self._comp1_path)
self.assertTrue(comp1[ExternalsDescription.REQUIRED])
repo = comp1[ExternalsDescription.REPO]
self.assertEqual(repo[ExternalsDescription.PROTOCOL],
self._comp1_protocol)
self.assertEqual(repo[ExternalsDescription.REPO_URL], self._comp1_url)
self.assertEqual(repo[ExternalsDescription.TAG], self._comp1_tag)
self.assertEqual(EMPTY_STR, comp1[ExternalsDescription.EXTERNALS])
def _check_comp2(self, model):
"""Test that component two was constucted correctly.
"""
self.assertTrue(self._comp2_name in model)
comp2 = model[self._comp2_name]
self.assertEqual(comp2[ExternalsDescription.PATH], self._comp2_path)
self.assertFalse(comp2[ExternalsDescription.REQUIRED])
repo = comp2[ExternalsDescription.REPO]
self.assertEqual(repo[ExternalsDescription.PROTOCOL],
self._comp2_protocol)
self.assertEqual(repo[ExternalsDescription.REPO_URL], self._comp2_url)
self.assertEqual(repo[ExternalsDescription.BRANCH], self._comp2_branch)
self.assertEqual(self._comp2_externals,
comp2[ExternalsDescription.EXTERNALS])
def test_one_tag_required(self):
"""Test that a component source with a tag is correctly parsed.
"""
config = config_parser()
self._setup_comp1(config)
self._setup_externals_description(config)
model = ExternalsDescriptionConfigV1(config)
print(model)
self._check_comp1(model)
def test_one_branch_externals(self):
"""Test that a component source with a branch is correctly parsed.
"""
config = config_parser()
self._setup_comp2(config)
self._setup_externals_description(config)
model = ExternalsDescriptionConfigV1(config)
print(model)
self._check_comp2(model)
def test_two_sources(self):
"""Test that multiple component sources are correctly parsed.
"""
config = config_parser()
self._setup_comp1(config)
self._setup_comp2(config)
self._setup_externals_description(config)
model = ExternalsDescriptionConfigV1(config)
print(model)
self._check_comp1(model)
self._check_comp2(model)
def test_cfg_v1_reject_unknown_item(self):
"""Test that a v1 description object will reject unknown items
"""
config = config_parser()
self._setup_comp1(config)
self._setup_externals_description(config)
config.set(self._comp1_name, 'junk', 'foobar')
with self.assertRaises(RuntimeError):
ExternalsDescriptionConfigV1(config)
def test_cfg_v1_reject_v2(self):
"""Test that a v1 description object won't try to parse a v2 file.
"""
config = config_parser()
self._setup_comp1(config)
self._setup_externals_description(config)
config.set(DESCRIPTION_SECTION, VERSION_ITEM, '2.0.1')
with self.assertRaises(RuntimeError):
ExternalsDescriptionConfigV1(config)
def test_cfg_v1_reject_v1_too_new(self):
"""Test that a v1 description object won't try to parse a v2 file.
"""
config = config_parser()
self._setup_comp1(config)
self._setup_externals_description(config)
config.set(DESCRIPTION_SECTION, VERSION_ITEM, '1.100.0')
with self.assertRaises(RuntimeError):
ExternalsDescriptionConfigV1(config)
class TestReadExternalsDescription(unittest.TestCase):
"""Test the application logic of read_externals_description_file
"""
TMP_FAKE_DIR = 'fake'
def setUp(self):
"""Setup directory for tests
"""
if not os.path.exists(self.TMP_FAKE_DIR):
os.makedirs(self.TMP_FAKE_DIR)
def tearDown(self):
"""Cleanup tmp stuff on the file system
"""
if os.path.exists(self.TMP_FAKE_DIR):
shutil.rmtree(self.TMP_FAKE_DIR)
def test_no_file_error(self):
"""Test that a runtime error is raised when the file does not exist
"""
root_dir = os.getcwd()
filename = 'this-file-should-not-exist'
with self.assertRaises(RuntimeError):
read_externals_description_file(root_dir, filename)
def test_no_dir_error(self):
"""Test that a runtime error is raised when the file does not exist
"""
root_dir = '/path/to/some/repo'
filename = 'externals.cfg'
with self.assertRaises(RuntimeError):
read_externals_description_file(root_dir, filename)
def test_no_invalid_error(self):
"""Test that a runtime error is raised when the file format is invalid
"""
root_dir = os.getcwd()
filename = 'externals.cfg'
file_path = os.path.join(root_dir, filename)
file_path = os.path.abspath(file_path)
contents = """
<source_tree version='1.0.0'>
invalid file format
</sourc_tree>"""
with open(file_path, 'w') as fhandle:
fhandle.write(contents)
with self.assertRaises(RuntimeError):
read_externals_description_file(root_dir, filename)
os.remove(file_path)
class TestCreateExternalsDescription(unittest.TestCase):
"""Test the application logic of creat_externals_description
"""
def setUp(self):
"""Create config object used as basis for all tests
"""
self._config = config_parser()
self._gmconfig = config_parser()
self.setup_config()
def setup_config(self):
"""Boiler plate construction of xml string for componet 1
"""
# Create a standard externals config with a single external
name = 'test'
self._config.add_section(name)
self._config.set(name, ExternalsDescription.PATH, 'externals')
self._config.set(name, ExternalsDescription.PROTOCOL, 'git')
self._config.set(name, ExternalsDescription.REPO_URL, '/path/to/repo')
self._config.set(name, ExternalsDescription.TAG, 'test_tag')
self._config.set(name, ExternalsDescription.REQUIRED, 'True')
self._config.add_section(DESCRIPTION_SECTION)
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, '1.0.0')
# Create a .gitmodules test
name = 'submodule "gitmodules_test"'
self._gmconfig.add_section(name)
self._gmconfig.set(name, "path", 'externals/test')
self._gmconfig.set(name, "url", '/path/to/repo')
# NOTE(goldy, 2019-03) Should test other possible keywords such as
# fetchRecurseSubmodules, ignore, and shallow
@staticmethod
def setup_dict_config():
"""Create the full container dictionary with simple and mixed use
externals
"""
rdatat = {ExternalsDescription.PROTOCOL: 'git',
ExternalsDescription.REPO_URL: 'simple-ext.git',
ExternalsDescription.TAG: 'tag1'}
rdatab = {ExternalsDescription.PROTOCOL: 'git',
ExternalsDescription.REPO_URL: 'simple-ext.git',
ExternalsDescription.BRANCH: 'feature2'}
rdatam = {ExternalsDescription.PROTOCOL: 'git',
ExternalsDescription.REPO_URL: 'mixed-cont-ext.git',
ExternalsDescription.BRANCH: 'master'}
desc = {'simp_tag': {ExternalsDescription.REQUIRED: True,
ExternalsDescription.PATH: 'simp_tag',
ExternalsDescription.EXTERNALS: EMPTY_STR,
ExternalsDescription.REPO: rdatat},
'simp_branch' : {ExternalsDescription.REQUIRED: True,
ExternalsDescription.PATH: 'simp_branch',
ExternalsDescription.EXTERNALS: EMPTY_STR,
ExternalsDescription.REPO: rdatab},
'simp_opt': {ExternalsDescription.REQUIRED: False,
ExternalsDescription.PATH: 'simp_opt',
ExternalsDescription.EXTERNALS: EMPTY_STR,
ExternalsDescription.REPO: rdatat},
'mixed_req': {ExternalsDescription.REQUIRED: True,
ExternalsDescription.PATH: 'mixed_req',
ExternalsDescription.EXTERNALS: 'sub-ext.cfg',
ExternalsDescription.REPO: rdatam}}
return desc
def test_cfg_v1_ok(self):
"""Test that a correct cfg v1 object is created by create_externals_description
"""
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, '1.0.3')
ext = create_externals_description(self._config, model_format='cfg')
self.assertIsInstance(ext, ExternalsDescriptionConfigV1)
def test_cfg_v1_unknown_version(self):
"""Test that a config file with unknown schema version is rejected by
create_externals_description.
"""
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, '100.0.3')
with self.assertRaises(RuntimeError):
create_externals_description(self._config, model_format='cfg')
def test_dict(self):
"""Test that a correct cfg v1 object is created by create_externals_description
"""
rdata = {ExternalsDescription.PROTOCOL: 'git',
ExternalsDescription.REPO_URL: '/path/to/repo',
ExternalsDescription.TAG: 'tagv1',
}
desc = {
'test': {
ExternalsDescription.REQUIRED: False,
ExternalsDescription.PATH: '../fake',
ExternalsDescription.EXTERNALS: EMPTY_STR,
ExternalsDescription.REPO: rdata, },
}
ext = create_externals_description(desc, model_format='dict')
self.assertIsInstance(ext, ExternalsDescriptionDict)
def test_cfg_component_dict(self):
"""Verify that create_externals_description works with a dictionary
"""
# create the top level externals file
desc = self.setup_dict_config()
# Check external with all repos
external = create_externals_description(desc, model_format='dict')
self.assertIsInstance(external, ExternalsDescriptionDict)
self.assertTrue('simp_tag' in external)
self.assertTrue('simp_branch' in external)
self.assertTrue('simp_opt' in external)
self.assertTrue('mixed_req' in external)
def test_cfg_exclude_component_dict(self):
"""Verify that exclude component checkout works with a dictionary
"""
# create the top level externals file
desc = self.setup_dict_config()
# Test an excluded repo
external = create_externals_description(desc, model_format='dict',
exclude=['simp_tag',
'simp_opt'])
self.assertIsInstance(external, ExternalsDescriptionDict)
self.assertFalse('simp_tag' in external)
self.assertTrue('simp_branch' in external)
self.assertFalse('simp_opt' in external)
self.assertTrue('mixed_req' in external)
def test_cfg_opt_component_dict(self):
"""Verify that exclude component checkout works with a dictionary
"""
# create the top level externals file
desc = self.setup_dict_config()
# Test an excluded repo
external = create_externals_description(desc, model_format='dict',
components=['simp_tag',
'simp_opt'])
self.assertIsInstance(external, ExternalsDescriptionDict)
self.assertTrue('simp_tag' in external)
self.assertFalse('simp_branch' in external)
self.assertTrue('simp_opt' in external)
self.assertFalse('mixed_req' in external)
def test_cfg_unknown_version(self):
"""Test that a runtime error is raised when an unknown file version is
received
"""
self._config.set(DESCRIPTION_SECTION, VERSION_ITEM, '123.456.789')
with self.assertRaises(RuntimeError):
create_externals_description(self._config, model_format='cfg')
def test_cfg_unknown_format(self):
"""Test that a runtime error is raised when an unknown format string is
received
"""
with self.assertRaises(RuntimeError):
create_externals_description(self._config, model_format='unknown')
if __name__ == '__main__':
unittest.main()