Bug 1023790 - [manifestparser] Add support for parent link. r=hskupin, r=jmaher

This commit is contained in:
Andrei Eftimie 2014-10-02 14:23:21 +02:00
parent 0a58c01b3c
commit 76452a6326
15 changed files with 183 additions and 14 deletions

View File

@ -124,6 +124,13 @@ You can also include other manifests:
[include:subdir/anothermanifest.ini]
And reference parent manifests to inherit keys and values from the DEFAULT
section, without adding possible included tests.
.. code-block:: text
[parent:../manifest.ini]
Manifests are included relative to the directory of the manifest with
the `[include:]` directive unless they are absolute paths.
@ -140,6 +147,22 @@ must start on a new line, inline comments are not supported.
In the example above, the 'color' property will have the value 'red #
not a valid comment'.
Special variable server-root
````````````````````````````
There is a special variable called `server-root` used for paths on the system.
This variable is deemed a path and will be expanded into its absolute form.
Because of the inheritant nature of the key/value pairs, if one requires a
system path, it must be absolute for it to be of any use in any included file.
.. code-block:: text
[DEFAULTS]
server-root = ../data
[test1.js]
server-root = test1/data
Manifest Conditional Expressions
````````````````````````````````
The conditional expressions used in manifests are parsed using the *ExpressionParser* class.

View File

