Hide keyboard shortcuts

Hot-keys 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

1# This file is part of ctrl_mpexec. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21 

22"""Simple unit test for cmdLineFwk module. 

23""" 

24 

25import click 

26from types import SimpleNamespace 

27import contextlib 

28import copy 

29from dataclasses import dataclass 

30import logging 

31import os 

32import pickle 

33import re 

34import shutil 

35import tempfile 

36from typing import NamedTuple 

37import unittest 

38 

39from lsst.ctrl.mpexec.cmdLineFwk import CmdLineFwk 

40from lsst.ctrl.mpexec.cli.opt import run_options 

41from lsst.ctrl.mpexec.cli.utils import ( 

42 _ACTION_ADD_TASK, 

43 _ACTION_CONFIG, 

44 _ACTION_CONFIG_FILE, 

45 _ACTION_ADD_INSTRUMENT, 

46 PipetaskCommand, 

47) 

48from lsst.daf.butler import Config, Quantum, Registry 

49from lsst.daf.butler.registry import RegistryConfig 

50from lsst.obs.base import Instrument 

51import lsst.pex.config as pexConfig 

52from lsst.pipe.base import (Pipeline, PipelineTaskConfig, QuantumGraph, TaskDef, PipelineTaskConnections) 

53import lsst.pipe.base.connectionTypes as cT 

54import lsst.utils.tests 

55from lsst.pipe.base.tests.simpleQGraph import ( 

56 AddTaskFactoryMock, 

57 makeSimpleButler, 

58 makeSimplePipeline, 

59 makeSimpleQGraph, 

60 populateButler) 

61from lsst.utils.tests import temporaryDirectory 

62 

63 

64logging.basicConfig(level=getattr(logging, os.environ.get("UNIT_TEST_LOGGING_LEVEL", "INFO"), logging.INFO)) 

65 

66# Have to monkey-patch Instrument.fromName() to not retrieve non-existing 

67# instrument from registry, these tests can run fine without actual instrument 

68# and implementing full mock for Instrument is too complicated. 

69Instrument.fromName = lambda name, reg: None 69 ↛ exitline 69 didn't run the lambda on line 69

70 

71 

72@contextlib.contextmanager 

73def makeTmpFile(contents=None, suffix=None): 

74 """Context manager for generating temporary file name. 

75 

76 Temporary file is deleted on exiting context. 

77 

78 Parameters 

79 ---------- 

80 contents : `bytes` 

81 Data to write into a file. 

82 """ 

83 fd, tmpname = tempfile.mkstemp(suffix=suffix) 

84 if contents: 

85 os.write(fd, contents) 

86 os.close(fd) 

87 yield tmpname 

88 with contextlib.suppress(OSError): 

89 os.remove(tmpname) 

90 

91 

92@contextlib.contextmanager 

93def makeSQLiteRegistry(create=True): 

94 """Context manager to create new empty registry database. 

95 

96 Yields 

97 ------ 

98 config : `RegistryConfig` 

99 Registry configuration for initialized registry database. 

100 """ 

101 with temporaryDirectory() as tmpdir: 

102 uri = f"sqlite:///{tmpdir}/gen3.sqlite" 

103 config = RegistryConfig() 

104 config["db"] = uri 

105 if create: 

106 Registry.createFromConfig(config) 

107 yield config 

108 

109 

110class SimpleConnections(PipelineTaskConnections, dimensions=(), 

111 defaultTemplates={"template": "simple"}): 

112 schema = cT.InitInput(doc="Schema", 

113 name="{template}schema", 

114 storageClass="SourceCatalog") 

115 

116 

117class SimpleConfig(PipelineTaskConfig, pipelineConnections=SimpleConnections): 

118 field = pexConfig.Field(dtype=str, doc="arbitrary string") 

119 

120 def setDefaults(self): 

121 PipelineTaskConfig.setDefaults(self) 

122 

123 

124def _makeArgs(registryConfig=None, **kwargs): 

125 """Return parsed command line arguments. 

126 

127 By default butler_config is set to `Config` populated with some defaults, 

128 it can be overridden completely by keyword argument. 

129 

130 Parameters 

131 ---------- 

132 cmd : `str`, optional 

133 Produce arguments for this pipetask command. 

134 registryConfig : `RegistryConfig`, optional 

135 Override for registry configuration. 

136 **kwargs 

137 Overrides for other arguments. 

138 """ 

