Belle II Software  release-06-01-15
test_skim_framework.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 """
13 Tests to ensure that the structure of the skim package is maintained.
14 """
15 
16 
17 from importlib import import_module
18 from inspect import getmembers, isclass
19 import os
20 from pathlib import Path
21 import unittest
22 
23 from basf2 import find_file
24 from skim.registry import Registry
25 from skim import BaseSkim
26 
27 
28 class TestSkimRegistry(unittest.TestCase):
29  """Test case for skim registry."""
30 
31  ExistentModulePaths = Path(find_file("skim/scripts/skim/WGs")).glob("*.py")
32  ExistentModules = [
33  module.stem
34  for module in ExistentModulePaths
35  if module.stem not in ["__init__", "registry"]
36  ]
37 
38  def assertIsSubclass(self, cls, parent_cls, msg=None):
39  """Fail if `cls` is not a subclass of `parent_cls`."""
40  if not issubclass(cls, parent_cls):
41  standardMsg = "%r is not a subclass of %r" % (cls, parent_cls)
42  self.fail(self._formatMessage(msg, standardMsg))
43 
44  def test_code_format(self):
45  """Check the codes are the correct format (8 digits)."""
46  # https://confluence.desy.de/x/URdYBQ
47  for code in Registry.codes:
48  self.assertEqual(len(code), 8, "Incorrect length skim code")
49  self.assertTrue(code.isdigit(), "Must consist of digits")
50 
51  def test_unique_codes(self):
52  """Check that there aren't two skims registered with the same code."""
53  self.assertEqual(
54  len(Registry.codes), len(set(Registry.codes)), "Duplicated skim code"
55  )
56 
57  def test_unique_names(self):
58  """Check that there aren't two skims registered with the same name."""
59  self.assertEqual(
60  len(Registry.names), len(set(Registry.names)), "Duplicated skim name"
61  )
62 
63  def test_invalid_names(self):
64  """Check that that no registered skims have invalid names."""
65  for name in Registry.names:
66  self.assertFalse(
67  name.startswith("Base"),
68  (
69  f"Invalid skim name in registry: {name}. Registed skim names cannot"
70  " begin with 'Base'; this word is reserved for subclassing purposes."
71  ),
72  )
73 
74  def test_encode(self):
75  """Check that we raise a LookupError if the skim name doesn't exist."""
76  with self.assertRaises(LookupError):
77  Registry.encode_skim_name("SomeNonExistentSkimName")
78 
79  def test_decode(self):
80  """Check that we raise a LookupError if the skim code doesn't exist."""
81  with self.assertRaises(LookupError):
82  Registry.decode_skim_code("1337")
83 
84  def test_modules_exist(self):
85  """Check that all modules listed in registry exist in skim/scripts/skim/."""
86  for module in Registry.modules:
87  self.assertIn(
88  module,
89  self.ExistentModulesExistentModules,
90  (
91  f"Module {module} listed in registry, but does not exist in "
92  "skim/scripts/skim/."
93  ),
94  )
95 
97  """Check that there is no overlap between skim and module names."""
98  duplicates = set(Registry.modules).intersection(Registry.names)
99  self.assertEqual(
100  set(),
101  duplicates,
102  f"Name used for both a skim and a module: {', '.join(duplicates)}",
103  )
104 
105  def test_skims_exist(self):
106  """Check that the registry is correct about the location of every skim.
107 
108  This test uses the information from the registry, and checks for missing skims
109  in the modules.
110  """
111  for ModuleName in Registry.modules:
112  SkimModule = import_module(f"skim.WGs.{ModuleName}")
113  for SkimName in Registry.get_skims_in_module(ModuleName):
114  # Check the skim is defined in the module
115  self.assertIn(
116  SkimName,
117  SkimModule.__dict__.keys(),
118  (
119  f"Registry lists {SkimName} as existing in skim.WGs.{ModuleName}, "
120  "but no such skim found!"
121  ),
122  )
123 
124  # Check that it is defined as a subclass of BaseSkim
125  SkimClass = getattr(SkimModule, SkimName)
126  self.assertIsSubclassassertIsSubclass(
127  SkimClass,
128  BaseSkim,
129  f"Skim {SkimName} must be defined as a subclass of BaseSkim.",
130  )
131 
133  """Check that every skim defined in a module is listed in the registry.
134 
135  This test uses the information from the modules, and checks for missing or
136  incorrect skim information in the registry.
137  """
138  for ModuleName in self.ExistentModulesExistentModules:
139  SkimModule = import_module(f"skim.WGs.{ModuleName}")
140 
141  # Inspect the module, and find all BaseSkim subclasses
142  SkimNames = [
143  obj[0]
144  for obj in getmembers(SkimModule, isclass)
145  if (
146  issubclass(obj[1], BaseSkim)
147  and obj[1] is not BaseSkim
148  and obj[0] != "CombinedSkim"
149  # Allow "Base" at beginning of skims, for subclassing
150  and not obj[0].startswith("Base")
151  )
152  ]
153 
154  # Check the skim is listed in the registry with the same name
155  for SkimName in SkimNames:
156  self.assertIn(
157  SkimName,
158  Registry.names,
159  (
160  f"Skim {SkimName} defined in skim/scripts/skim/{ModuleName}.py, "
161  "but not listed in registry."
162  ),
163  )
164 
165  # Check the module we found the skim in is the module listed in the registry
166  for SkimName in SkimNames:
167  ExpectedModuleName = Registry.get_skim_module(SkimName)
168  self.assertEqual(
169  ExpectedModuleName,
170  ModuleName,
171  (
172  f"Skim {SkimName} defined in "
173  f"skim/scripts/skim/{ModuleName}.py, but listed in registry as "
174  f"belonging to skim/scripts/skim/{ExpectedModuleName}.py."
175  ),
176  )
177 
178 
179 class TestSkimValidation(unittest.TestCase):
181  """
182  Check that all skims with a ``validation_histograms`` method defined have a
183  script in skim/validation/, and vice versa. This unit test exists to make sure
184  that the code auto-generated by ``b2skim-generate-validation`` stays up to date.
185  """
186  SkimsWithValidationMethod = [
187  skim
188  for skim in Registry.names
189  if not Registry.get_skim_function(skim)()._method_unchanged(
190  "validation_histograms"
191  )
192  ]
193  SkimsWithValidationScript = [script.stem for script in Path(find_file("skim/validation")).glob("*.py")]
194 
195  for skim in SkimsWithValidationMethod:
196  self.assertIn(
197  skim,
198  SkimsWithValidationScript,
199  (
200  f"Skim {skim} has a `validation_histograms` method defined, but no "
201  "script has been added to skim/validation/ yet. Please add this "
202  "script using:\n"
203  f" $ b2skim-generate-validation --skims {skim} --in-place"
204  ),
205  )
206  for skim in SkimsWithValidationScript:
207  self.assertIn(
208  skim,
209  SkimsWithValidationMethod,
210  (
211  f"Skim {skim} has a script in skim/validation/, but no "
212  "`validation_histograms` method defined."
213  ),
214  )
215 
216  @unittest.skipIf( not os.getenv("BELLE2_VALIDATION_DATA_DIR"),
217  "BELLE2_VALIDATION_DATA_DIR environment variable not set."
218  )
220  """
221  Check that all ``validation_sample`` attributes of skims point to existing files.
222  """
223  for skim in Registry.names:
224  SkimObject = Registry.get_skim_function(skim)()
225  # Don't bother checking sample if no `validation_histograms` method is defined
226  if SkimObject._method_unchanged("validation_histograms"):
227  continue
228 
229  try:
230  find_file(SkimObject.validation_sample, data_type="validation")
231  except FileNotFoundError:
232  self.fail(f"{skim}.validation_sample does not point to an existing validation file.")
233 
234 
235 if __name__ == "__main__":
236  unittest.main()
237 
def assertIsSubclass(self, cls, parent_cls, msg=None)