Coverage for tests/test_pipelineIR.py: 15%

188 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-06 10:56 +0000

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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28import os 

29import tempfile 

30import textwrap 

31import unittest 

32 

33import lsst.utils.tests 

34from lsst.pipe.base.pipelineIR import ConfigIR, PipelineIR, PipelineSubsetCtrl 

35 

36# Find where the test pipelines exist and store it in an environment variable. 

37os.environ["TESTDIR"] = os.path.dirname(__file__) 

38 

39 

40class ConfigIRTestCase(unittest.TestCase): 

41 """A test case for ConfigIR Objects 

42 

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

44 so it should be tested here 

45 """ 

46 

47 def setUp(self): 

48 pass 

49 

50 def tearDown(self): 

51 pass 

52 

53 def testMergeConfig(self): 

54 # Create some configs to merge 

55 config1 = ConfigIR( 

56 python="config.foo=6", dataId={"visit": 7}, file=["test1.py"], rest={"a": 1, "b": 2} 

57 ) 

58 config2 = ConfigIR(python=None, dataId=None, file=["test2.py"], rest={"c": 1, "d": 2}) 

59 config3 = ConfigIR(python="config.bar=7", dataId=None, file=["test3.py"], rest={"c": 1, "d": 2}) 

60 config4 = ConfigIR(python=None, dataId=None, file=["test4.py"], rest={"c": 3, "e": 4}) 

61 config5 = ConfigIR(rest={"f": 5, "g": 6}) 

62 config6 = ConfigIR(rest={"h": 7, "i": 8}) 

63 config7 = ConfigIR(rest={"h": 9}) 

64 

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

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

67 

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

69 # elements 

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

71 

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

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

74 

75 # merge config2 into config1 

76 merge_result = list(config5.maybe_merge(config6)) 

77 self.assertEqual(len(merge_result), 1) 

78 self.assertEqual(config5.rest, {"f": 5, "g": 6, "h": 7, "i": 8}) 

79 

80 # Cant merge configs with shared keys 

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

82 

83 

84class PipelineIRTestCase(unittest.TestCase): 

85 """A test case for PipelineIR objects""" 

86 

87 def setUp(self): 

88 pass 

89 

90 def tearDown(self): 

91 pass 

92 

93 def testPipelineIRInitChecks(self): 

94 # Missing description 

95 pipeline_str = """ 

96 tasks: 

97 a: module.A 

98 """ 

99 with self.assertRaises(ValueError): 

100 PipelineIR.from_string(pipeline_str) 

101 

102 # Missing tasks 

103 pipeline_str = """ 

104 description: Test Pipeline 

105 """ 

106 with self.assertRaises(ValueError): 

107 PipelineIR.from_string(pipeline_str) 

108 

109 # This should raise a FileNotFoundError, as there are imported defined 

110 # so the __init__ method should pass but the imported file does not 

111 # exist 

112 pipeline_str = textwrap.dedent( 

113 """ 

114 description: Test Pipeline 

115 imports: /dummy_pipeline.yaml 

116 """ 

117 ) 

118 

119 with self.assertRaises(FileNotFoundError): 

120 PipelineIR.from_string(pipeline_str) 

121 

122 def testTaskParsing(self): 

123 # Should be able to parse a task defined both ways 

124 pipeline_str = textwrap.dedent( 

125 """ 

126 description: Test Pipeline 

127 tasks: 

128 modA: test.modA 

129 modB: 

130 class: test.modB 

131 """ 

132 ) 

133 

134 pipeline = PipelineIR.from_string(pipeline_str) 

135 self.assertEqual(list(pipeline.tasks.keys()), ["modA", "modB"]) 

136 self.assertEqual([t.klass for t in pipeline.tasks.values()], ["test.modA", "test.modB"]) 

137 

138 def testImportParsing(self): 

139 # This should raise, as the two pipelines, both define the same label 