139 # Use a mock to get the default value of arguments to 'run'. 

140 

141 mock = unittest.mock.Mock() 

142 

143 @click.command(cls=PipetaskCommand) 

144 @run_options() 

145 def fake_run(ctx, **kwargs): 

146 """Fake "pipetask run" command for gathering input arguments. 

147 

148 The arguments & options should always match the arguments & options in 

149 the "real" command function `lsst.ctrl.mpexec.cli.cmd.run`. 

150 """ 

151 mock(**kwargs) 

152 

153 runner = click.testing.CliRunner() 

154 result = runner.invoke(fake_run) 

155 if result.exit_code != 0: 

156 raise RuntimeError(f"Failure getting default args from 'fake_run': {result}") 

157 mock.assert_called_once() 

158 args = mock.call_args[1] 

159 args["enableLsstDebug"] = args.pop("debug") 

160 args["execution_butler_location"] = args.pop("save_execution_butler") 

161 if "pipeline_actions" not in args: 

162 args["pipeline_actions"] = [] 

163 args = SimpleNamespace(**args) 

164 

165 # override butler_config with our defaults 

166 if "butler_config" not in kwargs: 

167 args.butler_config = Config() 

168 if registryConfig: 

169 args.butler_config["registry"] = registryConfig 

170 # The default datastore has a relocatable root, so we need to specify 

171 # some root here for it to use 

172 args.butler_config.configFile = "." 

173 

174 # override arguments from keyword parameters 

175 for key, value in kwargs.items(): 

176 setattr(args, key, value) 

177 return args 

178 

179 

180class FakeDSType(NamedTuple): 

181 name: str 

182 

183 

184@dataclass(frozen=True) 

185class FakeDSRef: 

186 datasetType: str 

187 dataId: tuple 

188 

189 

190# Task class name used by tests, needs to be importable 

191_TASK_CLASS = "lsst.pipe.base.tests.simpleQGraph.AddTask" 

192 

193 

194def _makeQGraph(): 

195 """Make a trivial QuantumGraph with one quantum. 

196 

197 The only thing that we need to do with this quantum graph is to pickle 

198 it, the quanta in this graph are not usable for anything else. 

199 

200 Returns 

201 ------- 

202 qgraph : `~lsst.pipe.base.QuantumGraph` 

203 """ 

204 taskDef = TaskDef(taskName=_TASK_CLASS, config=SimpleConfig()) 

205 quanta = [Quantum(taskName=_TASK_CLASS, 

206 inputs={FakeDSType("A"): [FakeDSRef("A", (1, 2))]})] # type: ignore 

207 qgraph = QuantumGraph({taskDef: set(quanta)}) 

208 return qgraph 

209 

210 

211class CmdLineFwkTestCase(unittest.TestCase): 

212 """A test case for CmdLineFwk 

213 """ 

214 

215 def testMakePipeline(self): 

216 """Tests for CmdLineFwk.makePipeline method 

217 """ 

218 fwk = CmdLineFwk() 

219 

220 # make empty pipeline 

221 args = _makeArgs() 

222 pipeline = fwk.makePipeline(args) 

223 self.assertIsInstance(pipeline, Pipeline) 

224 self.assertEqual(len(pipeline), 0) 

225 

226 # few tests with serialization 

227 with makeTmpFile() as tmpname: 

228 # make empty pipeline and store it in a file 

229 args = _makeArgs(save_pipeline=tmpname) 

230 pipeline = fwk.makePipeline(args) 

231 self.assertIsInstance(pipeline, Pipeline) 

232 

233 # read pipeline from a file 

234 args = _makeArgs(pipeline=tmpname) 

235 pipeline = fwk.makePipeline(args) 

236 self.assertIsInstance(pipeline, Pipeline) 

237 self.assertEqual(len(pipeline), 0) 

238 

239 # single task pipeline, task name can be anything here 

240 actions = [ 

241 _ACTION_ADD_TASK("TaskOne:task1") 

242 ] 

243 args = _makeArgs(pipeline_actions=actions) 

244 pipeline = fwk.makePipeline(args) 

245 self.assertIsInstance(pipeline, Pipeline) 

246 self.assertEqual(len(pipeline), 1) 

247 

248 # many task pipeline 

