Daniel Silverstone pushed to branch danielsilverstone-ct/further-optimisations at BuildStream / buildstream
Commits:
-
1ed63e54
by Angelos Evripiotis at 2019-02-11T09:24:48Z
-
02e48209
by Angelos Evripiotis at 2019-02-11T09:24:48Z
-
4336e3bf
by Angelos Evripiotis at 2019-02-11T09:24:48Z
-
3f6c5000
by Angelos Evripiotis at 2019-02-11T09:24:48Z
-
adde0c94
by Angelos Evripiotis at 2019-02-11T09:24:48Z
-
a66f8379
by Jürg Billeter at 2019-02-11T13:52:54Z
-
a482b008
by Javier Jardón at 2019-02-11T19:23:02Z
-
0816b8b1
by Jürg Billeter at 2019-02-11T21:29:29Z
-
a7aed65a
by Jürg Billeter at 2019-02-12T05:52:07Z
-
8b9e1d24
by Jürg Billeter at 2019-02-12T05:52:07Z
-
86a9048a
by Jürg Billeter at 2019-02-12T07:08:18Z
-
4bf6c4e0
by Daniel Silverstone at 2019-02-12T08:43:33Z
-
9fa3fbdc
by Daniel Silverstone at 2019-02-12T08:43:33Z
-
32a3b2b8
by Daniel Silverstone at 2019-02-12T08:43:33Z
-
8af554d6
by Daniel Silverstone at 2019-02-12T08:43:33Z
-
41d27be7
by Daniel Silverstone at 2019-02-12T08:43:33Z
-
4fe126b4
by Daniel Silverstone at 2019-02-12T09:02:42Z
8 changed files:
- .gitlab-ci.yml
- buildstream/_cachekey.py
- buildstream/_cas/cascache.py
- buildstream/_includes.py
- buildstream/_yaml.py
- buildstream/element.py
- buildstream/utils.py
- tests/format/include.py
Changes:
... | ... | @@ -166,6 +166,7 @@ tests-wsl: |
166 | 166 |
|
167 | 167 |
script:
|
168 | 168 |
- "${TEST_COMMAND}"
|
169 |
+ when: manual
|
|
169 | 170 |
|
170 | 171 |
# Automatically build documentation for every commit, we want to know
|
171 | 172 |
# if building documentation fails even if we're not deploying it.
|
... | ... | @@ -40,3 +40,20 @@ def generate_key(value): |
40 | 40 |
ordered = _yaml.node_sanitize(value)
|
41 | 41 |
string = pickle.dumps(ordered)
|
42 | 42 |
return hashlib.sha256(string).hexdigest()
|
43 |
+ |
|
44 |
+ |
|
45 |
+# generate_key_pre_sanitized()
|
|
46 |
+#
|
|
47 |
+# Generate an sha256 hex digest from the given value. The value
|
|
48 |
+# must be (a) compatible with generate_key() and (b) already have
|
|
49 |
+# been passed through _yaml.node_sanitize()
|
|
50 |
+#
|
|
51 |
+# Args:
|
|
52 |
+# value: A sanitized value to get a key for
|
|
53 |
+#
|
|
54 |
+# Returns:
|
|
55 |
+# (str): An sha256 hex digest of the given value
|
|
56 |
+#
|
|
57 |
+def generate_key_pre_sanitized(value):
|
|
58 |
+ string = pickle.dumps(value)
|
|
59 |
+ return hashlib.sha256(string).hexdigest()
|
... | ... | @@ -35,6 +35,8 @@ from .._exceptions import CASCacheError |
35 | 35 |
|
36 | 36 |
from .casremote import BlobNotFound, _CASBatchRead, _CASBatchUpdate
|
37 | 37 |
|
38 |
+_BUFFER_SIZE = 65536
|
|
39 |
+ |
|
38 | 40 |
|
39 | 41 |
# A CASCache manages a CAS repository as specified in the Remote Execution API.
|
40 | 42 |
#
|
... | ... | @@ -371,7 +373,7 @@ class CASCache(): |
371 | 373 |
with contextlib.ExitStack() as stack:
|
372 | 374 |
if path is not None and link_directly:
|
373 | 375 |
tmp = stack.enter_context(open(path, 'rb'))
|
374 |
- for chunk in iter(lambda: tmp.read(4096), b""):
|
|
376 |
+ for chunk in iter(lambda: tmp.read(_BUFFER_SIZE), b""):
|
|
375 | 377 |
h.update(chunk)
|
376 | 378 |
else:
|
377 | 379 |
tmp = stack.enter_context(utils._tempnamedfile(dir=self.tmpdir))
|
... | ... | @@ -380,7 +382,7 @@ class CASCache(): |
380 | 382 |
|
381 | 383 |
if path:
|
382 | 384 |
with open(path, 'rb') as f:
|
383 |
- for chunk in iter(lambda: f.read(4096), b""):
|
|
385 |
+ for chunk in iter(lambda: f.read(_BUFFER_SIZE), b""):
|
|
384 | 386 |
h.update(chunk)
|
385 | 387 |
tmp.write(chunk)
|
386 | 388 |
else:
|
... | ... | @@ -40,19 +40,34 @@ class Includes: |
40 | 40 |
includes = [_yaml.node_get(node, str, '(@)')]
|
41 | 41 |
else:
|
42 | 42 |
includes = _yaml.node_get(node, list, '(@)', default_value=None)
|
43 |
+ |
|
44 |
+ include_provenance = None
|
|
43 | 45 |
if '(@)' in node:
|
46 |
+ include_provenance = _yaml.node_get_provenance(node, key='(@)')
|
|
44 | 47 |
del node['(@)']
|
45 | 48 |
|
46 | 49 |
if includes:
|
47 | 50 |
for include in reversed(includes):
|
48 | 51 |
if only_local and ':' in include:
|
49 | 52 |
continue
|
50 |
- include_node, file_path, sub_loader = self._include_file(include,
|
|
51 |
- current_loader)
|
|
53 |
+ try:
|
|
54 |
+ include_node, file_path, sub_loader = self._include_file(include,
|
|
55 |
+ current_loader)
|
|
56 |
+ except LoadError as e:
|
|
57 |
+ if e.reason == LoadErrorReason.MISSING_FILE:
|
|
58 |
+ message = "{}: Include block references a file that could not be found: '{}'.".format(
|
|
59 |
+ include_provenance, include)
|
|
60 |
+ raise LoadError(LoadErrorReason.MISSING_FILE, message) from e
|
|
61 |
+ elif e.reason == LoadErrorReason.LOADING_DIRECTORY:
|
|
62 |
+ message = "{}: Include block references a directory instead of a file: '{}'.".format(
|
|
63 |
+ include_provenance, include)
|
|
64 |
+ raise LoadError(LoadErrorReason.LOADING_DIRECTORY, message) from e
|
|
65 |
+ else:
|
|
66 |
+ raise
|
|
67 |
+ |
|
52 | 68 |
if file_path in included:
|
53 |
- provenance = _yaml.node_get_provenance(node)
|
|
54 | 69 |
raise LoadError(LoadErrorReason.RECURSIVE_INCLUDE,
|
55 |
- "{}: trying to recursively include {}". format(provenance,
|
|
70 |
+ "{}: trying to recursively include {}". format(include_provenance,
|
|
56 | 71 |
file_path))
|
57 | 72 |
# Because the included node will be modified, we need
|
58 | 73 |
# to copy it so that we do not modify the toplevel
|
... | ... | @@ -101,7 +116,7 @@ class Includes: |
101 | 116 |
file_path = os.path.join(directory, include)
|
102 | 117 |
key = (current_loader, file_path)
|
103 | 118 |
if key not in self._loaded:
|
104 |
- self._loaded[key] = _yaml.load(os.path.join(directory, include),
|
|
119 |
+ self._loaded[key] = _yaml.load(file_path,
|
|
105 | 120 |
shortname=shortname,
|
106 | 121 |
project=project,
|
107 | 122 |
copy_tree=self._copy_tree)
|
... | ... | @@ -365,8 +365,8 @@ _sentinel = object() |
365 | 365 |
#
|
366 | 366 |
def node_get(node, expected_type, key, indices=None, *, default_value=_sentinel, allow_none=False):
|
367 | 367 |
value = node.get(key, default_value)
|
368 |
- provenance = node_get_provenance(node)
|
|
369 | 368 |
if value is _sentinel:
|
369 |
+ provenance = node_get_provenance(node)
|
|
370 | 370 |
raise LoadError(LoadErrorReason.INVALID_DATA,
|
371 | 371 |
"{}: Dictionary did not contain expected key '{}'".format(provenance, key))
|
372 | 372 |
|
... | ... | @@ -922,9 +922,20 @@ RoundTripRepresenter.add_representer(SanitizedDict, |
922 | 922 |
# Only dicts are ordered, list elements are left in order.
|
923 | 923 |
#
|
924 | 924 |
def node_sanitize(node):
|
925 |
+ # Short-circuit None which occurs ca. twice per element
|
|
926 |
+ if node is None:
|
|
927 |
+ return node
|
|
928 |
+ |
|
929 |
+ node_type = type(node)
|
|
930 |
+ # Next short-circuit integers, floats, strings, booleans, and tuples
|
|
931 |
+ if node_type in (int, float, str, bool, tuple):
|
|
932 |
+ return node
|
|
933 |
+ # Now short-circuit lists
|
|
934 |
+ elif node_type is list:
|
|
935 |
+ return [node_sanitize(elt) for elt in node]
|
|
925 | 936 |
|
926 |
- if isinstance(node, collections.abc.Mapping):
|
|
927 |
- |
|
937 |
+ # Finally ChainMap and dict, and other Mappings need special handling
|
|
938 |
+ if node_type in (dict, ChainMap) or isinstance(node, collections.Mapping):
|
|
928 | 939 |
result = SanitizedDict()
|
929 | 940 |
|
930 | 941 |
key_list = [key for key, _ in node_items(node)]
|
... | ... | @@ -932,10 +943,10 @@ def node_sanitize(node): |
932 | 943 |
result[key] = node_sanitize(node[key])
|
933 | 944 |
|
934 | 945 |
return result
|
935 |
- |
|
936 | 946 |
elif isinstance(node, list):
|
937 | 947 |
return [node_sanitize(elt) for elt in node]
|
938 | 948 |
|
949 |
+ # Everything else (such as commented scalars) just gets returned as-is.
|
|
939 | 950 |
return node
|
940 | 951 |
|
941 | 952 |
|
... | ... | @@ -1064,15 +1075,48 @@ class ChainMap(collections.ChainMap): |
1064 | 1075 |
return default
|
1065 | 1076 |
|
1066 | 1077 |
|
1078 |
+# Node copying
|
|
1079 |
+#
|
|
1080 |
+# Unfortunately we copy nodes a *lot* and `isinstance()` is super-slow when
|
|
1081 |
+# things from collections.abc get involved. The result is the following
|
|
1082 |
+# intricate but substantially faster group of tuples and the use of `in`.
|
|
1083 |
+#
|
|
1084 |
+# If any of the {node,list}_{chain_,}_copy routines raise a ValueError
|
|
1085 |
+# then it's likely additional types need adding to these tuples.
|
|
1086 |
+ |
|
1087 |
+# When chaining a copy, these types are skipped since the ChainMap will
|
|
1088 |
+# retrieve them from the source node when needed. Other copiers might copy
|
|
1089 |
+# them, so we call them __quick_types.
|
|
1090 |
+__quick_types = (str, bool,
|
|
1091 |
+ yaml.scalarstring.PreservedScalarString,
|
|
1092 |
+ yaml.scalarstring.SingleQuotedScalarString,
|
|
1093 |
+ yaml.scalarstring.DoubleQuotedScalarString)
|
|
1094 |
+ |
|
1095 |
+# These types have to be iterated like a dictionary
|
|
1096 |
+__dict_types = (dict, ChainMap, yaml.comments.CommentedMap)
|
|
1097 |
+ |
|
1098 |
+# These types have to be iterated like a list
|
|
1099 |
+__list_types = (list, yaml.comments.CommentedSeq)
|
|
1100 |
+ |
|
1101 |
+# These are the provenance types, which have to be cloned rather than any other
|
|
1102 |
+# copying tactic.
|
|
1103 |
+__provenance_types = (Provenance, DictProvenance, MemberProvenance, ElementProvenance)
|
|
1104 |
+ |
|
1105 |
+ |
|
1067 | 1106 |
def node_chain_copy(source):
|
1068 | 1107 |
copy = ChainMap({}, source)
|
1069 | 1108 |
for key, value in source.items():
|
1070 |
- if isinstance(value, collections.abc.Mapping):
|
|
1109 |
+ value_type = type(value)
|
|
1110 |
+ if value_type in __dict_types:
|
|
1071 | 1111 |
copy[key] = node_chain_copy(value)
|
1072 |
- elif isinstance(value, list):
|
|
1112 |
+ elif value_type in __list_types:
|
|
1073 | 1113 |
copy[key] = list_chain_copy(value)
|
1074 |
- elif isinstance(value, Provenance):
|
|
1114 |
+ elif value_type in __provenance_types:
|
|
1075 | 1115 |
copy[key] = value.clone()
|
1116 |
+ elif value_type in __quick_types:
|
|
1117 |
+ pass # No need to copy these, the chainmap deals with it
|
|
1118 |
+ else:
|
|
1119 |
+ raise ValueError("Unable to be quick about node_chain_copy of {}".format(value_type))
|
|
1076 | 1120 |
|
1077 | 1121 |
return copy
|
1078 | 1122 |
|
... | ... | @@ -1080,14 +1124,17 @@ def node_chain_copy(source): |
1080 | 1124 |
def list_chain_copy(source):
|
1081 | 1125 |
copy = []
|
1082 | 1126 |
for item in source:
|
1083 |
- if isinstance(item, collections.abc.Mapping):
|
|
1127 |
+ item_type = type(item)
|
|
1128 |
+ if item_type in __dict_types:
|
|
1084 | 1129 |
copy.append(node_chain_copy(item))
|
1085 |
- elif isinstance(item, list):
|
|
1130 |
+ elif item_type in __list_types:
|
|
1086 | 1131 |
copy.append(list_chain_copy(item))
|
1087 |
- elif isinstance(item, Provenance):
|
|
1132 |
+ elif item_type in __provenance_types:
|
|
1088 | 1133 |
copy.append(item.clone())
|
1089 |
- else:
|
|
1134 |
+ elif item_type in __quick_types:
|
|
1090 | 1135 |
copy.append(item)
|
1136 |
+ else: # Fallback
|
|
1137 |
+ raise ValueError("Unable to be quick about list_chain_copy of {}".format(item_type))
|
|
1091 | 1138 |
|
1092 | 1139 |
return copy
|
1093 | 1140 |
|
... | ... | @@ -1095,14 +1142,17 @@ def list_chain_copy(source): |
1095 | 1142 |
def node_copy(source):
|
1096 | 1143 |
copy = {}
|
1097 | 1144 |
for key, value in source.items():
|
1098 |
- if isinstance(value, collections.abc.Mapping):
|
|
1145 |
+ value_type = type(value)
|
|
1146 |
+ if value_type in __dict_types:
|
|
1099 | 1147 |
copy[key] = node_copy(value)
|
1100 |
- elif isinstance(value, list):
|
|
1148 |
+ elif value_type in __list_types:
|
|
1101 | 1149 |
copy[key] = list_copy(value)
|
1102 |
- elif isinstance(value, Provenance):
|
|
1150 |
+ elif value_type in __provenance_types:
|
|
1103 | 1151 |
copy[key] = value.clone()
|
1104 |
- else:
|
|
1152 |
+ elif value_type in __quick_types:
|
|
1105 | 1153 |
copy[key] = value
|
1154 |
+ else:
|
|
1155 |
+ raise ValueError("Unable to be quick about node_copy of {}".format(value_type))
|
|
1106 | 1156 |
|
1107 | 1157 |
ensure_provenance(copy)
|
1108 | 1158 |
|
... | ... | @@ -1112,18 +1162,25 @@ def node_copy(source): |
1112 | 1162 |
def list_copy(source):
|
1113 | 1163 |
copy = []
|
1114 | 1164 |
for item in source:
|
1115 |
- if isinstance(item, collections.abc.Mapping):
|
|
1165 |
+ item_type = type(item)
|
|
1166 |
+ if item_type in __dict_types:
|
|
1116 | 1167 |
copy.append(node_copy(item))
|
1117 |
- elif isinstance(item, list):
|
|
1168 |
+ elif item_type in __list_types:
|
|
1118 | 1169 |
copy.append(list_copy(item))
|
1119 |
- elif isinstance(item, Provenance):
|
|
1170 |
+ elif item_type in __provenance_types:
|
|
1120 | 1171 |
copy.append(item.clone())
|
1121 |
- else:
|
|
1172 |
+ elif item_type in __quick_types:
|
|
1122 | 1173 |
copy.append(item)
|
1174 |
+ else:
|
|
1175 |
+ raise ValueError("Unable to be quick about list_copy of {}".format(item_type))
|
|
1123 | 1176 |
|
1124 | 1177 |
return copy
|
1125 | 1178 |
|
1126 | 1179 |
|
1180 |
+# Helpers for the assertions
|
|
1181 |
+__node_assert_composition_directives = ('(>)', '(<)', '(=)')
|
|
1182 |
+ |
|
1183 |
+ |
|
1127 | 1184 |
# node_final_assertions()
|
1128 | 1185 |
#
|
1129 | 1186 |
# This must be called on a fully loaded and composited node,
|
... | ... | @@ -1142,22 +1199,26 @@ def node_final_assertions(node): |
1142 | 1199 |
# indicates that the user intended to override a list which
|
1143 | 1200 |
# never existed in the underlying data
|
1144 | 1201 |
#
|
1145 |
- if key in ['(>)', '(<)', '(=)']:
|
|
1202 |
+ if key in __node_assert_composition_directives:
|
|
1146 | 1203 |
provenance = node_get_provenance(node, key)
|
1147 | 1204 |
raise LoadError(LoadErrorReason.TRAILING_LIST_DIRECTIVE,
|
1148 | 1205 |
"{}: Attempt to override non-existing list".format(provenance))
|
1149 | 1206 |
|
1150 |
- if isinstance(value, collections.abc.Mapping):
|
|
1207 |
+ value_type = type(value)
|
|
1208 |
+ |
|
1209 |
+ if value_type in __dict_types:
|
|
1151 | 1210 |
node_final_assertions(value)
|
1152 |
- elif isinstance(value, list):
|
|
1211 |
+ elif value_type in __list_types:
|
|
1153 | 1212 |
list_final_assertions(value)
|
1154 | 1213 |
|
1155 | 1214 |
|
1156 | 1215 |
def list_final_assertions(values):
|
1157 | 1216 |
for value in values:
|
1158 |
- if isinstance(value, collections.abc.Mapping):
|
|
1217 |
+ value_type = type(value)
|
|
1218 |
+ |
|
1219 |
+ if value_type in __dict_types:
|
|
1159 | 1220 |
node_final_assertions(value)
|
1160 |
- elif isinstance(value, list):
|
|
1221 |
+ elif value_type in __list_types:
|
|
1161 | 1222 |
list_final_assertions(value)
|
1162 | 1223 |
|
1163 | 1224 |
|
... | ... | @@ -2108,11 +2108,14 @@ class Element(Plugin): |
2108 | 2108 |
}
|
2109 | 2109 |
|
2110 | 2110 |
self.__cache_key_dict['fatal-warnings'] = sorted(project._fatal_warnings)
|
2111 |
+ self.__cache_key_dict['dependencies'] = []
|
|
2112 |
+ self.__cache_key_dict = _yaml.node_sanitize(self.__cache_key_dict)
|
|
2111 | 2113 |
|
2112 |
- cache_key_dict = self.__cache_key_dict.copy()
|
|
2113 |
- cache_key_dict['dependencies'] = dependencies
|
|
2114 |
+ # This replacement is safe since OrderedDict replaces the value,
|
|
2115 |
+ # leaving its location in the dictionary alone.
|
|
2116 |
+ self.__cache_key_dict['dependencies'] = _yaml.node_sanitize(dependencies)
|
|
2114 | 2117 |
|
2115 |
- return _cachekey.generate_key(cache_key_dict)
|
|
2118 |
+ return _cachekey.generate_key_pre_sanitized(self.__cache_key_dict)
|
|
2116 | 2119 |
|
2117 | 2120 |
# __can_build_incrementally()
|
2118 | 2121 |
#
|
... | ... | @@ -235,7 +235,7 @@ def sha256sum(filename): |
235 | 235 |
try:
|
236 | 236 |
h = hashlib.sha256()
|
237 | 237 |
with open(filename, "rb") as f:
|
238 |
- for chunk in iter(lambda: f.read(4096), b""):
|
|
238 |
+ for chunk in iter(lambda: f.read(65536), b""):
|
|
239 | 239 |
h.update(chunk)
|
240 | 240 |
|
241 | 241 |
except OSError as e:
|
1 | 1 |
import os
|
2 |
+import textwrap
|
|
2 | 3 |
import pytest
|
3 | 4 |
from buildstream import _yaml
|
4 | 5 |
from buildstream._exceptions import ErrorDomain, LoadErrorReason
|
... | ... | @@ -27,6 +28,46 @@ def test_include_project_file(cli, datafiles): |
27 | 28 |
assert loaded['included'] == 'True'
|
28 | 29 |
|
29 | 30 |
|
31 |
+def test_include_missing_file(cli, tmpdir):
|
|
32 |
+ tmpdir.join('project.conf').write('{"name": "test"}')
|
|
33 |
+ element = tmpdir.join('include_missing_file.bst')
|
|
34 |
+ |
|
35 |
+ # Normally we would use dicts and _yaml.dump to write such things, but here
|
|
36 |
+ # we want to be sure of a stable line and column number.
|
|
37 |
+ element.write(textwrap.dedent("""
|
|
38 |
+ kind: manual
|
|
39 |
+ |
|
40 |
+ "(@)":
|
|
41 |
+ - nosuch.yaml
|
|
42 |
+ """).strip())
|
|
43 |
+ |
|
44 |
+ result = cli.run(project=str(tmpdir), args=['show', str(element.basename)])
|
|
45 |
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE)
|
|
46 |
+ # Make sure the root cause provenance is in the output.
|
|
47 |
+ assert 'line 4 column 2' in result.stderr
|
|
48 |
+ |
|
49 |
+ |
|
50 |
+def test_include_dir(cli, tmpdir):
|
|
51 |
+ tmpdir.join('project.conf').write('{"name": "test"}')
|
|
52 |
+ tmpdir.mkdir('subdir')
|
|
53 |
+ element = tmpdir.join('include_dir.bst')
|
|
54 |
+ |
|
55 |
+ # Normally we would use dicts and _yaml.dump to write such things, but here
|
|
56 |
+ # we want to be sure of a stable line and column number.
|
|
57 |
+ element.write(textwrap.dedent("""
|
|
58 |
+ kind: manual
|
|
59 |
+ |
|
60 |
+ "(@)":
|
|
61 |
+ - subdir/
|
|
62 |
+ """).strip())
|
|
63 |
+ |
|
64 |
+ result = cli.run(project=str(tmpdir), args=['show', str(element.basename)])
|
|
65 |
+ result.assert_main_error(
|
|
66 |
+ ErrorDomain.LOAD, LoadErrorReason.LOADING_DIRECTORY)
|
|
67 |
+ # Make sure the root cause provenance is in the output.
|
|
68 |
+ assert 'line 4 column 2' in result.stderr
|
|
69 |
+ |
|
70 |
+ |
|
30 | 71 |
@pytest.mark.datafiles(DATA_DIR)
|
31 | 72 |
def test_include_junction_file(cli, tmpdir, datafiles):
|
32 | 73 |
project = os.path.join(str(datafiles), 'junction')
|
... | ... | @@ -47,7 +88,7 @@ def test_include_junction_file(cli, tmpdir, datafiles): |
47 | 88 |
|
48 | 89 |
|
49 | 90 |
@pytest.mark.datafiles(DATA_DIR)
|
50 |
-def test_include_junction_options(cli, tmpdir, datafiles):
|
|
91 |
+def test_include_junction_options(cli, datafiles):
|
|
51 | 92 |
project = os.path.join(str(datafiles), 'options')
|
52 | 93 |
|
53 | 94 |
result = cli.run(project=project, args=[
|
... | ... | @@ -128,7 +169,7 @@ def test_junction_element_not_partial_project_file(cli, tmpdir, datafiles): |
128 | 169 |
|
129 | 170 |
|
130 | 171 |
@pytest.mark.datafiles(DATA_DIR)
|
131 |
-def test_include_element_overrides(cli, tmpdir, datafiles):
|
|
172 |
+def test_include_element_overrides(cli, datafiles):
|
|
132 | 173 |
project = os.path.join(str(datafiles), 'overrides')
|
133 | 174 |
|
134 | 175 |
result = cli.run(project=project, args=[
|
... | ... | @@ -143,7 +184,7 @@ def test_include_element_overrides(cli, tmpdir, datafiles): |
143 | 184 |
|
144 | 185 |
|
145 | 186 |
@pytest.mark.datafiles(DATA_DIR)
|
146 |
-def test_include_element_overrides_composition(cli, tmpdir, datafiles):
|
|
187 |
+def test_include_element_overrides_composition(cli, datafiles):
|
|
147 | 188 |
project = os.path.join(str(datafiles), 'overrides')
|
148 | 189 |
|
149 | 190 |
result = cli.run(project=project, args=[
|
... | ... | @@ -158,7 +199,7 @@ def test_include_element_overrides_composition(cli, tmpdir, datafiles): |
158 | 199 |
|
159 | 200 |
|
160 | 201 |
@pytest.mark.datafiles(DATA_DIR)
|
161 |
-def test_include_element_overrides_sub_include(cli, tmpdir, datafiles):
|
|
202 |
+def test_include_element_overrides_sub_include(cli, datafiles):
|
|
162 | 203 |
project = os.path.join(str(datafiles), 'sub-include')
|
163 | 204 |
|
164 | 205 |
result = cli.run(project=project, args=[
|
... | ... | @@ -192,7 +233,7 @@ def test_junction_do_not_use_included_overrides(cli, tmpdir, datafiles): |
192 | 233 |
|
193 | 234 |
|
194 | 235 |
@pytest.mark.datafiles(DATA_DIR)
|
195 |
-def test_conditional_in_fragment(cli, tmpdir, datafiles):
|
|
236 |
+def test_conditional_in_fragment(cli, datafiles):
|
|
196 | 237 |
project = os.path.join(str(datafiles), 'conditional')
|
197 | 238 |
|
198 | 239 |
result = cli.run(project=project, args=[
|
... | ... | @@ -222,7 +263,7 @@ def test_inner(cli, datafiles): |
222 | 263 |
|
223 | 264 |
|
224 | 265 |
@pytest.mark.datafiles(DATA_DIR)
|
225 |
-def test_recusive_include(cli, tmpdir, datafiles):
|
|
266 |
+def test_recursive_include(cli, datafiles):
|
|
226 | 267 |
project = os.path.join(str(datafiles), 'recursive')
|
227 | 268 |
|
228 | 269 |
result = cli.run(project=project, args=[
|
... | ... | @@ -231,6 +272,7 @@ def test_recusive_include(cli, tmpdir, datafiles): |
231 | 272 |
'--format', '%{vars}',
|
232 | 273 |
'element.bst'])
|
233 | 274 |
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.RECURSIVE_INCLUDE)
|
275 |
+ assert 'line 2 column 2' in result.stderr
|
|
234 | 276 |
|
235 | 277 |
|
236 | 278 |
@pytest.mark.datafiles(DATA_DIR)
|