[Notes] [Git][BuildStream/buildstream][Qinusty/600-recursive-variables] 2 commits: Add cyclic check within variable resolution



Title: GitLab

Qinusty pushed to branch Qinusty/600-recursive-variables at BuildStream / buildstream

Commits:

7 changed files:

Changes:

  • buildstream/_exceptions.py
    ... ... @@ -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
     #
    

  • buildstream/_variables.py
    ... ... @@ -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,17 @@ 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
    +                print(key, value)
    
    139
    +                print(matched)
    
    140
    +                print(resolved_var)
    
    141
    +                if _wrap_variable(key) in resolved_var:
    
    142
    +                    referenced_through = find_recursive_variable(key, matched, variables)
    
    143
    +                    raise LoadError(LoadErrorReason.RECURSIVE_VARIABLE,
    
    144
    +                                    "{}: ".format(_yaml.node_get_provenance(variables, key)) +
    
    145
    +                                    ("Variable '{}' expands to contain a reference to itself. " +
    
    146
    +                                     "Perhaps '{}' contains '{}").format(key, referenced_through, _wrap_variable(key)))
    
    147
    +
    
    135 148
                     resolved[key] = resolved_var
    
    136 149
                     unmatched += item_unmatched
    
    137 150
     
    
    ... ... @@ -168,8 +181,20 @@ class Variables():
    168 181
         # Helper function to fetch information about the node referring to a variable
    
    169 182
         #
    
    170 183
         def _find_references(self, varname):
    
    171
    -        fullname = '%{' + varname + '}'
    
    184
    +        fullname = _wrap_variable(varname)
    
    172 185
             for key, value in _yaml.node_items(self.original):
    
    173 186
                 if fullname in value:
    
    174 187
                     provenance = _yaml.node_get_provenance(self.original, key)
    
    175 188
                     yield (key, provenance)
    
    189
    +
    
    190
    +
    
    191
    +def find_recursive_variable(variable, matched_variables, all_vars):
    
    192
    +    matched_values = (_yaml.node_get(all_vars, str, key) for key in matched_variables)
    
    193
    +    for key, value in zip(matched_variables, matched_values):
    
    194
    +        if _wrap_variable(variable) in value:
    
    195
    +            return key
    
    196
    +    else:
    
    197
    +        return None
    
    198
    +
    
    199
    +def _wrap_variable(var):
    
    200
    +    return "%{" + var + "}"

  • dev-requirements.txt
    ... ... @@ -8,3 +8,4 @@ pytest-env
    8 8
     pytest-pep8
    
    9 9
     pytest-pylint
    
    10 10
     pytest-xdist
    
    11
    +pytest-timeout

  • tests/format/variables.py
    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
    +@pytest.mark.timeout(3, method="signal")
    
    78
    +@pytest.mark.datafiles(os.path.join(DATA_DIR, 'cyclic_variables'))
    
    79
    +def test_cyclic_variables(cli, datafiles):
    
    80
    +    print_warning("Performing cyclic test, if this test times out it will " +
    
    81
    +                  "exit the test sequence")
    
    82
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    83
    +    result = cli.run(project=project, silent=True, args=[
    
    84
    +        "build", "cyclic.bst"
    
    85
    +    ])
    
    86
    +    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.RECURSIVE_VARIABLE)
    
    87
    +
    
    88
    +
    
    89
    +def print_warning(msg):
    
    90
    +    RED, END = "\033[91m", "\033[0m"
    
    91
    +    print(("\n{}{}{}").format(RED, msg, END), file=sys.stderr)
    
    92
    +

  • tests/format/variables/cyclic_variables/cyclic.bst
    1
    +kind: manual
    
    2
    +
    
    3
    +variables:
    
    4
    +  a: "%{prefix}/a"
    
    5
    +  prefix: "%{a}/some_prefix/"
    \ No newline at end of file

  • tests/format/variables/cyclic_variables/project.conf
    1
    +name: test

  • tests/testutils/runcli.py
    ... ... @@ -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
    



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]