summaryrefslogtreecommitdiffstats
path: root/library/test/src/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'library/test/src/helpers')
-rw-r--r--library/test/src/helpers/concurrency.rs14
-rw-r--r--library/test/src/helpers/exit_code.rs20
-rw-r--r--library/test/src/helpers/isatty.rs32
-rw-r--r--library/test/src/helpers/metrics.rs50
-rw-r--r--library/test/src/helpers/mod.rs8
-rw-r--r--library/test/src/helpers/shuffle.rs67
6 files changed, 191 insertions, 0 deletions
diff --git a/library/test/src/helpers/concurrency.rs b/library/test/src/helpers/concurrency.rs
new file mode 100644
index 000000000..eb2111573
--- /dev/null
+++ b/library/test/src/helpers/concurrency.rs
@@ -0,0 +1,14 @@
+//! Helper module which helps to determine amount of threads to be used
+//! during tests execution.
+use std::{env, num::NonZeroUsize, thread};
+
+pub fn get_concurrency() -> usize {
+ if let Ok(value) = env::var("RUST_TEST_THREADS") {
+ match value.parse::<NonZeroUsize>().ok() {
+ Some(n) => n.get(),
+ _ => panic!("RUST_TEST_THREADS is `{value}`, should be a positive integer."),
+ }
+ } else {
+ thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
+ }
+}
diff --git a/library/test/src/helpers/exit_code.rs b/library/test/src/helpers/exit_code.rs
new file mode 100644
index 000000000..f762f8881
--- /dev/null
+++ b/library/test/src/helpers/exit_code.rs
@@ -0,0 +1,20 @@
+//! Helper module to detect subprocess exit code.
+
+use std::process::ExitStatus;
+
+#[cfg(not(unix))]
+pub fn get_exit_code(status: ExitStatus) -> Result<i32, String> {
+ status.code().ok_or_else(|| "received no exit code from child process".into())
+}
+
+#[cfg(unix)]
+pub fn get_exit_code(status: ExitStatus) -> Result<i32, String> {
+ use std::os::unix::process::ExitStatusExt;
+ match status.code() {
+ Some(code) => Ok(code),
+ None => match status.signal() {
+ Some(signal) => Err(format!("child process exited with signal {signal}")),
+ None => Err("child process exited with unknown signal".into()),
+ },
+ }
+}
diff --git a/library/test/src/helpers/isatty.rs b/library/test/src/helpers/isatty.rs
new file mode 100644
index 000000000..874ecc376
--- /dev/null
+++ b/library/test/src/helpers/isatty.rs
@@ -0,0 +1,32 @@
+//! Helper module which provides a function to test
+//! if stdout is a tty.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ pub fn stdout_isatty() -> bool {
+ unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
+ }
+ } else if #[cfg(windows)] {
+ pub fn stdout_isatty() -> bool {
+ type DWORD = u32;
+ type BOOL = i32;
+ type HANDLE = *mut u8;
+ type LPDWORD = *mut u32;
+ const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD;
+ extern "system" {
+ fn GetStdHandle(which: DWORD) -> HANDLE;
+ fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL;
+ }
+ unsafe {
+ let handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ let mut out = 0;
+ GetConsoleMode(handle, &mut out) != 0
+ }
+ }
+ } else {
+ // FIXME: Implement isatty on SGX
+ pub fn stdout_isatty() -> bool {
+ false
+ }
+ }
+}
diff --git a/library/test/src/helpers/metrics.rs b/library/test/src/helpers/metrics.rs
new file mode 100644
index 000000000..f77a23e68
--- /dev/null
+++ b/library/test/src/helpers/metrics.rs
@@ -0,0 +1,50 @@
+//! Benchmark metrics.
+use std::collections::BTreeMap;
+
+#[derive(Clone, PartialEq, Debug, Copy)]
+pub struct Metric {
+ value: f64,
+ noise: f64,
+}
+
+impl Metric {
+ pub fn new(value: f64, noise: f64) -> Metric {
+ Metric { value, noise }
+ }
+}
+
+#[derive(Clone, PartialEq)]
+pub struct MetricMap(BTreeMap<String, Metric>);
+
+impl MetricMap {
+ pub fn new() -> MetricMap {
+ MetricMap(BTreeMap::new())
+ }
+
+ /// Insert a named `value` (+/- `noise`) metric into the map. The value
+ /// must be non-negative. The `noise` indicates the uncertainty of the
+ /// metric, which doubles as the "noise range" of acceptable
+ /// pairwise-regressions on this named value, when comparing from one
+ /// metric to the next using `compare_to_old`.
+ ///
+ /// If `noise` is positive, then it means this metric is of a value
+ /// you want to see grow smaller, so a change larger than `noise` in the
+ /// positive direction represents a regression.
+ ///
+ /// If `noise` is negative, then it means this metric is of a value
+ /// you want to see grow larger, so a change larger than `noise` in the
+ /// negative direction represents a regression.
+ pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) {
+ let m = Metric { value, noise };
+ self.0.insert(name.to_owned(), m);
+ }
+
+ pub fn fmt_metrics(&self) -> String {
+ let v = self
+ .0
+ .iter()
+ .map(|(k, v)| format!("{}: {} (+/- {})", *k, v.value, v.noise))
+ .collect::<Vec<_>>();
+ v.join(", ")
+ }
+}
diff --git a/library/test/src/helpers/mod.rs b/library/test/src/helpers/mod.rs
new file mode 100644
index 000000000..049cadf86
--- /dev/null
+++ b/library/test/src/helpers/mod.rs
@@ -0,0 +1,8 @@
+//! Module with common helpers not directly related to tests
+//! but used in `libtest`.
+
+pub mod concurrency;
+pub mod exit_code;
+pub mod isatty;
+pub mod metrics;
+pub mod shuffle;
diff --git a/library/test/src/helpers/shuffle.rs b/library/test/src/helpers/shuffle.rs
new file mode 100644
index 000000000..ca503106c
--- /dev/null
+++ b/library/test/src/helpers/shuffle.rs
@@ -0,0 +1,67 @@
+use crate::cli::TestOpts;
+use crate::types::{TestDescAndFn, TestId, TestName};
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+pub fn get_shuffle_seed(opts: &TestOpts) -> Option<u64> {
+ opts.shuffle_seed.or_else(|| {
+ if opts.shuffle {
+ Some(
+ SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("Failed to get system time")
+ .as_nanos() as u64,
+ )
+ } else {
+ None
+ }
+ })
+}
+
+pub fn shuffle_tests(shuffle_seed: u64, tests: &mut [(TestId, TestDescAndFn)]) {
+ let test_names: Vec<&TestName> = tests.iter().map(|test| &test.1.desc.name).collect();
+ let test_names_hash = calculate_hash(&test_names);
+ let mut rng = Rng::new(shuffle_seed, test_names_hash);
+ shuffle(&mut rng, tests);
+}
+
+// `shuffle` is from `rust-analyzer/src/cli/analysis_stats.rs`.
+fn shuffle<T>(rng: &mut Rng, slice: &mut [T]) {
+ for i in 0..slice.len() {
+ randomize_first(rng, &mut slice[i..]);
+ }
+
+ fn randomize_first<T>(rng: &mut Rng, slice: &mut [T]) {
+ assert!(!slice.is_empty());
+ let idx = rng.rand_range(0..slice.len() as u64) as usize;
+ slice.swap(0, idx);
+ }
+}
+
+struct Rng {
+ state: u64,
+ extra: u64,
+}
+
+impl Rng {
+ fn new(seed: u64, extra: u64) -> Self {
+ Self { state: seed, extra }
+ }
+
+ fn rand_range(&mut self, range: core::ops::Range<u64>) -> u64 {
+ self.rand_u64() % (range.end - range.start) + range.start
+ }
+
+ fn rand_u64(&mut self) -> u64 {
+ self.state = calculate_hash(&(self.state, self.extra));
+ self.state
+ }
+}
+
+// `calculate_hash` is from `core/src/hash/mod.rs`.
+fn calculate_hash<T: core::hash::Hash>(t: &T) -> u64 {
+ let mut s = DefaultHasher::new();
+ t.hash(&mut s);
+ s.finish()
+}