@ -386,6 +386,13 @@ def read_ini(fp, variables=None, default='DEFAULT',
local_dict['skip-if'] = "(%s) || (%s)" % (variables['skip-if'].split('#')[0], local_dict['skip-if'].split('#')[0])
variables.update(local_dict)
# server-root is an os path declared relative to the manifest file.
# inheritance demands we expand it as absolute
if 'server-root' in variables:
root = os.path.join(os.path.dirname(fp.name),
variables['server-root'])
variables['server-root'] = os.path.abspath(root)
return variables
sections = [(i, interpret_variables(variables, j)) for i, j in sections]
@ -399,6 +406,7 @@ class ManifestParser(object):
def __init__(self, manifests=(), defaults=None, strict=True):
self._defaults = defaults or {}
self._ancestor_defaults = {}
self.tests = []
self.manifest_defaults = {}
self.strict = strict
@ -412,7 +420,30 @@ class ManifestParser(object):
### methods for reading manifests
def _read(self, root, filename, defaults):
def _read(self, root, filename, defaults, defaults_only=False):
"""
Internal recursive method for reading and parsing manifests.
Stores all found tests in self.tests
:param root: The base path
:param filename: File object or string path for the base manifest file
:param defaults: Options that apply to all items
:param defaults_only: If True will only gather options, not include
tests. Used for upstream parent includes
(default False)
"""
def read_file(type):
include_file = section.split(type, 1)[-1]
include_file = normalize_path(include_file)
if not os.path.isabs(include_file):
include_file = os.path.join(self.getRelativeRoot(here), include_file)
if not os.path.exists(include_file):
message = "Included file '%s' does not exist" % include_file
if self.strict:
raise IOError(message)
else:
sys.stderr.write("%s\n" % message)
return
return include_file
# get directory of this file if not file-like object
if isinstance(filename, string):
@ -442,26 +473,32 @@ class ManifestParser(object):
if 'subsuite' in data:
subsuite = data['subsuite']
# read the parent manifest if specified
if section.startswith('parent:'):
include_file = read_file('parent:')
if include_file:
self._read(root, include_file, {}, True)
continue
# If this is a parent include we only load the defaults into ancestor
if defaults_only:
self._ancestor_defaults = dict(data.items() + self._ancestor_defaults.items())
break
# a file to include
# TODO: keep track of included file structure:
# self.manifests = {'manifest.ini': 'relative/path.ini'}
if section.startswith('include:'):
include_file = section.split('include:', 1)[-1]
include_file = normalize_path(include_file)
if not os.path.isabs(include_file):
include_file = os.path.join(self.getRelativeRoot(here), include_file)
if not os.path.exists(include_file):
message = "Included file '%s' does not exist" % include_file
if self.strict:
raise IOError(message)
else:
sys.stderr.write("%s\n" % message)
continue
include_defaults = data.copy()
self._read(root, include_file, include_defaults)
include_file = read_file('include:')
if include_file:
include_defaults = data.copy()
self._read(root, include_file, include_defaults)
continue
# otherwise an item
# apply ancestor defaults, while maintaining current file priority
data = dict(self._ancestor_defaults.items() + data.items())
test = data
test['name'] = section

View File

@ -0,0 +1 @@
[include:invalid.ini]

View File

@ -0,0 +1,5 @@
[DEFAULT]
x = level_1
[test_1]
[test_2]

View File

@ -0,0 +1,5 @@
[DEFAULT]
server-root = ../root
other-root = ../root
[test_1]

View File

@ -0,0 +1,3 @@
[parent:../level_1.ini]
[test_2]

View File

@ -0,0 +1,3 @@
[parent:../level_1_server-root.ini]
[test_2]

View File

@ -0,0 +1,3 @@
[parent:../level_2.ini]
[test_3]

View File

@ -0,0 +1,6 @@
[parent:../level_2.ini]
[DEFAULT]
x = level_3
[test_3]

View File

@ -0,0 +1,3 @@
[parent:../level_2_server-root.ini]
[test_3]

View File

@ -0,0 +1 @@
# dummy spot for "test_3" test

View File

@ -0,0 +1 @@
# dummy spot for "test_2" test

View File

@ -0,0 +1 @@
# dummy spot for "test_1" test

View File

@ -106,6 +106,83 @@ class TestManifestParser(unittest.TestCase):
self.assertEqual(buffer.getvalue().strip(),
'[DEFAULT]\nfoo = bar\n\n[fleem]\nsubsuite = \n\n[include/flowers]\nblue = ocean\nred = roses\nsubsuite = \nyellow = submarine')
def test_invalid_path(self):
"""
Test invalid path should not throw when not strict
"""
manifest = os.path.join(here, 'include-invalid.ini')
parser = ManifestParser(manifests=(manifest,), strict=False)
def test_parent_inheritance(self):
"""
Test parent manifest variable inheritance
Specifically tests that inherited variables from parent includes
properly propagate downstream
"""
parent_example = os.path.join(here, 'parent', 'level_1', 'level_2',
'level_3', 'level_3.ini')
parser = ManifestParser(manifests=(parent_example,))
# Parent manifest test should not be included
self.assertEqual(parser.get('name'),
['test_3'])
self.assertEqual([(test['name'], os.path.basename(test['manifest'])) for test in parser.tests],
[('test_3', 'level_3.ini')])
# DEFAULT values should be the ones from level 1
self.assertEqual(parser.get('name', x='level_1'),
['test_3'])
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'x': 'level_1'})
self.assertEqual(buffer.getvalue().strip(),
'[DEFAULT]\nx = level_1\n\n[test_3]\nsubsuite =')
def test_parent_defaults(self):
"""
Test downstream variables should overwrite upstream variables
"""
parent_example = os.path.join(here, 'parent', 'level_1', 'level_2',
'level_3', 'level_3_default.ini')
parser = ManifestParser(manifests=(parent_example,))
# Parent manifest test should not be included
self.assertEqual(parser.get('name'),
['test_3'])
self.assertEqual([(test['name'], os.path.basename(test['manifest'])) for test in parser.tests],
[('test_3', 'level_3_default.ini')])
# DEFAULT values should be the ones from level 3
self.assertEqual(parser.get('name', x='level_3'),
['test_3'])
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'x': 'level_3'})
self.assertEqual(buffer.getvalue().strip(),
'[DEFAULT]\nx = level_3\n\n[test_3]\nsubsuite =')
def test_server_root(self):
"""
Test server_root properly expands as an absolute path
"""
server_example = os.path.join(here, 'parent', 'level_1', 'level_2',
'level_3', 'level_3_server-root.ini')
parser = ManifestParser(manifests=(server_example,))
# A regular variable will inherit its value directly
self.assertEqual(parser.get('name', **{'other-root': '../root'}),
['test_3'])
# server-root will expand its value as an absolute path
# we will not find anything for the original value
self.assertEqual(parser.get('name', **{'server-root': '../root'}), [])
# check that the path has expanded
self.assertEqual(parser.get('server-root')[0],
os.path.join(here, 'parent', 'root'))
def test_copy(self):
"""Test our ability to copy a set of manifests"""