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

195 statements  

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/>. 

21 

22import os 

23import tempfile 

24import textwrap 

25import unittest 

26 

27import lsst.utils.tests 

28from lsst.pipe.base.pipelineIR import ConfigIR, PipelineIR 

29 

30 

31class ConfigIRTestCase(unittest.TestCase): 

32 """A test case for ConfigIR Objects 

33 

34 ConfigIR contains a method that is not exercised by the PipelineIR task, 

35 so it should be tested here 

36 """ 

37 

38 def setUp(self): 

39 pass 

40 

41 def tearDown(self): 

42 pass 

43 

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}) 

55 

56 # Merge configs with different dataIds, this should yield two elements 

57 self.assertEqual(list(config1.maybe_merge(config2)), [config1, config2]) 

58 

59 # Merge configs with python blocks defined, this should yield two 

60 # elements 

61 self.assertEqual(list(config1.maybe_merge(config3)), [config1, config3]) 

62 

63 # Merge configs with file defined, this should yield two elements 

64 self.assertEqual(list(config2.maybe_merge(config4)), [config2, config4]) 

65 

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}) 

70 

71 # Cant merge configs with shared keys 

72 self.assertEqual(list(config6.maybe_merge(config7)), [config6, config7]) 

73 

74 

75class PipelineIRTestCase(unittest.TestCase): 

76 """A test case for PipelineIR objects""" 

77 

78 def setUp(self): 

79 pass 

80 

81 def tearDown(self): 

82 pass 

83 

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) 

92 

93 # Missing tasks 

94 pipeline_str = """ 

95 description: Test Pipeline 

96 """ 

97 with self.assertRaises(ValueError): 

98 PipelineIR.from_string(pipeline_str) 

99 

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 ) 

109 

110 with self.assertRaises(FileNotFoundError): 

111 PipelineIR.from_string(pipeline_str) 

112 

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 ) 

124 

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"]) 

128 

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) 

142 

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"])) 

155 

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 ) 

166 

167 pipeline = PipelineIR.from_string(pipeline_str) 

168 self.assertEqual(set(pipeline.tasks.keys()), set(["modA", "modB"])) 

169 

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 ) 

181 

182 with self.assertRaises(ValueError): 

183 PipelineIR.from_string(pipeline_str) 

184 

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 ) 

193 

194 pipeline = PipelineIR.from_string(pipeline_str) 

195 self.assertEqual(pipeline.contracts[0].contract, "modA.b == modA.c") 

196 

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 ) 

206 

207 pipeline = PipelineIR.from_string(pipeline_str) 

208 self.assertEqual(pipeline.contracts, []) 

209 

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}) 

226 

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}) 

243 

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"}) 

255 

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"}) 

272 

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) 

285 

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) 

299 

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) 

311 

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"}) 

326 

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) 

338 

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 

349 

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 ) 

359 

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") 

372 

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) 

384 

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") 

396 

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) 

409 

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") 

427 

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") 

433 

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 ) 

447 

448 pipeline = PipelineIR.from_string(pipeline_str) 

449 self.assertEqual(pipeline.contracts[0].msg, "Test message") 

450 

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"}) 

474 

475 self.assertEqual(pipeline.labeled_subsets["subset1"].subset, {"modA", "modB"}) 

476 self.assertEqual(pipeline.labeled_subsets["subset1"].description, None) 

477 

478 self.assertEqual(pipeline.labeled_subsets["subset2"].subset, {"modC", "modD"}) 

479 self.assertEqual(pipeline.labeled_subsets["subset2"].description, "A test named subset") 

480 

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) 

501 

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) 

522 

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 ) 

533 

534 pipeline = PipelineIR.from_string(pipeline_str) 

535 self.assertEqual(pipeline.instrument, "dummyCam") 

536 

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 ) 

552 

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}) 

557 

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 ) 

571 

572 pipeline = PipelineIR.from_string(pipeline_str) 

573 self.assertEqual(pipeline.tasks["modA"].config[0].file, ["testfile.py", "otherFile.py"]) 

574 

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 ) 

590 

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) 

596 

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 ) 

624 

625 pipeline = PipelineIR.from_string(pipeline_str) 

626 

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) 

632 

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 ) 

643 

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"]) 

652 

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() 

681 

682 # verify subsets 

683 self.assertEqual(list(pipeline.labeled_subsets.keys()), ["subset2", "subset1"]) 

684 self.assertEqual(list(primitives["subsets"].keys()), ["subset1", "subset2"]) 

685 

686 # verify parameters 

687 self.assertEqual(list(pipeline.parameters.mapping.keys()), ["value2", "value1"]) 

688 self.assertEqual(list(primitives["parameters"].keys()), ["value1", "value2"]) 

689 

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 ) 

697 

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) 

711 

712 

713class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

714 pass 

715 

716 

717def setup_module(module): 

718 lsst.utils.tests.init() 

719 

720 

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()