Coverage for tests/test_testUtils.py: 22%
311 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-30 10:01 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-30 10:01 +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# (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 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 <https://www.gnu.org/licenses/>.
28"""Unit tests for `lsst.pipe.base.tests`, a library for testing
29PipelineTask subclasses.
30"""
32import shutil
33import tempfile
34import unittest
36import lsst.daf.butler
37import lsst.daf.butler.tests as butlerTests
38import lsst.pex.config
39import lsst.utils.tests
40from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections, Struct, connectionTypes
41from lsst.pipe.base.testUtils import (
42 assertValidInitOutput,
43 assertValidOutput,
44 getInitInputs,
45 lintConnections,
46 makeQuantum,
47 runTestQuantum,
48)
51class VisitConnections(PipelineTaskConnections, dimensions={"instrument", "visit"}):
52 """Test connections class involving a visit."""
54 initIn = connectionTypes.InitInput(
55 name="VisitInitIn",
56 storageClass="StructuredData",
57 multiple=False,
58 )
59 a = connectionTypes.Input(
60 name="VisitA",
61 storageClass="StructuredData",
62 multiple=False,
63 dimensions={"instrument", "visit"},
64 )
65 b = connectionTypes.Input(
66 name="VisitB",
67 storageClass="StructuredData",
68 multiple=False,
69 dimensions={"instrument", "visit"},
70 )
71 initOut = connectionTypes.InitOutput(
72 name="VisitInitOut",
73 storageClass="StructuredData",
74 multiple=True,
75 )
76 outA = connectionTypes.Output(
77 name="VisitOutA",
78 storageClass="StructuredData",
79 multiple=False,
80 dimensions={"instrument", "visit"},
81 )
82 outB = connectionTypes.Output(
83 name="VisitOutB",
84 storageClass="StructuredData",
85 multiple=False,
86 dimensions={"instrument", "visit"},
87 )
89 def __init__(self, *, config=None):
90 super().__init__(config=config)
92 if not config.doUseInitIn:
93 self.initInputs.remove("initIn")
96class PatchConnections(PipelineTaskConnections, dimensions={"skymap", "tract"}):
97 """Test Connections class for patch."""
99 a = connectionTypes.Input(
100 name="PatchA",
101 storageClass="StructuredData",
102 multiple=True,
103 dimensions={"skymap", "tract", "patch"},
104 )
105 b = connectionTypes.PrerequisiteInput(
106 name="PatchB",
107 storageClass="StructuredData",
108 multiple=False,
109 dimensions={"skymap", "tract"},
110 )
111 initOutA = connectionTypes.InitOutput(
112 name="PatchInitOutA",
113 storageClass="StructuredData",
114 multiple=False,
115 )
116 initOutB = connectionTypes.InitOutput(
117 name="PatchInitOutB",
118 storageClass="StructuredData",
119 multiple=False,
120 )
121 out = connectionTypes.Output(
122 name="PatchOut",
123 storageClass="StructuredData",
124 multiple=True,
125 dimensions={"skymap", "tract", "patch"},
126 )
128 def __init__(self, *, config=None):
129 super().__init__(config=config)
131 if not config.doUseB:
132 self.prerequisiteInputs.remove("b")
135class SkyPixConnections(PipelineTaskConnections, dimensions={"skypix"}):
136 """Test connections class for a SkyPix."""
138 a = connectionTypes.Input(
139 name="PixA",
140 storageClass="StructuredData",
141 dimensions={"skypix"},
142 )
143 out = connectionTypes.Output(
144 name="PixOut",
145 storageClass="StructuredData",
146 dimensions={"skypix"},
147 )
150class VisitConfig(PipelineTaskConfig, pipelineConnections=VisitConnections):
151 """Config for Visit."""
153 doUseInitIn = lsst.pex.config.Field(default=False, dtype=bool, doc="test")
156class PatchConfig(PipelineTaskConfig, pipelineConnections=PatchConnections):
157 """Config for Patch."""
159 doUseB = lsst.pex.config.Field(default=True, dtype=bool, doc="test")
162class SkyPixConfig(PipelineTaskConfig, pipelineConnections=SkyPixConnections):
163 """Config for SkyPix."""
166class VisitTask(PipelineTask):
167 """Visit-based Task."""
169 ConfigClass = VisitConfig
170 _DefaultName = "visit"
172 def __init__(self, initInputs=None, **kwargs):
173 super().__init__(initInputs=initInputs, **kwargs)
174 self.initOut = [
175 butlerTests.MetricsExample(data=[1, 2]),
176 butlerTests.MetricsExample(data=[3, 4]),
177 ]
179 def run(self, a, b):
180 outA = butlerTests.MetricsExample(data=(a.data + b.data))
181 outB = butlerTests.MetricsExample(data=(a.data * max(b.data)))
182 return Struct(outA=outA, outB=outB)
185class PatchTask(PipelineTask):
186 """Patch-based Task."""
188 ConfigClass = PatchConfig
189 _DefaultName = "patch"
191 def __init__(self, **kwargs):
192 super().__init__(**kwargs)
193 self.initOutA = butlerTests.MetricsExample(data=[1, 2, 4])
194 self.initOutB = butlerTests.MetricsExample(data=[1, 2, 3])
196 def run(self, a, b=None):
197 if self.config.doUseB:
198 out = [butlerTests.MetricsExample(data=(oneA.data + b.data)) for oneA in a]
199 else:
200 out = a
201 return Struct(out=out)
204class SkyPixTask(PipelineTask):
205 """Skypix-based Task."""
207 ConfigClass = SkyPixConfig
208 _DefaultName = "skypix"
210 def run(self, a):
211 return Struct(out=a)
214class PipelineTaskTestSuite(lsst.utils.tests.TestCase):
215 """Test pipeline task."""
217 @classmethod
218 def setUpClass(cls):
219 super().setUpClass()
220 # Repository should be re-created for each test case, but
221 # this has a prohibitive run-time cost at present
222 cls.root = tempfile.mkdtemp()
224 cls.repo = butlerTests.makeTestRepo(cls.root)
225 butlerTests.addDataIdValue(cls.repo, "instrument", "notACam")
226 butlerTests.addDataIdValue(cls.repo, "visit", 101)
227 butlerTests.addDataIdValue(cls.repo, "visit", 102)
228 butlerTests.addDataIdValue(cls.repo, "skymap", "sky")
229 butlerTests.addDataIdValue(cls.repo, "tract", 42)
230 butlerTests.addDataIdValue(cls.repo, "patch", 0)
231 butlerTests.addDataIdValue(cls.repo, "patch", 1)
232 butlerTests.registerMetricsExample(cls.repo)
234 for typeName in {"VisitA", "VisitB", "VisitOutA", "VisitOutB"}:
235 butlerTests.addDatasetType(cls.repo, typeName, {"instrument", "visit"}, "StructuredData")
236 for typeName in {"PatchA", "PatchOut"}:
237 butlerTests.addDatasetType(cls.repo, typeName, {"skymap", "tract", "patch"}, "StructuredData")
238 butlerTests.addDatasetType(cls.repo, "PatchB", {"skymap", "tract"}, "StructuredData")
239 for typeName in {"PixA", "PixOut"}:
240 butlerTests.addDatasetType(cls.repo, typeName, {"htm7"}, "StructuredData")
241 butlerTests.addDatasetType(cls.repo, "VisitInitIn", set(), "StructuredData")
243 @classmethod
244 def tearDownClass(cls):
245 shutil.rmtree(cls.root, ignore_errors=True)
246 super().tearDownClass()
248 def setUp(self):
249 super().setUp()
250 self.butler = butlerTests.makeTestCollection(self.repo, uniqueId=self.id())
252 def _makeVisitTestData(self, dataId):
253 """Create dummy datasets suitable for VisitTask.
255 This method updates ``self.butler`` directly.
257 Parameters
258 ----------
259 dataId : any data ID type
260 The (shared) ID for the datasets to create.
262 Returns
263 -------
264 datasets : `dict` [`str`, `list`]
265 A dictionary keyed by dataset type. Its values are the list of
266 integers used to create each dataset. The datasets stored in the
267 butler are `lsst.daf.butler.tests.MetricsExample` objects with
268 these lists as their ``data`` argument, but the lists are easier
269 to manipulate in test code.
270 """
271 inInit = [4, 2]
272 inA = [1, 2, 3]
273 inB = [4, 0, 1]
274 self.butler.put(butlerTests.MetricsExample(data=inA), "VisitA", dataId)
275 self.butler.put(butlerTests.MetricsExample(data=inB), "VisitB", dataId)
276 self.butler.put(butlerTests.MetricsExample(data=inInit), "VisitInitIn", set())
277 return {
278 "VisitA": inA,
279 "VisitB": inB,
280 "VisitInitIn": inInit,
281 }
283 def _makePatchTestData(self, dataId):
284 """Create dummy datasets suitable for PatchTask.
286 This method updates ``self.butler`` directly.
288 Parameters
289 ----------
290 dataId : any data ID type
291 The (shared) ID for the datasets to create. Any patch ID is
292 overridden to create multiple datasets.
294 Returns
295 -------
296 datasets : `dict` [`str`, `list` [`tuple` [data ID, `list`]]]
297 A dictionary keyed by dataset type. Its values are the data ID
298 of each dataset and the list of integers used to create each. The
299 datasets stored in the butler are
300 `lsst.daf.butler.tests.MetricsExample` objects with these lists as
301 their ``data`` argument, but the lists are easier to manipulate
302 in test code.
303 """
304 inA = [1, 2, 3]
305 inB = [4, 0, 1]
306 datasets = {"PatchA": [], "PatchB": []}
307 for patch in {0, 1}:
308 self.butler.put(butlerTests.MetricsExample(data=(inA + [patch])), "PatchA", dataId, patch=patch)
309 datasets["PatchA"].append((dict(dataId, patch=patch), inA + [patch]))
310 self.butler.put(butlerTests.MetricsExample(data=inB), "PatchB", dataId)
311 datasets["PatchB"].append((dataId, inB))
312 return datasets
314 def testMakeQuantumNoSuchDatatype(self):
315 config = VisitConfig()
316 config.connections.a = "Visit"
317 task = VisitTask(config=config)
319 dataId = {"instrument": "notACam", "visit": 102}
320 self._makeVisitTestData(dataId)
322 with self.assertRaises(ValueError):
323 makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "b", "outA", "outB"}})
325 def testMakeQuantumInvalidDimension(self):
326 config = VisitConfig()
327 config.connections.a = "PatchA"
328 task = VisitTask(config=config)
329 dataIdV = {"instrument": "notACam", "visit": 102}
330 dataIdVExtra = {"instrument": "notACam", "visit": 102, "detector": 42}
331 dataIdP = {"skymap": "sky", "tract": 42, "patch": 0}
333 inA = [1, 2, 3]
334 inB = [4, 0, 1]
335 self.butler.put(butlerTests.MetricsExample(data=inA), "VisitA", dataIdV)
336 self.butler.put(butlerTests.MetricsExample(data=inA), "PatchA", dataIdP)
337 self.butler.put(butlerTests.MetricsExample(data=inB), "VisitB", dataIdV)
339 # dataIdV is correct everywhere, dataIdP should error
340 with self.assertRaises(ValueError):
341 makeQuantum(
342 task,
343 self.butler,
344 dataIdV,
345 {
346 "a": dataIdP,
347 "b": dataIdV,
348 "outA": dataIdV,
349 "outB": dataIdV,
350 },
351 )
352 with self.assertRaises(ValueError):
353 makeQuantum(
354 task,
355 self.butler,
356 dataIdP,
357 {
358 "a": dataIdV,
359 "b": dataIdV,
360 "outA": dataIdV,
361 "outB": dataIdV,
362 },
363 )
364 # should not accept small changes, either
365 with self.assertRaises(ValueError):
366 makeQuantum(
367 task,
368 self.butler,
369 dataIdV,
370 {
371 "a": dataIdV,
372 "b": dataIdV,
373 "outA": dataIdVExtra,
374 "outB": dataIdV,
375 },
376 )
377 with self.assertRaises(ValueError):
378 makeQuantum(
379 task,
380 self.butler,
381 dataIdVExtra,
382 {
383 "a": dataIdV,
384 "b": dataIdV,
385 "outA": dataIdV,
386 "outB": dataIdV,
387 },
388 )
390 def testMakeQuantumMissingMultiple(self):
391 task = PatchTask()
393 dataId = {"skymap": "sky", "tract": 42}
394 self._makePatchTestData(dataId)
396 with self.assertRaises(ValueError):
397 makeQuantum(
398 task,
399 self.butler,
400 dataId,
401 {
402 "a": dict(dataId, patch=0),
403 "b": dataId,
404 "out": [dict(dataId, patch=patch) for patch in {0, 1}],
405 },
406 )
408 def testMakeQuantumExtraMultiple(self):
409 task = PatchTask()
411 dataId = {"skymap": "sky", "tract": 42}
412 self._makePatchTestData(dataId)
414 with self.assertRaises(ValueError):
415 makeQuantum(
416 task,
417 self.butler,
418 dataId,
419 {
420 "a": [dict(dataId, patch=patch) for patch in {0, 1}],
421 "b": [dataId],
422 "out": [dict(dataId, patch=patch) for patch in {0, 1}],
423 },
424 )
426 def testMakeQuantumMissingDataId(self):
427 task = VisitTask()
429 dataId = {"instrument": "notACam", "visit": 102}
430 self._makeVisitTestData(dataId)
432 with self.assertRaises(ValueError):
433 makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "outA", "outB"}})
434 with self.assertRaises(ValueError):
435 makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "b", "outB"}})
437 def testMakeQuantumCorruptedDataId(self):
438 task = VisitTask()
440 dataId = {"instrument": "notACam", "visit": 102}
441 self._makeVisitTestData(dataId)
443 with self.assertRaises(ValueError):
444 # fourth argument should be a mapping keyed by component name
445 makeQuantum(task, self.butler, dataId, dataId)
447 def testRunTestQuantumVisitWithRun(self):
448 task = VisitTask()
450 dataId = {"instrument": "notACam", "visit": 102}
451 data = self._makeVisitTestData(dataId)
453 quantum = makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "b", "outA", "outB"}})
454 runTestQuantum(task, self.butler, quantum, mockRun=False)
456 # Can we use runTestQuantum to verify that task.run got called with
457 # correct inputs/outputs?
458 self.assertTrue(self.butler.exists("VisitOutA", dataId))
459 self.assertEqual(
460 self.butler.get("VisitOutA", dataId),
461 butlerTests.MetricsExample(data=(data["VisitA"] + data["VisitB"])),
462 )
463 self.assertTrue(self.butler.exists("VisitOutB", dataId))
464 self.assertEqual(
465 self.butler.get("VisitOutB", dataId),
466 butlerTests.MetricsExample(data=(data["VisitA"] * max(data["VisitB"]))),
467 )
469 def testRunTestQuantumPatchWithRun(self):
470 task = PatchTask()
472 dataId = {"skymap": "sky", "tract": 42}
473 data = self._makePatchTestData(dataId)
475 quantum = makeQuantum(
476 task,
477 self.butler,
478 dataId,
479 {
480 "a": [dataset[0] for dataset in data["PatchA"]],
481 "b": dataId,
482 "out": [dataset[0] for dataset in data["PatchA"]],
483 },
484 )
485 runTestQuantum(task, self.butler, quantum, mockRun=False)
487 # Can we use runTestQuantum to verify that task.run got called with
488 # correct inputs/outputs?
489 inB = data["PatchB"][0][1]
490 for dataset in data["PatchA"]:
491 patchId = dataset[0]
492 self.assertTrue(self.butler.exists("PatchOut", patchId))
493 self.assertEqual(
494 self.butler.get("PatchOut", patchId), butlerTests.MetricsExample(data=(dataset[1] + inB))
495 )
497 def testRunTestQuantumVisitMockRun(self):
498 task = VisitTask()
500 dataId = {"instrument": "notACam", "visit": 102}
501 data = self._makeVisitTestData(dataId)
503 quantum = makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "b", "outA", "outB"}})
504 run = runTestQuantum(task, self.butler, quantum, mockRun=True)
506 # Can we use the mock to verify that task.run got called with the
507 # correct inputs?
508 run.assert_called_once_with(
509 a=butlerTests.MetricsExample(data=data["VisitA"]),
510 b=butlerTests.MetricsExample(data=data["VisitB"]),
511 )
513 def testRunTestQuantumPatchMockRun(self):
514 task = PatchTask()
516 dataId = {"skymap": "sky", "tract": 42}
517 data = self._makePatchTestData(dataId)
519 quantum = makeQuantum(
520 task,
521 self.butler,
522 dataId,
523 {
524 # Use lists, not sets, to ensure order agrees with test
525 # assertion.
526 "a": [dataset[0] for dataset in data["PatchA"]],
527 "b": dataId,
528 "out": [dataset[0] for dataset in data["PatchA"]],
529 },
530 )
531 run = runTestQuantum(task, self.butler, quantum, mockRun=True)
533 # Can we use the mock to verify that task.run got called with the
534 # correct inputs?
535 run.assert_called_once_with(
536 a=[butlerTests.MetricsExample(data=dataset[1]) for dataset in data["PatchA"]],
537 b=butlerTests.MetricsExample(data=data["PatchB"][0][1]),
538 )
540 def testRunTestQuantumPatchOptionalInput(self):
541 config = PatchConfig()
542 config.doUseB = False
543 task = PatchTask(config=config)
545 dataId = {"skymap": "sky", "tract": 42}
546 data = self._makePatchTestData(dataId)
548 quantum = makeQuantum(
549 task,
550 self.butler,
551 dataId,
552 {
553 # Use lists, not sets, to ensure order agrees with test
554 # assertion.
555 "a": [dataset[0] for dataset in data["PatchA"]],
556 "out": [dataset[0] for dataset in data["PatchA"]],
557 },
558 )
559 run = runTestQuantum(task, self.butler, quantum, mockRun=True)
561 # Can we use the mock to verify that task.run got called with the
562 # correct inputs?
563 run.assert_called_once_with(
564 a=[butlerTests.MetricsExample(data=dataset[1]) for dataset in data["PatchA"]]
565 )
567 def testAssertValidOutputPass(self):
568 task = VisitTask()
570 inA = butlerTests.MetricsExample(data=[1, 2, 3])
571 inB = butlerTests.MetricsExample(data=[4, 0, 1])
572 result = task.run(inA, inB)
574 # should not throw
575 assertValidOutput(task, result)
577 def testAssertValidOutputMissing(self):
578 task = VisitTask()
580 def run(a, b):
581 return Struct(outA=a)
583 task.run = run
585 inA = butlerTests.MetricsExample(data=[1, 2, 3])
586 inB = butlerTests.MetricsExample(data=[4, 0, 1])
587 result = task.run(inA, inB)
589 with self.assertRaises(AssertionError):
590 assertValidOutput(task, result)
592 def testAssertValidOutputSingle(self):
593 task = PatchTask()
595 def run(a, b):
596 return Struct(out=b)
598 task.run = run
600 inA = butlerTests.MetricsExample(data=[1, 2, 3])
601 inB = butlerTests.MetricsExample(data=[4, 0, 1])
602 result = task.run([inA], inB)
604 with self.assertRaises(AssertionError):
605 assertValidOutput(task, result)
607 def testAssertValidOutputMultiple(self):
608 task = VisitTask()
610 def run(a, b):
611 return Struct(outA=[a], outB=b)
613 task.run = run
615 inA = butlerTests.MetricsExample(data=[1, 2, 3])
616 inB = butlerTests.MetricsExample(data=[4, 0, 1])
617 result = task.run(inA, inB)
619 with self.assertRaises(AssertionError):
620 assertValidOutput(task, result)
622 def testAssertValidInitOutputPass(self):
623 task = VisitTask()
624 # should not throw
625 assertValidInitOutput(task)
627 task = PatchTask()
628 # should not throw
629 assertValidInitOutput(task)
631 def testAssertValidInitOutputMissing(self):
632 class BadVisitTask(VisitTask):
633 def __init__(self, **kwargs):
634 PipelineTask.__init__(self, **kwargs) # Bypass VisitTask constructor
635 pass # do not set fields
637 task = BadVisitTask()
639 with self.assertRaises(AssertionError):
640 assertValidInitOutput(task)
642 def testAssertValidInitOutputSingle(self):
643 class BadVisitTask(VisitTask):
644 def __init__(self, **kwargs):
645 PipelineTask.__init__(self, **kwargs) # Bypass VisitTask constructor
646 self.initOut = butlerTests.MetricsExample(data=[1, 2])
648 task = BadVisitTask()
650 with self.assertRaises(AssertionError):
651 assertValidInitOutput(task)
653 def testAssertValidInitOutputMultiple(self):
654 class BadPatchTask(PatchTask):
655 def __init__(self, **kwargs):
656 PipelineTask.__init__(self, **kwargs) # Bypass PatchTask constructor
657 self.initOutA = [butlerTests.MetricsExample(data=[1, 2, 4])]
658 self.initOutB = butlerTests.MetricsExample(data=[1, 2, 3])
660 task = BadPatchTask()
662 with self.assertRaises(AssertionError):
663 assertValidInitOutput(task)
665 def testGetInitInputs(self):
666 dataId = {"instrument": "notACam", "visit": 102}
667 data = self._makeVisitTestData(dataId)
669 self.assertEqual(getInitInputs(self.butler, VisitConfig()), {})
671 config = VisitConfig()
672 config.doUseInitIn = True
673 self.assertEqual(
674 getInitInputs(self.butler, config),
675 {"initIn": butlerTests.MetricsExample(data=data["VisitInitIn"])},
676 )
678 def testSkypixHandling(self):
679 task = SkyPixTask()
681 dataId = {"htm7": 157227} # connection declares skypix, but Butler uses htm7
682 data = butlerTests.MetricsExample(data=[1, 2, 3])
683 self.butler.put(data, "PixA", dataId)
685 quantum = makeQuantum(task, self.butler, dataId, {key: dataId for key in {"a", "out"}})
686 run = runTestQuantum(task, self.butler, quantum, mockRun=True)
688 # PixA dataset should have been retrieved by runTestQuantum
689 run.assert_called_once_with(a=data)
691 def testLintConnectionsOk(self):
692 lintConnections(VisitConnections)
693 lintConnections(PatchConnections)
694 lintConnections(SkyPixConnections)
696 def testLintConnectionsMissingMultiple(self):
697 class BadConnections(PipelineTaskConnections, dimensions={"tract", "patch", "skymap"}):
698 coadds = connectionTypes.Input(
699 name="coadd_calexp",
700 storageClass="ExposureF",
701 # Some authors use list rather than set; check that linter
702 # can handle it.
703 dimensions=["tract", "patch", "band", "skymap"],
704 )
706 with self.assertRaises(AssertionError):
707 lintConnections(BadConnections)
708 lintConnections(BadConnections, checkMissingMultiple=False)
710 def testLintConnectionsExtraMultiple(self):
711 class BadConnections(
712 PipelineTaskConnections,
713 # Some authors use list rather than set.
714 dimensions=["tract", "patch", "band", "skymap"],
715 ):
716 coadds = connectionTypes.Input(
717 name="coadd_calexp",
718 storageClass="ExposureF",
719 multiple=True,
720 dimensions={"tract", "patch", "band", "skymap"},
721 )
723 with self.assertRaises(AssertionError):
724 lintConnections(BadConnections)
725 lintConnections(BadConnections, checkUnnecessaryMultiple=False)
728class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
729 """Run file leak tests."""
732def setup_module(module):
733 """Configure pytest."""
734 lsst.utils.tests.init()
737if __name__ == "__main__":
738 lsst.utils.tests.init()
739 unittest.main()