#![allow(clippy::unwrap_used, clippy::expect_used)]
use kaish_kernel::{Kernel, KernelConfig};
fn kernel() -> Kernel {
Kernel::new(KernelConfig::transient().with_skip_validation(true)).expect("kernel")
}
async fn refused(script: &str) -> String {
let k = kernel();
let r = k.execute(script).await.expect("execute");
assert_ne!(r.code, 0, "{script:?} must refuse, got out={:?}", r.text_out());
assert!(
r.text_out().is_empty(),
"{script:?} must print nothing when it refuses, got {:?}",
r.text_out()
);
r.err
}
async fn ok(script: &str) -> String {
let k = kernel();
let r = k.execute(script).await.expect("execute");
assert_eq!(r.code, 0, "{script:?} must succeed: {}", r.err);
r.text_out().to_string()
}
#[tokio::test]
async fn hex_spelling_names_the_base_reader_and_the_decimal() {
let msg = refused("printf '%d' 0xff").await;
assert!(msg.contains("0xff"), "must quote the value: {msg}");
assert!(msg.contains("$(( 0xff ))"), "must name the base reader: {msg}");
assert!(msg.contains("255"), "must name the decimal: {msg}");
}
#[tokio::test]
async fn a_leading_zero_names_octal_and_decimal() {
let msg = refused("printf '%d' 007").await;
assert!(msg.contains("007"), "must quote the value: {msg}");
assert!(msg.contains("8#7"), "must name the octal spelling: {msg}");
assert!(msg.contains("`7`"), "must name the decimal spelling: {msg}");
}
#[tokio::test]
async fn a_negative_leading_zero_keeps_its_sign_in_the_fix() {
let msg = refused("printf '%d' -- -007").await;
assert!(msg.contains("-8#7"), "octal fix must keep the sign: {msg}");
assert!(msg.contains("`-7`"), "decimal fix must keep the sign: {msg}");
}
#[tokio::test]
async fn a_non_numeric_string_is_refused_not_zeroed() {
let msg = refused("printf '%d' abc").await;
assert!(msg.contains("abc"), "must quote the value: {msg}");
}
#[tokio::test]
async fn a_fractional_value_is_refused_for_an_integer_conversion() {
let msg = refused("printf '%d' 1.5").await;
assert!(msg.contains("1.5"), "must quote the value: {msg}");
}
#[tokio::test]
async fn a_float_past_the_integer_range_names_the_limit() {
let msg = refused("printf '%d' 1e19").await;
assert!(msg.contains("64-bit"), "must name the limit: {msg}");
}
#[tokio::test]
async fn a_non_numeric_string_is_refused_for_a_float_conversion_too() {
let msg = refused("printf '%f' abc").await;
assert!(msg.contains("abc"), "must quote the value: {msg}");
}
#[tokio::test]
async fn an_empty_operand_is_refused_in_its_own_words() {
let msg = refused(r#"printf '%d' """#).await;
assert!(msg.contains("empty"), "must name the emptiness: {msg}");
}
#[tokio::test]
async fn an_empty_variable_is_refused_the_same_way() {
let msg = refused(r#"x=""; printf '%d' "$x""#).await;
assert!(msg.contains("empty"), "must name the emptiness: {msg}");
}
#[tokio::test]
async fn the_integer_bounds_convert_exactly() {
assert_eq!(ok("printf '%d' -- -9223372036854775808").await, "-9223372036854775808");
assert_eq!(ok("printf '%d' 9223372036854775807").await, "9223372036854775807");
}
#[tokio::test]
async fn a_string_operand_one_past_the_negative_bound_refuses() {
let msg = refused(r#"printf '%d' '-9223372036854775809'"#).await;
assert!(msg.contains("64-bit"), "must name the limit: {msg}");
assert_eq!(
ok(r#"printf '%d' '-9223372036854775808'"#).await,
"-9223372036854775808"
);
}
#[tokio::test]
async fn a_string_operand_past_the_positive_bound_refuses() {
let msg = refused(r#"printf '%d' '9223372036854775808'"#).await;
assert!(msg.contains("64-bit"), "must name the limit: {msg}");
assert_eq!(
ok(r#"printf '%d' '9223372036854775807'"#).await,
"9223372036854775807"
);
}
#[tokio::test]
async fn a_quoted_string_operand_takes_the_json_reading() {
assert_eq!(ok(r#"printf '%d' '1e3'"#).await, "1000");
assert_eq!(ok(r#"printf '%d' '42'"#).await, "42");
assert_eq!(ok(r#"printf '%.1f' '1.5'"#).await, "1.5");
}
#[tokio::test]
async fn an_explicit_plus_is_read_and_does_not_dodge_the_rules() {
assert_eq!(ok(r#"printf '%d' '+7'"#).await, "7");
let msg = refused(r#"printf '%d' '+007'"#).await;
assert!(msg.contains("8#7"), "must still name the octal spelling: {msg}");
}
#[tokio::test]
async fn the_non_kaish_base_spellings_name_the_kaish_ones() {
let binary = refused("printf '%d' 0b101").await;
assert!(binary.contains("2#101"), "must name the binary spelling: {binary}");
let octal = refused("printf '%d' 0o17").await;
assert!(octal.contains("8#17"), "must name the octal spelling: {octal}");
}
#[tokio::test]
async fn a_fractional_leading_zero_is_not_offered_an_octal_fix() {
let msg = refused(r#"printf '%d' '007.5'"#).await;
assert!(msg.contains("7.5"), "must name the decimal: {msg}");
assert!(!msg.contains("8#7.5"), "must not invent a fractional octal: {msg}");
}
#[tokio::test]
async fn a_magnitude_past_f64_names_the_range_but_a_word_does_not() {
let big = refused(r#"printf '%d' '1e999'"#).await;
assert!(big.contains("64-bit"), "must name the range: {big}");
let word = refused("printf '%d' inf").await;
assert!(!word.contains("64-bit"), "a word is not a range problem: {word}");
}
#[tokio::test]
async fn a_refusal_discards_text_already_formatted_before_it() {
let msg = refused("printf 'x%d' abc").await;
assert!(msg.contains("abc"), "{msg}");
let msg = refused("printf '%d-%d' 1 abc").await;
assert!(msg.contains("abc"), "{msg}");
}
#[tokio::test]
async fn a_refusal_in_a_later_cycling_pass_discards_the_earlier_passes() {
let msg = refused("printf '%d\\n' 1 abc").await;
assert!(msg.contains("abc"), "{msg}");
}
#[tokio::test]
async fn every_numeric_conversion_defaults_a_missing_operand() {
assert_eq!(ok("printf '%x'").await, "0");
assert_eq!(ok("printf '%o'").await, "0");
assert_eq!(ok("printf '%.1f'").await, "0.0");
assert_eq!(ok("printf '%c'").await, "");
}
#[tokio::test]
async fn a_character_conversion_past_the_range_is_refused() {
let msg = refused("printf '%c' 4294967296").await;
assert!(msg.contains("4294967296"), "must quote the value: {msg}");
}
#[tokio::test]
async fn plain_integers_still_convert() {
assert_eq!(ok("printf '%d' 42").await, "42");
assert_eq!(ok("printf '%d' -- -5").await, "-5");
assert_eq!(ok("printf '%d' 0").await, "0");
}
#[tokio::test]
async fn a_missing_operand_is_still_zero() {
assert_eq!(ok("printf '%d'").await, "0");
assert_eq!(ok("printf '%d-%d' 7").await, "7-0");
}
#[tokio::test]
async fn exponent_notation_is_a_json_number_and_converts() {
assert_eq!(ok("printf '%d' 1e3").await, "1000");
}
#[tokio::test]
async fn a_boolean_still_converts() {
assert_eq!(ok("printf '%d' true").await, "1");
assert_eq!(ok("printf '%d' false").await, "0");
}
#[tokio::test]
async fn float_conversions_are_unaffected() {
assert_eq!(ok("printf '%.1f' 1.5").await, "1.5");
assert_eq!(ok("printf '%.1f' 2").await, "2.0");
}
#[tokio::test]
async fn a_string_conversion_takes_any_text() {
assert_eq!(ok("printf '%s' 0xff").await, "0xff");
assert_eq!(ok("printf '%s' 007").await, "007");
assert_eq!(ok("printf '%s' abc").await, "abc");
}
#[tokio::test]
async fn awk_still_coerces_a_non_numeric_string_to_zero() {
assert_eq!(ok(r#"awk 'BEGIN { printf "%d\n", "abc" }'"#).await, "0\n");
}
#[tokio::test]
async fn awk_still_reads_a_leading_zero_as_decimal() {
assert_eq!(ok(r#"awk 'BEGIN { printf "%d\n", "007" }'"#).await, "7\n");
}