#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use common::{kernel_at, run};
use std::collections::HashSet;
use tempfile::tempdir;
#[tokio::test]
async fn stays_in_bounds_and_varies() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let mut seen: HashSet<i64> = HashSet::new();
for _ in 0..200 {
let (out, code) = run(&kernel, "random --max 6").await;
assert_eq!(code, 0, "random --max 6 should succeed: {out:?}");
let n: i64 = out.parse().expect("random --max 6 prints an integer");
assert!((0..=6).contains(&n), "{n} outside 0..=6");
seen.insert(n);
}
assert!(
seen.len() >= 2,
"200 draws of random --max 6 produced only {:?} — looks non-random",
seen
);
}
#[tokio::test]
async fn min_equals_max_prints_that_value() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "random --min 5 --max 5").await;
assert_eq!(code, 0);
assert_eq!(out, "5");
}
#[tokio::test]
async fn negative_range_stays_in_range() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
for _ in 0..50 {
let (out, code) = run(&kernel, "random --min -5 --max 5").await;
assert_eq!(code, 0);
let n: i64 = out.parse().expect("integer output");
assert!((-5..=5).contains(&n), "{n} outside -5..=5");
}
}
#[tokio::test]
async fn command_substitution_is_typed_as_number() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "typeof $(random)").await;
assert_eq!(code, 0, "typeof $(random) should succeed: {out:?}");
assert_eq!(out, "number");
}
#[tokio::test]
async fn captured_value_is_arithmetic_ready() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "x=$(random --min 3 --max 3); echo $((x * 2))").await;
assert_eq!(code, 0, "arithmetic on captured random should succeed: {out:?}");
assert_eq!(out, "6");
}
#[tokio::test]
async fn min_greater_than_max_is_an_error() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let result = kernel.execute("random --min 10 --max 5").await.unwrap();
assert_eq!(result.code, 2, "got: {:?}", result.err);
assert!(
result.err.contains("--min 10 is greater than --max 5"),
"error should name both bounds: {:?}",
result.err
);
}
#[tokio::test]
async fn positional_argument_is_an_error() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let result = kernel.execute("random 100").await.unwrap();
assert_eq!(result.code, 2, "got: {:?}", result.err);
assert!(
result.err.contains("--max 100"),
"error should name the fix: {:?}",
result.err
);
}
#[tokio::test]
async fn non_integer_bound_is_an_error() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let result = kernel.execute("random --max abc").await.unwrap();
assert_eq!(result.code, 2, "got: {:?}", result.err);
}
#[tokio::test]
async fn json_output_is_a_number() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let result = kernel.execute("random --max 6 --json").await.unwrap();
assert!(result.ok(), "random --json should succeed: {:?}", result);
let parsed: serde_json::Value =
serde_json::from_str(result.text_out().trim()).expect("random --json is JSON");
assert!(parsed.is_number(), "random --json should be a JSON number: {parsed}");
}
#[tokio::test]
async fn full_i64_span_succeeds() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(
&kernel,
"random --min -9223372036854775808 --max 9223372036854775807",
)
.await;
assert_eq!(code, 0, "full i64 span should succeed: {out:?}");
let n: i64 = out.parse().expect("integer output");
assert!((i64::MIN..=i64::MAX).contains(&n));
}