Coverage for tests/test_pipelineIR.py: 19%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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# (http://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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22import os
23import tempfile
24import textwrap
25import unittest
27import lsst.utils.tests
28from lsst.pipe.base.pipelineIR import ConfigIR, PipelineIR
31class ConfigIRTestCase(unittest.TestCase):
32 """A test case for ConfigIR Objects
34 ConfigIR contains a method that is not exercised by the PipelineIR task,
35 so it should be tested here
36 """
38 def setUp(self):
39 pass
41 def tearDown(self):
42 pass
44 def testMergeConfig(self):
45 # Create some configs to merge
46 config1 = ConfigIR(
47 python="config.foo=6", dataId={"visit": 7}, file=["test1.py"], rest={"a": 1, "b": 2}
48 )
49 config2 = ConfigIR(python=None, dataId=None, file=["test2.py"], rest={"c": 1, "d": 2})
50 config3 = ConfigIR(python="config.bar=7", dataId=None, file=["test3.py"], rest={"c": 1, "d": 2})
51 config4 = ConfigIR(python=None, dataId=None, file=["test4.py"], rest={"c": 3, "e": 4})
52 config5 = ConfigIR(rest={"f": 5, "g": 6})
53 config6 = ConfigIR(rest={"h": 7, "i": 8})
54 config7 = ConfigIR(rest={"h": 9})
56 # Merge configs with different dataIds, this should yield two elements
57 self.assertEqual(list(config1.maybe_merge(config2)), [config1, config2])
59 # Merge configs with python blocks defined, this should yield two
60 # elements
61 self.assertEqual(list(config1.maybe_merge(config3)), [config1, config3])
63 # Merge configs with file defined, this should yield two elements
64 self.assertEqual(list(config2.maybe_merge(config4)), [config2, config4])
66 # merge config2 into config1
67 merge_result = list(config5.maybe_merge(config6))
68 self.assertEqual(len(merge_result), 1)
69 self.assertEqual(config5.rest, {"f": 5, "g": 6, "h": 7, "i": 8})
71 # Cant merge configs with shared keys
72 self.assertEqual(list(config6.maybe_merge(config7)), [config6, config7])
75class PipelineIRTestCase(unittest.TestCase):
76 """A test case for PipelineIR objects"""
78 def setUp(self):
79 pass
81 def tearDown(self):
82 pass
84 def testPipelineIRInitChecks(self):
85 # Missing description
86 pipeline_str = """
87 tasks:
88 a: module.A
89 """
90 with self.assertRaises(ValueError):
91 PipelineIR.from_string(pipeline_str)
93 # Missing tasks
94 pipeline_str = """
95 description: Test Pipeline
96 """
97 with self.assertRaises(ValueError):
98 PipelineIR.from_string(pipeline_str)
100 # This should raise a FileNotFoundError, as there are imported defined
101 # so the __init__ method should pass but the imported file does not
102 # exist
103 pipeline_str = textwrap.dedent(
104 """
105 description: Test Pipeline
106 imports: /dummy_pipeline.yaml
107 """
108 )
110 with self.assertRaises(FileNotFoundError):
111 PipelineIR.from_string(pipeline_str)
113 def testTaskParsing(self):
114 # Should be able to parse a task defined both ways
115 pipeline_str = textwrap.dedent(
116 """
117 description: Test Pipeline
118 tasks:
119 modA: test.modA
120 modB:
121 class: test.modB
122 """
123 )
125 pipeline = PipelineIR.from_string(pipeline_str)
126 self.assertEqual(list(pipeline.tasks.keys()), ["modA", "modB"])
127 self.assertEqual([t.klass for t in pipeline.tasks.values()], ["test.modA", "test.modB"])
129 def testImportParsing(self):
130 # This should raise, as the two pipelines, both define the same label
131 pipeline_str = textwrap.dedent(
132 """
133 description: Test Pipeline
134 imports:
135 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
136 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
137 """
138 )
139 # "modA" is the duplicated label, and it should appear in the error.
140 with self.assertRaisesRegex(ValueError, "modA"):
141 PipelineIR.from_string(pipeline_str)
143 # This should pass, as the conflicting task is excluded
144 pipeline_str = textwrap.dedent(
145 """
146 description: Test Pipeline
147 imports:
148 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
149 exclude: modA
150 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
151 """
152 )
153 pipeline = PipelineIR.from_string(pipeline_str)
154 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
156 # This should pass, as the conflicting task is no in includes
157 pipeline_str = textwrap.dedent(
158 """
159 description: Test Pipeline
160 imports:
161 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
162 include: modB
163 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
164 """
165 )
167 pipeline = PipelineIR.from_string(pipeline_str)
168 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
170 # Test that you cant include and exclude a task
171 pipeline_str = textwrap.dedent(
172 """
173 description: Test Pipeline
174 imports:
175 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
176 exclude: modA
177 include: modB
178 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
179 """
180 )
182 with self.assertRaises(ValueError):
183 PipelineIR.from_string(pipeline_str)
185 # Test that contracts are imported
186 pipeline_str = textwrap.dedent(
187 """
188 description: Test Pipeline
189 imports:
190 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
191 """
192 )
194 pipeline = PipelineIR.from_string(pipeline_str)
195 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
197 # Test that contracts are not imported
198 pipeline_str = textwrap.dedent(
199 """
200 description: Test Pipeline
201 imports:
202 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
203 importContracts: False
204 """
205 )
207 pipeline = PipelineIR.from_string(pipeline_str)
208 self.assertEqual(pipeline.contracts, [])
210 # Test that configs are imported when defining the same task again
211 # with the same label
212 pipeline_str = textwrap.dedent(
213 """
214 description: Test Pipeline
215 imports:
216 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
217 tasks:
218 modA:
219 class: "test.moduleA"
220 config:
221 value2: 2
222 """
223 )
224 pipeline = PipelineIR.from_string(pipeline_str)
225 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value1": 1, "value2": 2})
227 # Test that configs are not imported when redefining the task
228 # associated with a label
229 pipeline_str = textwrap.dedent(
230 """
231 description: Test Pipeline
232 imports:
233 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
234 tasks:
235 modA:
236 class: "test.moduleAReplace"
237 config:
238 value2: 2
239 """
240 )
241 pipeline = PipelineIR.from_string(pipeline_str)
242 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value2": 2})
244 # Test that named subsets are imported
245 pipeline_str = textwrap.dedent(
246 """
247 description: Test Pipeline
248 imports:
249 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
250 """
251 )
252 pipeline = PipelineIR.from_string(pipeline_str)
253 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
254 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modA"})
256 # Test that imported and redeclaring a named subset works
257 pipeline_str = textwrap.dedent(
258 """
259 description: Test Pipeline
260 imports:
261 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
262 tasks:
263 modE: "test.moduleE"
264 subsets:
265 modSubset:
266 - modE
267 """
268 )
269 pipeline = PipelineIR.from_string(pipeline_str)
270 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
271 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modE"})
273 # Test that imported from two pipelines that both declare a named
274 # subset with the same name fails
275 pipeline_str = textwrap.dedent(
276 """
277 description: Test Pipeline
278 imports:
279 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
280 - $PIPE_BASE_DIR/tests/testPipeline3.yaml
281 """
282 )
283 with self.assertRaises(ValueError):
284 PipelineIR.from_string(pipeline_str)
286 # Test that imported a named subset that duplicates a label declared
287 # in this pipeline fails
288 pipeline_str = textwrap.dedent(
289 """
290 description: Test Pipeline
291 imports:
292 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
293 tasks:
294 modSubset: "test.moduleE"
295 """
296 )
297 with self.assertRaises(ValueError):
298 PipelineIR.from_string(pipeline_str)
300 # Test that imported fails if a named subset and task label conflict
301 pipeline_str = textwrap.dedent(
302 """
303 description: Test Pipeline
304 imports:
305 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
306 - $PIPE_BASE_DIR/tests/testPipeline4.yaml
307 """
308 )
309 with self.assertRaises(ValueError):
310 PipelineIR.from_string(pipeline_str)
312 def testReadParameters(self):
313 # verify that parameters section are read in from a pipeline
314 pipeline_str = textwrap.dedent(
315 """
316 description: Test Pipeline
317 parameters:
318 value1: A
319 value2: B
320 tasks:
321 modA: ModuleA
322 """
323 )
324 pipeline = PipelineIR.from_string(pipeline_str)
325 self.assertEqual(pipeline.parameters.mapping, {"value1": "A", "value2": "B"})
327 def testTaskParameterLabel(self):
328 # verify that "parameters" cannot be used as a task label
329 pipeline_str = textwrap.dedent(
330 """
331 description: Test Pipeline
332 tasks:
333 parameters: modA
334 """
335 )
336 with self.assertRaises(ValueError):
337 PipelineIR.from_string(pipeline_str)
339 def testParameterImporting(self):
340 # verify that importing parameters happens correctly
341 pipeline_str = textwrap.dedent(
342 """
343 description: Test Pipeline
344 imports:
345 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
346 - location: $PIPE_BASE_DIR/tests/testPipeline2.yaml
347 exclude:
348 - modA
350 parameters:
351 value4: valued
352 """
353 )
354 pipeline = PipelineIR.from_string(pipeline_str)
355 self.assertEqual(
356 pipeline.parameters.mapping,
357 {"value4": "valued", "value1": "valueNew", "value2": "valueB", "value3": "valueC"},
358 )
360 def testImportingInstrument(self):
361 # verify an instrument is imported, or ignored, (Or otherwise modified
362 # for potential future use)
363 pipeline_str = textwrap.dedent(
364 """
365 description: Test Pipeline
366 imports:
367 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
368 """
369 )
370 pipeline = PipelineIR.from_string(pipeline_str)
371 self.assertEqual(pipeline.instrument, "test.instrument")
373 # verify that an imported pipeline can have its instrument set to None
374 pipeline_str = textwrap.dedent(
375 """
376 description: Test Pipeline
377 imports:
378 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
379 instrument: None
380 """
381 )
382 pipeline = PipelineIR.from_string(pipeline_str)
383 self.assertEqual(pipeline.instrument, None)
385 # verify that an imported pipeline can have its instrument modified
386 pipeline_str = textwrap.dedent(
387 """
388 description: Test Pipeline
389 imports:
390 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
391 instrument: new.instrument
392 """
393 )
394 pipeline = PipelineIR.from_string(pipeline_str)
395 self.assertEqual(pipeline.instrument, "new.instrument")
397 # Test that multiple instruments can't be defined,
398 # and that the error message tells you what instruments were found.
399 pipeline_str = textwrap.dedent(
400 """
401 description: Test Pipeline
402 instrument: new.instrument
403 imports:
404 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
405 """
406 )
407 with self.assertRaisesRegex(ValueError, "new.instrument .* test.instrument."):
408 PipelineIR.from_string(pipeline_str)
410 def testParameterConfigFormatting(self):
411 # verify that a config properly is formatted with parameters
412 pipeline_str = textwrap.dedent(
413 """
414 description: Test Pipeline
415 parameters:
416 value1: A
417 tasks:
418 modA:
419 class: ModuleA
420 config:
421 testKey: parameters.value1
422 """
423 )
424 pipeline = PipelineIR.from_string(pipeline_str)
425 newConfig = pipeline.tasks["modA"].config[0].formatted(pipeline.parameters)
426 self.assertEqual(newConfig.rest["testKey"], "A")
428 def testReadContracts(self):
429 # Verify that contracts are read in from a pipeline
430 location = os.path.expandvars("$PIPE_BASE_DIR/tests/testPipeline1.yaml")
431 pipeline = PipelineIR.from_file(location)
432 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
434 # Verify that a contract message is loaded
435 pipeline_str = textwrap.dedent(
436 """
437 description: Test Pipeline
438 tasks:
439 modA: test.modA
440 modB:
441 class: test.modB
442 contracts:
443 - contract: modA.foo == modB.Bar
444 msg: "Test message"
445 """
446 )
448 pipeline = PipelineIR.from_string(pipeline_str)
449 self.assertEqual(pipeline.contracts[0].msg, "Test message")
451 def testReadNamedSubsets(self):
452 pipeline_str = textwrap.dedent(
453 """
454 description: Test Pipeline
455 tasks:
456 modA: test.modA
457 modB:
458 class: test.modB
459 modC: test.modC
460 modD: test.modD
461 subsets:
462 subset1:
463 - modA
464 - modB
465 subset2:
466 subset:
467 - modC
468 - modD
469 description: "A test named subset"
470 """
471 )
472 pipeline = PipelineIR.from_string(pipeline_str)
473 self.assertEqual(pipeline.labeled_subsets.keys(), {"subset1", "subset2"})
475 self.assertEqual(pipeline.labeled_subsets["subset1"].subset, {"modA", "modB"})
476 self.assertEqual(pipeline.labeled_subsets["subset1"].description, None)
478 self.assertEqual(pipeline.labeled_subsets["subset2"].subset, {"modC", "modD"})
479 self.assertEqual(pipeline.labeled_subsets["subset2"].description, "A test named subset")
481 # verify that forgetting a subset key is an error
482 pipeline_str = textwrap.dedent(
483 """
484 description: Test Pipeline
485 tasks:
486 modA: test.modA
487 modB:
488 class: test.modB
489 modC: test.modC
490 modD: test.modD
491 subsets:
492 subset2:
493 sub:
494 - modC
495 - modD
496 description: "A test named subset"
497 """
498 )
499 with self.assertRaises(ValueError):
500 PipelineIR.from_string(pipeline_str)
502 # verify putting a label in a named subset that is not in the task is
503 # an error
504 pipeline_str = textwrap.dedent(
505 """
506 description: Test Pipeline
507 tasks:
508 modA: test.modA
509 modB:
510 class: test.modB
511 modC: test.modC
512 modD: test.modD
513 subsets:
514 subset2:
515 - modC
516 - modD
517 - modE
518 """
519 )
520 with self.assertRaises(ValueError):
521 PipelineIR.from_string(pipeline_str)
523 def testInstrument(self):
524 # Verify that if instrument is defined it is parsed out
525 pipeline_str = textwrap.dedent(
526 """
527 description: Test Pipeline
528 instrument: dummyCam
529 tasks:
530 modA: test.moduleA
531 """
532 )
534 pipeline = PipelineIR.from_string(pipeline_str)
535 self.assertEqual(pipeline.instrument, "dummyCam")
537 def testReadTaskConfig(self):
538 # Verify that a task with a config is read in correctly
539 pipeline_str = textwrap.dedent(
540 """
541 description: Test Pipeline
542 tasks:
543 modA:
544 class: test.moduleA
545 config:
546 propertyA: 6
547 propertyB: 7
548 file: testfile.py
549 python: "config.testDict['a'] = 9"
550 """
551 )
553 pipeline = PipelineIR.from_string(pipeline_str)
554 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py"])
555 self.assertEqual(pipeline.tasks["modA"].config[0].python, "config.testDict['a'] = 9")
556 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
558 # Verify that multiple files are read fine
559 pipeline_str = textwrap.dedent(
560 """
561 description: Test Pipeline
562 tasks:
563 modA:
564 class: test.moduleA
565 config:
566 file:
567 - testfile.py
568 - otherFile.py
569 """
570 )
572 pipeline = PipelineIR.from_string(pipeline_str)
573 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py", "otherFile.py"])
575 # Test reading multiple Config entries
576 pipeline_str = textwrap.dedent(
577 """
578 description: Test Pipeline
579 tasks:
580 modA:
581 class: test.moduleA
582 config:
583 - propertyA: 6
584 propertyB: 7
585 dataId: {"visit": 6}
586 - propertyA: 8
587 propertyB: 9
588 """
589 )
591 pipeline = PipelineIR.from_string(pipeline_str)
592 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
593 self.assertEqual(pipeline.tasks["modA"].config[0].dataId, {"visit": 6})
594 self.assertEqual(pipeline.tasks["modA"].config[1].rest, {"propertyA": 8, "propertyB": 9})
595 self.assertEqual(pipeline.tasks["modA"].config[1].dataId, None)
597 def testSerialization(self):
598 # Test creating a pipeline, writing it to a file, reading the file
599 pipeline_str = textwrap.dedent(
600 """
601 description: Test Pipeline
602 instrument: dummyCam
603 imports:
604 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
605 instrument: None
606 tasks:
607 modC:
608 class: test.moduleC
609 config:
610 - propertyA: 6
611 propertyB: 7
612 dataId: {"visit": 6}
613 - propertyA: 8
614 propertyB: 9
615 modD: test.moduleD
616 contracts:
617 - modA.foo == modB.bar
618 subsets:
619 subA:
620 - modA
621 - modC
622 """
623 )
625 pipeline = PipelineIR.from_string(pipeline_str)
627 # Create the temp file, write and read
628 with tempfile.NamedTemporaryFile() as tf:
629 pipeline.to_file(tf.name)
630 loaded_pipeline = PipelineIR.from_file(tf.name)
631 self.assertEqual(pipeline, loaded_pipeline)
633 def testSorting(self):
634 pipeline_str = textwrap.dedent(
635 """
636 description: Test Pipeline
637 tasks:
638 modA: test.modA
639 modB:
640 class: test.modB
641 """
642 )
644 pipeline = PipelineIR.from_string(pipeline_str)
645 newKeyOrder = ["modB", "modA"]
646 pipeline.reorder_tasks(newKeyOrder)
647 self.assertEqual(list(pipeline.tasks.keys()), newKeyOrder)
648 with self.assertRaises(KeyError):
649 pipeline.reorder_tasks(["modB"])
650 with self.assertRaises(KeyError):
651 pipeline.reorder_tasks(["modD"])
653 def testSortingPrimitives(self):
654 pipeline_str = textwrap.dedent(
655 """
656 description: Test Pipeline
657 parameters:
658 value2: A
659 value1: B
660 tasks:
661 modB: ModuleB
662 modA: ModuleA
663 contracts:
664 - contract: modB.foo == modA.Bar
665 msg: "Test message"
666 - contract: modA.foo == modB.Bar
667 msg: "Test message"
668 subsets:
669 subset2:
670 - modA
671 - modB
672 subset1:
673 subset:
674 - modA
675 - modB
676 description: "A test named subset"
677 """
678 )
679 pipeline = PipelineIR.from_string(pipeline_str)
680 primitives = pipeline.to_primitives()
682 # verify subsets
683 self.assertEqual(list(pipeline.labeled_subsets.keys()), ["subset2", "subset1"])
684 self.assertEqual(list(primitives["subsets"].keys()), ["subset1", "subset2"])
686 # verify parameters
687 self.assertEqual(list(pipeline.parameters.mapping.keys()), ["value2", "value1"])
688 self.assertEqual(list(primitives["parameters"].keys()), ["value1", "value2"])
690 # verify contracts
691 self.assertEqual(
692 [c.contract for c in pipeline.contracts], ["modB.foo == modA.Bar", "modA.foo == modB.Bar"]
693 )
694 self.assertEqual(
695 [c["contract"] for c in primitives["contracts"]], ["modA.foo == modB.Bar", "modB.foo == modA.Bar"]
696 )
698 def testPipelineYamlLoader(self):
699 # Tests that an exception is thrown in the case a key is used multiple
700 # times in a given scope within a pipeline file
701 pipeline_str = textwrap.dedent(
702 """
703 description: Test Pipeline
704 tasks:
705 modA: test1
706 modB: test2
707 modA: test3
708 """
709 )
710 self.assertRaises(KeyError, PipelineIR.from_string, pipeline_str)
713class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
714 pass
717def setup_module(module):
718 lsst.utils.tests.init()
721if __name__ == "__main__": 721 ↛ 722line 721 didn't jump to line 722, because the condition on line 721 was never true
722 lsst.utils.tests.init()
723 unittest.main()