|
50 | 50 | PROVENANCE_DATASET_TYPE_NAME, |
51 | 51 | PROVENANCE_STORAGE_CLASS, |
52 | 52 | ) |
| 53 | +from lsst.pipe.base.mp_graph_executor import MPGraphExecutorError |
| 54 | +from lsst.pipe.base.quantum_graph import ProvenanceQuantumGraph |
53 | 55 | from lsst.pipe.base.quantum_graph_builder import OutputExistsError |
54 | 56 | from lsst.pipe.base.separable_pipeline_executor import SeparablePipelineExecutor |
55 | 57 | from lsst.pipe.base.tests.mocks import ( |
@@ -1153,60 +1155,106 @@ class SeparablePipelineExecutorMockTests(lsst.utils.tests.TestCase): |
1153 | 1155 | the lsst.pipe.base.tests.mocks system to define complex pipelines. |
1154 | 1156 | """ |
1155 | 1157 |
|
| 1158 | + def setUp(self): |
| 1159 | + # 'base.yaml' adds an instrument, 'Cam1', with four detectors and |
| 1160 | + # two physical filters. |
| 1161 | + self.helper, _ = self.enterContext(DirectButlerRepo.make_temporary("base.yaml")) |
| 1162 | + |
| 1163 | + def run_base_test( |
| 1164 | + self, b_config: DynamicTestPipelineTaskConfig, expected_error: type[Exception] | None |
| 1165 | + ) -> ProvenanceQuantumGraph: |
| 1166 | + """Build and run a quantum graph with three tasks and four data IDs, |
| 1167 | + with customization of the middle task. |
| 1168 | + """ |
| 1169 | + self.helper.add_task("a", dimensions=["detector"]) |
| 1170 | + self.helper.add_task("b", dimensions=["detector"], config=b_config) |
| 1171 | + self.helper.add_task("c", dimensions=["detector"]) |
| 1172 | + qg = self.helper.make_quantum_graph() |
| 1173 | + self.helper.butler.collections.register(qg.header.output_run) |
| 1174 | + qg.init_output_run(self.helper.butler, existing=False) |
| 1175 | + executor = SeparablePipelineExecutor( |
| 1176 | + self.helper.butler.clone(collections=qg.header.inputs, run=qg.header.output_run) |
| 1177 | + ) |
| 1178 | + provenance_type = lsst.daf.butler.DatasetType( |
| 1179 | + PROVENANCE_DATASET_TYPE_NAME, |
| 1180 | + self.helper.butler.dimensions.empty, |
| 1181 | + PROVENANCE_STORAGE_CLASS, |
| 1182 | + ) |
| 1183 | + self.helper.butler.registry.registerDatasetType(provenance_type) |
| 1184 | + provenance_ref = lsst.daf.butler.DatasetRef( |
| 1185 | + provenance_type, |
| 1186 | + lsst.daf.butler.DataCoordinate.make_empty(self.helper.butler.dimensions), |
| 1187 | + run=qg.header.output_run, |
| 1188 | + ) |
| 1189 | + if expected_error is None: |
| 1190 | + executor.run_pipeline(qg, provenance_dataset_ref=provenance_ref) |
| 1191 | + else: |
| 1192 | + with self.assertRaises(expected_error): |
| 1193 | + executor.run_pipeline(qg, provenance_dataset_ref=provenance_ref) |
| 1194 | + provenance_graph = self.helper.butler.get(provenance_ref) |
| 1195 | + self.assertEqual(len(provenance_graph.quanta_by_task), 3) |
| 1196 | + self.assertEqual(len(provenance_graph.quanta_by_task["a"]), 4) |
| 1197 | + self.assertEqual(len(provenance_graph.quanta_by_task["b"]), 4) |
| 1198 | + self.assertEqual(len(provenance_graph.quanta_by_task["c"]), 4) |
| 1199 | + return provenance_graph |
| 1200 | + |
1156 | 1201 | def test_no_work_chain_provenance(self): |
1157 | 1202 | """Test provenance recording when a NoWorkFound error chains to |
1158 | 1203 | downstream tasks during execution. |
1159 | 1204 | """ |
| 1205 | + b_config = DynamicTestPipelineTaskConfig() |
| 1206 | + b_config.fail_exception = "lsst.pipe.base.NoWorkFound" |
| 1207 | + b_config.fail_condition = "detector=2" |
| 1208 | + provenance_graph = self.run_base_test(b_config, expected_error=None) |
| 1209 | + xgraph = provenance_graph.quantum_only_xgraph |
| 1210 | + for quantum_id in provenance_graph.quanta_by_task["a"].values(): |
| 1211 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL) |
| 1212 | + self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
| 1213 | + for data_id, quantum_id in provenance_graph.quanta_by_task["b"].items(): |
| 1214 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL) |
| 1215 | + if data_id["detector"] == 2: |
| 1216 | + self.assertTrue(xgraph.nodes[quantum_id]["caveats"] & QuantumSuccessCaveats.NO_WORK) |
| 1217 | + else: |
| 1218 | + self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
| 1219 | + for data_id, quantum_id in provenance_graph.quanta_by_task["c"].items(): |
| 1220 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL) |
| 1221 | + if data_id["detector"] == 2: |
| 1222 | + self.assertTrue( |
| 1223 | + xgraph.nodes[quantum_id]["caveats"] & QuantumSuccessCaveats.ADJUST_QUANTUM_RAISED |
| 1224 | + ) |
| 1225 | + else: |
| 1226 | + self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
| 1227 | + |
| 1228 | + def test_failure_block_provenance(self): |
| 1229 | + """Test provenance recording when an exception blocks one branch of a |
| 1230 | + QG but not another. |
| 1231 | + """ |
1160 | 1232 | # 'base.yaml' adds an instrument, 'Cam1', with four detectors and |
1161 | 1233 | # 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(): |
| 1234 | + b_config = DynamicTestPipelineTaskConfig() |
| 1235 | + b_config.fail_exception = "builtins.RuntimeError" |
| 1236 | + b_config.fail_condition = "detector=2" |
| 1237 | + provenance_graph = self.run_base_test(b_config, MPGraphExecutorError) |
| 1238 | + self.assertEqual(len(provenance_graph.quanta_by_task), 3) |
| 1239 | + self.assertEqual(len(provenance_graph.quanta_by_task["a"]), 4) |
| 1240 | + self.assertEqual(len(provenance_graph.quanta_by_task["b"]), 4) |
| 1241 | + self.assertEqual(len(provenance_graph.quanta_by_task["c"]), 4) |
| 1242 | + xgraph = provenance_graph.quantum_only_xgraph |
| 1243 | + for quantum_id in provenance_graph.quanta_by_task["a"].values(): |
| 1244 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL) |
| 1245 | + self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
| 1246 | + for data_id, quantum_id in provenance_graph.quanta_by_task["b"].items(): |
| 1247 | + if data_id["detector"] == 2: |
| 1248 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.FAILED) |
| 1249 | + else: |
1194 | 1250 | self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.SUCCESSFUL) |
1195 | 1251 | self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
1196 | | - for data_id, quantum_id in provenance_graph.quanta_by_task["b"].items(): |
| 1252 | + for data_id, quantum_id in provenance_graph.quanta_by_task["c"].items(): |
| 1253 | + if data_id["detector"] == 2: |
| 1254 | + self.assertEqual(xgraph.nodes[quantum_id]["status"], QuantumAttemptStatus.BLOCKED) |
| 1255 | + else: |
1197 | 1256 | 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) |
| 1257 | + self.assertEqual(xgraph.nodes[quantum_id]["caveats"], QuantumSuccessCaveats.NO_CAVEATS) |
1210 | 1258 |
|
1211 | 1259 |
|
1212 | 1260 | class MemoryTester(lsst.utils.tests.MemoryTestCase): |
|
0 commit comments