140 pipeline_str = textwrap.dedent( 

141 """ 

142 description: Test Pipeline 

143 imports: 

144 - $TESTDIR/testPipeline1.yaml 

145 - $TESTDIR/testPipeline2.yaml 

146 """ 

147 ) 

148 # "modA" is the duplicated label, and it should appear in the error. 

149 with self.assertRaisesRegex(ValueError, "modA"): 

150 PipelineIR.from_string(pipeline_str) 

151 

152 # This should pass, as the conflicting task is excluded 

153 pipeline_str = textwrap.dedent( 

154 """ 

155 description: Test Pipeline 

156 imports: 

157 - location: $TESTDIR/testPipeline1.yaml 

158 exclude: modA 

159 - $TESTDIR/testPipeline2.yaml 

160 """ 

161 ) 

162 pipeline = PipelineIR.from_string(pipeline_str) 

163 self.assertEqual(set(pipeline.tasks.keys()), {"modA", "modB"}) 

164 

165 # This should pass, as the conflicting task is no in includes 

166 pipeline_str = textwrap.dedent( 

167 """ 

168 description: Test Pipeline 

169 imports: 

170 - location: $TESTDIR/testPipeline1.yaml 

171 include: modB 

172 labeledSubsetModifyMode: DROP 

173 - $TESTDIR/testPipeline2.yaml 

174 """ 

175 ) 

176 

177 pipeline = PipelineIR.from_string(pipeline_str) 

178 self.assertEqual(set(pipeline.tasks.keys()), {"modA", "modB"}) 

179 

180 # Test that you cant include and exclude a task 

181 pipeline_str = textwrap.dedent( 

182 """ 

183 description: Test Pipeline 

184 imports: 

185 - location: $TESTDIR/testPipeline1.yaml 

186 exclude: modA 

187 include: modB 

188 labeledSubsetModifyMode: EDIT 

189 - $TESTDIR/testPipeline2.yaml 

190 """ 

191 ) 

192 

193 with self.assertRaises(ValueError): 

194 PipelineIR.from_string(pipeline_str) 

195 

196 # Test unknown labeledSubsetModifyModes raise 

197 pipeline_str = textwrap.dedent( 

198 """ 

199 description: Test Pipeline 

200 imports: 

201 - location: $TESTDIR/testPipeline1.yaml 

202 exclude: modA 

203 include: modB 

204 labeledSubsetModifyMode: WRONG 

205 - $TESTDIR/testPipeline2.yaml 

206 """ 

207 ) 

208 with self.assertRaises(ValueError): 

209 PipelineIR.from_string(pipeline_str) 

210 

211 # Test that contracts are imported 

212 pipeline_str = textwrap.dedent( 

213 """ 

214 description: Test Pipeline 

215 imports: 

216 - $TESTDIR/testPipeline1.yaml 

217 """ 

218 ) 

219 

220 pipeline = PipelineIR.from_string(pipeline_str) 

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

222 

223 # Test that contracts are not imported 

224 pipeline_str = textwrap.dedent( 

225 """ 

226 description: Test Pipeline 

227 imports: 

228 - location: $TESTDIR/testPipeline1.yaml 

229 importContracts: False 

230 """ 

231 ) 

232 

233 pipeline = PipelineIR.from_string(pipeline_str) 

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

235 

236 # Test that configs are imported when defining the same task again 

237 # with the same label 

238 pipeline_str = textwrap.dedent( 

239 """ 

240 description: Test Pipeline 

241 imports: 

242 - $TESTDIR/testPipeline2.yaml 

243 tasks: 

244 modA: 

245 class: "test.moduleA" 

246 config: 

247 value2: 2 

248 """ 

249 ) 

250 pipeline = PipelineIR.from_string(pipeline_str) 

251 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value1": 1, "value2": 2}) 

252 

253 # Test that configs are not imported when redefining the task 

254 # associated with a label 