249 actions = [ 

250 _ACTION_ADD_TASK("TaskOne:task1a"), 

251 _ACTION_ADD_TASK("TaskTwo:task2"), 

252 _ACTION_ADD_TASK("TaskOne:task1b") 

253 ] 

254 args = _makeArgs(pipeline_actions=actions) 

255 pipeline = fwk.makePipeline(args) 

256 self.assertIsInstance(pipeline, Pipeline) 

257 self.assertEqual(len(pipeline), 3) 

258 

259 # single task pipeline with config overrides, need real task class 

260 actions = [ 

261 _ACTION_ADD_TASK(f"{_TASK_CLASS}:task"), 

262 _ACTION_CONFIG("task:addend=100") 

263 ] 

264 args = _makeArgs(pipeline_actions=actions) 

265 pipeline = fwk.makePipeline(args) 

266 taskDefs = list(pipeline.toExpandedPipeline()) 

267 self.assertEqual(len(taskDefs), 1) 

268 self.assertEqual(taskDefs[0].config.addend, 100) 

269 

270 overrides = b"config.addend = 1000\n" 

271 with makeTmpFile(overrides) as tmpname: 

272 actions = [ 

273 _ACTION_ADD_TASK(f"{_TASK_CLASS}:task"), 

274 _ACTION_CONFIG_FILE("task:" + tmpname) 

275 ] 

276 args = _makeArgs(pipeline_actions=actions) 

277 pipeline = fwk.makePipeline(args) 

278 taskDefs = list(pipeline.toExpandedPipeline()) 

279 self.assertEqual(len(taskDefs), 1) 

280 self.assertEqual(taskDefs[0].config.addend, 1000) 

281 

282 # Check --instrument option, for now it only checks that it does not 

283 # crash. 

284 actions = [ 

285 _ACTION_ADD_TASK(f"{_TASK_CLASS}:task"), 

286 _ACTION_ADD_INSTRUMENT("Instrument") 

287 ] 

288 args = _makeArgs(pipeline_actions=actions) 

289 pipeline = fwk.makePipeline(args) 

290 

291 def testMakeGraphFromSave(self): 

292 """Tests for CmdLineFwk.makeGraph method. 

293 

294 Only most trivial case is tested that does not do actual graph 

295 building. 

296 """ 

297 fwk = CmdLineFwk() 

298 

299 with makeTmpFile(suffix=".qgraph") as tmpname, makeSQLiteRegistry() as registryConfig: 

300 

301 # make non-empty graph and store it in a file 

302 qgraph = _makeQGraph() 

303 with open(tmpname, "wb") as saveFile: 

304 qgraph.save(saveFile) 

305 args = _makeArgs(qgraph=tmpname, registryConfig=registryConfig, execution_butler_location=None) 

306 qgraph = fwk.makeGraph(None, args) 

307 self.assertIsInstance(qgraph, QuantumGraph) 

308 self.assertEqual(len(qgraph), 1) 

309 

310 # will fail if graph id does not match 

311 args = _makeArgs( 

312 qgraph=tmpname, 

313 qgraph_id="R2-D2 is that you?", 

314 registryConfig=registryConfig, 

315 execution_butler_location=None 

316 ) 

317 with self.assertRaisesRegex(ValueError, "graphID does not match"): 

318 fwk.makeGraph(None, args) 

319 

320 # save with wrong object type 

321 with open(tmpname, "wb") as saveFile: 

322 pickle.dump({}, saveFile) 

323 args = _makeArgs(qgraph=tmpname, registryConfig=registryConfig, execution_butler_location=None) 

324 with self.assertRaises(ValueError): 

325 fwk.makeGraph(None, args) 

326 

327 # reading empty graph from pickle should work but makeGraph() 

328 # will return None and make a warning 

329 qgraph = QuantumGraph(dict()) 

330 with open(tmpname, "wb") as saveFile: 

331 qgraph.save(saveFile) 

332 args = _makeArgs(qgraph=tmpname, registryConfig=registryConfig, execution_butler_location=None) 

333 with self.assertWarnsRegex(UserWarning, "QuantumGraph is empty"): 

334 # this also tests that warning is generated for empty graph 

335 qgraph = fwk.makeGraph(None, args) 

336 self.assertIs(qgraph, None) 

337 

338 def testShowPipeline(self): 

339 """Test for --show options for pipeline. 

340 """ 

341 fwk = CmdLineFwk() 

342 

