Coverage for pesummary/cli/summarytest.py: 0.0%
173 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-09 22:34 +0000
1# Licensed under an MIT style license -- see LICENSE.md
3import subprocess
4import os
5import sys
6import pesummary
7from pesummary.core.cli.parser import ArgumentParser as _ArgumentParser
8from pesummary.core.fetch import download_dir, download_and_read_file
9from pesummary.utils.utils import logger
10from pesummary.utils.decorators import tmp_directory
11import numpy as np
12import glob
13from pathlib import Path
15__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"]
16ALLOWED = [
17 "executables", "imports", "tests", "workflow", "skymap", "bilby",
18 "bilby_pipe", "pycbc", "lalinference", "GWTC1", "GWTC2", "GWTC3", "examples"
19]
21PESUMMARY_DIR = Path(pesummary.__file__).parent.parent
24class ArgumentParser(_ArgumentParser):
25 def _pesummary_options(self):
26 options = super(ArgumentParser, self)._pesummary_options()
27 options.update(
28 {
29 "--type": {
30 "short": "-t",
31 "required": True,
32 "type": str,
33 "help": (
34 "The test you wish to run. Available tests are: "
35 "{}".format(", ".join(ALLOWED))
36 )
37 },
38 "--coverage": {
39 "short": "-c",
40 "default": False,
41 "action": "store_true",
42 "help": "Generare a coverage report for the testing suite"
43 },
44 "--mark": {
45 "short": "-m",
46 "default": "",
47 "type": str,
48 "help": "only run tests matching given mark expression"
49 },
50 "--ignore": {
51 "short": "-i",
52 "nargs": "+",
53 "default": [],
54 "help": "Testing scripts you wish to ignore"
55 },
56 "--expression": {
57 "short": "-k",
58 "type": str,
59 "help": (
60 "Run tests which contain names that match the given "
61 "string expression"
62 ),
63 },
64 "--pytest_config": {
65 "help": "Path to configuration file to use with pytest"
66 },
67 "--output": {
68 "default": ".",
69 "help": (
70 "Directory to store the output from the testing scripts"
71 ),
72 "short": "-o",
73 },
74 "--repository": {
75 "short": "-r",
76 "help": "Location of the pesummary repository",
77 "default": os.path.join(".", "pesummary")
78 },
79 }
80 )
81 return options
84def launch(
85 command, check_call=True, err=subprocess.DEVNULL, out=subprocess.DEVNULL
86):
87 """Launch a subprocess and run a command line
89 Parameters
90 ----------
91 command: str
92 command you wish to run
93 """
94 logger.info("Launching subprocess to run: '{}'".format(command))
95 if check_call:
96 return subprocess.check_call(command, shell=True)
97 p = subprocess.Popen(command, shell=True, stdout=out, stderr=err)
98 return p
101def executables(*args, **kwargs):
102 """Test all pesummary executables
103 """
104 command_line = (
105 "bash {}".format(
106 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "executables.sh")
107 )
108 )
109 return launch(command_line)
112def imports(*args, **kwargs):
113 """Test all pesummary imports
114 """
115 command_line = (
116 "bash {}".format(
117 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "imports.sh")
118 )
119 )
120 return launch(command_line)
123def tests(*args, output="./", multi_process=1, **kwargs):
124 """Run the pesummary testing suite
125 """
126 from pesummary.gw.fetch import fetch_open_samples
128 # download files for tests
129 logger.info(f"Downloading files for tests ({download_dir})")
130 download_path = Path(download_dir)
131 download_path.mkdir(parents=True, exist_ok=True)
132 download_and_read_file(
133 "https://dcc.ligo.org/public/0168/P2000183/008/GW190814_posterior_samples.h5",
134 outdir=download_path,
135 read_file=False,
136 download_kwargs=dict(
137 cache=True,
138 pkgname="pesummary",
139 )
140 )
141 download_and_read_file(
142 "https://dcc.ligo.org/public/0163/P190412/012/GW190412_posterior_samples_v3.h5",
143 outdir=download_path,
144 read_file=False,
145 cache=True,
146 pkgname="pesummary",
147 download_kwargs=dict(
148 cache=True,
149 pkgname="pesummary",
150 )
151 )
152 fetch_open_samples(
153 "GW190424_180648",
154 read_file=False,
155 outdir=download_dir,
156 unpack=True,
157 path="GW190424_180648.h5",
158 catalog="GWTC-2",
159 download_kwargs=dict(
160 cache=True,
161 pkgname="pesummary",
162 timeout=60,
163 )
164 )
166 # launch pytest job
167 command_line = (
168 "{} -m pytest --full-trace --verbose "
169 "--reruns 2 --pyargs pesummary.tests ".format(
170 sys.executable,
171 )
172 )
173 if multi_process > 1:
174 command_line += f"-n {multi_process} --max-worker-restart=2 "
175 if kwargs.get("pytest_config", None) is not None:
176 command_line += "-c {} ".format(kwargs.get("pytest_config"))
177 if kwargs.get("coverage", False):
178 command_line += (
179 "--cov=pesummary --cov-report html:{}/htmlcov --cov-report "
180 "term:skip-covered --cov-append ".format(output)
181 )
182 for ignore in kwargs.get("ignore", []):
183 command_line += "--ignore {} ".format(ignore)
184 if len(kwargs.get("mark", "")):
185 command_line += "-m '{}' ".format(kwargs.get("mark"))
186 if kwargs.get("expression", None) is not None:
187 command_line += "-k {} ".format(kwargs.get("expression"))
188 launch(command_line)
189 if kwargs.get("coverage", False):
190 command_line = "coverage-badge -o {} -f".format(
191 os.path.join(output, "coverage_badge.svg")
192 )
193 launch(command_line)
196@tmp_directory
197def workflow(*args, multi_process=1, **kwargs):
198 """Run the pesummary.tests.workflow_test test
199 """
200 command_line = (
201 "{} -m pytest -n {} --max-worker-restart=2 --reruns 2 --pyargs "
202 "pesummary.tests.workflow_test ".format(sys.executable, multi_process)
203 )
204 if kwargs.get("pytest_config", None) is not None:
205 command_line += "-c {} ".format(kwargs.get("pytest_config"))
206 if kwargs.get("expression", None) is not None:
207 command_line += "-k '{}' ".format(kwargs.get("expression"))
208 return launch(command_line)
211def skymap(*args, output="./", **kwargs):
212 """Run the pesummary.tests.ligo_skymap_test
213 """
214 command_line = "{} -m pytest --pyargs pesummary.tests.ligo_skymap_test ".format(
215 sys.executable
216 )
217 if kwargs.get("coverage", False):
218 command_line += (
219 "--cov=pesummary --cov-report html:{}/htmlcov --cov-report "
220 "term:skip-covered --cov-append ".format(output)
221 )
222 return launch(command_line)
225@tmp_directory
226def lalinference(*args, **kwargs):
227 """Test a lalinference run
228 """
229 command_line = "bash {}".format(
230 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "lalinference.sh")
231 )
232 return launch(command_line)
235@tmp_directory
236def bilby(*args, **kwargs):
237 """Test a bilby run
238 """
239 command_line = "bash {}".format(
240 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "bilby.sh")
241 )
242 _ = launch(command_line)
243 command_line = "bash {}".format(
244 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "bilby_mcmc.sh")
245 )
246 return launch(command_line)
249@tmp_directory
250def bilby_pipe(*args, **kwargs):
251 """Test a bilby_pipe run
252 """
253 command_line = "bash {}".format(
254 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "bilby_pipe.sh")
255 )
256 return launch(command_line)
259@tmp_directory
260def pycbc(*args, **kwargs):
261 """Test a pycbc run
262 """
263 command_line = "bash {}".format(
264 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "pycbc.sh")
265 )
266 return launch(command_line)
269def _public_pesummary_result_file(event, catalog=None, unpack=True, **kwargs):
270 """Test that pesummary can load in a previously released pesummary result
271 file
272 """
273 from pesummary.gw.fetch import fetch_open_samples
275 download = fetch_open_samples(
276 event, catalog=catalog, read_file=False, delete_on_exit=False,
277 outdir="./", unpack=unpack, download_kwargs={"timeout": 120}
278 )
279 command_line = "{} {} -f {}.h5".format(
280 sys.executable,
281 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "existing_file.py"),
282 os.path.join(download, download) if unpack else str(download).split(".h5")[0]
283 )
284 return launch(command_line)
287def _grab_event_names_from_gwosc(webpage):
288 """Grab a list of event names from a GWOSC 'Event Portal' web page
290 Parameters
291 ----------
292 webpage: str
293 web page url that you wish to grab data from
294 """
295 from bs4 import BeautifulSoup
296 import requests
297 page = requests.get(webpage)
298 soup = BeautifulSoup(page.content, 'html.parser')
299 entries = soup.find_all("td")
300 events = [
301 e.text.strip().replace(" ", "") for e in entries if "GW" in e.text
302 and "GWTC" not in e.text
303 ]
304 return events
307@tmp_directory
308def GWTCN(
309 *args, catalog=None, size=5, include_exceptional=[], **kwargs
310):
311 """Test that pesummary can load a random selection of samples from the
312 GWTC-2 or GWTC-3 data releases
314 Parameters
315 ----------
316 catalog: str
317 name of the gravitational wave catalog you wish to consider
318 size: int, optional
319 number of events to randomly draw. Default 5
320 include_exceptional: list, optional
321 List of exceptional event candidates to include in the random selection
322 of events. This means that the total number of events could be as
323 large as size + N where N is the length of include_exceptional. Default
324 []
325 """
326 if catalog is None:
327 raise ValueError("Please provide a valid catalog")
328 events = _grab_event_names_from_gwosc(
329 "https://www.gw-openscience.org/eventapi/html/{}/".format(catalog)
330 )
331 specified = np.random.choice(events, replace=False, size=size).tolist()
332 if len(include_exceptional):
333 for event in include_exceptional:
334 if event not in specified:
335 specified.append(event)
336 for event in specified:
337 _ = _public_pesummary_result_file(event, catalog=catalog, **kwargs)
338 return
341@tmp_directory
342def GWTC2(*args, **kwargs):
343 """Test that pesummary can load a random selection of samples from the
344 GWTC-2 data release
346 Parameters
347 ----------
348 size: int, optional
349 number of events to randomly draw. Default 5
350 include_exceptional: list, optional
351 List of exceptional event candidates to include in the random selection
352 of events. This means that the total number of events could be as
353 large as size + N where N is the length of include_exceptional. Default
354 []
355 """
356 return GWTCN(
357 *args, catalog="GWTC-2", unpack=True,
358 include_exceptional=["GW190412", "GW190425", "GW190521", "GW190814"],
359 **kwargs
360 )
363@tmp_directory
364def GWTC3(*args, **kwargs):
365 """Test that pesummary can load a random selection of samples from the
366 GWTC-3 data release
368 Parameters
369 ----------
370 size: int, optional
371 number of events to randomly draw. Default 5
372 include_exceptional: list, optional
373 List of exceptional event candidates to include in the random selection
374 of events. This means that the total number of events could be as
375 large as size + N where N is the length of include_exceptional. Default
376 []
377 """
378 return GWTCN(*args, catalog="GWTC-3-confident", unpack=False, **kwargs)
381@tmp_directory
382def GWTC1(*args, **kwargs):
383 """Test that pesummary works on the GWTC1 data files
384 """
385 command_line = (
386 "curl -O https://dcc.ligo.org/public/0157/P1800370/004/GWTC-1_sample_release.tar.gz"
387 )
388 launch(command_line)
389 command_line = "tar -xf GWTC-1_sample_release.tar.gz"
390 launch(command_line)
391 command_line = "{} {} -f {} -t {}".format(
392 sys.executable,
393 os.path.join(PESUMMARY_DIR, "pesummary", "tests", "existing_file.py"),
394 "GWTC-1_sample_release/GW150914_GWTC-1.hdf5",
395 "pesummary.gw.file.formats.GWTC1.GWTC1"
396 )
397 launch(command_line)
398 command_line = (
399 "summarypages --webdir ./GWTC1 --no_ligo_skymap --samples "
400 "GWTC-1_sample_release/GW150914_GWTC-1.hdf5 "
401 "GWTC-1_sample_release/GW170817_GWTC-1.hdf5 --path_to_samples "
402 "None IMRPhenomPv2NRT_highSpin_posterior --labels GW150914 GW170818 "
403 "--gw"
404 )
405 return launch(command_line)
408@tmp_directory
409def examples(*args, repository=os.path.join(".", "pesummary"), **kwargs):
410 """Test that the examples in the `pesummary` repository work
411 """
412 examples_dir = os.path.join(repository, "examples")
413 gw_examples = os.path.join(examples_dir, "gw")
414 core_examples = os.path.join(examples_dir, "core")
415 shell_scripts = glob.glob(os.path.join(gw_examples, "*.sh"))
416 process = {}
417 for script in shell_scripts:
418 command_line = f"bash {script}"
419 p = launch(command_line, check_call=False)
420 process[command_line] = p
421 python_scripts = glob.glob(os.path.join(gw_examples, "*.py"))
422 python_scripts += [os.path.join(core_examples, "bounded_kdeplot.py")]
423 for script in python_scripts:
424 command_line = f"python {script}"
425 p = launch(command_line, check_call=False)
426 process[command_line] = p
427 failed = []
428 while len(process):
429 _remove = []
430 for key, item in process.items():
431 if item.poll() is not None and item.returncode != 0:
432 failed.append(key)
433 elif item.poll() is not None:
434 logger.info("The following test passed: {}".format(key))
435 _remove.append(key)
436 for key in _remove:
437 process.pop(key)
438 if len(failed):
439 raise ValueError(
440 "The following tests failed: {}".format(", ".join(failed))
441 )
442 return
445def main(args=None):
446 """Top level interface for `summarytest`
447 """
448 parser = ArgumentParser()
449 parser.add_known_options_to_parser(
450 [
451 "--type", "--coverage", "--mark", "--ignore", "--expression",
452 "--pytest_config", "--output", "--repository", "--multi_process"
453 ]
454 )
455 opts, unknown = parser.parse_known_args(args=args)
456 if opts.type not in ALLOWED:
457 raise NotImplementedError(
458 "Invalid test type {}. Please choose one from the following: "
459 "{}".format(opts.type, ", ".join(ALLOWED))
460 )
461 type_mapping = {_type: eval(_type) for _type in ALLOWED}
462 try:
463 type_mapping[opts.type](
464 coverage=opts.coverage, mark=opts.mark, expression=opts.expression,
465 ignore=opts.ignore, pytest_config=opts.pytest_config,
466 output=opts.output, repository=os.path.abspath(opts.repository),
467 multi_process=opts.multi_process
468 )
469 except subprocess.CalledProcessError as e:
470 raise ValueError(
471 "The {} test failed with error {}".format(opts.type, e)
472 )
475if __name__ == "__main__":
476 main()