255 pipeline_str = textwrap.dedent( 

256 """ 

257 description: Test Pipeline 

258 imports: 

259 - $TESTDIR/testPipeline2.yaml 

260 tasks: 

261 modA: 

262 class: "test.moduleAReplace" 

263 config: 

264 value2: 2 

265 """ 

266 ) 

267 pipeline = PipelineIR.from_string(pipeline_str) 

268 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"value2": 2}) 

269 

270 # Test that named subsets are imported 

271 pipeline_str = textwrap.dedent( 

272 """ 

273 description: Test Pipeline 

274 imports: 

275 - $TESTDIR/testPipeline2.yaml 

276 """ 

277 ) 

278 pipeline = PipelineIR.from_string(pipeline_str) 

279 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"}) 

280 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modA"}) 

281 

282 # Test that imported and redeclaring a named subset works 

283 pipeline_str = textwrap.dedent( 

284 """ 

285 description: Test Pipeline 

286 imports: 

287 - $TESTDIR/testPipeline2.yaml 

288 tasks: 

289 modE: "test.moduleE" 

290 subsets: 

291 modSubset: 

292 - modE 

293 """ 

294 ) 

295 pipeline = PipelineIR.from_string(pipeline_str) 

296 self.assertEqual(pipeline.labeled_subsets.keys(), {"modSubset"}) 

297 self.assertEqual(pipeline.labeled_subsets["modSubset"].subset, {"modE"}) 

298 

299 # Test that imported from two pipelines that both declare a named 

300 # subset with the same name fails 

301 pipeline_str = textwrap.dedent( 

302 """ 

303 description: Test Pipeline 

304 imports: 

305 - $TESTDIR/testPipeline2.yaml 

306 - $TESTDIR/testPipeline3.yaml 

307 """ 

308 ) 

309 with self.assertRaises(ValueError): 

310 PipelineIR.from_string(pipeline_str) 

311 

312 # Test that imported a named subset that duplicates a label declared 

313 # in this pipeline fails 

314 pipeline_str = textwrap.dedent( 

315 """ 

316 description: Test Pipeline 

317 imports: 

318 - $TESTDIR/testPipeline2.yaml 

319 tasks: 

320 modSubset: "test.moduleE" 

321 """ 

322 ) 

323 with self.assertRaises(ValueError): 

324 PipelineIR.from_string(pipeline_str) 

325 

326 # Test that imported fails if a named subset and task label conflict 

327 pipeline_str = textwrap.dedent( 

328 """ 

329 description: Test Pipeline 

330 imports: 

331 - $TESTDIR/testPipeline2.yaml 

332 - $TESTDIR/testPipeline4.yaml 

333 """ 

334 ) 

335 with self.assertRaises(ValueError): 

336 PipelineIR.from_string(pipeline_str) 

337 

338 def testReadParameters(self): 

339 # verify that parameters section are read in from a pipeline 

340 pipeline_str = textwrap.dedent( 

341 """ 

342 description: Test Pipeline 

343 parameters: 

344 value1: A 

345 value2: B 

346 tasks: 

347 modA: ModuleA 

348 """ 

349 ) 

350 pipeline = PipelineIR.from_string(pipeline_str) 

351 self.assertEqual(pipeline.parameters.mapping, {"value1": "A", "value2": "B"}) 

352 

353 def testTaskParameterLabel(self): 

354 # verify that "parameters" cannot be used as a task label 

355 pipeline_str = textwrap.dedent( 

356 """ 

357 description: Test Pipeline 

358 tasks: 

359 parameters: modA 

360 """ 

361 ) 

362 with self.assertRaises(ValueError): 

363 PipelineIR.from_string(pipeline_str) 

364 

365 def testParameterImporting(self): 

366 # verify that importing parameters happens correctly 