343 actions = [ 

344 _ACTION_ADD_TASK(f"{_TASK_CLASS}:task"), 

345 _ACTION_CONFIG("task:addend=100") 

346 ] 

347 args = _makeArgs(pipeline_actions=actions) 

348 pipeline = fwk.makePipeline(args) 

349 

350 args.show = ["pipeline"] 

351 fwk.showInfo(args, pipeline) 

352 args.show = ["config"] 

353 fwk.showInfo(args, pipeline) 

354 args.show = ["history=task::addend"] 

355 fwk.showInfo(args, pipeline) 

356 args.show = ["tasks"] 

357 fwk.showInfo(args, pipeline) 

358 

359 

360class CmdLineFwkTestCaseWithButler(unittest.TestCase): 

361 """A test case for CmdLineFwk 

362 """ 

363 

364 def setUp(self): 

365 super().setUpClass() 

366 self.root = tempfile.mkdtemp() 

367 self.nQuanta = 5 

368 self.pipeline = makeSimplePipeline(nQuanta=self.nQuanta) 

369 

370 def tearDown(self): 

371 shutil.rmtree(self.root, ignore_errors=True) 

372 super().tearDownClass() 

373 

374 def testSimpleQGraph(self): 

375 """Test successfull execution of trivial quantum graph. 

376 """ 

377 args = _makeArgs(butler_config=self.root, input="test", output="output") 

378 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

379 populateButler(self.pipeline, butler) 

380 

381 fwk = CmdLineFwk() 

382 taskFactory = AddTaskFactoryMock() 

383 

384 qgraph = fwk.makeGraph(self.pipeline, args) 

385 self.assertEqual(len(qgraph.taskGraph), self.nQuanta) 

386 self.assertEqual(len(qgraph), self.nQuanta) 

387 

388 # run whole thing 

389 fwk.runPipeline(qgraph, taskFactory, args) 

390 self.assertEqual(taskFactory.countExec, self.nQuanta) 

391 

392 def testSimpleQGraphNoSkipExisting_inputs(self): 

393 """Test for case when output data for one task already appears in 

394 _input_ collection, but no ``--extend-run`` or ``-skip-existing`` 

395 option is present. 

396 """ 

397 args = _makeArgs( 

398 butler_config=self.root, 

399 input="test", 

400 output="output", 

401 ) 

402 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

403 populateButler( 

404 self.pipeline, butler, 

405 datasetTypes={args.input: [ 

406 "add_dataset0", 

407 "add_dataset1", "add2_dataset1", 

408 "add_init_output1", 

409 "task0_config", 

410 "task0_metadata", 

411 "task0_log", 

412 ]} 

413 ) 

414 

415 fwk = CmdLineFwk() 

416 taskFactory = AddTaskFactoryMock() 

417 

418 qgraph = fwk.makeGraph(self.pipeline, args) 

419 self.assertEqual(len(qgraph.taskGraph), self.nQuanta) 

420 # With current implementation graph has all nQuanta quanta, but when 

421 # executing one quantum is skipped. 

422 self.assertEqual(len(qgraph), self.nQuanta) 

423 

424 # run whole thing 

425 fwk.runPipeline(qgraph, taskFactory, args) 

426 self.assertEqual(taskFactory.countExec, self.nQuanta) 

427 

428 def testSimpleQGraphSkipExisting_inputs(self): 

429 """Test for ``--skip-existing`` with output data for one task already 

430 appears in _input_ collection. No ``--extend-run`` option is needed 

431 for this case. 

432 """ 

433 args = _makeArgs( 

434 butler_config=self.root, 

435 input="test", 

436 output="output", 

437 skip_existing_in=("test", ), 

438 ) 

439 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

440 populateButler( 

441 self.pipeline, butler, 

442 datasetTypes={args.input: [ 

443 "add_dataset0", 

444 "add_dataset1", "add2_dataset1", 

445 "add_init_output1", 

446 "task0_config", 

447 "task0_metadata", 

448 "task0_log", 

449 ]} 

450 ) 

451 

452 fwk = CmdLineFwk() 

453 taskFactory = AddTaskFactoryMock() 

454 

455 qgraph = fwk.makeGraph(self.pipeline, args) 

456 self.assertEqual(len(qgraph.taskGraph), self.nQuanta) 

457 self.assertEqual(len(qgraph), self.nQuanta - 1) 

458 

459 # run whole thing 

460 fwk.runPipeline(qgraph, taskFactory, args) 

