-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CI: Run unit tests against installed wheels.
Instead of running the unit tests against an “editable install” of the PDR git repo, build an sdist and wheel from the git repo, then, in separate jobs that don’t ever check out the git repo, install the wheel and run the tests from the sdist against the installed wheel. This has several advantages. Most importantly, it verifies that we generate *correct* binary packages, as we were not doing until the previous commit. Testing an editable install is also subtly different in terms of things like sys.path than testing something that’s been officially installed to site-packages.[^1] Finally, this puts us in a better position to create *conda* packages (as a separate project). It would be possible to add further automation that uploads the sdist and wheel to PyPI if the commit has a release tag. The mechanism for tweaking .coveragerc for CI’s needs has also been overhauled, using a helper Python script that parses .coveragerc exactly the same way coverage.py itself does (i.e. using configparser). This should be much more reliable, both because we no longer need to make assumptions about what goes where in the file, and because the Python script can also use the coverage.py API to compute the path- remapping configuration (yes, coverage.py ought to do this itself, filed nedbat/coveragepy#1840 ). And it means we can strip all the junk that’s in .coveragerc solely for CI’s needs back out. [^1]: https://setuptools.pypa.io/en/latest/userguide/development_mode.html#limitations
- Loading branch information
Showing
4 changed files
with
513 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
#! /usr/bin/env python3 | ||
|
||
""" | ||
Read a .coveragerc from stdin, adjust it for use in a CI build, and | ||
write it back out to stdout. | ||
If files are listed on the command line, they are assumed to be | ||
coverage databases, and a [paths] section is added to the .coveragerc | ||
(replacing any existing [paths] section) that instructs coverage.py | ||
to treat the common path prefix of each coverage database's files | ||
as equivalent. When used this way, coverage.py must be importable. | ||
""" | ||
|
||
import sys | ||
|
||
from argparse import ArgumentParser | ||
from configparser import ConfigParser | ||
from pathlib import Path | ||
|
||
|
||
DATABASE_NAME = "coverage.dat" | ||
|
||
|
||
def remap_paths_for_databases(cfg, databases): | ||
from coverage import CoverageData | ||
from collections import defaultdict | ||
from os.path import commonprefix | ||
from pathlib import PurePosixPath, PureWindowsPath | ||
|
||
prefixes = set() | ||
for db_fname in databases: | ||
db = CoverageData(basename=db_fname) | ||
db.read() | ||
prefixes.add(commonprefix(list(db.measured_files()))) | ||
|
||
packages = defaultdict(set) | ||
for p in prefixes: | ||
if '\\' in p or (len(p) >= 2 and p[0].isalpha() and p[1] == ':'): | ||
name = PureWindowsPath(p).name | ||
else: | ||
name = PurePosixPath(p).name | ||
packages[name].add(p) | ||
|
||
pkg_names = sorted(packages.keys()) | ||
|
||
cfg["run"]["relative_files"] = "true" | ||
cfg["run"]["source_pkgs"] = " ".join(pkg_names) | ||
|
||
cfg["paths"] = {} | ||
for pkg in pkg_names: | ||
pkg_paths = ['', pkg + '/'] | ||
pkg_paths.extend(sorted(packages[pkg])) | ||
cfg["paths"]["src_" + pkg] = "\n".join(pkg_paths) | ||
|
||
|
||
def adjust_omit(cfg): | ||
""" | ||
Adjust the "omit" setting to be more appropriate for use in CI; | ||
the stock .coveragerc is tailored for interactive use. | ||
""" | ||
GLOBS_TO_DROP = ( | ||
"*/formats/*", | ||
"*/pvl_utils.py", | ||
) | ||
|
||
run_section = cfg["run"] | ||
pruned_omit_globs = [] | ||
for glob in run_section.get("omit", "").splitlines(): | ||
glob = glob.strip() | ||
if glob not in GLOBS_TO_DROP: | ||
pruned_omit_globs.append(glob) | ||
|
||
if ( | ||
len(pruned_omit_globs) == 0 | ||
or len(pruned_omit_globs) == 1 and pruned_omit_globs[0] == "" | ||
): | ||
del run_section["omit"] | ||
else: | ||
run_section["omit"] = "\n".join(pruned_omit_globs) | ||
|
||
|
||
def change_database_name(cfg): | ||
""" | ||
Give the coverage database a more convenient name for use in | ||
cross-platform CI. | ||
""" | ||
cfg["run"]["data_file"] = str(Path.cwd() / DATABASE_NAME) | ||
|
||
|
||
def main(): | ||
ap = ArgumentParser(description=__doc__) | ||
ap.add_argument("databases", nargs="*", | ||
help="Coverage databases to be merged") | ||
args = ap.parse_args() | ||
|
||
# this must match how coverage.py initializes ConfigParser | ||
cfg = ConfigParser(interpolation=None) | ||
|
||
with sys.stdin as ifp: | ||
cfg.read_file(ifp, source="<stdin>") | ||
|
||
if args.databases: | ||
remap_paths_for_databases(cfg, args.databases) | ||
|
||
adjust_omit(cfg) | ||
change_database_name(cfg) | ||
|
||
with sys.stdout as ofp: | ||
cfg.write(ofp) | ||
|
||
|
||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#! /usr/bin/env python3 | ||
|
||
""" | ||
Find GNU tar, whose pathname transformation options we need, and which | ||
is named 'tar' on Github's Linux and Windows CI runners but 'gtar' on | ||
their MacOS runners. | ||
""" | ||
|
||
import os | ||
import stat | ||
import sys | ||
|
||
from argparse import ArgumentParser | ||
from pathlib import Path | ||
|
||
|
||
if os.name == "nt": | ||
EXE_SUFFIX = ".exe" | ||
def is_executable_mode(mode): | ||
return True | ||
else: | ||
EXE_SUFFIX = "" | ||
def is_executable_mode(mode): | ||
return (stat.S_IMODE(mode) & 0o111) != 0 | ||
|
||
|
||
def is_executable_file(path, debug): | ||
if debug: | ||
sys.stderr.write(f" {path}: ") | ||
try: | ||
st = os.stat(path) | ||
except FileNotFoundError: | ||
if debug: | ||
sys.stderr.write("not found\n") | ||
return False | ||
|
||
if not stat.S_ISREG(st.st_mode): | ||
if debug: | ||
sys.stderr.write("not a regular file (mode={})\n" | ||
.format(stat.filemode(st.st_mode))) | ||
return False | ||
|
||
if not is_executable_mode(st.st_mode): | ||
if debug: | ||
sys.stderr.write("not executable (mode={}, os={})\n" | ||
.format(stat.filemode(st.st_mode, os.name))) | ||
return False | ||
|
||
if debug: | ||
sys.stderr.write(" ok\n") | ||
return True | ||
|
||
|
||
|
||
def find_gnu_tar(debug=False): | ||
GTAR_CMD = "gtar" + EXE_SUFFIX | ||
TAR_CMD = "tar" + EXE_SUFFIX | ||
candidate = None | ||
for d in os.get_exec_path(): | ||
# Resolve symlinks in the directory components of the path, | ||
# but *not* the command name, because changing the command | ||
# name might alter the behavior of the command. | ||
p = Path(d).resolve() | ||
if debug: | ||
sys.stderr.write(f"checking {p}\n") | ||
gtar = p / GTAR_CMD | ||
tar = p / TAR_CMD | ||
if is_executable_file(gtar, debug): | ||
# gtar is preferred | ||
return gtar | ||
if is_executable_file(tar, debug): | ||
# use tar only if we don't find a gtar later in the path | ||
candidate = tar | ||
if candidate is not None: | ||
return candidate | ||
sys.stderr.write(f"neither {GTAR_CMD} nor {TAR_CMD} found in PATH\n") | ||
sys.exit(1) | ||
|
||
|
||
def main(): | ||
ap = ArgumentParser(description=__doc__) | ||
ap.add_argument("--debug", action="store_true", | ||
help="Print debugging information during the search") | ||
args = ap.parse_args() | ||
|
||
sys.stdout.write(str(find_gnu_tar(args.debug)) + "\n") | ||
sys.exit(0) | ||
|
||
|
||
main() |
Oops, something went wrong.