Qinusty pushed to branch master at BuildStream / buildstream
Commits:
-
3c8646e3
by Josh Smith at 2018-08-29T16:34:22Z
-
d41c42d6
by Josh Smith at 2018-08-29T16:34:22Z
-
c7b72c30
by Qinusty at 2018-08-29T16:58:48Z
8 changed files:
- .gitlab-ci.yml
- buildstream/_exceptions.py
- buildstream/_variables.py
- dev-requirements.txt
- tests/format/variables.py
- + tests/format/variables/cyclic_variables/cyclic.bst
- + tests/format/variables/cyclic_variables/project.conf
- tests/testutils/runcli.py
Changes:
| ... | ... | @@ -84,25 +84,25 @@ source_dist: |
| 84 | 84 |
- coverage-linux/
|
| 85 | 85 |
|
| 86 | 86 |
tests-debian-9:
|
| 87 |
- image: buildstream/testsuite-debian:9-master-114-4cab18e3
|
|
| 87 |
+ image: buildstream/testsuite-debian:9-master-117-aa3a33b3
|
|
| 88 | 88 |
<<: *linux-tests
|
| 89 | 89 |
|
| 90 | 90 |
tests-fedora-27:
|
| 91 |
- image: buildstream/testsuite-fedora:27-master-114-4cab18e3
|
|
| 91 |
+ image: buildstream/testsuite-fedora:27-master-117-aa3a33b3
|
|
| 92 | 92 |
<<: *linux-tests
|
| 93 | 93 |
|
| 94 | 94 |
tests-fedora-28:
|
| 95 |
- image: buildstream/testsuite-fedora:28-master-114-4cab18e3
|
|
| 95 |
+ image: buildstream/testsuite-fedora:28-master-117-aa3a33b3
|
|
| 96 | 96 |
<<: *linux-tests
|
| 97 | 97 |
|
| 98 | 98 |
tests-ubuntu-18.04:
|
| 99 |
- image: buildstream/testsuite-ubuntu:18.04-master-114-4cab18e3
|
|
| 99 |
+ image: buildstream/testsuite-ubuntu:18.04-master-117-aa3a33b3
|
|
| 100 | 100 |
<<: *linux-tests
|
| 101 | 101 |
|
| 102 | 102 |
tests-unix:
|
| 103 | 103 |
# Use fedora here, to a) run a test on fedora and b) ensure that we
|
| 104 | 104 |
# can get rid of ostree - this is not possible with debian-8
|
| 105 |
- image: buildstream/testsuite-fedora:27-master-114-4cab18e3
|
|
| 105 |
+ image: buildstream/testsuite-fedora:27-master-117-aa3a33b3
|
|
| 106 | 106 |
stage: test
|
| 107 | 107 |
variables:
|
| 108 | 108 |
BST_FORCE_BACKEND: "unix"
|
| ... | ... | @@ -217,6 +217,9 @@ class LoadErrorReason(Enum): |
| 217 | 217 |
# A recursive include has been encountered.
|
| 218 | 218 |
RECURSIVE_INCLUDE = 21
|
| 219 | 219 |
|
| 220 |
+ # A recursive variable has been encountered
|
|
| 221 |
+ RECURSIVE_VARIABLE = 22
|
|
| 222 |
+ |
|
| 220 | 223 |
|
| 221 | 224 |
# LoadError
|
| 222 | 225 |
#
|
| ... | ... | @@ -61,7 +61,7 @@ class Variables(): |
| 61 | 61 |
# LoadError, if the string contains unresolved variable references.
|
| 62 | 62 |
#
|
| 63 | 63 |
def subst(self, string):
|
| 64 |
- substitute, unmatched = self._subst(string, self.variables)
|
|
| 64 |
+ substitute, unmatched, _ = self._subst(string, self.variables)
|
|
| 65 | 65 |
unmatched = list(set(unmatched))
|
| 66 | 66 |
if unmatched:
|
| 67 | 67 |
if len(unmatched) == 1:
|
| ... | ... | @@ -82,6 +82,7 @@ class Variables(): |
| 82 | 82 |
def subst_callback(match):
|
| 83 | 83 |
nonlocal variables
|
| 84 | 84 |
nonlocal unmatched
|
| 85 |
+ nonlocal matched
|
|
| 85 | 86 |
|
| 86 | 87 |
token = match.group(0)
|
| 87 | 88 |
varname = match.group(1)
|
| ... | ... | @@ -91,6 +92,7 @@ class Variables(): |
| 91 | 92 |
# We have to check if the inner string has variables
|
| 92 | 93 |
# and return unmatches for those
|
| 93 | 94 |
unmatched += re.findall(_VARIABLE_MATCH, value)
|
| 95 |
+ matched += [varname]
|
|
| 94 | 96 |
else:
|
| 95 | 97 |
# Return unmodified token
|
| 96 | 98 |
unmatched += [varname]
|
| ... | ... | @@ -98,10 +100,11 @@ class Variables(): |
| 98 | 100 |
|
| 99 | 101 |
return value
|
| 100 | 102 |
|
| 103 |
+ matched = []
|
|
| 101 | 104 |
unmatched = []
|
| 102 | 105 |
replacement = re.sub(_VARIABLE_MATCH, subst_callback, string)
|
| 103 | 106 |
|
| 104 |
- return (replacement, unmatched)
|
|
| 107 |
+ return (replacement, unmatched, matched)
|
|
| 105 | 108 |
|
| 106 | 109 |
# Variable resolving code
|
| 107 | 110 |
#
|
| ... | ... | @@ -131,7 +134,15 @@ class Variables(): |
| 131 | 134 |
# Ensure stringness of the value before substitution
|
| 132 | 135 |
value = _yaml.node_get(variables, str, key)
|
| 133 | 136 |
|
| 134 |
- resolved_var, item_unmatched = self._subst(value, variables)
|
|
| 137 |
+ resolved_var, item_unmatched, matched = self._subst(value, variables)
|
|
| 138 |
+ |
|
| 139 |
+ if _wrap_variable(key) in resolved_var:
|
|
| 140 |
+ referenced_through = find_recursive_variable(key, matched, variables)
|
|
| 141 |
+ raise LoadError(LoadErrorReason.RECURSIVE_VARIABLE,
|
|
| 142 |
+ "{}: ".format(_yaml.node_get_provenance(variables, key)) +
|
|
| 143 |
+ ("Variable '{}' expands to contain a reference to itself. " +
|
|
| 144 |
+ "Perhaps '{}' contains '{}").format(key, referenced_through, _wrap_variable(key)))
|
|
| 145 |
+ |
|
| 135 | 146 |
resolved[key] = resolved_var
|
| 136 | 147 |
unmatched += item_unmatched
|
| 137 | 148 |
|
| ... | ... | @@ -168,8 +179,21 @@ class Variables(): |
| 168 | 179 |
# Helper function to fetch information about the node referring to a variable
|
| 169 | 180 |
#
|
| 170 | 181 |
def _find_references(self, varname):
|
| 171 |
- fullname = '%{' + varname + '}'
|
|
| 182 |
+ fullname = _wrap_variable(varname)
|
|
| 172 | 183 |
for key, value in _yaml.node_items(self.original):
|
| 173 | 184 |
if fullname in value:
|
| 174 | 185 |
provenance = _yaml.node_get_provenance(self.original, key)
|
| 175 | 186 |
yield (key, provenance)
|
| 187 |
+ |
|
| 188 |
+ |
|
| 189 |
+def find_recursive_variable(variable, matched_variables, all_vars):
|
|
| 190 |
+ matched_values = (_yaml.node_get(all_vars, str, key) for key in matched_variables)
|
|
| 191 |
+ for key, value in zip(matched_variables, matched_values):
|
|
| 192 |
+ if _wrap_variable(variable) in value:
|
|
| 193 |
+ return key
|
|
| 194 |
+ else:
|
|
| 195 |
+ return None
|
|
| 196 |
+ |
|
| 197 |
+ |
|
| 198 |
+def _wrap_variable(var):
|
|
| 199 |
+ return "%{" + var + "}"
|
| ... | ... | @@ -8,3 +8,4 @@ pytest-env |
| 8 | 8 |
pytest-pep8
|
| 9 | 9 |
pytest-pylint
|
| 10 | 10 |
pytest-xdist
|
| 11 |
+pytest-timeout
|
| 1 | 1 |
import os
|
| 2 | 2 |
import pytest
|
| 3 |
+import sys
|
|
| 3 | 4 |
from buildstream import _yaml
|
| 4 | 5 |
from buildstream._exceptions import ErrorDomain, LoadErrorReason
|
| 5 | 6 |
from tests.testutils.runcli import cli
|
| ... | ... | @@ -72,3 +73,20 @@ def test_missing_variable(cli, datafiles, tmpdir): |
| 72 | 73 |
])
|
| 73 | 74 |
result.assert_main_error(ErrorDomain.LOAD,
|
| 74 | 75 |
LoadErrorReason.UNRESOLVED_VARIABLE)
|
| 76 |
+ |
|
| 77 |
+ |
|
| 78 |
+@pytest.mark.timeout(3, method="signal")
|
|
| 79 |
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'cyclic_variables'))
|
|
| 80 |
+def test_cyclic_variables(cli, datafiles):
|
|
| 81 |
+ print_warning("Performing cyclic test, if this test times out it will " +
|
|
| 82 |
+ "exit the test sequence")
|
|
| 83 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 84 |
+ result = cli.run(project=project, silent=True, args=[
|
|
| 85 |
+ "build", "cyclic.bst"
|
|
| 86 |
+ ])
|
|
| 87 |
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.RECURSIVE_VARIABLE)
|
|
| 88 |
+ |
|
| 89 |
+ |
|
| 90 |
+def print_warning(msg):
|
|
| 91 |
+ RED, END = "\033[91m", "\033[0m"
|
|
| 92 |
+ print(("\n{}{}{}").format(RED, msg, END), file=sys.stderr)
|
| 1 |
+kind: manual
|
|
| 2 |
+ |
|
| 3 |
+variables:
|
|
| 4 |
+ a: "%{prefix}/a"
|
|
| 5 |
+ prefix: "%{a}/some_prefix/"
|
|
| \ No newline at end of file |
| 1 |
+name: test
|
| ... | ... | @@ -94,14 +94,28 @@ class Result(): |
| 94 | 94 |
# error_reason (any): The reason field of the error which occurred
|
| 95 | 95 |
# fail_message (str): An optional message to override the automatic
|
| 96 | 96 |
# assertion error messages
|
| 97 |
+ # debug (bool): If true, prints information regarding the exit state of the result()
|
|
| 97 | 98 |
# Raises:
|
| 98 | 99 |
# (AssertionError): If any of the assertions fail
|
| 99 | 100 |
#
|
| 100 | 101 |
def assert_main_error(self,
|
| 101 | 102 |
error_domain,
|
| 102 | 103 |
error_reason,
|
| 103 |
- fail_message=''):
|
|
| 104 |
- |
|
| 104 |
+ fail_message='',
|
|
| 105 |
+ *, debug=False):
|
|
| 106 |
+ if debug:
|
|
| 107 |
+ print(
|
|
| 108 |
+ """
|
|
| 109 |
+ Exit code: {}
|
|
| 110 |
+ Exception: {}
|
|
| 111 |
+ Domain: {}
|
|
| 112 |
+ Reason: {}
|
|
| 113 |
+ """.format(
|
|
| 114 |
+ self.exit_code,
|
|
| 115 |
+ self.exception,
|
|
| 116 |
+ self.exception.domain,
|
|
| 117 |
+ self.exception.reason
|
|
| 118 |
+ ))
|
|
| 105 | 119 |
assert self.exit_code == -1, fail_message
|
| 106 | 120 |
assert self.exc is not None, fail_message
|
| 107 | 121 |
assert self.exception is not None, fail_message
|