367 pipeline_str = textwrap.dedent( 

368 """ 

369 description: Test Pipeline 

370 imports: 

371 - $TESTDIR/testPipeline1.yaml 

372 - location: $TESTDIR/testPipeline2.yaml 

373 exclude: 

374 - modA 

375 

376 parameters: 

377 value4: valued 

378 """ 

379 ) 

380 pipeline = PipelineIR.from_string(pipeline_str) 

381 self.assertEqual( 

382 pipeline.parameters.mapping, 

383 {"value4": "valued", "value1": "valueNew", "value2": "valueB", "value3": "valueC"}, 

384 ) 

385 

386 def testImportingInstrument(self): 

387 # verify an instrument is imported, or ignored, (Or otherwise modified 

388 # for potential future use) 

389 pipeline_str = textwrap.dedent( 

390 """ 

391 description: Test Pipeline 

392 imports: 

393 - $TESTDIR/testPipeline1.yaml 

394 """ 

395 ) 

396 pipeline = PipelineIR.from_string(pipeline_str) 

397 self.assertEqual(pipeline.instrument, "test.instrument") 

398 

399 # verify that an imported pipeline can have its instrument set to None 

400 pipeline_str = textwrap.dedent( 

401 """ 

402 description: Test Pipeline 

403 imports: 

404 - location: $TESTDIR/testPipeline1.yaml 

405 instrument: None 

406 """ 

407 ) 

408 pipeline = PipelineIR.from_string(pipeline_str) 

409 self.assertEqual(pipeline.instrument, None) 

410 

411 # verify that an imported pipeline can have its instrument modified 

412 pipeline_str = textwrap.dedent( 

413 """ 

414 description: Test Pipeline 

415 imports: 

416 - location: $TESTDIR/testPipeline1.yaml 

417 instrument: new.instrument 

418 """ 

419 ) 

420 pipeline = PipelineIR.from_string(pipeline_str) 

421 self.assertEqual(pipeline.instrument, "new.instrument") 

422 

423 # Test that multiple instruments can't be defined, 

424 # and that the error message tells you what instruments were found. 

425 pipeline_str = textwrap.dedent( 

426 """ 

427 description: Test Pipeline 

428 instrument: new.instrument 

429 imports: 

430 - location: $TESTDIR/testPipeline1.yaml 

431 """ 

432 ) 

433 with self.assertRaisesRegex(ValueError, "new.instrument .* test.instrument."): 

434 PipelineIR.from_string(pipeline_str) 

435 

436 def testParameterConfigFormatting(self): 

437 # verify that a config properly is formatted with parameters 

438 pipeline_str = textwrap.dedent( 

439 """ 

440 description: Test Pipeline 

441 parameters: 

442 value1: A 

443 tasks: 

444 modA: 

445 class: ModuleA 

446 config: 

447 testKey: parameters.value1 

448 """ 

449 ) 

450 pipeline = PipelineIR.from_string(pipeline_str) 

451 newConfig = pipeline.tasks["modA"].config[0].formatted(pipeline.parameters) 

452 self.assertEqual(newConfig.rest["testKey"], "A") 

453 

454 def testReadContracts(self): 

455 # Verify that contracts are read in from a pipeline 

456 location = "$TESTDIR/testPipeline1.yaml" 

457 pipeline = PipelineIR.from_uri(location) 

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

459 

460 # Verify that a contract message is loaded 

461 pipeline_str = textwrap.dedent( 

462 """ 

463 description: Test Pipeline 

464 tasks: 

465 modA: test.modA 

466 modB: 

467 class: test.modB 

468 contracts: 

469 - contract: modA.foo == modB.Bar 

470 msg: "Test message" 

471 """ 

472 ) 

473 

474 pipeline = PipelineIR.from_string(pipeline_str) 

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

476 

477 def testReadNamedSubsets(self): 

