Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 0840d59

Browse files
committed
Use rustdoc output to create a list of public API
Rather than collecting a list of file names in `libm-test/build.rs`, just use a script to parse rustdoc's JSON output.
1 parent 3785902 commit 0840d59

File tree

6 files changed

+283
-69
lines changed

6 files changed

+283
-69
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ jobs:
9696
run: ./ci/download-musl.sh
9797
shell: bash
9898

99+
- name: Verify API list
100+
if: matrix.os == 'ubuntu-24.04'
101+
run: python3 etc/update-api-list.py --check
102+
99103
# Non-linux tests just use our raw script
100104
- name: Run locally
101105
if: matrix.os != 'ubuntu-24.04' || contains(matrix.target, 'wasm')

crates/libm-test/build.rs

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,8 @@
1-
use std::fmt::Write;
2-
use std::fs;
3-
41
#[path = "../../configure.rs"]
52
mod configure;
63
use configure::Config;
74

85
fn main() {
96
let cfg = Config::from_env();
10-
11-
list_all_tests(&cfg);
12-
137
configure::emit_test_config(&cfg);
148
}
15-
16-
/// Create a list of all source files in an array. This can be used for making sure that
17-
/// all functions are tested or otherwise covered in some way.
18-
// FIXME: it would probably be better to use rustdoc JSON output to get public functions.
19-
fn list_all_tests(cfg: &Config) {
20-
let math_src = cfg.manifest_dir.join("../../src/math");
21-
22-
let mut files = fs::read_dir(math_src)
23-
.unwrap()
24-
.map(|f| f.unwrap().path())
25-
.filter(|entry| entry.is_file())
26-
.map(|f| f.file_stem().unwrap().to_str().unwrap().to_owned())
27-
.collect::<Vec<_>>();
28-
files.sort();
29-
30-
let mut s = "pub const ALL_FUNCTIONS: &[&str] = &[".to_owned();
31-
for f in files {
32-
if f == "mod" {
33-
// skip mod.rs
34-
continue;
35-
}
36-
write!(s, "\"{f}\",").unwrap();
37-
}
38-
write!(s, "];").unwrap();
39-
40-
let outfile = cfg.out_dir.join("all_files.rs");
41-
fs::write(outfile, s).unwrap();
42-
}

crates/libm-test/src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ pub use test_traits::{CheckOutput, GenerateInput, Hex, TupleCall};
2323
/// propagate.
2424
pub type TestResult<T = (), E = anyhow::Error> = Result<T, E>;
2525

