cook-build 0.4.1__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cook-build-0.4.1 → cook-build-0.4.3}/PKG-INFO +1 -1
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/__main__.py +4 -2
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/controller.py +16 -13
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/PKG-INFO +1 -1
- {cook-build-0.4.1 → cook-build-0.4.3}/setup.py +1 -1
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_controller.py +47 -1
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_main.py +1 -1
- {cook-build-0.4.1 → cook-build-0.4.3}/LICENSE +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/README.rst +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/__init__.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/actions.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/contexts.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/manager.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/task.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook/util.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/SOURCES.txt +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/dependency_links.txt +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/entry_points.txt +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/requires.txt +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/cook_build.egg-info/top_level.txt +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/setup.cfg +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_actions.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_contexts.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_examples.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_manager.py +0 -0
- {cook-build-0.4.1 → cook-build-0.4.3}/tests/test_util.py +0 -0
|
@@ -45,14 +45,15 @@ class Command:
|
|
|
45
45
|
Abstract base class for commands.
|
|
46
46
|
"""
|
|
47
47
|
NAME: Optional[str] = None
|
|
48
|
+
ALLOW_EMPTY_PATTERN: bool = False
|
|
48
49
|
|
|
49
50
|
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
50
51
|
parser.add_argument("--re", "-r", action="store_true",
|
|
51
52
|
help="use regular expressions for pattern matching instead of glob")
|
|
52
53
|
parser.add_argument("--all", "-a", action="store_true",
|
|
53
54
|
help="include tasks starting with `_` prefix")
|
|
54
|
-
parser.add_argument("tasks", nargs="*",
|
|
55
|
-
help="task or tasks to execute as regular expressions")
|
|
55
|
+
parser.add_argument("tasks", nargs="*" if self.ALLOW_EMPTY_PATTERN else "+",
|
|
56
|
+
help="task or tasks to execute as regular expressions or glob patterns")
|
|
56
57
|
|
|
57
58
|
def execute(self, controller: Controller, args: argparse.Namespace) -> None:
|
|
58
59
|
raise NotImplementedError
|
|
@@ -103,6 +104,7 @@ class LsCommand(Command):
|
|
|
103
104
|
List tasks.
|
|
104
105
|
"""
|
|
105
106
|
NAME = "ls"
|
|
107
|
+
ALLOW_EMPTY_PATTERN = True
|
|
106
108
|
|
|
107
109
|
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
108
110
|
parser.add_argument("--stale", "-s", action="store_true", help="only show stale tasks")
|
|
@@ -221,13 +221,9 @@ class Controller:
|
|
|
221
221
|
try:
|
|
222
222
|
item: Optional[Tuple["Task", sys._OptExcInfo]] = \
|
|
223
223
|
output_queue.get(timeout=interval)
|
|
224
|
-
except Empty:
|
|
224
|
+
except Empty: # pragma: no cover
|
|
225
225
|
continue
|
|
226
226
|
|
|
227
|
-
# Check if the stop event is set and abort if so.
|
|
228
|
-
if stop.is_set():
|
|
229
|
-
break
|
|
230
|
-
|
|
231
227
|
assert item is not None, "output queue returned `None`; this is a bug"
|
|
232
228
|
|
|
233
229
|
# Unpack the results.
|
|
@@ -240,7 +236,11 @@ class Controller:
|
|
|
240
236
|
}
|
|
241
237
|
self.connection.execute(QUERIES["upsert_task_failed"], params)
|
|
242
238
|
self.connection.commit()
|
|
243
|
-
raise util.FailedTaskError(task=task) from exc_info[1]
|
|
239
|
+
raise util.FailedTaskError(exc_info[1], task=task) from exc_info[1]
|
|
240
|
+
|
|
241
|
+
# Check if the stop event is set and abort if so.
|
|
242
|
+
if stop.is_set():
|
|
243
|
+
break
|
|
244
244
|
|
|
245
245
|
# Add tasks that are now leaf nodes to the tree.
|
|
246
246
|
predecessors = list(dependencies.predecessors(task))
|
|
@@ -250,13 +250,6 @@ class Controller:
|
|
|
250
250
|
if out_degree == 0:
|
|
251
251
|
input_queue.put(node)
|
|
252
252
|
|
|
253
|
-
# Verify that targets were created.
|
|
254
|
-
for target in task.targets:
|
|
255
|
-
if not target.is_file():
|
|
256
|
-
raise util.FailedTaskError(f"`{task}` did not create `{target}`",
|
|
257
|
-
task=task)
|
|
258
|
-
LOGGER.debug("%s created `%s`", task, target)
|
|
259
|
-
|
|
260
253
|
# Update the status in the database.
|
|
261
254
|
params = {
|
|
262
255
|
"name": task.name,
|
|
@@ -295,8 +288,17 @@ class Controller:
|
|
|
295
288
|
|
|
296
289
|
start = datetime.now()
|
|
297
290
|
try:
|
|
291
|
+
# Execute the task.
|
|
298
292
|
LOGGER.info("executing %s ...", task)
|
|
299
293
|
task.execute(stop)
|
|
294
|
+
|
|
295
|
+
# Check that all targets were created.
|
|
296
|
+
for target in task.targets:
|
|
297
|
+
if not target.is_file():
|
|
298
|
+
raise FileNotFoundError(f"task {task} did not create target {target}")
|
|
299
|
+
LOGGER.debug("%s created `%s`", task, target)
|
|
300
|
+
|
|
301
|
+
# Add the result to the output queue and report success.
|
|
300
302
|
output_queue.put((task, None))
|
|
301
303
|
delta = util.format_timedelta(datetime.now() - start)
|
|
302
304
|
LOGGER.log(logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
@@ -305,6 +307,7 @@ class Controller:
|
|
|
305
307
|
exc_info = sys.exc_info()
|
|
306
308
|
delta = util.format_timedelta(datetime.now() - start)
|
|
307
309
|
LOGGER.exception("failed to execute %s after %s", task, delta, exc_info=exc_info)
|
|
310
|
+
stop.set()
|
|
308
311
|
output_queue.put((task, sys.exc_info()))
|
|
309
312
|
|
|
310
313
|
# Put anything on the queue in case the parent is waiting.
|
|
@@ -220,11 +220,57 @@ def test_stop_long_running_subprocess(m: Manager, conn: Connection) -> None:
|
|
|
220
220
|
thread = threading.Thread(target=_target)
|
|
221
221
|
thread.start()
|
|
222
222
|
|
|
223
|
-
with patch("cook.util.StopEvent", return_value=stop), Timer() as timer
|
|
223
|
+
with patch("cook.util.StopEvent", return_value=stop), Timer() as timer, \
|
|
224
|
+
pytest.raises(FailedTaskError, match="SIGTERM: 15"):
|
|
224
225
|
c.execute(task)
|
|
225
226
|
|
|
226
227
|
assert 1 < timer.duration and timer.duration < 2
|
|
227
228
|
assert not thread.is_alive()
|
|
228
229
|
|
|
229
230
|
|
|
231
|
+
def test_set_stop_between_tasks(m: Manager, conn: Connection) -> None:
|
|
232
|
+
calls = []
|
|
233
|
+
|
|
234
|
+
def _action(task):
|
|
235
|
+
calls.append(task)
|
|
236
|
+
time.sleep(0.5)
|
|
237
|
+
|
|
238
|
+
task1 = m.create_task("sleep1", action=FunctionAction(_action))
|
|
239
|
+
task2 = m.create_task("sleep2", action=FunctionAction(_action))
|
|
240
|
+
c = Controller(m.resolve_dependencies(), conn)
|
|
241
|
+
|
|
242
|
+
# Set the stop interval shortly after dispatching the tasks.
|
|
243
|
+
stop = StopEvent(10)
|
|
244
|
+
|
|
245
|
+
def _target():
|
|
246
|
+
time.sleep(0.3)
|
|
247
|
+
stop.set()
|
|
248
|
+
thread = threading.Thread(target=_target)
|
|
249
|
+
thread.start()
|
|
250
|
+
|
|
251
|
+
with patch("cook.util.StopEvent", return_value=stop), Timer() as timer:
|
|
252
|
+
c.execute([task1, task2])
|
|
253
|
+
|
|
254
|
+
assert 0.5 < timer.duration and timer.duration < 1
|
|
255
|
+
assert not thread.is_alive()
|
|
256
|
+
assert len(calls) == 1
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_no_start_after_failure(m: Manager, conn: Connection) -> None:
|
|
260
|
+
calls = []
|
|
261
|
+
|
|
262
|
+
def _action(task):
|
|
263
|
+
calls.append(task)
|
|
264
|
+
raise ValueError
|
|
265
|
+
|
|
266
|
+
task1 = m.create_task("sleep1", action=FunctionAction(_action))
|
|
267
|
+
task2 = m.create_task("sleep2", action=FunctionAction(_action))
|
|
268
|
+
c = Controller(m.resolve_dependencies(), conn)
|
|
269
|
+
|
|
270
|
+
with pytest.raises(FailedTaskError):
|
|
271
|
+
c.execute([task1, task2])
|
|
272
|
+
|
|
273
|
+
assert len(calls) == 1
|
|
274
|
+
|
|
275
|
+
|
|
230
276
|
# TODO: add tests to verify what happens when tasks are cancelled, e.g., by `KeyboardInterrupt`.
|
|
@@ -15,7 +15,7 @@ def test_blah_recipe_run(tmp_wd: Path) -> None:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@pytest.mark.parametrize("patterns, expected", [
|
|
18
|
-
([], ["create_source", "compile", "link", "run"]),
|
|
18
|
+
(["*"], ["create_source", "compile", "link", "run"]),
|
|
19
19
|
(["c*"], ["create_source", "compile"]),
|
|
20
20
|
(["--re", r"^\w{3}\w?$"], ["link", "run"]),
|
|
21
21
|
(["run"], ["run"]),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|