Coverage for tests/test_pipelineIR.py: 15%

201 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-14 02:24 -0800

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# Find where the test pipelines exist and store it in an environment variable. 

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

32 

33 

34class ConfigIRTestCase(unittest.TestCase): 

35 """A test case for ConfigIR Objects 

36 

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

38 so it should be tested here 

39 """ 

40 

41 def setUp(self): 

42 pass 

43 

44 def tearDown(self): 

45 pass 

46 

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

58 

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

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

61 

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

63 # elements 

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

65 

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

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

68 

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

73 

74 # Cant merge configs with shared keys 

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

76 

77 

78class PipelineIRTestCase(unittest.TestCase): 

79 """A test case for PipelineIR objects""" 

80 

81 def setUp(self): 

82 pass 

83 

84 def tearDown(self): 

85 pass 

86 

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) 

95 

96 # Missing tasks 

97 pipeline_str = """ 

98 description: Test Pipeline 

99 """ 

100 with self.assertRaises(ValueError): 

101 PipelineIR.from_string(pipeline_str) 

102 

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 ) 

112 

113 with self.assertRaises(FileNotFoundError): 

114 PipelineIR.from_string(pipeline_str) 

115 

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 ) 

127 

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

131 

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) 

145 

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

158 

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 ) 

169 

170 pipeline = PipelineIR.from_string(pipeline_str) 

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

172 

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 ) 

184 

185 with self.assertRaises(ValueError): 

186 PipelineIR.from_string(pipeline_str) 

187 

188 # Test that contracts are imported 

189 pipeline_str = textwrap.dedent( 

190 """ 

191 description: Test Pipeline 

192 imports: 

193 - $TESTDIR/testPipeline1.yaml 

194 """ 

195 ) 

196 

197 pipeline = PipelineIR.from_string(pipeline_str) 

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

199 

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 ) 

209 

210 pipeline = PipelineIR.from_string(pipeline_str) 

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

212 

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

229 

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

246 

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

258 

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

275 

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) 

288 

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) 

302 

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) 

314 

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

329 

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) 

341 

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 

352 

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 ) 

362 

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

375 

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) 

387 

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

399 

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) 

412 

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

430 

431 def testReadContracts(self): 

432 # Verify that contracts are read in from a pipeline 

433 location = "$TESTDIR/testPipeline1.yaml" 

434 pipeline = PipelineIR.from_uri(location) 

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

436 

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 ) 

450 

451 pipeline = PipelineIR.from_string(pipeline_str) 

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

453 

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

477 

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

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

480 

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

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

483 

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) 

504 

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) 

525 

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 ) 

536 

537 pipeline = PipelineIR.from_string(pipeline_str) 

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

539 

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 ) 

555 

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

560 

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 ) 

574 

575 pipeline = PipelineIR.from_string(pipeline_str) 

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

577 

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 ) 

593 

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) 

599 

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 ) 

627 

628 pipeline = PipelineIR.from_string(pipeline_str) 

629 

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) 

635 

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 ) 

646 

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

655 

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

684 

685 # verify subsets 

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

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

688 

689 # verify parameters 

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

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

692 

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 ) 

700 

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) 

714 

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) 

723 

724 

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

726 pass 

727 

728 

729def setup_module(module): 

730 lsst.utils.tests.init() 

731 

732 

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