Skip to content

Commit 067e97c

Browse files
authored
Merge pull request rust-lang#388 from rust-lang/tgross35/extensive
Add exhaustive/extensive tests
2 parents 9080785 + 9b08ee5 commit 067e97c

File tree

12 files changed

+677
-10
lines changed

12 files changed

+677
-10
lines changed

.github/workflows/main.yml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ env:
1414
jobs:
1515
test:
1616
name: Build and test
17-
timeout-minutes: 20
17+
timeout-minutes: 25
1818
strategy:
1919
fail-fast: false
2020
matrix:
@@ -186,13 +186,70 @@ jobs:
186186
rustup component add rustfmt
187187
- run: cargo fmt -- --check
188188

189+
# Determine which extensive tests should be run based on changed files.
190+
calculate_extensive_matrix:
191+
name: Calculate job matrix
192+
runs-on: ubuntu-24.04
193+
outputs:
194+
matrix: ${{ steps.script.outputs.matrix }}
195+
steps:
196+
- uses: actions/checkout@v4
197+
with:
198+
fetch-depth: 100
199+
- name: Fetch pull request ref
200+
run: git fetch origin "$GITHUB_REF:$GITHUB_REF"
201+
- run: python3 ci/calculate-exhaustive-matrix.py >> "$GITHUB_OUTPUT"
202+
id: script
203+
204+
extensive:
205+
name: Extensive tests for ${{ matrix.ty }}
206+
needs:
207+
# Wait on `clippy` so we have some confidence that the crate will build
208+
- clippy
209+
- calculate_extensive_matrix
210+
runs-on: ubuntu-24.04
211+
timeout-minutes: 80
212+
strategy:
213+
matrix:
214+
# Use the output from `calculate_extensive_matrix` to calculate the matrix
215+
# FIXME: it would be better to run all jobs (i.e. all types) but mark those that
216+
# didn't change as skipped, rather than completely excluding the job. However,
217+
# this is not currently possible https://github.com/actions/runner/issues/1985.
218+
include: ${{ fromJSON(needs.calculate_extensive_matrix.outputs.matrix).matrix }}
219+
env:
220+
CHANGED: ${{ matrix.changed }}
221+
steps:
222+
- uses: actions/checkout@v4
223+
- name: Install Rust
224+
run: |
225+
rustup update nightly --no-self-update
226+
rustup default nightly
227+
- uses: Swatinem/rust-cache@v2
228+
- name: Download musl source
229+
run: ./ci/download-musl.sh
230+
- name: Run extensive tests
231+
run: |
232+
echo "Changed: '$CHANGED'"
233+
if [ -z "$CHANGED" ]; then
234+
echo "No tests to run, exiting."
235+
exit
236+
fi
237+
238+
LIBM_EXTENSIVE_TESTS="$CHANGED" cargo t \
239+
--features test-multiprecision,unstable \
240+
--release -- extensive
241+
- name: Print test logs if available
242+
run: if [ -f "target/test-log.txt" ]; then cat target/test-log.txt; fi
243+
shell: bash
244+
189245
success:
190246
needs:
191247
- test
192248
- builtins
193249
- benchmarks
194250
- msrv
195251
- rustfmt
252+
- extensive
196253
runs-on: ubuntu-24.04
197254
# GitHub branch protection is exceedingly silly and treats "jobs skipped because a dependency
198255
# failed" as success. So we have to do some contortions to ensure the job fails if any of its