478 pipeline_str = textwrap.dedent( 

479 """ 

480 description: Test Pipeline 

481 tasks: 

482 modA: test.modA 

483 modB: 

484 class: test.modB 

485 modC: test.modC 

486 modD: test.modD 

487 subsets: 

488 subset1: 

489 - modA 

490 - modB 

491 subset2: 

492 subset: 

493 - modC 

494 - modD 

495 description: "A test named subset" 

496 """ 

497 ) 

498 pipeline = PipelineIR.from_string(pipeline_str) 

499 self.assertEqual(pipeline.labeled_subsets.keys(), {"subset1", "subset2"}) 

500 

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

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

503 

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

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

506 

507 # verify that forgetting a subset key is an error 

508 pipeline_str = textwrap.dedent( 

509 """ 

510 description: Test Pipeline 

511 tasks: 

512 modA: test.modA 

513 modB: 

514 class: test.modB 

515 modC: test.modC 

516 modD: test.modD 

517 subsets: 

518 subset2: 

519 sub: 

520 - modC 

521 - modD 

522 description: "A test named subset" 

523 """ 

524 ) 

525 with self.assertRaises(ValueError): 

526 PipelineIR.from_string(pipeline_str) 

527 

528 # verify putting a label in a named subset that is not in the task is 

529 # an error 

530 pipeline_str = textwrap.dedent( 

531 """ 

532 description: Test Pipeline 

533 tasks: 

534 modA: test.modA 

535 modB: 

536 class: test.modB 

537 modC: test.modC 

538 modD: test.modD 

539 subsets: 

540 subset2: 

541 - modC 

542 - modD 

543 - modE 

544 """ 

545 ) 

546 with self.assertRaises(ValueError): 

547 PipelineIR.from_string(pipeline_str) 

548 

549 def testSubsettingPipeline(self): 

550 pipeline_str = textwrap.dedent( 

551 """ 

552 description: Test Pipeline 

553 tasks: 

554 modA: test.modA 

555 modB: 

556 class: test.modB 

557 modC: test.modC 

558 modD: test.modD 

559 subsets: 

560 subset1: 

561 - modA 

562 - modB 

563 subset2: 

564 subset: 

565 - modC 

566 - modD 

567 description: "A test named subset" 

568 """ 

569 ) 

570 pipeline = PipelineIR.from_string(pipeline_str) 

571 # verify that creating a pipeline subset with the default drop behavior 

572 # removes any labeled subset that contains a label not in the set of 

573 # all task labels. 

574 pipelineSubset1 = pipeline.subset_from_labels({"modA", "modB", "modC"}) 

575 self.assertEqual(pipelineSubset1.labeled_subsets.keys(), {"subset1"}) 

576 # verify that creating a pipeline subset with the edit behavior 

577 # edits any labeled subset that contains a label not in the set of 

578 # all task labels. 

579 pipelineSubset2 = pipeline.subset_from_labels({"modA", "modB", "modC"}, PipelineSubsetCtrl.EDIT) 

580 self.assertEqual(pipelineSubset2.labeled_subsets.keys(), {"subset1", "subset2"}) 

581 self.assertEqual(pipelineSubset2.labeled_subsets["subset2"].subset, {"modC"}) 

582 

583 def testInstrument(self): 

584 # Verify that if instrument is defined it is parsed out 

585 pipeline_str = textwrap.dedent( 

586 """ 

587 description: Test Pipeline 

588 instrument: dummyCam 

589 tasks: 

590 modA: test.moduleA 

591 """ 

592 ) 

593 

594 pipeline = PipelineIR.from_string(pipeline_str) 

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

596 

597 def testReadTaskConfig(self): 

598 # Verify that a task with a config is read in correctly 

599 pipeline_str = textwrap.dedent( 

600 """ 

601 description: Test Pipeline 

602 tasks: 

603 modA: 

604 class: test.moduleA 

605 config: 

606 propertyA: 6 

607 propertyB: 7 

608 file: testfile.py 

609 python: "config.testDict['a'] = 9" 

610 """ 

611 ) 

612 

