Coverage for pesummary/cli/summarytest.py: 0.0%

173 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-02 08:42 +0000

1# Licensed under an MIT style license -- see LICENSE.md 

2 

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 

14 

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] 

20 

21PESUMMARY_DIR = Path(pesummary.__file__).parent.parent 

22 

23 

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 

82 

83 

84def launch( 

85 command, check_call=True, err=subprocess.DEVNULL, out=subprocess.DEVNULL 

86): 

87 """Launch a subprocess and run a command line 

88 

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 

99 

100 

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) 

110 

111 

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) 

121 

122 

123def tests(*args, output="./", multi_process=1, **kwargs): 

124 """Run the pesummary testing suite 

125 """ 

126 from pesummary.gw.fetch import fetch_open_samples 

127 

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 ) 

165 

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) 

194 

195 

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) 

209 

210 

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) 

223 

224 

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) 

233 

234 

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) 

247 

248 

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) 

257 

258 

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) 

267 

268 

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 

274 

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) 

285 

286 

287def _grab_event_names_from_gwosc(webpage): 

288 """Grab a list of event names from a GWOSC 'Event Portal' web page 

289 

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 

305 

306 

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 

313 

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 

339 

340 

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 

345 

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 ) 

361 

362 

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 

367 

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) 

379 

380 

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) 

406 

407 

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 

443 

444 

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 ) 

473 

474 

475if __name__ == "__main__": 

476 main()