26-
// List of all files present in libm's source
27-
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));
28-
2926
/// True if `EMULATED` is set and nonempty. Used to determine how many iterations to run.
3027
pub const fn emulated() -> bool {
3128
match option_env!("EMULATED") {
@@ -34,3 +31,12 @@ pub const fn emulated() -> bool {
3431
Some(_) => true,
3532
}
3633
}
34+
35+
/// True if `CI` is set and nonempty.
36+
pub const fn ci() -> bool {
37+
match option_env!("CI") {
38+
Some(s) if s.is_empty() => false,
39+
None => false,
40+
Some(_) => true,
41+
}
42+
}
Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,60 @@
11
//! Ensure that `for_each_function!` isn't missing any symbols.
22
3-
/// Files in `src/` that do not export a testable symbol.
4-
const ALLOWED_SKIPS: &[&str] = &[
5-
// Not a generic test function
6-
"fenv",
7-
// Nonpublic functions
8-
"expo2",
9-
"k_cos",
10-
"k_cosf",
11-
"k_expo2",
12-
"k_expo2f",
13-
"k_sin",
14-
"k_sinf",
15-
"k_tan",
16-
"k_tanf",
17-
"rem_pio2",
18-
"rem_pio2_large",
19-
"rem_pio2f",
20-
];
3+
use std::collections::HashSet;
4+
use std::env;
5+
use std::path::Path;
6+
use std::process::Command;
217

228
macro_rules! callback {
239
(
2410
fn_name: $name:ident,
25-
extra: [$push_to:ident],
11+
extra: [$set:ident],
2612
) => {
27-
$push_to.push(stringify!($name));
13+
let name = stringify!($name);
14+
let new = $set.insert(name);
15+
assert!(new, "duplicate function `{name}` in `ALL_OPERATIONS`");
2816
};
2917
}
3018

3119
#[test]
3220
fn test_for_each_function_all_included() {
33-
let mut included = Vec::new();
34-
let mut missing = Vec::new();
21+
let all_functions: HashSet<_> = include_str!("../../../etc/function-list.txt")
22+
.lines()
23+
.filter(|line| !line.starts_with("#"))
24+
.collect();
25+
26+
let mut tested = HashSet::new();
3527

3628
libm_macros::for_each_function! {
3729
callback: callback,
38-
extra: [included],
30+
extra: [tested],
3931
};
4032

41-
for f in libm_test::ALL_FUNCTIONS {
42-
if !included.contains(f) && !ALLOWED_SKIPS.contains(f) {
43-
missing.push(f)
44-
}
45-
}
46-
47-
if !missing.is_empty() {
33+
let untested = all_functions.difference(&tested);
34+
if untested.clone().next().is_some() {
4835
panic!(
49-
"missing tests for the following: {missing:#?} \
36+
"missing tests for the following: {untested:#?} \
5037
\nmake sure any new functions are entered in \
51-
`ALL_FUNCTIONS` (in `libm-macros`)."
38+
`ALL_OPERATIONS` (in `libm-macros`)."
5239
);
5340
}
41+
assert_eq!(all_functions, tested);
42+
}
43+
44+
#[test]
45+
fn ensure_list_updated() {
46+
if libm_test::ci() {
47+
// Most CI tests run in Docker where we don't have Python or Rustdoc, so it's easiest
48+
// to just run the python file directly when it is available.
49+
eprintln!("skipping test; CI runs the python file directly");
50+
return;
51+
}
52+
53+
let res = Command::new("python3")
54+
.arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../etc/update-api-list.py"))
55+
.arg("--check")
56+
.status()
57+
.unwrap();
58+
59+
assert!(res.success(), "May need to run `./etc/update-api-list.py`");
5460
}

etc/function-list.txt

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# autogenerated by update-api-list.py
2+
acos
3+
acosf
4+
acosh
5+
acoshf
6+
asin
7+
asinf
8+
asinh
9+
asinhf
10+
atan
11+
atan2
12+
atan2f
13+
atanf
14+
atanh
15+
atanhf
16+
cbrt
17+
cbrtf
18+
ceil
19+
ceilf
20+
copysign
21+
copysignf
22+
cos
23+
cosf
24+
cosh
25+
coshf
26+
erf
27+
erfc
28+
erfcf
29+
erff
30+
exp
31+
exp10
32+
exp10f
33+
exp2
34+
exp2f
35+
expf
36+
expm1
37+
expm1f
38+
fabs
39+
fabsf
40+
fdim
41+
fdimf
42+
floor
43+
floorf
44+
fma
45+
fmaf
46+
fmax
47+
fmaxf
48+
fmin
49+
fminf
50+
fmod
51+
fmodf
52+
frexp
53+
frexpf
54+
hypot
55+
hypotf
56+
ilogb
57+
ilogbf
58+
j0
59+
j0f
60+
j1
61+
j1f
62+
jn
63+
jnf
64+
ldexp
65+
ldexpf
66+
lgamma
67+
lgamma_r
68+
lgammaf
69+
lgammaf_r
70+
log
71+
log10
72+
log10f
73+
log1p
74+
log1pf
75+
log2
76+
log2f
77+
logf
78+
modf
79+
modff
80+
nextafter
81+
nextafterf
82+
pow
83+
powf
84+
remainder
85+
remainderf
86+
remquo
87+
remquof
88+
rint
89+
rintf
90+
round
91+
roundf
92+
scalbn
93+
scalbnf
94+
sin
95+
sincos
96+
sincosf
97+
sinf
98+
sinh
99+
sinhf
100+
sqrt
101+
sqrtf
102+
tan
103+
tanf
104+
tanh
105+
tanhf
106+
tgamma
107+
tgammaf
108+
trunc
109+
truncf
110+
y0
111+
y0f
112+
y1
113+
y1f
114+
yn
115+
ynf

0 commit comments

Comments
 (0)