Coverage for tests/test_pipelineIR.py: 18%
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
2# This file is part of pipe_base.
3#
4# Developed for the LSST Data Management System.
5# This product includes software developed by the LSST Project
6# (http://www.lsst.org).
7# See the COPYRIGHT file at the top-level directory of this distribution
8# for details of code ownership.
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
23import os
24import tempfile
25import textwrap
26import unittest
28from lsst.pipe.base.pipelineIR import PipelineIR, ConfigIR
29import lsst.utils.tests
32class ConfigIRTestCase(unittest.TestCase):
33 """A test case for ConfigIR Objects
35 ConfigIR contains a method that is not exercised by the PipelineIR task,
36 so it should be tested here
37 """
39 def setUp(self):
40 pass
42 def tearDown(self):
43 pass
45 def testMergeConfig(self):
46 # Create some configs to merge
47 config1 = ConfigIR(python="config.foo=6", dataId={"visit": 7}, file=["test1.py"],
48 rest={"a": 1, "b": 2})
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
77 """
79 def setUp(self):
80 pass
82 def tearDown(self):
83 pass
85 def testPipelineIRInitChecks(self):
86 # Missing description
87 pipeline_str = """
88 tasks:
89 a: module.A
90 """
91 with self.assertRaises(ValueError):
92 PipelineIR.from_string(pipeline_str)
94 # Missing tasks
95 pipeline_str = """
96 description: Test Pipeline
97 """
98 with self.assertRaises(ValueError):
99 PipelineIR.from_string(pipeline_str)
101 # This should raise a FileNotFoundError, as there are imported defined
102 # so the __init__ method should pass but the imported file does not
103 # exist
104 pipeline_str = textwrap.dedent("""
105 description: Test Pipeline
106 imports: /dummy_pipeline.yaml
107 """)
109 with self.assertRaises(FileNotFoundError):
110 PipelineIR.from_string(pipeline_str)
112 def testTaskParsing(self):
113 # Should be able to parse a task defined both ways
114 pipeline_str = textwrap.dedent("""
115 description: Test Pipeline
116 tasks:
117 modA: test.modA
118 modB:
119 class: test.modB
120 """)
122 pipeline = PipelineIR.from_string(pipeline_str)
123 self.assertEqual(list(pipeline.tasks.keys()), ["modA", "modB"])
124 self.assertEqual([t.klass for t in pipeline.tasks.values()], ["test.modA", "test.modB"])
126 def testImportParsing(self):
127 # This should raise, as the two pipelines, both define the same label
128 pipeline_str = textwrap.dedent("""
129 description: Test Pipeline
130 imports:
131 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
132 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
133 """)
134 # "modA" is the duplicated label, and it should appear in the error.
135 with self.assertRaisesRegex(ValueError, "modA"):
136 PipelineIR.from_string(pipeline_str)
138 # This should pass, as the conflicting task is excluded
139 pipeline_str = textwrap.dedent("""
140 description: Test Pipeline
141 imports:
142 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
143 exclude: modA
144 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
145 """)
146 pipeline = PipelineIR.from_string(pipeline_str)
147 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
149 # This should pass, as the conflicting task is no in includes
150 pipeline_str = textwrap.dedent("""
151 description: Test Pipeline
152 imports:
153 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
154 include: modB
155 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
156 """)
158 pipeline = PipelineIR.from_string(pipeline_str)
159 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"]))
161 # Test that you cant include and exclude a task
162 pipeline_str = textwrap.dedent("""
163 description: Test Pipeline
164 imports:
165 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
166 exclude: modA
167 include: modB
168 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
169 """)
171 with self.assertRaises(ValueError):
172 PipelineIR.from_string(pipeline_str)
174 # Test that contracts are imported
175 pipeline_str = textwrap.dedent("""
176 description: Test Pipeline
177 imports:
178 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
179 """)
181 pipeline = PipelineIR.from_string(pipeline_str)
182 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
184 # Test that contracts are not imported
185 pipeline_str = textwrap.dedent("""
186 description: Test Pipeline
187 imports:
188 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
189 importContracts: False
190 """)
192 pipeline = PipelineIR.from_string(pipeline_str)
193 self.assertEqual(pipeline.contracts, [])
195 # Test that configs are imported when defining the same task again
196 # with the same label
197 pipeline_str = textwrap.dedent("""
198 description: Test Pipeline
199 imports:
200 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
201 tasks:
202 modA:
203 class: "test.moduleA"
204 config:
205 value2: 2
206 """)
207 pipeline = PipelineIR.from_string(pipeline_str)
208 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value1": 1, "value2": 2})
210 # Test that configs are not imported when redefining the task
211 # associated with a label
212 pipeline_str = textwrap.dedent("""
213 description: Test Pipeline
214 imports:
215 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
216 tasks:
217 modA:
218 class: "test.moduleAReplace"
219 config:
220 value2: 2
221 """)
222 pipeline = PipelineIR.from_string(pipeline_str)
223 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value2": 2})
225 # Test that named subsets are imported
226 pipeline_str = textwrap.dedent("""
227 description: Test Pipeline
228 imports:
229 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
230 """)
231 pipeline = PipelineIR.from_string(pipeline_str)
232 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
233 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modA"})
235 # Test that imported and redeclaring a named subset works
236 pipeline_str = textwrap.dedent("""
237 description: Test Pipeline
238 imports:
239 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
240 tasks:
241 modE: "test.moduleE"
242 subsets:
243 modSubset:
244 - modE
245 """)
246 pipeline = PipelineIR.from_string(pipeline_str)
247 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"})
248 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modE"})
250 # Test that imported from two pipelines that both declare a named
251 # subset with the same name fails
252 pipeline_str = textwrap.dedent("""
253 description: Test Pipeline
254 imports:
255 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
256 - $PIPE_BASE_DIR/tests/testPipeline3.yaml
257 """)
258 with self.assertRaises(ValueError):
259 PipelineIR.from_string(pipeline_str)
261 # Test that imported a named subset that duplicates a label declared
262 # in this pipeline fails
263 pipeline_str = textwrap.dedent("""
264 description: Test Pipeline
265 imports:
266 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
267 tasks:
268 modSubset: "test.moduleE"
269 """)
270 with self.assertRaises(ValueError):
271 PipelineIR.from_string(pipeline_str)
273 # Test that imported fails if a named subset and task label conflict
274 pipeline_str = textwrap.dedent("""
275 description: Test Pipeline
276 imports:
277 - $PIPE_BASE_DIR/tests/testPipeline2.yaml
278 - $PIPE_BASE_DIR/tests/testPipeline4.yaml
279 """)
280 with self.assertRaises(ValueError):
281 PipelineIR.from_string(pipeline_str)
283 def testReadParameters(self):
284 # verify that parameters section are read in from a pipeline
285 pipeline_str = textwrap.dedent("""
286 description: Test Pipeline
287 parameters:
288 value1: A
289 value2: B
290 tasks:
291 modA: ModuleA
292 """)
293 pipeline = PipelineIR.from_string(pipeline_str)
294 self.assertEqual(pipeline.parameters.mapping, {"value1": "A", "value2": "B"})
296 def testTaskParameterLabel(self):
297 # verify that "parameters" cannot be used as a task label
298 pipeline_str = textwrap.dedent("""
299 description: Test Pipeline
300 tasks:
301 parameters: modA
302 """)
303 with self.assertRaises(ValueError):
304 PipelineIR.from_string(pipeline_str)
306 def testParameterImporting(self):
307 # verify that importing parameters happens correctly
308 pipeline_str = textwrap.dedent("""
309 description: Test Pipeline
310 imports:
311 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
312 - location: $PIPE_BASE_DIR/tests/testPipeline2.yaml
313 exclude:
314 - modA
316 parameters:
317 value4: valued
318 """)
319 pipeline = PipelineIR.from_string(pipeline_str)
320 self.assertEqual(pipeline.parameters.mapping, {"value4": "valued", "value1": "valueNew",
321 "value2": "valueB",
322 "value3": "valueC"})
324 def testImportingInstrument(self):
325 # verify an instrument is imported, or ignored, (Or otherwise modified
326 # for potential future use)
327 pipeline_str = textwrap.dedent("""
328 description: Test Pipeline
329 imports:
330 - $PIPE_BASE_DIR/tests/testPipeline1.yaml
331 """)
332 pipeline = PipelineIR.from_string(pipeline_str)
333 self.assertEqual(pipeline.instrument, "test.instrument")
335 # verify that an imported pipeline can have its instrument set to None
336 pipeline_str = textwrap.dedent("""
337 description: Test Pipeline
338 imports:
339 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
340 instrument: None
341 """)
342 pipeline = PipelineIR.from_string(pipeline_str)
343 self.assertEqual(pipeline.instrument, None)
345 # verify that an imported pipeline can have its instrument modified
346 pipeline_str = textwrap.dedent("""
347 description: Test Pipeline
348 imports:
349 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
350 instrument: new.instrument
351 """)
352 pipeline = PipelineIR.from_string(pipeline_str)
353 self.assertEqual(pipeline.instrument, "new.instrument")
355 # Test that multiple instruments can't be defined,
356 # and that the error message tells you what instruments were found.
357 pipeline_str = textwrap.dedent("""
358 description: Test Pipeline
359 instrument: new.instrument
360 imports:
361 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
362 """)
363 with self.assertRaisesRegex(ValueError, "new.instrument .* test.instrument."):
364 PipelineIR.from_string(pipeline_str)
366 def testParameterConfigFormatting(self):
367 # verify that a config properly is formatted with parameters
368 pipeline_str = textwrap.dedent("""
369 description: Test Pipeline
370 parameters:
371 value1: A
372 tasks:
373 modA:
374 class: ModuleA
375 config:
376 testKey: parameters.value1
377 """)
378 pipeline = PipelineIR.from_string(pipeline_str)
379 newConfig = pipeline.tasks['modA'].config[0].formatted(pipeline.parameters)
380 self.assertEqual(newConfig.rest['testKey'], "A")
382 def testReadContracts(self):
383 # Verify that contracts are read in from a pipeline
384 location = os.path.expandvars("$PIPE_BASE_DIR/tests/testPipeline1.yaml")
385 pipeline = PipelineIR.from_file(location)
386 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c")
388 # Verify that a contract message is loaded
389 pipeline_str = textwrap.dedent("""
390 description: Test Pipeline
391 tasks:
392 modA: test.modA
393 modB:
394 class: test.modB
395 contracts:
396 - contract: modA.foo == modB.Bar
397 msg: "Test message"
398 """)
400 pipeline = PipelineIR.from_string(pipeline_str)
401 self.assertEqual(pipeline.contracts[0].msg, "Test message")
403 def testReadNamedSubsets(self):
404 pipeline_str = textwrap.dedent("""
405 description: Test Pipeline
406 tasks:
407 modA: test.modA
408 modB:
409 class: test.modB
410 modC: test.modC
411 modD: test.modD
412 subsets:
413 subset1:
414 - modA
415 - modB
416 subset2:
417 subset:
418 - modC
419 - modD
420 description: "A test named subset"
421 """)
422 pipeline = PipelineIR.from_string(pipeline_str)
423 self.assertEqual(pipeline.labeled_subsets.keys(), {"subset1", "subset2"})
425 self.assertEqual(pipeline.labeled_subsets["subset1"].subset, {"modA", "modB"})
426 self.assertEqual(pipeline.labeled_subsets["subset1"].description, None)
428 self.assertEqual(pipeline.labeled_subsets["subset2"].subset, {"modC", "modD"})
429 self.assertEqual(pipeline.labeled_subsets["subset2"].description,
430 "A test named subset")
432 # verify that forgetting a subset key is an error
433 pipeline_str = textwrap.dedent("""
434 description: Test Pipeline
435 tasks:
436 modA: test.modA
437 modB:
438 class: test.modB
439 modC: test.modC
440 modD: test.modD
441 subsets:
442 subset2:
443 sub:
444 - modC
445 - modD
446 description: "A test named subset"
447 """)
448 with self.assertRaises(ValueError):
449 PipelineIR.from_string(pipeline_str)
451 # verify putting a label in a named subset that is not in the task is
452 # an error
453 pipeline_str = textwrap.dedent("""
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 subset2:
463 - modC
464 - modD
465 - modE
466 """)
467 with self.assertRaises(ValueError):
468 PipelineIR.from_string(pipeline_str)
470 def testInstrument(self):
471 # Verify that if instrument is defined it is parsed out
472 pipeline_str = textwrap.dedent("""
473 description: Test Pipeline
474 instrument: dummyCam
475 tasks:
476 modA: test.moduleA
477 """)
479 pipeline = PipelineIR.from_string(pipeline_str)
480 self.assertEqual(pipeline.instrument, "dummyCam")
482 def testReadTaskConfig(self):
483 # Verify that a task with a config is read in correctly
484 pipeline_str = textwrap.dedent("""
485 description: Test Pipeline
486 tasks:
487 modA:
488 class: test.moduleA
489 config:
490 propertyA: 6
491 propertyB: 7
492 file: testfile.py
493 python: "config.testDict['a'] = 9"
494 """)
496 pipeline = PipelineIR.from_string(pipeline_str)
497 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py"])
498 self.assertEqual(pipeline.tasks["modA"].config[0].python, "config.testDict['a'] = 9")
499 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
501 # Verify that multiple files are read fine
502 pipeline_str = textwrap.dedent("""
503 description: Test Pipeline
504 tasks:
505 modA:
506 class: test.moduleA
507 config:
508 file:
509 - testfile.py
510 - otherFile.py
511 """)
513 pipeline = PipelineIR.from_string(pipeline_str)
514 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py", "otherFile.py"])
516 # Test reading multiple Config entries
517 pipeline_str = textwrap.dedent("""
518 description: Test Pipeline
519 tasks:
520 modA:
521 class: test.moduleA
522 config:
523 - propertyA: 6
524 propertyB: 7
525 dataId: {"visit": 6}
526 - propertyA: 8
527 propertyB: 9
528 """)
530 pipeline = PipelineIR.from_string(pipeline_str)
531 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7})
532 self.assertEqual(pipeline.tasks["modA"].config[0].dataId, {"visit": 6})
533 self.assertEqual(pipeline.tasks["modA"].config[1].rest, {"propertyA": 8, "propertyB": 9})
534 self.assertEqual(pipeline.tasks["modA"].config[1].dataId, None)
536 def testSerialization(self):
537 # Test creating a pipeline, writing it to a file, reading the file
538 pipeline_str = textwrap.dedent("""
539 description: Test Pipeline
540 instrument: dummyCam
541 imports:
542 - location: $PIPE_BASE_DIR/tests/testPipeline1.yaml
543 instrument: None
544 tasks:
545 modC:
546 class: test.moduleC
547 config:
548 - propertyA: 6
549 propertyB: 7
550 dataId: {"visit": 6}
551 - propertyA: 8
552 propertyB: 9
553 modD: test.moduleD
554 contracts:
555 - modA.foo == modB.bar
556 subsets:
557 subA:
558 - modA
559 - modC
560 """)
562 pipeline = PipelineIR.from_string(pipeline_str)
564 # Create the temp file, write and read
565 with tempfile.NamedTemporaryFile() as tf:
566 pipeline.to_file(tf.name)
567 loaded_pipeline = PipelineIR.from_file(tf.name)
568 self.assertEqual(pipeline, loaded_pipeline)
570 def testPipelineYamlLoader(self):
571 # Tests that an exception is thrown in the case a key is used multiple
572 # times in a given scope within a pipeline file
573 pipeline_str = textwrap.dedent("""
574 description: Test Pipeline
575 tasks:
576 modA: test1
577 modB: test2
578 modA: test3
579 """)
580 self.assertRaises(KeyError, PipelineIR.from_string, pipeline_str)
583class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
584 pass
587def setup_module(module):
588 lsst.utils.tests.init()
591if __name__ == "__main__": 591 ↛ 592line 591 didn't jump to line 592, because the condition on line 591 was never true
592 lsst.utils.tests.init()
593 unittest.main()