613 pipeline = PipelineIR.from_string(pipeline_str) 

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

615 self.assertEqual(pipeline.tasks["modA"].config[0].python, "config.testDict['a'] = 9") 

616 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7}) 

617 

618 # Verify that multiple files are read fine 

619 pipeline_str = textwrap.dedent( 

620 """ 

621 description: Test Pipeline 

622 tasks: 

623 modA: 

624 class: test.moduleA 

625 config: 

626 file: 

627 - testfile.py 

628 - otherFile.py 

629 """ 

630 ) 

631 

632 pipeline = PipelineIR.from_string(pipeline_str) 

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

634 

635 # Test reading multiple Config entries 

636 pipeline_str = textwrap.dedent( 

637 """ 

638 description: Test Pipeline 

639 tasks: 

640 modA: 

641 class: test.moduleA 

642 config: 

643 - propertyA: 6 

644 propertyB: 7 

645 dataId: {"visit": 6} 

646 - propertyA: 8 

647 propertyB: 9 

648 """ 

649 ) 

650 

651 pipeline = PipelineIR.from_string(pipeline_str) 

652 self.assertEqual(pipeline.tasks["modA"].config[0].rest, {"propertyA": 6, "propertyB": 7}) 

653 self.assertEqual(pipeline.tasks["modA"].config[0].dataId, {"visit": 6}) 

654 self.assertEqual(pipeline.tasks["modA"].config[1].rest, {"propertyA": 8, "propertyB": 9}) 

655 self.assertEqual(pipeline.tasks["modA"].config[1].dataId, None) 

656 

657 def testSerialization(self): 

658 # Test creating a pipeline, writing it to a file, reading the file 

659 pipeline_str = textwrap.dedent( 

660 """ 

661 description: Test Pipeline 

662 instrument: dummyCam 

663 imports: 

664 - location: $TESTDIR/testPipeline1.yaml 

665 instrument: None 

666 tasks: 

667 modC: 

668 class: test.moduleC 

669 config: 

670 - propertyA: 6 

671 propertyB: 7 

672 dataId: {"visit": 6} 

673 - propertyA: 8 

674 propertyB: 9 

675 modD: test.moduleD 

676 contracts: 

677 - modA.foo == modB.bar 

678 subsets: 

679 subA: 

680 - modA 

681 - modC 

682 """ 

683 ) 

684 

685 pipeline = PipelineIR.from_string(pipeline_str) 

686 

687 # Create the temp file, write and read 

688 with tempfile.NamedTemporaryFile() as tf: 

689 pipeline.write_to_uri(tf.name) 

690 loaded_pipeline = PipelineIR.from_uri(tf.name) 

691 self.assertEqual(pipeline, loaded_pipeline) 

692 

693 def testPipelineYamlLoader(self): 

694 # Tests that an exception is thrown in the case a key is used multiple 

695 # times in a given scope within a pipeline file 

696 pipeline_str = textwrap.dedent( 

697 """ 

698 description: Test Pipeline 

699 tasks: 

700 modA: test1 

701 modB: test2 

702 modA: test3 

703 """ 

704 ) 

705 self.assertRaises(KeyError, PipelineIR.from_string, pipeline_str) 

706 

707 def testMultiLineStrings(self): 

708 """Test that multi-line strings in pipelines are written with 

709 '|' continuation-syntax instead of explicit newlines. 

710 """ 

711 pipeline_ir = PipelineIR({"description": "Line 1\nLine2\n", "tasks": {"modA": "task1"}}) 

712 string = str(pipeline_ir) 

713 self.assertIn("|", string) 

714 self.assertNotIn(r"\n", string) 

715 

716 

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

718 """Run file leak tests.""" 

719 

720 

721def setup_module(module): 

722 """Configure pytest.""" 

723 lsst.utils.tests.init() 

724 

725 

726if __name__ == "__main__": 

727 lsst.utils.tests.init() 

728 unittest.main()