build(test): benchmark safe concurrency ramps
req: test/004
This commit is contained in:
+1
-1
@@ -597,7 +597,7 @@ what a valid business email is.
|
||||
003 Generated registries are validated by compile-time tests: missing handler implementations produce test failures with actionable messages.
|
||||
|
||||
### req: test/004
|
||||
004 Repository-wide verification uses a resource-aware runner that caps Cargo build jobs and Rust test threads from available CPU and memory. Browser E2E runs as an isolated step and can be skipped explicitly when browser infrastructure is unavailable.
|
||||
004 Repository-wide verification uses a resource-aware runner that caps Cargo build jobs and Rust test threads from available CPU and memory. User-requested concurrency cannot exceed the detected safe cap. Browser E2E runs as an isolated step and can be skipped explicitly when browser infrastructure is unavailable.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+87
-17
@@ -1,11 +1,13 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::time::Instant;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let mut args = env::args().skip(1);
|
||||
match args.next().as_deref() {
|
||||
Some("test") | None => run_test_plan(),
|
||||
Some("bench") => run_bench_plan(),
|
||||
Some("help") | Some("--help") | Some("-h") => {
|
||||
print_help();
|
||||
ExitCode::SUCCESS
|
||||
@@ -20,24 +22,14 @@ fn main() -> ExitCode {
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"slhx-ci — resource-aware project checks\n\n cargo run -p slhx-xtask -- test\n\nEnvironment overrides:\n SLHX_CI_JOBS=N compile jobs\n SLHX_CI_TEST_THREADS=N Rust test threads\n SLHX_CI_SKIP_BROWSER=1 skip browser E2E"
|
||||
"slhx-ci — resource-aware project checks\n\n cargo run -p slhx-xtask -- test\n cargo run -p slhx-xtask -- bench\n\nEnvironment overrides:\n SLHX_CI_JOBS=N compile jobs, capped by detected resources\n SLHX_CI_TEST_THREADS=N Rust test threads, capped by detected resources\n SLHX_CI_SKIP_BROWSER=1 skip browser E2E"
|
||||
);
|
||||
}
|
||||
|
||||
fn run_test_plan() -> ExitCode {
|
||||
// req: test/004
|
||||
let budget = Budget::detect();
|
||||
eprintln!(
|
||||
"slhx-ci: cpus={} mem={}GiB jobs={} test_threads={} browser={}",
|
||||
budget.cpus,
|
||||
budget
|
||||
.mem_gib
|
||||
.map(|mem| mem.to_string())
|
||||
.unwrap_or_else(|| "unknown".into()),
|
||||
budget.jobs,
|
||||
budget.test_threads,
|
||||
if budget.skip_browser { "skip" } else { "run" }
|
||||
);
|
||||
budget.report();
|
||||
|
||||
let mut steps = vec![
|
||||
Step::new(
|
||||
@@ -71,6 +63,40 @@ fn run_test_plan() -> ExitCode {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
fn run_bench_plan() -> ExitCode {
|
||||
// req: test/004
|
||||
let budget = Budget::detect();
|
||||
budget.report();
|
||||
eprintln!(
|
||||
"slhx-ci: benchmarking small safe test slices from 1 to {} job(s)",
|
||||
budget.jobs
|
||||
);
|
||||
|
||||
let steps = [
|
||||
Step::new("bench-xtask", ["test", "-p", "slhx-xtask"]),
|
||||
Step::new("bench-runtime", ["test", "-p", "slhx-js"]),
|
||||
];
|
||||
|
||||
for jobs in bench_values(budget.jobs) {
|
||||
let bench_budget = budget.with_jobs(jobs);
|
||||
for step in &steps {
|
||||
let started = Instant::now();
|
||||
if let Err(code) = step.run(&bench_budget) {
|
||||
return code;
|
||||
}
|
||||
println!(
|
||||
"bench\t{}\tjobs={}\ttest_threads={}\telapsed_ms={}",
|
||||
step.name,
|
||||
bench_budget.jobs,
|
||||
bench_budget.test_threads,
|
||||
started.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Budget {
|
||||
cpus: usize,
|
||||
@@ -108,8 +134,11 @@ impl Budget {
|
||||
let cpus = cpus.max(1);
|
||||
let mem_jobs = mem_gib.map_or(cpus, |gib| (gib / 2).max(1));
|
||||
let auto_jobs = cpus.min(mem_jobs).clamp(1, 6);
|
||||
let jobs = jobs_override.unwrap_or(auto_jobs).max(1);
|
||||
let test_threads = test_threads_override.unwrap_or(jobs.min(4)).max(1);
|
||||
let jobs = jobs_override.unwrap_or(auto_jobs).clamp(1, auto_jobs);
|
||||
let max_test_threads = jobs.min(4);
|
||||
let test_threads = test_threads_override
|
||||
.unwrap_or(max_test_threads)
|
||||
.clamp(1, max_test_threads);
|
||||
Self {
|
||||
cpus,
|
||||
mem_gib,
|
||||
@@ -118,6 +147,41 @@ impl Budget {
|
||||
skip_browser,
|
||||
}
|
||||
}
|
||||
|
||||
fn report(&self) {
|
||||
eprintln!(
|
||||
"slhx-ci: cpus={} mem={}GiB jobs={} test_threads={} browser={}",
|
||||
self.cpus,
|
||||
self.mem_gib
|
||||
.map(|mem| mem.to_string())
|
||||
.unwrap_or_else(|| "unknown".into()),
|
||||
self.jobs,
|
||||
self.test_threads,
|
||||
if self.skip_browser { "skip" } else { "run" }
|
||||
);
|
||||
}
|
||||
|
||||
fn with_jobs(self, jobs: usize) -> Self {
|
||||
let jobs = jobs.clamp(1, self.jobs);
|
||||
Self {
|
||||
jobs,
|
||||
test_threads: self.test_threads.min(jobs.min(4)).max(1),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_values(limit: usize) -> Vec<usize> {
|
||||
let limit = limit.max(1);
|
||||
let mut values = Vec::new();
|
||||
let mut value = 1;
|
||||
while value < limit {
|
||||
values.push(value);
|
||||
value *= 2;
|
||||
}
|
||||
values.push(limit);
|
||||
values.dedup();
|
||||
values
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -269,10 +333,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn budget_allows_explicit_overrides() {
|
||||
fn budget_clamps_explicit_overrides_to_resource_budget() {
|
||||
let budget = Budget::from_resources(2, Some(2), Some(8), Some(7), true);
|
||||
|
||||
assert_eq!(budget.jobs, 8);
|
||||
assert_eq!(budget.test_threads, 7);
|
||||
assert_eq!(budget.jobs, 1);
|
||||
assert_eq!(budget.test_threads, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bench_values_grow_gradually_and_include_limit() {
|
||||
assert_eq!(super::bench_values(1), vec![1]);
|
||||
assert_eq!(super::bench_values(6), vec![1, 2, 4, 6]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user