461 self.assertEqual(taskFactory.countExec, self.nQuanta - 1) 

462 

463 def testSimpleQGraphSkipExisting_outputs(self): 

464 """Test for ``--skip-existing`` with output data for one task already 

465 appears in _output_ collection. The ``--extend-run`` option is needed 

466 for this case. 

467 """ 

468 args = _makeArgs( 

469 butler_config=self.root, 

470 input="test", 

471 output_run="output/run", 

472 skip_existing_in=("output/run", ), 

473 ) 

474 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

475 populateButler( 

476 self.pipeline, butler, datasetTypes={ 

477 args.input: ["add_dataset0"], 

478 args.output_run: [ 

479 "add_dataset1", "add2_dataset1", 

480 "add_init_output1", 

481 "task0_metadata", 

482 "task0_log", 

483 ] 

484 } 

485 ) 

486 

487 fwk = CmdLineFwk() 

488 taskFactory = AddTaskFactoryMock() 

489 

490 # fails without --extend-run 

491 with self.assertRaisesRegex(ValueError, "--extend-run was not given"): 

492 qgraph = fwk.makeGraph(self.pipeline, args) 

493 

494 # retry with --extend-run 

495 args.extend_run = True 

496 qgraph = fwk.makeGraph(self.pipeline, args) 

497 

498 self.assertEqual(len(qgraph.taskGraph), self.nQuanta) 

499 # Graph does not include quantum for first task 

500 self.assertEqual(len(qgraph), self.nQuanta - 1) 

501 

502 # run whole thing 

503 fwk.runPipeline(qgraph, taskFactory, args) 

504 self.assertEqual(taskFactory.countExec, self.nQuanta - 1) 

505 

506 def testSimpleQGraphOutputsFail(self): 

507 """Test continuing execution of trivial quantum graph with partial 

508 outputs. 

509 """ 

510 args = _makeArgs(butler_config=self.root, input="test", output="output") 

511 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

512 populateButler(self.pipeline, butler) 

513 

514 fwk = CmdLineFwk() 

515 taskFactory = AddTaskFactoryMock(stopAt=3) 

516 

517 qgraph = fwk.makeGraph(self.pipeline, args) 

518 self.assertEqual(len(qgraph), self.nQuanta) 

519 

520 # run first three quanta 

521 with self.assertRaises(RuntimeError): 

522 fwk.runPipeline(qgraph, taskFactory, args) 

523 self.assertEqual(taskFactory.countExec, 3) 

524 

525 butler.registry.refresh() 

526 

527 # drop one of the two outputs from one task 

528 ref1 = butler.registry.findDataset("add2_dataset2", collections=args.output, 

529 instrument="INSTR", detector=0) 

530 self.assertIsNotNone(ref1) 

531 # also drop the metadata output 

532 ref2 = butler.registry.findDataset("task1_metadata", collections=args.output, 

533 instrument="INSTR", detector=0) 

534 self.assertIsNotNone(ref2) 

535 butler.pruneDatasets([ref1, ref2], disassociate=True, unstore=True, purge=True) 

536 

537 taskFactory.stopAt = -1 

538 args.skip_existing_in = (args.output, ) 

539 args.extend_run = True 

540 args.no_versions = True 

541 excRe = "Registry inconsistency while checking for existing outputs.*" 

542 with self.assertRaisesRegex(RuntimeError, excRe): 

543 fwk.runPipeline(qgraph, taskFactory, args) 

544 

545 def testSimpleQGraphClobberOutputs(self): 

546 """Test continuing execution of trivial quantum graph with 

547 --clobber-outputs. 

548 """ 

549 args = _makeArgs(butler_config=self.root, input="test", output="output") 

550 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

551 populateButler(self.pipeline, butler) 

552 

553 fwk = CmdLineFwk() 

554 taskFactory = AddTaskFactoryMock(stopAt=3) 

555 

556 qgraph = fwk.makeGraph(self.pipeline, args) 

557 

558 # should have one task and number of quanta 

559 self.assertEqual(len(qgraph), self.nQuanta) 

560 

561 # run first three quanta 

562 with self.assertRaises(RuntimeError): 

563 fwk.runPipeline(qgraph, taskFactory, args) 

564 self.assertEqual(taskFactory.countExec, 3) 

565 

566 butler.registry.refresh() 

567 

568 # drop one of the two outputs from one task 

