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 

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

22 

23import os 

24import tempfile 

25import textwrap 

26import unittest 

27 

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

29import lsst.utils.tests 

30 

31 

32class ConfigIRTestCase(unittest.TestCase): 

33 """A test case for ConfigIR Objects 

34 

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

36 so it should be tested here 

37 """ 

38 

39 def setUp(self): 

40 pass 

41 

42 def tearDown(self): 

43 pass 

44 

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

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 

79 def setUp(self): 

80 pass 

81 

82 def tearDown(self): 

83 pass 

84 

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) 

93 

94 # Missing tasks 

95 pipeline_str = """ 

96 description: Test Pipeline 

97 """ 

98 with self.assertRaises(ValueError): 

99 PipelineIR.from_string(pipeline_str) 

100 

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

108 

109 with self.assertRaises(FileNotFoundError): 

110 PipelineIR.from_string(pipeline_str) 

111 

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

121 

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

125 

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) 

137 

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

148 

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

157 

158 pipeline = PipelineIR.from_string(pipeline_str) 

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

160 

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

170 

171 with self.assertRaises(ValueError): 

172 PipelineIR.from_string(pipeline_str) 

173 

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

180 

181 pipeline = PipelineIR.from_string(pipeline_str) 

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

183 

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

191 

192 pipeline = PipelineIR.from_string(pipeline_str) 

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

194 

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

209 

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

224 

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

234 

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

249 

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) 

260 

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) 

272 

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) 

282 

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

295 

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) 

305 

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 

315 

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

323 

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

334 

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) 

344 

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

354 

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) 

365 

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

381 

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

387 

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

399 

400 pipeline = PipelineIR.from_string(pipeline_str) 

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

402 

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

424 

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

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

427 

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

429 self.assertEqual(pipeline.labeled_subsets["subset2"].description, 

430 "A test named subset") 

431 

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) 

450 

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) 

469 

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

478 

479 pipeline = PipelineIR.from_string(pipeline_str) 

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

481 

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

495 

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

500 

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

512 

513 pipeline = PipelineIR.from_string(pipeline_str) 

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

515 

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

529 

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) 

535 

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

561 

562 pipeline = PipelineIR.from_string(pipeline_str) 

563 

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) 

569 

570 def testSorting(self): 

571 pipeline_str = textwrap.dedent(""" 

572 description: Test Pipeline 

573 tasks: 

574 modA: test.modA 

575 modB: 

576 class: test.modB 

577 """) 

578 

579 pipeline = PipelineIR.from_string(pipeline_str) 

580 newKeyOrder = ['modB', 'modA'] 

581 pipeline.reorder_tasks(newKeyOrder) 

582 self.assertEqual(list(pipeline.tasks.keys()), newKeyOrder) 

583 with self.assertRaises(KeyError): 

584 pipeline.reorder_tasks(['modB']) 

585 with self.assertRaises(KeyError): 

586 pipeline.reorder_tasks(['modD']) 

587 

588 def testSortingPrimitives(self): 

589 pipeline_str = textwrap.dedent(""" 

590 description: Test Pipeline 

591 parameters: 

592 value2: A 

593 value1: B 

594 tasks: 

595 modB: ModuleB 

596 modA: ModuleA 

597 contracts: 

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

599 msg: "Test message" 

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

601 msg: "Test message" 

602 subsets: 

603 subset2: 

604 - modA 

605 - modB 

606 subset1: 

607 subset: 

608 - modA 

609 - modB 

610 description: "A test named subset" 

611 """) 

612 pipeline = PipelineIR.from_string(pipeline_str) 

613 primitives = pipeline.to_primitives() 

614 

615 # verify subsets 

616 self.assertEqual(list(pipeline.labeled_subsets.keys()), ['subset2', 'subset1']) 

617 self.assertEqual(list(primitives['subsets'].keys()), ['subset1', 'subset2']) 

618 

619 # verify parameters 

620 self.assertEqual(list(pipeline.parameters.mapping.keys()), ['value2', 'value1']) 

621 self.assertEqual(list(primitives['parameters'].keys()), ['value1', 'value2']) 

622 

623 # verify contracts 

624 self.assertEqual([c.contract for c in pipeline.contracts], 

625 ["modB.foo == modA.Bar", "modA.foo == modB.Bar"]) 

626 self.assertEqual([c['contract'] for c in primitives['contracts']], 

627 ["modA.foo == modB.Bar", "modB.foo == modA.Bar"]) 

628 

629 def testPipelineYamlLoader(self): 

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

631 # times in a given scope within a pipeline file 

632 pipeline_str = textwrap.dedent(""" 

633 description: Test Pipeline 

634 tasks: 

635 modA: test1 

636 modB: test2 

637 modA: test3 

638 """) 

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

640 

641 

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

643 pass 

644 

645 

646def setup_module(module): 

647 lsst.utils.tests.init() 

648 

649 

650if __name__ == "__main__": 650 ↛ 651line 650 didn't jump to line 651, because the condition on line 650 was never true

651 lsst.utils.tests.init() 

652 unittest.main()