144 lines
4.5 KiB
Rust
144 lines
4.5 KiB
Rust
//! Tests for `loopd`'s configuration. Do not edit: these define the required behaviour.
|
|
|
|
use loopd::config::{Config, ConfigError, Limits, Sampling};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
fn fixture(name: &str) -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures/config")
|
|
.join(name)
|
|
}
|
|
|
|
#[test]
|
|
fn minimal_file_gets_the_documented_defaults() {
|
|
let c = Config::load(&fixture("minimal.toml")).unwrap();
|
|
assert_eq!(
|
|
c.infer.socket,
|
|
PathBuf::from("/run/boxmaker/infer/infer.sock")
|
|
);
|
|
assert_eq!(c.infer.model, "ornith-1.5-35b-a3b");
|
|
assert_eq!((c.slots.main, c.slots.background), (0, 1));
|
|
assert_eq!(c.expect.n_ctx, 131_072);
|
|
assert_eq!(c.expect.slots, 2);
|
|
assert_eq!(
|
|
c.expect.template_sha256.to_hex(),
|
|
"f55f52930aa8bf44ab5cb85f99370fcc3c56e9a85640b812086d5330bce5d86b"
|
|
);
|
|
assert_eq!(
|
|
c.sampling,
|
|
Sampling {
|
|
temperature: 0.6,
|
|
top_p: 0.95,
|
|
top_k: 20
|
|
}
|
|
);
|
|
let want = Limits {
|
|
poll_ms: 5_000,
|
|
busy_wait_ms: 600_000,
|
|
load_wait_ms: 180_000,
|
|
idle_grace_ms: 30_000,
|
|
liveness_ms: 30_000,
|
|
thinking_cap: 4_096,
|
|
thinking_overrun: 256,
|
|
max_tokens: 8_192,
|
|
queue_len: 8,
|
|
retry_attempts: 4,
|
|
retry_backoff_ms: vec![2_000, 8_000, 30_000],
|
|
retry_window_ms: 300_000,
|
|
};
|
|
assert_eq!(c.limits, want);
|
|
assert_eq!(Limits::default(), want);
|
|
}
|
|
|
|
#[test]
|
|
fn full_file_overrides_every_default() {
|
|
let c = Config::load(&fixture("full.toml")).unwrap();
|
|
assert_eq!(
|
|
c.sampling,
|
|
Sampling {
|
|
temperature: 0.2,
|
|
top_p: 0.9,
|
|
top_k: 40
|
|
}
|
|
);
|
|
let want = Limits {
|
|
poll_ms: 50,
|
|
busy_wait_ms: 200,
|
|
load_wait_ms: 300,
|
|
idle_grace_ms: 150,
|
|
liveness_ms: 100,
|
|
thinking_cap: 20,
|
|
thinking_overrun: 10,
|
|
max_tokens: 512,
|
|
queue_len: 1,
|
|
retry_attempts: 2,
|
|
retry_backoff_ms: vec![10],
|
|
retry_window_ms: 1_000,
|
|
};
|
|
assert_eq!(c.limits, want);
|
|
}
|
|
|
|
#[test]
|
|
fn a_partial_limits_table_keeps_the_other_defaults() {
|
|
let text = std::fs::read_to_string(fixture("minimal.toml")).unwrap();
|
|
let c = Config::parse(&format!("{text}\n[limits]\nliveness_ms = 1234\n")).unwrap();
|
|
assert_eq!(c.limits.liveness_ms, 1234);
|
|
assert_eq!(c.limits.poll_ms, 5_000);
|
|
}
|
|
|
|
/// Every table in the file must reject a key it does not know: a misspelt limit that silently
|
|
/// fell back to its default would be a limit the owner believes is set and is not.
|
|
#[test]
|
|
fn unknown_keys_are_errors_in_every_table() {
|
|
let text = std::fs::read_to_string(fixture("full.toml")).unwrap();
|
|
assert!(Config::parse(&text).is_ok());
|
|
for table in ["infer", "slots", "expect", "sampling", "limits"] {
|
|
let header = format!("[{table}]\n");
|
|
assert!(text.contains(&header), "fixture has no [{table}] table");
|
|
let bad = text.replacen(&header, &format!("{header}zz_unknown = 1\n"), 1);
|
|
assert!(
|
|
Config::parse(&bad).is_err(),
|
|
"[{table}] accepted an unknown key"
|
|
);
|
|
}
|
|
assert!(
|
|
Config::parse(&format!("zz_unknown = 1\n{text}")).is_err(),
|
|
"top level"
|
|
);
|
|
assert!(
|
|
Config::parse(&format!("{text}\n[zz_unknown]\nx = 1\n")).is_err(),
|
|
"unknown table"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn required_tables_and_values_are_checked() {
|
|
let text = std::fs::read_to_string(fixture("minimal.toml")).unwrap();
|
|
for table in ["infer", "slots", "expect"] {
|
|
let without: String = text
|
|
.split("\n\n")
|
|
.filter(|block| !block.trim_start().starts_with(&format!("[{table}]")))
|
|
.collect::<Vec<_>>()
|
|
.join("\n\n");
|
|
assert!(Config::parse(&without).is_err(), "[{table}] is required");
|
|
}
|
|
let bad_hash = text.replace("f55f5293", "F55F5293");
|
|
assert!(
|
|
Config::parse(&bad_hash).is_err(),
|
|
"the hash must be lowercase hex"
|
|
);
|
|
let negative = text.replace("main = 0", "main = -1");
|
|
assert!(Config::parse(&negative).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn load_reports_which_file_failed() {
|
|
let missing = fixture("does-not-exist.toml");
|
|
match Config::load(&missing) {
|
|
Err(ConfigError::Read(path, _)) => assert_eq!(path, missing),
|
|
other => panic!("expected a read error, got {other:?}"),
|
|
}
|
|
let e: Box<dyn std::error::Error> = Box::new(Config::load(&missing).unwrap_err());
|
|
assert!(e.to_string().contains("does-not-exist.toml"));
|
|
}
|