569 ref1 = butler.registry.findDataset("add2_dataset2", collections=args.output, 

570 dataId=dict(instrument="INSTR", detector=0)) 

571 self.assertIsNotNone(ref1) 

572 # also drop the metadata output 

573 ref2 = butler.registry.findDataset("task1_metadata", collections=args.output, 

574 dataId=dict(instrument="INSTR", detector=0)) 

575 self.assertIsNotNone(ref2) 

576 butler.pruneDatasets([ref1, ref2], disassociate=True, unstore=True, purge=True) 

577 

578 taskFactory.stopAt = -1 

579 args.skip_existing = True 

580 args.extend_run = True 

581 args.clobber_outputs = True 

582 args.no_versions = True 

583 fwk.runPipeline(qgraph, taskFactory, args) 

584 # number of executed quanta is incremented 

585 self.assertEqual(taskFactory.countExec, self.nQuanta + 1) 

586 

587 def testSimpleQGraphReplaceRun(self): 

588 """Test repeated execution of trivial quantum graph with 

589 --replace-run. 

590 """ 

591 args = _makeArgs( 

592 butler_config=self.root, 

593 input="test", 

594 output="output", 

595 output_run="output/run1") 

596 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

597 populateButler(self.pipeline, butler) 

598 

599 fwk = CmdLineFwk() 

600 taskFactory = AddTaskFactoryMock() 

601 

602 qgraph = fwk.makeGraph(self.pipeline, args) 

603 

604 # should have one task and number of quanta 

605 self.assertEqual(len(qgraph), self.nQuanta) 

606 

607 # deep copy is needed because quanta are updated in place 

608 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

609 self.assertEqual(taskFactory.countExec, self.nQuanta) 

610 

611 # need to refresh collections explicitly (or make new butler/registry) 

612 butler.registry.refresh() 

613 collections = set(butler.registry.queryCollections(...)) 

614 self.assertEqual(collections, {"test", "output", "output/run1"}) 

615 

616 # number of datasets written by pipeline: 

617 # - nQuanta of init_outputs 

618 # - nQuanta of configs 

619 # - packages (single dataset) 

620 # - nQuanta * two output datasets 

621 # - nQuanta of metadata 

622 # - nQuanta of log output 

623 n_outputs = self.nQuanta * 6 + 1 

624 refs = butler.registry.queryDatasets(..., collections="output/run1") 

625 self.assertEqual(len(list(refs)), n_outputs) 

626 

627 # re-run with --replace-run (--inputs is ignored, as long as it hasn't 

628 # changed) 

629 args.replace_run = True 

630 args.output_run = "output/run2" 

631 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

632 

633 butler.registry.refresh() 

634 collections = set(butler.registry.queryCollections(...)) 

635 self.assertEqual(collections, {"test", "output", "output/run1", "output/run2"}) 

636 

637 # new output collection 

638 refs = butler.registry.queryDatasets(..., collections="output/run2") 

639 self.assertEqual(len(list(refs)), n_outputs) 

640 

641 # old output collection is still there 

642 refs = butler.registry.queryDatasets(..., collections="output/run1") 

643 self.assertEqual(len(list(refs)), n_outputs) 

644 

645 # re-run with --replace-run and --prune-replaced=unstore 

646 args.replace_run = True 

647 args.prune_replaced = "unstore" 

648 args.output_run = "output/run3" 

649 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

650 

651 butler.registry.refresh() 

652 collections = set(butler.registry.queryCollections(...)) 

653 self.assertEqual(collections, {"test", "output", "output/run1", "output/run2", "output/run3"}) 

654 

655 # new output collection 

656 refs = butler.registry.queryDatasets(..., collections="output/run3") 

657 self.assertEqual(len(list(refs)), n_outputs) 

658 

659 # old output collection is still there, and it has all datasets but 

660 # non-InitOutputs are not in datastore 

661 refs = butler.registry.queryDatasets(..., collections="output/run2") 

662 refs = list(refs) 

663 self.assertEqual(len(refs), n_outputs) 

664 initOutNameRe = re.compile("packages|task.*_config|add_init_output.*") 

665 for ref in refs: 

666 if initOutNameRe.fullmatch(ref.datasetType.name): 

667 butler.get(ref, collections="output/run2") 

668 else: 

669 with self.assertRaises(FileNotFoundError): 

670 butler.get(ref, collections="output/run2") 

671 

