Coverage for tests/test_pipelineIR.py: 17%
201 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-18 11:52 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-18 11:52 -0700
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
30# Find where the test pipelines exist and store it in an environment variable.
31os.environ["TESTDIR"] = os.path.dirname(__file__)
34class ConfigIRTestCase(unittest.TestCase):
35 """A test case for ConfigIR Objects
37 ConfigIR contains a method that is not exercised by the PipelineIR task,
38 so it should be tested here
39 """
41 def setUp(self):
42 pass
44 def tearDown(self):
45 pass
47 def testMergeConfig(self):
48 # Create some configs to merge
49 config1 = ConfigIR(
50 python="config.foo=6", dataId={"visit": 7}, file=["test1.py"], rest={"a": 1, "b": 2}
51 )
52 config2 = ConfigIR(python=None, dataId=None, file=["test2.py"], rest={"c": 1, "d": 2})
53 config3 = ConfigIR(python="config.bar=7", dataId=None, file=["test3.py"], rest={"c": 1, "d": 2})
54 config4 = ConfigIR(python=None, dataId=None, file=["test4.py"], rest={"c": 3, "e": 4})
55 config5 = ConfigIR(rest={"f": 5, "g": 6})
56 config6 = ConfigIR(rest={"h": 7, "i": 8})
57 config7 = ConfigIR(rest={"h": 9})
59 # Merge configs with different dataIds, this should yield two elements
60 self.assertEqual(list(config1.maybe_merge(config2)), [config1, config2])
62 # Merge configs with python blocks defined, this should yield two
63 # elements
64 self.assertEqual(list(config1.maybe_merge(config3)), [config1, config3])
66 # Merge configs with file defined, this should yield two elements
67 self.assertEqual(list(config2.maybe_merge(config4)), [config2, config4])
69 # merge config2 into config1
70 merge_result = list(config5.maybe_merge(config6))
71 self.assertEqual(len(merge_result), 1)
72 self.assertEqual(config5.rest, {"f": 5, "g": 6, "h": 7, "i": 8})
74 # Cant merge configs with shared keys
75 self.assertEqual(list(config6.maybe_merge(config7)), [config6, config7])
78class PipelineIRTestCase(unittest.TestCase):
79 """A test case for PipelineIR objects"""
81 def setUp(self):
82 pass
84 def tearDown(self):
85 pass
87 def testPipelineIRInitChecks(self):
88 # Missing description
89 pipeline_str = """
90 tasks:
91 a: module.A
92 """
93 with self.assertRaises(ValueError):
94 PipelineIR.from_string(pipeline_str)
96 # Missing tasks
97 pipeline_str = """
98 description: Test Pipeline
99 """
100 with self.assertRaises(ValueError):
101 PipelineIR.from_string(pipeline_str)
103 # This should raise a FileNotFoundError, as there are imported defined
104 # so the __init__ method should pass but the imported file does not
105 # exist
106 pipeline_str = textwrap.dedent(
107 """
108 description: Test Pipeline
109 imports: /dummy_pipeline.yaml
110 """
111 )
113 with self.assertRaises(FileNotFoundError):
114 PipelineIR.from_string(pipeline_str)
116 def testTaskParsing(self):
117 # Should be able to parse a task defined both ways
118 pipeline_str = textwrap.dedent(
119 """
120 description: Test Pipeline
121 tasks:
122 modA: test.modA
123 modB:
124 class: test.modB
125 """
126 )
128 pipeline = PipelineIR.from_string(pipeline_str)
129 self.assertEqual(list(pipeline.tasks.keys()), ["modA", "modB"])
130 self.assertEqual([t.klass for t in pipeline.tasks.values()], ["test.modA", "test.modB"])
132 def testImportParsing(self):
133 # This should raise, as the two pipelines, both define the same label
134 pipeline_str = textwrap.dedent(
135 """
136 description: Test Pipeline
137 imports:
138 - $TESTDIR/testPipeline1.yaml
139 - $TESTDIR/testPipeline2.yaml
140 """
141 )
142 # "modA" is the duplicated label, and it should appear in the error.
143 with self.assertRaisesRegex(ValueError, "modA"):
144 PipelineIR.from_string(pipeline_str)
146 # This should pass, as the conflicting task is excluded
147 pipeline_str = textwrap.dedent(
148 """
149 description: Test Pipeline
150 imports:
151 - location: $TESTDIR/testPipeline1.yaml
152 exclude: modA
153 - $TESTDIR/testPipeline2.yaml
154 """
155 )
156 pipeline = PipelineIR.from_string(pipeline_str)
157 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
159 # This should pass, as the conflicting task is no in includes
160 pipeline_str = textwrap.dedent(
161 """
162 description: Test Pipeline
163 imports:
164 - location: $TESTDIR/testPipeline1.yaml
165 include: modB
166 - $TESTDIR/testPipeline2.yaml
167 """
168 )
170 pipeline = PipelineIR.from_string(pipeline_str)
171 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
173 # Test that you cant include and exclude a task
174 pipeline_str = textwrap.dedent(
175 """
176 description: Test Pipeline
177 imports:
178 - location: $TESTDIR/testPipeline1.yaml
179 exclude: modA
180 include: modB
181 - $TESTDIR/testPipeline2.yaml
182 """
183 )
185 with self.assertRaises(ValueError):
186 PipelineIR.from_string(pipeline_str)
188 # Test that contracts are imported
189 pipeline_str = textwrap.dedent(
190 """
191 description: Test Pipeline
192 imports:
193 - $TESTDIR/testPipeline1.yaml
194 """
195 )
197 pipeline = PipelineIR.from_string(pipeline_str)
198 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
200 # Test that contracts are not imported
201 pipeline_str = textwrap.dedent(
202 """
203 description: Test Pipeline
204 imports:
205 - location: $TESTDIR/testPipeline1.yaml
206 importContracts: False
207 """
208 )
210 pipeline = PipelineIR.from_string(pipeline_str)
211 self.assertEqual(pipeline.contracts, [])
213 # Test that configs are imported when defining the same task again
214 # with the same label
215 pipeline_str = textwrap.dedent(
216 """
217 description: Test Pipeline
218 imports:
219 - $TESTDIR/testPipeline2.yaml
220 tasks:
221 modA:
222 class: "test.moduleA"
223 config:
224 value2: 2
225 """
226 )
227 pipeline = PipelineIR.from_string(pipeline_str)
228 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value1": 1, "value2": 2})
230 # Test that configs are not imported when redefining the task
231 # associated with a label
232 pipeline_str = textwrap.dedent(
233 """
234 description: Test Pipeline
235 imports:
236 - $TESTDIR/testPipeline2.yaml
237 tasks:
238 modA:
239 class: "test.moduleAReplace"
240 config:
241 value2: 2
242 """
243 )
244 pipeline = PipelineIR.from_string(pipeline_str)
245 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value2": 2})
247 # Test that named subsets are imported
248 pipeline_str = textwrap.dedent(
249 """
250 description: Test Pipeline
251 imports:
252 - $TESTDIR/testPipeline2.yaml
253 """
254 )
255 pipeline = PipelineIR.from_string(pipeline_str)
256 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
257 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modA"})
259 # Test that imported and redeclaring a named subset works
260 pipeline_str = textwrap.dedent(
261 """
262 description: Test Pipeline
263 imports:
264 - $TESTDIR/testPipeline2.yaml
265 tasks:
266 modE: "test.moduleE"
267 subsets:
268 modSubset:
269 - modE
270 """
271 )
272 pipeline = PipelineIR.from_string(pipeline_str)
273 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
274 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modE"})
276 # Test that imported from two pipelines that both declare a named
277 # subset with the same name fails
278 pipeline_str = textwrap.dedent(
279 """
280 description: Test Pipeline
281 imports:
282 - $TESTDIR/testPipeline2.yaml
283 - $TESTDIR/testPipeline3.yaml
284 """
285 )
286 with self.assertRaises(ValueError):
287 PipelineIR.from_string(pipeline_str)
289 # Test that imported a named subset that duplicates a label declared
290 # in this pipeline fails
291 pipeline_str = textwrap.dedent(
292 """
293 description: Test Pipeline
294 imports:
295 - $TESTDIR/testPipeline2.yaml
296 tasks:
297 modSubset: "test.moduleE"
298 """
299 )
300 with self.assertRaises(ValueError):
301 PipelineIR.from_string(pipeline_str)
303 # Test that imported fails if a named subset and task label conflict
304 pipeline_str = textwrap.dedent(
305 """
306 description: Test Pipeline
307 imports:
308 - $TESTDIR/testPipeline2.yaml
309 - $TESTDIR/testPipeline4.yaml
310 """
311 )
312 with self.assertRaises(ValueError):
313 PipelineIR.from_string(pipeline_str)
315 def testReadParameters(self):
316 # verify that parameters section are read in from a pipeline
317 pipeline_str = textwrap.dedent(
318 """
319 description: Test Pipeline
320 parameters:
321 value1: A
322 value2: B
323 tasks:
324 modA: ModuleA
325 """
326 )
327 pipeline = PipelineIR.from_string(pipeline_str)
328 self.assertEqual(pipeline.parameters.mapping, {"value1": "A", "value2": "B"})
330 def testTaskParameterLabel(self):
331 # verify that "parameters" cannot be used as a task label
332 pipeline_str = textwrap.dedent(
333 """
334 description: Test Pipeline
335 tasks:
336 parameters: modA
337 """
338 )
339 with self.assertRaises(ValueError):
340 PipelineIR.from_string(pipeline_str)
342 def testParameterImporting(self):
343 # verify that importing parameters happens correctly
344 pipeline_str = textwrap.dedent(
345 """
346 description: Test Pipeline
347 imports:
348 - $TESTDIR/testPipeline1.yaml
349 - location: $TESTDIR/testPipeline2.yaml
350 exclude:
351 - modA
353 parameters:
354 value4: valued
355 """
356 )
357 pipeline = PipelineIR.from_string(pipeline_str)
358 self.assertEqual(
359 pipeline.parameters.mapping,
360 {"value4": "valued", "value1": "valueNew", "value2": "valueB", "value3": "valueC"},
361 )
363 def testImportingInstrument(self):
364 # verify an instrument is imported, or ignored, (Or otherwise modified
365 # for potential future use)
366 pipeline_str = textwrap.dedent(
367 """
368 description: Test Pipeline
369 imports:
370 - $TESTDIR/testPipeline1.yaml
371 """
372 )
373 pipeline = PipelineIR.from_string(pipeline_str)
374 self.assertEqual(pipeline.instrument, "test.instrument")
376 # verify that an imported pipeline can have its instrument set to None
377 pipeline_str = textwrap.dedent(
378 """
379 description: Test Pipeline
380 imports:
381 - location: $TESTDIR/testPipeline1.yaml
382 instrument: None
383 """
384 )
385 pipeline = PipelineIR.from_string(pipeline_str)
386 self.assertEqual(pipeline.instrument, None)
388 # verify that an imported pipeline can have its instrument modified
389 pipeline_str = textwrap.dedent(
390 """
391 description: Test Pipeline
392 imports:
393 - location: $TESTDIR/testPipeline1.yaml
394 instrument: new.instrument
395 """
396 )
397 pipeline = PipelineIR.from_string(pipeline_str)
398 self.assertEqual(pipeline.instrument, "new.instrument")
400 # Test that multiple instruments can't be defined,
401 # and that the error message tells you what instruments were found.
402 pipeline_str = textwrap.dedent(
403 """
404 description: Test Pipeline
405 instrument: new.instrument
406 imports:
407 - location: $TESTDIR/testPipeline1.yaml
408 """
409 )
410 with self.assertRaisesRegex(ValueError, "new.instrument .* test.instrument."):
411 PipelineIR.from_string(pipeline_str)
413 def testParameterConfigFormatting(self):
414 # verify that a config properly is formatted with parameters
415 pipeline_str = textwrap.dedent(
416 """
417 description: Test Pipeline
418 parameters:
419 value1: A
420 tasks:
421 modA:
422 class: ModuleA
423 config:
424 testKey: parameters.value1
425 """
426 )
427 pipeline = PipelineIR.from_string(pipeline_str)
428 newConfig = pipeline.tasks["modA"].config[0].formatted(pipeline.parameters)
429 self.assertEqual(newConfig.rest["testKey"], "A")
431 def testReadContracts(self):
432 # Verify that contracts are read in from a pipeline
433 location = os.path.expandvars("$TESTDIR/testPipeline1.yaml")
434 pipeline = PipelineIR.from_file(location)
435 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
437 # Verify that a contract message is loaded
438 pipeline_str = textwrap.dedent(
439 """
440 description: Test Pipeline
441 tasks:
442 modA: test.modA
443 modB:
444 class: test.modB
445 contracts:
446 - contract: modA.foo == modB.Bar
447 msg: "Test message"
448 """
449 )
451 pipeline = PipelineIR.from_string(pipeline_str)
452 self.assertEqual(pipeline.contracts[0].msg, "Test message")
454 def testReadNamedSubsets(self):
455 pipeline_str = textwrap.dedent(
456 """
457 description: Test Pipeline
458 tasks:
459 modA: test.modA
460 modB:
461 class: test.modB
462 modC: test.modC
463 modD: test.modD
464 subsets:
465 subset1:
466 - modA
467 - modB
468 subset2:
469 subset:
470 - modC
471 - modD
472 description: "A test named subset"
473 """
474 )
475 pipeline = PipelineIR.from_string(pipeline_str)
476 self.assertEqual(pipeline.labeled_subsets.keys(), {"subset1", "subset2"})
478 self.assertEqual(pipeline.labeled_subsets["subset1"].subset, {"modA", "modB"})
479 self.assertEqual(pipeline.labeled_subsets["subset1"].description, None)
481 self.assertEqual(pipeline.labeled_subsets["subset2"].subset, {"modC", "modD"})
482 self.assertEqual(pipeline.labeled_subsets["subset2"].description, "A test named subset")
484 # verify that forgetting a subset key is an error
485 pipeline_str = textwrap.dedent(
486 """
487 description: Test Pipeline
488 tasks:
489 modA: test.modA
490 modB:
491 class: test.modB
492 modC: test.modC
493 modD: test.modD
494 subsets:
495 subset2:
496 sub:
497 - modC
498 - modD
499 description: "A test named subset"
500 """
501 )
502 with self.assertRaises(ValueError):
503 PipelineIR.from_string(pipeline_str)
505 # verify putting a label in a named subset that is not in the task is
506 # an error
507 pipeline_str = textwrap.dedent(
508 """
509 description: Test Pipeline
510 tasks:
511 modA: test.modA
512 modB:
513 class: test.modB
514 modC: test.modC
515 modD: test.modD
516 subsets:
517 subset2:
518 - modC
519 - modD
520 - modE
521 """
522 )
523 with self.assertRaises(ValueError):
524 PipelineIR.from_string(pipeline_str)
526 def testInstrument(self):
527 # Verify that if instrument is defined it is parsed out
528 pipeline_str = textwrap.dedent(
529 """
530 description: Test Pipeline
531 instrument: dummyCam
532 tasks:
533 modA: test.moduleA
534 """
535 )
537 pipeline = PipelineIR.from_string(pipeline_str)
538 self.assertEqual(pipeline.instrument, "dummyCam")
540 def testReadTaskConfig(self):
541 # Verify that a task with a config is read in correctly
542 pipeline_str = textwrap.dedent(
543 """
544 description: Test Pipeline
545 tasks:
546 modA:
547 class: test.moduleA
548 config:
549 propertyA: 6
550 propertyB: 7
551 file: testfile.py
552 python: "config.testDict['a'] = 9"
553 """
554 )
556 pipeline = PipelineIR.from_string(pipeline_str)
557 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py"])
558 self.assertEqual(pipeline.tasks["modA"].config[0].python, "config.testDict['a'] = 9")
559 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
561 # Verify that multiple files are read fine
562 pipeline_str = textwrap.dedent(
563 """
564 description: Test Pipeline
565 tasks:
566 modA:
567 class: test.moduleA
568 config:
569 file:
570 - testfile.py
571 - otherFile.py
572 """
573 )
575 pipeline = PipelineIR.from_string(pipeline_str)
576 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py", "otherFile.py"])
578 # Test reading multiple Config entries
579 pipeline_str = textwrap.dedent(
580 """
581 description: Test Pipeline
582 tasks:
583 modA:
584 class: test.moduleA
585 config:
586 - propertyA: 6
587 propertyB: 7
588 dataId: {"visit": 6}
589 - propertyA: 8
590 propertyB: 9
591 """
592 )
594 pipeline = PipelineIR.from_string(pipeline_str)
595 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
596 self.assertEqual(pipeline.tasks["modA"].config[0].dataId, {"visit": 6})
597 self.assertEqual(pipeline.tasks["modA"].config[1].rest, {"propertyA": 8, "propertyB": 9})
598 self.assertEqual(pipeline.tasks["modA"].config[1].dataId, None)
600 def testSerialization(self):
601 # Test creating a pipeline, writing it to a file, reading the file
602 pipeline_str = textwrap.dedent(
603 """
604 description: Test Pipeline
605 instrument: dummyCam
606 imports:
607 - location: $TESTDIR/testPipeline1.yaml
608 instrument: None
609 tasks:
610 modC:
611 class: test.moduleC
612 config:
613 - propertyA: 6
614 propertyB: 7
615 dataId: {"visit": 6}
616 - propertyA: 8
617 propertyB: 9
618 modD: test.moduleD
619 contracts:
620 - modA.foo == modB.bar
621 subsets:
622 subA:
623 - modA
624 - modC
625 """
626 )
628 pipeline = PipelineIR.from_string(pipeline_str)
630 # Create the temp file, write and read
631 with tempfile.NamedTemporaryFile() as tf:
632 pipeline.write_to_uri(tf.name)
633 loaded_pipeline = PipelineIR.from_uri(tf.name)
634 self.assertEqual(pipeline, loaded_pipeline)
636 def testSorting(self):
637 pipeline_str = textwrap.dedent(
638 """
639 description: Test Pipeline
640 tasks:
641 modA: test.modA
642 modB:
643 class: test.modB
644 """
645 )
647 pipeline = PipelineIR.from_string(pipeline_str)
648 newKeyOrder = ["modB", "modA"]
649 pipeline.reorder_tasks(newKeyOrder)
650 self.assertEqual(list(pipeline.tasks.keys()), newKeyOrder)
651 with self.assertRaises(KeyError):
652 pipeline.reorder_tasks(["modB"])
653 with self.assertRaises(KeyError):
654 pipeline.reorder_tasks(["modD"])
656 def testSortingPrimitives(self):
657 pipeline_str = textwrap.dedent(
658 """
659 description: Test Pipeline
660 parameters:
661 value2: A
662 value1: B
663 tasks:
664 modB: ModuleB
665 modA: ModuleA
666 contracts:
667 - contract: modB.foo == modA.Bar
668 msg: "Test message"
669 - contract: modA.foo == modB.Bar
670 msg: "Test message"
671 subsets:
672 subset2:
673 - modA
674 - modB
675 subset1:
676 subset:
677 - modA
678 - modB
679 description: "A test named subset"
680 """
681 )
682 pipeline = PipelineIR.from_string(pipeline_str)
683 primitives = pipeline.to_primitives()
685 # verify subsets
686 self.assertEqual(list(pipeline.labeled_subsets.keys()), ["subset2", "subset1"])
687 self.assertEqual(list(primitives["subsets"].keys()), ["subset1", "subset2"])
689 # verify parameters
690 self.assertEqual(list(pipeline.parameters.mapping.keys()), ["value2", "value1"])
691 self.assertEqual(list(primitives["parameters"].keys()), ["value1", "value2"])
693 # verify contracts
694 self.assertEqual(
695 [c.contract for c in pipeline.contracts], ["modB.foo == modA.Bar", "modA.foo == modB.Bar"]
696 )
697 self.assertEqual(
698 [c["contract"] for c in primitives["contracts"]], ["modA.foo == modB.Bar", "modB.foo == modA.Bar"]
699 )
701 def testPipelineYamlLoader(self):
702 # Tests that an exception is thrown in the case a key is used multiple
703 # times in a given scope within a pipeline file
704 pipeline_str = textwrap.dedent(
705 """
706 description: Test Pipeline
707 tasks:
708 modA: test1
709 modB: test2
710 modA: test3
711 """
712 )
713 self.assertRaises(KeyError, PipelineIR.from_string, pipeline_str)
715 def testMultiLineStrings(self):
716 """Test that multi-line strings in pipelines are written with
717 '|' continuation-syntax instead of explicit newlines.
718 """
719 pipeline_ir = PipelineIR({"description": "Line 1\nLine2\n", "tasks": {"modA": "task1"}})
720 string = str(pipeline_ir)
721 self.assertIn("|", string)
722 self.assertNotIn(r"\n", string)
725class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
726 pass
729def setup_module(module):
730 lsst.utils.tests.init()
733if __name__ == "__main__": 733 ↛ 734line 733 didn't jump to line 734, because the condition on line 733 was never true
734 lsst.utils.tests.init()
735 unittest.main()