ci/calculate-exhaustive-matrix.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3
2+
"""Calculate which exhaustive tests should be run as part of CI.
3+
4+
This dynamically prepares a list of routines that had a source file change based on
5+
git history.
6+
"""
7+
8+
import subprocess as sp
9+
import sys
10+
import json
11+
from dataclasses import dataclass
12+
from os import getenv
13+
from pathlib import Path
14+
from typing import TypedDict
15+
16+
17+
REPO_ROOT = Path(__file__).parent.parent
18+
GIT = ["git", "-C", REPO_ROOT]
19+
20+
# Don't run exhaustive tests if these files change, even if they contaiin a function
21+
# definition.
22+
IGNORE_FILES = [
23+
"src/math/support/",
24+
"src/libm_helper.rs",
25+
"src/math/arch/intrinsics.rs",
26+
]
27+
28+
TYPES = ["f16", "f32", "f64", "f128"]
29+
30+
31+
class FunctionDef(TypedDict):
32+
"""Type for an entry in `function-definitions.json`"""
33+
34+
sources: list[str]
35+
type: str
36+
37+
38+
@dataclass
39+
class Context:
40+
gh_ref: str | None
41+
changed: list[Path]
42+
defs: dict[str, FunctionDef]
43+
44+
def __init__(self) -> None:
45+
self.gh_ref = getenv("GITHUB_REF")
46+
self.changed = []
47+
self._init_change_list()
48+
49+
with open(REPO_ROOT.joinpath("etc/function-definitions.json")) as f:
50+
defs = json.load(f)
51+
52+
defs.pop("__comment", None)
53+
self.defs = defs
54+
55+
def _init_change_list(self):
56+
"""Create a list of files that have been changed. This uses GITHUB_REF if
57+
available, otherwise a diff between `HEAD` and `master`.
58+
"""
59+
60+
# For pull requests, GitHub creates a ref `refs/pull/1234/merge` (1234 being
61+
# the PR number), and sets this as `GITHUB_REF`.
62+
ref = self.gh_ref
63+
eprint(f"using ref `{ref}`")
64+
if ref is None or "merge" not in ref:
65+
# If the ref is not for `merge` then we are not in PR CI
66+
eprint("No diff available for ref")
67+
return
68+
69+
# The ref is for a dummy merge commit. We can extract the merge base by
70+
# inspecting all parents (`^@`).
71+
merge_sha = sp.check_output(
72+
GIT + ["show-ref", "--hash", ref], text=True
73+
).strip()
74+
merge_log = sp.check_output(GIT + ["log", "-1", merge_sha], text=True)
75+
eprint(f"Merge:\n{merge_log}\n")
76+
77+
parents = (
78+
sp.check_output(GIT + ["rev-parse", f"{merge_sha}^@"], text=True)
79+
.strip()
80+
.splitlines()
81+
)
82+
assert len(parents) == 2, f"expected two-parent merge but got:\n{parents}"
83+
base = parents[0].strip()
84+
incoming = parents[1].strip()
85+
86+
eprint(f"base: {base}, incoming: {incoming}")
87+
textlist = sp.check_output(
88+
GIT + ["diff", base, incoming, "--name-only"], text=True
89+
)
90+
self.changed = [Path(p) for p in textlist.splitlines()]
91+
92+
@staticmethod
93+
def _ignore_file(fname: str) -> bool:
94+
return any(fname.startswith(pfx) for pfx in IGNORE_FILES)
95+
96+
def changed_routines(self) -> dict[str, list[str]]:
97+
"""Create a list of routines for which one or more files have been updated,
98+
separated by type.
99+
"""
100+
routines = set()
101+
for name, meta in self.defs.items():
102+
# Don't update if changes to the file should be ignored
103+
sources = (f for f in meta["sources"] if not self._ignore_file(f))
104+
105+
# Select changed files
106+
changed = [f for f in sources if Path(f) in self.changed]
107+
108+
if len(changed) > 0:
109+
eprint(f"changed files for {name}: {changed}")
110+
routines.add(name)
111+
112+
ret = {}
113+
for r in sorted(routines):
114+
ret.setdefault(self.defs[r]["type"], []).append(r)
115+
116+
return ret
117+
118+
def make_workflow_output(self) -> str:
119+
"""Create a JSON object a list items for each type's changed files, if any
120+
did change, and the routines that were affected by the change.
121+
"""
122+
changed = self.changed_routines()
123+
ret = []
124+
for ty in TYPES:
125+
ty_changed = changed.get(ty, [])
126+
item = {
127+
"ty": ty,
128+
"changed": ",".join(ty_changed),
129+
}
130+
ret.append(item)
131+
output = json.dumps({"matrix": ret}, separators=(",", ":"))
132+
eprint(f"output: {output}")
133+
return output
134+
135+
136+
def eprint(*args, **kwargs):
137+
"""Print to stderr."""
138+
print(*args, file=sys.stderr, **kwargs)
139+
140+
141+
def main():
142+
ctx = Context()
143+
output = ctx.make_workflow_output()
144+
print(f"matrix={output}")
145+
146+
147+
if __name__ == "__main__":
148+
main()

crates/libm-test/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ short-benchmarks = []
2626
[dependencies]
2727
anyhow = "1.0.90"
2828
az = { version = "1.2.1", optional = true }
29+
indicatif = { version = "0.17.9", default-features = false }
2930
libm = { path = "../..", features = ["unstable-public-internals"] }
3031
libm-macros = { path = "../libm-macros" }
3132
musl-math-sys = { path = "../musl-math-sys", optional = true }
3233
paste = "1.0.15"
3334
rand = "0.8.5"
3435
rand_chacha = "0.3.1"
36+
rayon = "1.10.0"
3537
rug = { version = "1.26.1", optional = true, default-features = false, features = ["float", "std"] }
3638

3739
[target.'cfg(target_family = "wasm")'.dependencies]
@@ -43,11 +45,18 @@ rand = { version = "0.8.5", optional = true }
4345

4446
[dev-dependencies]
4547
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
48+
libtest-mimic = "0.8.1"
4649

4750
[[bench]]
4851
name = "random"
4952
harness = false
5053

54+
[[test]]
55+
# No harness so that we can skip tests at runtime based on env. Prefixed with
56+
# `z` so these tests get run last.
57+
name = "z_extensive"
58+
harness = false
59+
5160
[lints.rust]
5261
# Values from the chared config.rs used by `libm` but not the test crate
5362
unexpected_cfgs = { level = "warn", check-cfg = [

crates/libm-test/src/gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod domain_logspace;
44
pub mod edge_cases;
5+
pub mod extensive;
56
pub mod random;
67

78
/// A wrapper to turn any iterator into an `ExactSizeIterator`. Asserts the final result to ensure

0 commit comments

Comments
 (0)