Coverage for tests / test_separable_pipeline_executor.py: 11%
696 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-27 01:31 -0800
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-27 01:31 -0800
1# This file is part of pipe_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
29import os
30import tempfile
31import unittest
33import lsst.daf.butler
34import lsst.daf.butler.tests as butlerTests
35import lsst.pex.config
36import lsst.utils.tests
37from lsst.daf.butler.registry import RegistryDefaults
38from lsst.pipe.base import (
39 Instrument,
40 Pipeline,
41 PipelineGraph,
42 QuantumAttemptStatus,
43 QuantumGraph,
44 QuantumSuccessCaveats,
45 TaskMetadata,
46)
47from lsst.pipe.base.all_dimensions_quantum_graph_builder import AllDimensionsQuantumGraphBuilder
48from lsst.pipe.base.automatic_connection_constants import (
49 PACKAGES_INIT_OUTPUT_NAME,
50 PROVENANCE_DATASET_TYPE_NAME,
51 PROVENANCE_STORAGE_CLASS,
52)
53from lsst.pipe.base.quantum_graph_builder import OutputExistsError
54from lsst.pipe.base.separable_pipeline_executor import SeparablePipelineExecutor
55from lsst.pipe.base.tests.mocks import (
56 DirectButlerRepo,
57 DynamicTestPipelineTaskConfig,
58)
59from lsst.resources import ResourcePath
60from lsst.utils.packages import Packages
62TESTDIR = os.path.abspath(os.path.dirname(__file__))
65class SeparablePipelineExecutorTests(lsst.utils.tests.TestCase):
66 """Test the SeparablePipelineExecutor API with a trivial task."""
68 pipeline_file = os.path.join(TESTDIR, "pipelines", "pipeline_separable.yaml")
70 def setUp(self):
71 repodir = tempfile.TemporaryDirectory()
72 # TemporaryDirectory warns on leaks; addCleanup also keeps it from
73 # getting garbage-collected.
74 self.addCleanup(tempfile.TemporaryDirectory.cleanup, repodir)
76 # standalone parameter forces the returned config to also include
77 # the information from the search paths.
78 config = lsst.daf.butler.Butler.makeRepo(
79 repodir.name, standalone=True, searchPaths=[os.path.join(TESTDIR, "config")]
80 )
81 butler = lsst.daf.butler.Butler.from_config(config, writeable=True)
82 self.enterContext(butler)
83 output = "fake"
84 output_run = f"{output}/{Instrument.makeCollectionTimestamp()}"
85 butler.registry.registerCollection(output_run, lsst.daf.butler.CollectionType.RUN)
86 butler.registry.registerCollection(output, lsst.daf.butler.CollectionType.CHAINED)
87 butler.registry.setCollectionChain(output, [output_run])
88 butler.registry.defaults = RegistryDefaults(collections=[output], run=output_run)
89 self.butler = butler
91 butlerTests.addDatasetType(self.butler, "input", set(), "StructuredDataDict")
92 butlerTests.addDatasetType(self.butler, "intermediate", set(), "StructuredDataDict")
93 butlerTests.addDatasetType(self.butler, "a_log", set(), "ButlerLogRecords")
94 butlerTests.addDatasetType(self.butler, "a_metadata", set(), "TaskMetadata")
95 butlerTests.addDatasetType(self.butler, "a_config", set(), "Config")
96 provenance_dataset_type = butlerTests.addDatasetType(
97 self.butler, PROVENANCE_DATASET_TYPE_NAME, set(), PROVENANCE_STORAGE_CLASS
98 )
99 self.provenance_ref = lsst.daf.butler.DatasetRef(
100 provenance_dataset_type,
101 lsst.daf.butler.DataCoordinate.make_empty(self.butler.dimensions),
102 run=butler.run,
103 )
105 def build_empty_quantum_graph(self) -> None:
106 pipeline_graph = PipelineGraph(universe=self.butler.dimensions)
107 pipeline_graph.resolve(self.butler.registry)
108 builder = AllDimensionsQuantumGraphBuilder(pipeline_graph, self.butler)
109 return builder.finish(attach_datastore_records=False).assemble()
111 def check_provenance_fullgraph(self):
112 provenance_qg = self.butler.get(self.provenance_ref)
113 empty_data_id = lsst.daf.butler.DataCoordinate.make_empty(self.butler.dimensions)
114 self.assertCountEqual(provenance_qg.quanta_by_task.keys(), {"a", "b"})
115 self.assertCountEqual(
116 provenance_qg.datasets_by_type.keys(),
117 {
118 "input",
119 "intermediate",
120 "output",
121 "a_config",
122 "b_config",
123 "a_metadata",
124 "b_metadata",
125 "a_log",
126 "b_log",
127 },
128 )
129 a_id = provenance_qg.quanta_by_task["a"][empty_data_id]
130 b_id = provenance_qg.quanta_by_task["b"][empty_data_id]
131 input_id = provenance_qg.datasets_by_type["input"][empty_data_id]
132 intermediate_id = provenance_qg.datasets_by_type["intermediate"][empty_data_id]
133 output_id = provenance_qg.datasets_by_type["output"][empty_data_id]
134 a_metadata_id = provenance_qg.datasets_by_type["a_metadata"][empty_data_id]
135 a_log_id = provenance_qg.datasets_by_type["a_log"][empty_data_id]
136 b_metadata_id = provenance_qg.datasets_by_type["b_metadata"][empty_data_id]
137 b_log_id = provenance_qg.datasets_by_type["b_log"][empty_data_id]
138 self.assertEqual(
139 provenance_qg.bipartite_xgraph.nodes[a_id]["status"], QuantumAttemptStatus.SUCCESSFUL
140 )
141 self.assertEqual(
142 provenance_qg.bipartite_xgraph.nodes[b_id]["status"], QuantumAttemptStatus.SUCCESSFUL
143 )
144 self.assertEqual(list(provenance_qg.bipartite_xgraph.predecessors(a_id)), [input_id])
145 self.assertEqual(
146 list(provenance_qg.bipartite_xgraph.successors(a_id)), [intermediate_id, a_metadata_id, a_log_id]
147 )
148 self.assertEqual(list(provenance_qg.bipartite_xgraph.predecessors(b_id)), [intermediate_id])
149 self.assertEqual(
150 list(provenance_qg.bipartite_xgraph.successors(b_id)), [output_id, b_metadata_id, b_log_id]
151 )
152 for datasets_by_data_id in provenance_qg.datasets_by_type.values():
153 for dataset_id in datasets_by_data_id.values():
154 self.assertTrue(provenance_qg.bipartite_xgraph.nodes[dataset_id]["produced"])
155 logs_pqg = self.butler.get(
156 self.provenance_ref.makeComponentRef("logs"),
157 parameters={"quanta": (a_id,), "datasets": (b_log_id,)},
158 )
159 self.assertEqual(list(logs_pqg[a_id][-1]), list(self.butler.get("a_log", empty_data_id)))
160 self.assertEqual(list(logs_pqg[b_log_id][-1]), list(self.butler.get("b_log", empty_data_id)))
161 metadata_pqg = self.butler.get(
162 self.provenance_ref.makeComponentRef("metadata"),
163 parameters={"quanta": (b_id,), "datasets": (a_metadata_id,)},
164 )
165 self.assertEqual(metadata_pqg[a_metadata_id][-1], self.butler.get("a_metadata", empty_data_id))
166 self.assertEqual(metadata_pqg[b_id][-1], self.butler.get("b_metadata", empty_data_id))
167 self.assertIsInstance(self.butler.get("run_provenance.packages"), Packages)
169 def check_provenance_emptygraph(self):
170 provenance_qg = self.butler.get(self.provenance_ref)
171 self.assertFalse(provenance_qg.bipartite_xgraph)
172 self.assertFalse(any(provenance_qg.quanta_by_task.values()))
173 self.assertFalse(any(provenance_qg.datasets_by_type.values()))
174 self.assertIsInstance(self.butler.get("run_provenance.packages"), Packages)
176 def test_pre_execute_qgraph_old(self):
177 # Too hard to make a quantum graph from scratch.
178 executor = SeparablePipelineExecutor(self.butler)
179 pipeline = Pipeline.fromFile(self.pipeline_file)
180 self.butler.put({"zero": 0}, "input")
181 graph = executor.make_quantum_graph(pipeline)
183 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
184 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
185 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
186 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
187 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
189 executor.pre_execute_qgraph(
190 graph,
191 register_dataset_types=False,
192 save_init_outputs=False,
193 save_versions=False,
194 )
195 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
196 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
198 def test_pre_execute_qgraph(self):
199 # Too hard to make a quantum graph from scratch.
200 executor = SeparablePipelineExecutor(self.butler)
201 pipeline = Pipeline.fromFile(self.pipeline_file)
202 self.butler.put({"zero": 0}, "input")
203 graph = executor.build_quantum_graph(pipeline)
205 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
206 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
207 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
208 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
209 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
211 executor.pre_execute_qgraph(
212 graph,
213 register_dataset_types=False,
214 save_init_outputs=False,
215 save_versions=False,
216 )
217 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
218 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
220 def test_pre_execute_qgraph_unconnected_old(self):
221 # Unconnected graph; see
222 # test_make_quantum_graph_nowhere_skippartial_clobber.
223 executor = SeparablePipelineExecutor(
224 self.butler,
225 skip_existing_in=[self.butler.run],
226 clobber_output=True,
227 )
228 pipeline = Pipeline.fromFile(self.pipeline_file)
229 self.butler.put({"zero": 0}, "input")
230 self.butler.put({"zero": 0}, "intermediate")
231 graph = executor.make_quantum_graph(pipeline)
233 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
234 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
235 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
236 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
237 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
239 executor.pre_execute_qgraph(
240 graph,
241 register_dataset_types=False,
242 save_init_outputs=False,
243 save_versions=False,
244 )
245 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
246 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
248 def test_pre_execute_qgraph_unconnected(self):
249 # Unconnected graph; see
250 # test_make_quantum_graph_nowhere_skippartial_clobber.
251 executor = SeparablePipelineExecutor(
252 self.butler,
253 skip_existing_in=[self.butler.run],
254 clobber_output=True,
255 )
256 pipeline = Pipeline.fromFile(self.pipeline_file)
257 self.butler.put({"zero": 0}, "input")
258 self.butler.put({"zero": 0}, "intermediate")
259 graph = executor.build_quantum_graph(pipeline)
261 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
262 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
263 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
264 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
265 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
267 executor.pre_execute_qgraph(
268 graph,
269 register_dataset_types=False,
270 save_init_outputs=False,
271 save_versions=False,
272 )
273 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
274 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
276 def test_pre_execute_qgraph_empty_old(self):
277 executor = SeparablePipelineExecutor(self.butler)
278 graph = QuantumGraph({}, universe=self.butler.dimensions)
280 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
281 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
282 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
283 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
284 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
286 executor.pre_execute_qgraph(
287 graph,
288 register_dataset_types=False,
289 save_init_outputs=False,
290 save_versions=False,
291 )
292 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
293 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
295 def test_pre_execute_qgraph_empty(self):
296 executor = SeparablePipelineExecutor(self.butler)
297 graph = self.build_empty_quantum_graph()
299 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
300 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
301 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
302 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
303 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
305 executor.pre_execute_qgraph(
306 graph,
307 register_dataset_types=False,
308 save_init_outputs=False,
309 save_versions=False,
310 )
311 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
312 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
314 def test_pre_execute_qgraph_register_old(self):
315 executor = SeparablePipelineExecutor(self.butler)
316 pipeline = Pipeline.fromFile(self.pipeline_file)
317 self.butler.put({"zero": 0}, "input")
318 graph = executor.make_quantum_graph(pipeline)
320 executor.pre_execute_qgraph(
321 graph,
322 register_dataset_types=True,
323 save_init_outputs=False,
324 save_versions=False,
325 )
326 self.assertEqual({d.name for d in self.butler.registry.queryDatasetTypes("output")}, {"output"})
327 self.assertEqual(
328 {d.name for d in self.butler.registry.queryDatasetTypes("b_*")},
329 {"b_config", "b_log", "b_metadata"},
330 )
331 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
332 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
334 def test_pre_execute_qgraph_register(self):
335 executor = SeparablePipelineExecutor(self.butler)
336 pipeline = Pipeline.fromFile(self.pipeline_file)
337 self.butler.put({"zero": 0}, "input")
338 graph = executor.build_quantum_graph(pipeline)
340 executor.pre_execute_qgraph(
341 graph,
342 register_dataset_types=True,
343 save_init_outputs=False,
344 save_versions=False,
345 )
346 self.assertEqual({d.name for d in self.butler.registry.queryDatasetTypes("output")}, {"output"})
347 self.assertEqual(
348 {d.name for d in self.butler.registry.queryDatasetTypes("b_*")},
349 {"b_config", "b_log", "b_metadata"},
350 )
351 self.assertFalse(self.butler.exists("a_config", {}, collections=[self.butler.run]))
352 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
354 def test_pre_execute_qgraph_init_outputs_old(self):
355 # Too hard to make a quantum graph from scratch.
356 executor = SeparablePipelineExecutor(self.butler)
357 pipeline = Pipeline.fromFile(self.pipeline_file)
358 self.butler.put({"zero": 0}, "input")
359 graph = executor.make_quantum_graph(pipeline)
361 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
362 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
363 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
364 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
365 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
367 executor.pre_execute_qgraph(
368 graph,
369 register_dataset_types=False,
370 save_init_outputs=True,
371 save_versions=False,
372 )
373 self.assertTrue(self.butler.exists("a_config", {}, collections=[self.butler.run]))
374 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
376 def test_pre_execute_qgraph_init_outputs(self):
377 # Too hard to make a quantum graph from scratch.
378 executor = SeparablePipelineExecutor(self.butler)
379 pipeline = Pipeline.fromFile(self.pipeline_file)
380 self.butler.put({"zero": 0}, "input")
381 graph = executor.build_quantum_graph(pipeline)
383 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
384 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
385 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
386 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
387 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
389 executor.pre_execute_qgraph(
390 graph,
391 register_dataset_types=False,
392 save_init_outputs=True,
393 save_versions=False,
394 )
395 self.assertTrue(self.butler.exists("a_config", {}, collections=[self.butler.run]))
396 self.assertFalse(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
398 def test_pre_execute_qgraph_versions_old(self):
399 executor = SeparablePipelineExecutor(self.butler)
400 pipeline = Pipeline.fromFile(self.pipeline_file)
401 self.butler.put({"zero": 0}, "input")
402 graph = executor.make_quantum_graph(pipeline)
404 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
405 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
406 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
407 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
408 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
410 executor.pre_execute_qgraph(
411 graph,
412 register_dataset_types=False,
413 save_init_outputs=True,
414 save_versions=True,
415 )
416 self.assertTrue(self.butler.exists("a_config", {}, collections=[self.butler.run]))
417 self.assertTrue(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
419 def test_pre_execute_qgraph_versions(self):
420 executor = SeparablePipelineExecutor(self.butler)
421 pipeline = Pipeline.fromFile(self.pipeline_file)
422 self.butler.put({"zero": 0}, "input")
423 graph = executor.build_quantum_graph(pipeline)
425 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
426 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
427 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
428 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
429 butlerTests.addDatasetType(self.butler, PACKAGES_INIT_OUTPUT_NAME, set(), "Packages")
431 executor.pre_execute_qgraph(
432 graph,
433 register_dataset_types=False,
434 save_init_outputs=True,
435 save_versions=True,
436 )
437 self.assertTrue(self.butler.exists("a_config", {}, collections=[self.butler.run]))
438 self.assertTrue(self.butler.exists(PACKAGES_INIT_OUTPUT_NAME, {}))
440 def test_init_badinput(self):
441 with lsst.daf.butler.Butler.from_config(butler=self.butler, collections=[], run="foo") as butler:
442 with self.assertRaises(ValueError):
443 SeparablePipelineExecutor(butler)
445 def test_init_badoutput(self):
446 with lsst.daf.butler.Butler.from_config(butler=self.butler, collections=["foo"]) as butler:
447 with self.assertRaises(ValueError):
448 SeparablePipelineExecutor(butler)
450 def test_make_pipeline_full(self):
451 executor = SeparablePipelineExecutor(self.butler)
452 for uri in [
453 self.pipeline_file,
454 ResourcePath(self.pipeline_file),
455 ResourcePath(self.pipeline_file).geturl(),
456 ]:
457 pipeline_graph = executor.make_pipeline(uri).to_graph()
458 self.assertEqual(set(pipeline_graph.tasks), {"a", "b"})
460 def test_make_pipeline_subset(self):
461 executor = SeparablePipelineExecutor(self.butler)
462 path = self.pipeline_file + "#a"
463 for uri in [
464 path,
465 ResourcePath(path),
466 ResourcePath(path).geturl(),
467 ]:
468 pipeline_graph = executor.make_pipeline(uri).to_graph()
469 self.assertEqual(set(pipeline_graph.tasks), {"a"})
471 def test_build_quantum_graph_nowhere_noskip_noclobber(self):
472 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
473 pipeline = Pipeline.fromFile(self.pipeline_file)
475 self.butler.put({"zero": 0}, "input")
477 graph = executor.make_quantum_graph(pipeline)
478 self.assertTrue(graph.isConnected)
479 self.assertEqual(len(graph), 2)
480 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"a"})
481 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
483 def test_make_quantum_graph_nowhere_noskip_noclobber(self):
484 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
485 pipeline = Pipeline.fromFile(self.pipeline_file)
487 self.butler.put({"zero": 0}, "input")
489 graph = executor.build_quantum_graph(pipeline)
490 self.assertEqual(len(graph), 2)
491 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
493 def test_make_quantum_graph_nowhere_noskip_noclobber_conflict(self):
494 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
495 pipeline = Pipeline.fromFile(self.pipeline_file)
497 self.butler.put({"zero": 0}, "input")
498 self.butler.put({"zero": 0}, "intermediate")
499 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
500 self.butler.put(TaskMetadata(), "a_metadata")
502 with self.assertRaises(OutputExistsError):
503 executor.build_quantum_graph(pipeline)
505 # TODO: need more complex task and Butler to test
506 # make_quantum_graph(where=...)
508 def test_build_quantum_graph_nowhere_skipnone_noclobber(self):
509 executor = SeparablePipelineExecutor(
510 self.butler,
511 skip_existing_in=[self.butler.run],
512 clobber_output=False,
513 )
514 pipeline = Pipeline.fromFile(self.pipeline_file)
516 self.butler.put({"zero": 0}, "input")
518 graph = executor.make_quantum_graph(pipeline)
519 self.assertTrue(graph.isConnected)
520 self.assertEqual(len(graph), 2)
521 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"a"})
522 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
524 def test_make_quantum_graph_nowhere_skipnone_noclobber(self):
525 executor = SeparablePipelineExecutor(
526 self.butler,
527 skip_existing_in=[self.butler.run],
528 clobber_output=False,
529 )
530 pipeline = Pipeline.fromFile(self.pipeline_file)
531 self.butler.put({"zero": 0}, "input")
532 graph = executor.build_quantum_graph(pipeline)
533 self.assertEqual(len(graph), 2)
534 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
536 def test_build_quantum_graph_nowhere_skiptotal_noclobber(self):
537 executor = SeparablePipelineExecutor(
538 self.butler,
539 skip_existing_in=[self.butler.run],
540 clobber_output=False,
541 )
542 pipeline = Pipeline.fromFile(self.pipeline_file)
544 self.butler.put({"zero": 0}, "input")
545 self.butler.put({"zero": 0}, "intermediate")
546 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
547 self.butler.put(TaskMetadata(), "a_metadata")
548 self.butler.put(lsst.pex.config.Config(), "a_config")
550 graph = executor.make_quantum_graph(pipeline)
551 self.assertTrue(graph.isConnected)
552 self.assertEqual(len(graph), 1)
553 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"b"})
554 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
556 def test_make_quantum_graph_nowhere_skiptotal_noclobber(self):
557 executor = SeparablePipelineExecutor(
558 self.butler,
559 skip_existing_in=[self.butler.run],
560 clobber_output=False,
561 )
562 pipeline = Pipeline.fromFile(self.pipeline_file)
564 self.butler.put({"zero": 0}, "input")
565 self.butler.put({"zero": 0}, "intermediate")
566 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
567 self.butler.put(TaskMetadata(), "a_metadata")
568 self.butler.put(lsst.pex.config.Config(), "a_config")
570 graph = executor.build_quantum_graph(pipeline)
571 self.assertEqual(len(graph), 1)
572 self.assertEqual(graph.header.n_task_quanta["a"], 0)
573 self.assertEqual(graph.header.n_task_quanta["b"], 1)
575 def test_make_quantum_graph_nowhere_skippartial_noclobber(self):
576 executor = SeparablePipelineExecutor(
577 self.butler,
578 skip_existing_in=[self.butler.run],
579 clobber_output=False,
580 )
581 pipeline = Pipeline.fromFile(self.pipeline_file)
583 self.butler.put({"zero": 0}, "input")
584 self.butler.put({"zero": 0}, "intermediate")
586 with self.assertRaises(OutputExistsError):
587 executor.build_quantum_graph(pipeline)
589 def test_build_quantum_graph_nowhere_noskip_clobber(self):
590 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
591 pipeline = Pipeline.fromFile(self.pipeline_file)
593 self.butler.put({"zero": 0}, "input")
595 graph = executor.make_quantum_graph(pipeline)
596 self.assertTrue(graph.isConnected)
597 self.assertEqual(len(graph), 2)
598 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"a"})
599 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
601 def test_make_quantum_graph_nowhere_noskip_clobber(self):
602 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
603 pipeline = Pipeline.fromFile(self.pipeline_file)
604 self.butler.put({"zero": 0}, "input")
605 graph = executor.build_quantum_graph(pipeline)
606 self.assertEqual(len(graph), 2)
607 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
609 def test_build_quantum_graph_nowhere_noskip_clobber_conflict(self):
610 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
611 pipeline = Pipeline.fromFile(self.pipeline_file)
613 self.butler.put({"zero": 0}, "input")
614 self.butler.put({"zero": 0}, "intermediate")
615 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
616 self.butler.put(TaskMetadata(), "a_metadata")
618 graph = executor.make_quantum_graph(pipeline)
619 self.assertTrue(graph.isConnected)
620 self.assertEqual(len(graph), 2)
621 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"a"})
622 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
624 def test_make_quantum_graph_nowhere_noskip_clobber_conflict(self):
625 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
626 pipeline = Pipeline.fromFile(self.pipeline_file)
627 self.butler.put({"zero": 0}, "input")
628 self.butler.put({"zero": 0}, "intermediate")
629 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
630 self.butler.put(TaskMetadata(), "a_metadata")
631 graph = executor.build_quantum_graph(pipeline)
632 self.assertEqual(len(graph), 2)
633 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
635 def test_build_quantum_graph_nowhere_skipnone_clobber(self):
636 executor = SeparablePipelineExecutor(
637 self.butler,
638 skip_existing_in=[self.butler.run],
639 clobber_output=True,
640 )
641 pipeline = Pipeline.fromFile(self.pipeline_file)
643 self.butler.put({"zero": 0}, "input")
645 graph = executor.make_quantum_graph(pipeline)
646 self.assertTrue(graph.isConnected)
647 self.assertEqual(len(graph), 2)
648 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"a"})
649 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
651 def test_make_quantum_graph_nowhere_skipnone_clobber(self):
652 executor = SeparablePipelineExecutor(
653 self.butler,
654 skip_existing_in=[self.butler.run],
655 clobber_output=True,
656 )
657 pipeline = Pipeline.fromFile(self.pipeline_file)
658 self.butler.put({"zero": 0}, "input")
659 graph = executor.build_quantum_graph(pipeline)
660 self.assertEqual(len(graph), 2)
661 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
663 def test_make_quantum_graph_nowhere_skiptotal_clobber(self):
664 executor = SeparablePipelineExecutor(
665 self.butler,
666 skip_existing_in=[self.butler.run],
667 clobber_output=True,
668 )
669 pipeline = Pipeline.fromFile(self.pipeline_file)
671 self.butler.put({"zero": 0}, "input")
672 self.butler.put({"zero": 0}, "intermediate")
673 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
674 self.butler.put(TaskMetadata(), "a_metadata")
675 self.butler.put(lsst.pex.config.Config(), "a_config")
677 graph = executor.make_quantum_graph(pipeline)
678 self.assertTrue(graph.isConnected)
679 self.assertEqual(len(graph), 1)
680 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"b"})
681 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
683 def test_build_quantum_graph_nowhere_skiptotal_clobber(self):
684 executor = SeparablePipelineExecutor(
685 self.butler,
686 skip_existing_in=[self.butler.run],
687 clobber_output=True,
688 )
689 pipeline = Pipeline.fromFile(self.pipeline_file)
691 self.butler.put({"zero": 0}, "input")
692 self.butler.put({"zero": 0}, "intermediate")
693 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
694 self.butler.put(TaskMetadata(), "a_metadata")
695 self.butler.put(lsst.pex.config.Config(), "a_config")
697 graph = executor.make_quantum_graph(pipeline)
698 self.assertTrue(graph.isConnected)
699 self.assertEqual(len(graph), 1)
700 self.assertEqual({q.taskDef.label for q in graph.inputQuanta}, {"b"})
701 self.assertEqual({q.taskDef.label for q in graph.outputQuanta}, {"b"})
703 def test_make_quantum_graph_nowhere_skippartial_clobber(self):
704 executor = SeparablePipelineExecutor(
705 self.butler,
706 skip_existing_in=[self.butler.run],
707 clobber_output=True,
708 )
709 pipeline = Pipeline.fromFile(self.pipeline_file)
710 self.butler.put({"zero": 0}, "input")
711 self.butler.put({"zero": 0}, "intermediate")
712 graph = executor.build_quantum_graph(pipeline)
713 self.assertEqual(len(graph), 2)
714 self.assertEqual(graph.quanta_by_task.keys(), {"a", "b"})
716 def test_make_quantum_graph_noinput(self):
717 executor = SeparablePipelineExecutor(self.butler)
718 pipeline = Pipeline.fromFile(self.pipeline_file)
720 graph = executor.make_quantum_graph(pipeline)
721 self.assertEqual(len(graph), 0)
723 def test_build_quantum_graph_noinput(self):
724 executor = SeparablePipelineExecutor(self.butler)
725 pipeline = Pipeline.fromFile(self.pipeline_file)
727 graph = executor.build_quantum_graph(pipeline)
728 self.assertEqual(len(graph), 0)
730 def test_make_quantum_graph_alloutput_skip(self):
731 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=[self.butler.run])
732 pipeline = Pipeline.fromFile(self.pipeline_file)
734 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
735 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
736 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
737 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
739 self.butler.put({"zero": 0}, "input")
740 self.butler.put({"zero": 0}, "intermediate")
741 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
742 self.butler.put(TaskMetadata(), "a_metadata")
743 self.butler.put({"zero": 0}, "output")
744 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "b_log")
745 self.butler.put(TaskMetadata(), "b_metadata")
746 self.butler.put(lsst.pex.config.Config(), "a_config")
747 self.butler.put(lsst.pex.config.Config(), "b_config")
749 graph = executor.make_quantum_graph(pipeline)
750 self.assertEqual(len(graph), 0)
752 def test_build_quantum_graph_alloutput_skip(self):
753 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=[self.butler.run])
754 pipeline = Pipeline.fromFile(self.pipeline_file)
756 butlerTests.addDatasetType(self.butler, "output", set(), "StructuredDataDict")
757 butlerTests.addDatasetType(self.butler, "b_log", set(), "ButlerLogRecords")
758 butlerTests.addDatasetType(self.butler, "b_metadata", set(), "TaskMetadata")
759 butlerTests.addDatasetType(self.butler, "b_config", set(), "Config")
761 self.butler.put({"zero": 0}, "input")
762 self.butler.put({"zero": 0}, "intermediate")
763 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
764 self.butler.put(TaskMetadata(), "a_metadata")
765 self.butler.put({"zero": 0}, "output")
766 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "b_log")
767 self.butler.put(TaskMetadata(), "b_metadata")
768 self.butler.put(lsst.pex.config.Config(), "a_config")
769 self.butler.put(lsst.pex.config.Config(), "b_config")
771 graph = executor.build_quantum_graph(pipeline)
772 self.assertEqual(len(graph), 0)
774 def test_run_pipeline_noskip_noclobber_fullgraph(self):
775 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
776 pipeline = Pipeline.fromFile(self.pipeline_file)
777 self.butler.put({"zero": 0}, "input")
778 graph = executor.make_quantum_graph(pipeline)
779 executor.pre_execute_qgraph(
780 graph,
781 register_dataset_types=True,
782 save_init_outputs=True,
783 save_versions=False,
784 )
786 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
787 self.butler.registry.refresh()
788 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
789 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
790 self.check_provenance_fullgraph()
792 def test_run_pipeline_noskip_noclobber_fullgraph_old(self):
793 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
794 pipeline = Pipeline.fromFile(self.pipeline_file)
795 self.butler.put({"zero": 0}, "input")
796 graph = executor.build_quantum_graph(pipeline)
797 executor.pre_execute_qgraph(
798 graph,
799 register_dataset_types=True,
800 save_init_outputs=True,
801 save_versions=False,
802 )
804 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
805 self.butler.registry.refresh()
806 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
807 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
808 self.check_provenance_fullgraph()
810 def test_run_pipeline_noskip_noclobber_emptygraph_old(self):
811 old_repo_size = self.butler.registry.queryDatasets(...).count()
813 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
814 graph = QuantumGraph({}, universe=self.butler.dimensions)
815 executor.pre_execute_qgraph(
816 graph,
817 register_dataset_types=True,
818 save_init_outputs=True,
819 save_versions=False,
820 )
822 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
823 self.butler.registry.refresh()
824 # Empty graph execution should do nothing but write provenance.
825 self.assertEqual(self.butler.registry.queryDatasets(...).count(), old_repo_size + 1)
826 self.check_provenance_emptygraph()
828 def test_run_pipeline_noskip_noclobber_emptygraph(self):
829 old_repo_size = self.butler.registry.queryDatasets(...).count()
831 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=False)
832 graph = self.build_empty_quantum_graph()
833 executor.pre_execute_qgraph(
834 graph,
835 register_dataset_types=True,
836 save_init_outputs=True,
837 save_versions=False,
838 )
840 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
841 self.butler.registry.refresh()
842 # Empty graph execution should do nothing but write provenance.
843 self.assertEqual(self.butler.registry.queryDatasets(...).count(), old_repo_size + 1)
844 self.check_provenance_emptygraph()
846 def test_run_pipeline_skipnone_noclobber_old(self):
847 executor = SeparablePipelineExecutor(
848 self.butler,
849 skip_existing_in=[self.butler.run],
850 clobber_output=False,
851 )
852 pipeline = Pipeline.fromFile(self.pipeline_file)
853 self.butler.put({"zero": 0}, "input")
854 graph = executor.make_quantum_graph(pipeline)
855 executor.pre_execute_qgraph(
856 graph,
857 register_dataset_types=True,
858 save_init_outputs=True,
859 save_versions=False,
860 )
862 executor.run_pipeline(graph)
863 self.butler.registry.refresh()
864 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
865 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
867 def test_run_pipeline_skipnone_noclobber(self):
868 executor = SeparablePipelineExecutor(
869 self.butler,
870 skip_existing_in=[self.butler.run],
871 clobber_output=False,
872 )
873 pipeline = Pipeline.fromFile(self.pipeline_file)
874 self.butler.put({"zero": 0}, "input")
875 graph = executor.build_quantum_graph(pipeline)
876 executor.pre_execute_qgraph(
877 graph,
878 register_dataset_types=True,
879 save_init_outputs=True,
880 save_versions=False,
881 )
883 executor.run_pipeline(graph)
884 self.butler.registry.refresh()
885 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
886 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
888 def test_run_pipeline_skiptotal_noclobber_old(self):
889 executor = SeparablePipelineExecutor(
890 self.butler,
891 skip_existing_in=[self.butler.run],
892 clobber_output=False,
893 )
894 pipeline = Pipeline.fromFile(self.pipeline_file)
895 self.butler.put({"zero": 0}, "input")
896 self.butler.put({"zero": 0}, "intermediate")
897 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
898 self.butler.put(TaskMetadata(), "a_metadata")
899 self.butler.put(lsst.pex.config.Config(), "a_config")
900 graph = executor.make_quantum_graph(pipeline)
901 executor.pre_execute_qgraph(
902 graph,
903 register_dataset_types=True,
904 save_init_outputs=True,
905 save_versions=False,
906 )
908 executor.run_pipeline(graph)
909 self.butler.registry.refresh()
910 self.assertEqual(self.butler.get("output"), {"zero": 0, "two": 2})
912 def test_run_pipeline_skiptotal_noclobber(self):
913 executor = SeparablePipelineExecutor(
914 self.butler,
915 skip_existing_in=[self.butler.run],
916 clobber_output=False,
917 )
918 pipeline = Pipeline.fromFile(self.pipeline_file)
919 self.butler.put({"zero": 0}, "input")
920 self.butler.put({"zero": 0}, "intermediate")
921 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
922 self.butler.put(TaskMetadata(), "a_metadata")
923 self.butler.put(lsst.pex.config.Config(), "a_config")
924 graph = executor.build_quantum_graph(pipeline)
925 executor.pre_execute_qgraph(
926 graph,
927 register_dataset_types=True,
928 save_init_outputs=True,
929 save_versions=False,
930 )
932 executor.run_pipeline(graph)
933 self.butler.registry.refresh()
934 self.assertEqual(self.butler.get("output"), {"zero": 0, "two": 2})
936 def test_run_pipeline_noskip_clobber_connected_old(self):
937 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
938 pipeline = Pipeline.fromFile(self.pipeline_file)
939 self.butler.put({"zero": 0}, "input")
940 graph = executor.make_quantum_graph(pipeline)
941 executor.pre_execute_qgraph(
942 graph,
943 register_dataset_types=True,
944 save_init_outputs=True,
945 save_versions=False,
946 )
948 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
949 self.butler.registry.refresh()
950 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
951 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
952 self.check_provenance_fullgraph()
954 def test_run_pipeline_noskip_clobber_connected(self):
955 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
956 pipeline = Pipeline.fromFile(self.pipeline_file)
957 self.butler.put({"zero": 0}, "input")
958 graph = executor.build_quantum_graph(pipeline)
959 executor.pre_execute_qgraph(
960 graph,
961 register_dataset_types=True,
962 save_init_outputs=True,
963 save_versions=False,
964 )
966 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
967 self.butler.registry.refresh()
968 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
969 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
970 self.check_provenance_fullgraph()
972 def test_run_pipeline_noskip_clobber_unconnected_old(self):
973 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
974 pipeline = Pipeline.fromFile(self.pipeline_file)
975 self.butler.put({"zero": 0}, "input")
976 self.butler.put({"zero": 0}, "intermediate")
977 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
978 self.butler.put(TaskMetadata(), "a_metadata")
979 graph = executor.make_quantum_graph(pipeline)
980 executor.pre_execute_qgraph(
981 graph,
982 register_dataset_types=True,
983 save_init_outputs=True,
984 save_versions=False,
985 )
987 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
988 self.butler.registry.refresh()
989 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
990 # The value of output is undefined; it depends on which task ran first.
991 self.assertTrue(self.butler.exists("output", {}))
993 def test_run_pipeline_noskip_clobber_unconnected(self):
994 executor = SeparablePipelineExecutor(self.butler, skip_existing_in=None, clobber_output=True)
995 pipeline = Pipeline.fromFile(self.pipeline_file)
996 self.butler.put({"zero": 0}, "input")
997 self.butler.put({"zero": 0}, "intermediate")
998 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
999 self.butler.put(TaskMetadata(), "a_metadata")
1000 graph = executor.build_quantum_graph(pipeline)
1001 executor.pre_execute_qgraph(
1002 graph,
1003 register_dataset_types=True,
1004 save_init_outputs=True,
1005 save_versions=False,
1006 )
1008 executor.run_pipeline(graph, provenance_dataset_ref=self.provenance_ref)
1009 self.butler.registry.refresh()
1010 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
1011 # The value of output is undefined; it depends on which task ran first.
1012 self.assertTrue(self.butler.exists("output", {}))
1013 self.check_provenance_fullgraph()
1015 def test_run_pipeline_skipnone_clobber_old(self):
1016 executor = SeparablePipelineExecutor(
1017 self.butler,
1018 skip_existing_in=[self.butler.run],
1019 clobber_output=True,
1020 )
1021 pipeline = Pipeline.fromFile(self.pipeline_file)
1022 self.butler.put({"zero": 0}, "input")
1023 graph = executor.make_quantum_graph(pipeline)
1024 executor.pre_execute_qgraph(
1025 graph,
1026 register_dataset_types=True,
1027 save_init_outputs=True,
1028 save_versions=False,
1029 )
1031 executor.run_pipeline(graph)
1032 self.butler.registry.refresh()
1033 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
1034 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
1036 def test_run_pipeline_skipnone_clobber(self):
1037 executor = SeparablePipelineExecutor(
1038 self.butler,
1039 skip_existing_in=[self.butler.run],
1040 clobber_output=True,
1041 )
1042 pipeline = Pipeline.fromFile(self.pipeline_file)
1043 self.butler.put({"zero": 0}, "input")
1044 graph = executor.build_quantum_graph(pipeline)
1045 executor.pre_execute_qgraph(
1046 graph,
1047 register_dataset_types=True,
1048 save_init_outputs=True,
1049 save_versions=False,
1050 )
1052 executor.run_pipeline(graph)
1053 self.butler.registry.refresh()
1054 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
1055 self.assertEqual(self.butler.get("output"), {"zero": 0, "one": 1, "two": 2})
1057 def test_run_pipeline_skiptotal_clobber_connected_old(self):
1058 executor = SeparablePipelineExecutor(
1059 self.butler,
1060 skip_existing_in=[self.butler.run],
1061 clobber_output=True,
1062 )
1063 pipeline = Pipeline.fromFile(self.pipeline_file)
1064 self.butler.put({"zero": 0}, "input")
1065 self.butler.put({"zero": 0}, "intermediate")
1066 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
1067 self.butler.put(TaskMetadata(), "a_metadata")
1068 self.butler.put(lsst.pex.config.Config(), "a_config")
1069 graph = executor.make_quantum_graph(pipeline)
1070 executor.pre_execute_qgraph(
1071 graph,
1072 register_dataset_types=True,
1073 save_init_outputs=True,
1074 save_versions=False,
1075 )
1077 executor.run_pipeline(graph)
1078 self.butler.registry.refresh()
1079 self.assertEqual(self.butler.get("output"), {"zero": 0, "two": 2})
1081 def test_run_pipeline_skiptotal_clobber_connected(self):
1082 executor = SeparablePipelineExecutor(
1083 self.butler,
1084 skip_existing_in=[self.butler.run],
1085 clobber_output=True,
1086 )
1087 pipeline = Pipeline.fromFile(self.pipeline_file)
1088 self.butler.put({"zero": 0}, "input")
1089 self.butler.put({"zero": 0}, "intermediate")
1090 self.butler.put(lsst.daf.butler.ButlerLogRecords.from_records([]), "a_log")
1091 self.butler.put(TaskMetadata(), "a_metadata")
1092 self.butler.put(lsst.pex.config.Config(), "a_config")
1093 graph = executor.build_quantum_graph(pipeline)
1094 executor.pre_execute_qgraph(
1095 graph,
1096 register_dataset_types=True,
1097 save_init_outputs=True,
1098 save_versions=False,
1099 )
1101 executor.run_pipeline(graph)
1102 self.butler.registry.refresh()
1103 self.assertEqual(self.butler.get("output"), {"zero": 0, "two": 2})
1105 def test_run_pipeline_skippartial_clobber_unconnected_old(self):
1106 executor = SeparablePipelineExecutor(
1107 self.butler,
1108 skip_existing_in=[self.butler.run],
1109 clobber_output=True,
1110 )
1111 pipeline = Pipeline.fromFile(self.pipeline_file)
1112 self.butler.put({"zero": 0}, "input")
1113 self.butler.put({"zero": 0}, "intermediate")
1114 graph = executor.make_quantum_graph(pipeline)
1115 executor.pre_execute_qgraph(
1116 graph,
1117 register_dataset_types=True,
1118 save_init_outputs=True,
1119 save_versions=False,
1120 )
1122 executor.run_pipeline(graph)
1123 self.butler.registry.refresh()
1124 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
1125 # The value of output is undefined; it depends on which task ran first.
1126 self.assertTrue(self.butler.exists("output", {}))
1128 def test_run_pipeline_skippartial_clobber_unconnected(self):
1129 executor = SeparablePipelineExecutor(
1130 self.butler,
1131 skip_existing_in=[self.butler.run],
1132 clobber_output=True,
1133 )
1134 pipeline = Pipeline.fromFile(self.pipeline_file)
1135 self.butler.put({"zero": 0}, "input")
1136 self.butler.put({"zero": 0}, "intermediate")
1137 graph = executor.build_quantum_graph(pipeline)
1138 executor.pre_execute_qgraph(
1139 graph,
1140 register_dataset_types=True,
1141 save_init_outputs=True,
1142 save_versions=False,
1143 )
1144 executor.run_pipeline(graph)
1145 self.butler.registry.refresh()
1146 self.assertEqual(self.butler.get("intermediate"), {"zero": 0, "one": 1})
1147 # The value of output is undefined; it depends on which task ran first.
1148 self.assertTrue(self.butler.exists("output", {}))
1151class SeparablePipelineExecutorMockTests(lsst.utils.tests.TestCase):
1152 """Additional tests for SeparablePipelineExecutor API that use
1153 the lsst.pipe.base.tests.mocks system to define complex pipelines.
1154 """
1156 def test_no_work_chain_provenance(self):
1157 """Test provenance recording when a NoWorkFound error chains to
1158 downstream tasks during execution.
1159 """
1160 # 'base.yaml' adds an instrument, 'Cam1', with four detectors and
1161 # two physical filters.
1162 with DirectButlerRepo.make_temporary("base.yaml") as (helper, _):
1163 helper.add_task("a", dimensions=["detector"])
1164 b_config = DynamicTestPipelineTaskConfig()
1165 b_config.fail_exception = "lsst.pipe.base.NoWorkFound"
1166 b_config.fail_condition = "detector=2"
1167 helper.add_task("b", dimensions=["detector"], config=b_config)
1168 helper.add_task("c", dimensions=["detector"])
1169 qg = helper.make_quantum_graph()
1170 helper.butler.collections.register(qg.header.output_run)
1171 qg.init_output_run(helper.butler, existing=False)
1172 provenance_type = lsst.daf.butler.DatasetType(
1173 PROVENANCE_DATASET_TYPE_NAME,
1174 helper.butler.dimensions.empty,
1175 PROVENANCE_STORAGE_CLASS,
1176 )
1177 helper.butler.registry.registerDatasetType(provenance_type)
1178 provenance_ref = lsst.daf.butler.DatasetRef(
1179 provenance_type,
1180 lsst.daf.butler.DataCoordinate.make_empty(helper.butler.dimensions),
1181 run=qg.header.output_run,
1182 )
1183 executor = SeparablePipelineExecutor(
1184 helper.butler.clone(collections=qg.header.inputs, run=qg.header.output_run)
1185 )
1186 executor.run_pipeline(qg, provenance_dataset_ref=provenance_ref)
1187 provenance_graph = helper.butler.get(provenance_ref)
1188 self.assertEqual(len(provenance_graph.quanta_by_task), 3)
1189 self.assertEqual(len(provenance_graph.quanta_by_task["a"]), 4)
1190 self.assertEqual(len(provenance_graph.quanta_by_task["b"]), 4)
1191 self.assertEqual(len(provenance_graph.quanta_by_task["c"]), 4)
1192 xgraph = provenance_graph.quantum_only_xgraph
1193 for quantum_id in provenance_graph.quanta_by_task["a"].values():
1194 self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL)
1195 self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS)
1196 for data_id, quantum_id in provenance_graph.quanta_by_task["b"].items():
1197 self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL)
1198 if data_id["detector"] == 2:
1199 self.assertTrue(xgraph.nodes[quantum_id]["caveats"] & QuantumSuccessCaveats.NO_WORK)
1200 else:
1201 self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS)
1202 for data_id, quantum_id in provenance_graph.quanta_by_task["c"].items():
1203 self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL)
1204 if data_id["detector"] == 2:
1205 self.assertTrue(
1206 xgraph.nodes[quantum_id]["caveats"] & QuantumSuccessCaveats.ADJUST_QUANTUM_RAISED
1207 )
1208 else:
1209 self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS)
1212class MemoryTester(lsst.utils.tests.MemoryTestCase):
1213 """Generic test for file leaks."""
1216def setup_module(module):
1217 """Set up the module for pytest.
1219 Parameters
1220 ----------
1221 module : `~types.ModuleType`
1222 Module to set up.
1223 """
1224 lsst.utils.tests.init()
1227if __name__ == "__main__":
1228 lsst.utils.tests.init()
1229 unittest.main()