672 # re-run with --replace-run and --prune-replaced=purge 

673 # This time also remove --input; passing the same inputs that we 

674 # started with and not passing inputs at all should be equivalent. 

675 args.input = None 

676 args.replace_run = True 

677 args.prune_replaced = "purge" 

678 args.output_run = "output/run4" 

679 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

680 

681 butler.registry.refresh() 

682 collections = set(butler.registry.queryCollections(...)) 

683 # output/run3 should disappear now 

684 self.assertEqual(collections, {"test", "output", "output/run1", "output/run2", "output/run4"}) 

685 

686 # new output collection 

687 refs = butler.registry.queryDatasets(..., collections="output/run4") 

688 self.assertEqual(len(list(refs)), n_outputs) 

689 

690 # Trying to run again with inputs that aren't exactly what we started 

691 # with is an error, and the kind that should not modify the data repo. 

692 with self.assertRaises(ValueError): 

693 args.input = ["test", "output/run2"] 

694 args.prune_replaced = None 

695 args.replace_run = True 

696 args.output_run = "output/run5" 

697 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

698 butler.registry.refresh() 

699 collections = set(butler.registry.queryCollections(...)) 

700 self.assertEqual(collections, {"test", "output", "output/run1", "output/run2", "output/run4"}) 

701 with self.assertRaises(ValueError): 

702 args.input = ["output/run2", "test"] 

703 args.prune_replaced = None 

704 args.replace_run = True 

705 args.output_run = "output/run6" 

706 fwk.runPipeline(copy.deepcopy(qgraph), taskFactory, args) 

707 butler.registry.refresh() 

708 collections = set(butler.registry.queryCollections(...)) 

709 self.assertEqual(collections, {"test", "output", "output/run1", "output/run2", "output/run4"}) 

710 

711 def testSubgraph(self): 

712 """Test successfull execution of trivial quantum graph. 

713 """ 

714 args = _makeArgs(butler_config=self.root, input="test", output="output") 

715 butler = makeSimpleButler(self.root, run=args.input, inMemory=False) 

716 populateButler(self.pipeline, butler) 

717 

718 fwk = CmdLineFwk() 

719 qgraph = fwk.makeGraph(self.pipeline, args) 

720 

721 # Select first two nodes for execution. This depends on node ordering 

722 # which I assume is the same as execution order. 

723 nNodes = 2 

724 nodeIds = [node.nodeId.number for node in qgraph] 

725 nodeIds = nodeIds[:nNodes] 

726 

727 self.assertEqual(len(qgraph.taskGraph), self.nQuanta) 

728 self.assertEqual(len(qgraph), self.nQuanta) 

729 

730 with makeTmpFile(suffix=".qgraph") as tmpname, makeSQLiteRegistry() as registryConfig: 

731 with open(tmpname, "wb") as saveFile: 

732 qgraph.save(saveFile) 

733 

734 args = _makeArgs(qgraph=tmpname, qgraph_node_id=nodeIds, registryConfig=registryConfig, 

735 execution_butler_location=None) 

736 fwk = CmdLineFwk() 

737 

738 # load graph, should only read a subset 

739 qgraph = fwk.makeGraph(pipeline=None, args=args) 

740 self.assertEqual(len(qgraph), nNodes) 

741 

742 def testShowGraph(self): 

743 """Test for --show options for quantum graph. 

744 """ 

745 fwk = CmdLineFwk() 

746 

747 nQuanta = 2 

748 butler, qgraph = makeSimpleQGraph(nQuanta, root=self.root) 

749 

750 args = _makeArgs(show=["graph"]) 

751 fwk.showInfo(args, pipeline=None, graph=qgraph) 

752 

753 def testShowGraphWorkflow(self): 

754 fwk = CmdLineFwk() 

755 

756 nQuanta = 2 

757 butler, qgraph = makeSimpleQGraph(nQuanta, root=self.root) 

758 

759 args = _makeArgs(show=["workflow"]) 

760 fwk.showInfo(args, pipeline=None, graph=qgraph) 

761 

762 # TODO: cannot test "uri" option presently, it instanciates 

763 # butler from command line options and there is no way to pass butler 

764 # mock to that code. 

765 

766 

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

768 pass 

769 

770 

771def setup_module(module): 

772 lsst.utils.tests.init() 

773 

774 

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

776 lsst.utils.tests.init() 

777 unittest.main()