lease v2: byte goldens, hold-loop and lease-helper tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9db62027eb
commit
9f10887169
3 changed files with 207 additions and 0 deletions
16
crates/claude-connector/tests/fixtures/lease_v2/README.md
vendored
Normal file
16
crates/claude-connector/tests/fixtures/lease_v2/README.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# lease_v2 fixtures
|
||||
|
||||
The exact bytes of the claude lease protocol v2: each file is one whole frame (a u32
|
||||
big-endian length, then the named-MessagePack map `{kind, value}`).
|
||||
|
||||
- `request.bin`: `claude_lease_request`.
|
||||
- `refusal_unknown_caller.bin`, `refusal_expired.bin`, `refusal_busy.bin`,
|
||||
`refusal_provider_rejected.bin`: `claude_lease_refusal`; `kind` is the variant name as a
|
||||
PascalCase string.
|
||||
- `grant.bin`: `claude_lease_grant` with `lease_id` and `plan_quota` carrying `five_hour` and
|
||||
`seven_day` windows (utilization_permille, resets_at_unix_ms, status, observed_at_unix_ms).
|
||||
On the wire the grant travels with one SCM_RIGHTS fd; the file holds the frame only.
|
||||
|
||||
Huginn's lease client vendors these files from this repository and tests against the same
|
||||
bytes. Generator: `tests/lease_wire_golden.rs`; run it with `LEASE_V2_REGENERATE=1`, then
|
||||
review the diff: a changed byte is a wire change.
|
||||
|
|
@ -543,3 +543,99 @@ fn connections_past_the_limit_are_dropped_and_finished_ones_free_their_place() {
|
|||
drop(first);
|
||||
assert!(granted_ok(&ask_until_granted(&harness, 2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_lease_survives_many_polls_while_its_holder_and_invocation_stay() {
|
||||
let harness = start(setup());
|
||||
let mut first = ask(&harness, 60_000);
|
||||
assert!(granted_ok(&first));
|
||||
// Three polls later the lease still holds: a second invocation is Busy.
|
||||
std::thread::sleep(Duration::from_millis(1_700));
|
||||
harness.enter(2);
|
||||
let second = ask(&harness, 60_000);
|
||||
assert_eq!(refusal_of(&second), Some(LeaseRefusalKind::Busy));
|
||||
// The holder talking does not end it either.
|
||||
first._stream.write_all(b"x").unwrap();
|
||||
std::thread::sleep(Duration::from_millis(1_200));
|
||||
harness.enter(3);
|
||||
assert_eq!(
|
||||
refusal_of(&ask(&harness, 60_000)),
|
||||
Some(LeaseRefusalKind::Busy)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_lease_ends_when_the_unit_runs_a_different_invocation() {
|
||||
let harness = start(setup());
|
||||
let first = ask(&harness, 60_000);
|
||||
assert!(granted_ok(&first));
|
||||
// The same unit, a new invocation: the one that leased is over.
|
||||
harness.enter(2);
|
||||
std::thread::sleep(Duration::from_millis(1_200));
|
||||
assert!(granted_ok(&ask(&harness, 60_000)));
|
||||
drop(first);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_lease_asks_the_system_about_its_holders_process_for_its_own_unit() {
|
||||
let harness = start(setup());
|
||||
let first = ask(&harness, 60_000);
|
||||
assert!(granted_ok(&first));
|
||||
std::thread::sleep(Duration::from_millis(1_200));
|
||||
let asked = harness.units.as_ref().unwrap().asked_about.lock().unwrap().clone();
|
||||
assert!(asked.len() >= 3, "the hold polled: {asked:?}");
|
||||
assert!(asked.iter().all(|(_, unit)| unit == UNIT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_account_resolves_to_its_uid_and_a_missing_one_to_none() {
|
||||
assert_eq!(claude_connector::user_uid("root"), Some(0));
|
||||
assert_eq!(claude_connector::user_uid("no-such-account-4c1f9a"), None);
|
||||
assert_eq!(claude_connector::user_uid("bad\0name"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_token_up_to_one_atomic_pipe_write_is_lent_and_a_longer_one_is_not() {
|
||||
let exact: &'static str = Box::leak("a".repeat(4096).into_boxed_str());
|
||||
let harness = start(Setup {
|
||||
token: Some(exact),
|
||||
..setup()
|
||||
});
|
||||
let mut answer = ask(&harness, 60_000);
|
||||
assert!(granted_ok(&answer));
|
||||
let mut read = Vec::new();
|
||||
File::from(answer.fds.remove(0))
|
||||
.read_to_end(&mut read)
|
||||
.unwrap();
|
||||
assert_eq!(read.len(), 4096);
|
||||
|
||||
let long: &'static str = Box::leak("a".repeat(4097).into_boxed_str());
|
||||
let harness = start(Setup {
|
||||
token: Some(long),
|
||||
..setup()
|
||||
});
|
||||
let refused = ask(&harness, 60_000);
|
||||
assert!(refused.message.is_none() && refused.fds.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_request_trickled_past_the_socket_timeout_is_dropped_without_a_lease() {
|
||||
let harness = start(setup());
|
||||
let mut stream = UnixStream::connect(&harness.socket).unwrap();
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(8)))
|
||||
.unwrap();
|
||||
stream.write_all(&[0, 0]).unwrap();
|
||||
let started = Instant::now();
|
||||
let mut byte = [0_u8; 1];
|
||||
let outcome = stream.read(&mut byte);
|
||||
// The listener gave up at its own two-second bound and closed the
|
||||
// connection: end of file or a reset, never our own read timing out.
|
||||
assert!(
|
||||
matches!(outcome, Ok(0))
|
||||
|| outcome.as_ref().is_err_and(|e| e.kind() == std::io::ErrorKind::ConnectionReset),
|
||||
"{outcome:?}"
|
||||
);
|
||||
assert!(started.elapsed() < Duration::from_secs(6));
|
||||
assert_eq!(harness.tokens.reads.load(Ordering::SeqCst), 0);
|
||||
}
|
||||
|
|
|
|||
95
crates/claude-connector/tests/lease_wire_golden.rs
Normal file
95
crates/claude-connector/tests/lease_wire_golden.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//! Pins the bytes of the lease protocol v2: every frame a lease client sends
|
||||
//! or receives. The fixtures in `fixtures/lease_v2` are the contract's bytes
|
||||
//! for other implementations (Huginn's lease client vendors them and tests
|
||||
//! against the same bytes); the types in `claude_connector::lease` own the
|
||||
//! shape. Regenerate with `LEASE_V2_REGENERATE=1`, then review the diff.
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use claude_connector::{
|
||||
LeaseGranted, LeaseMessage, LeaseRefusalKind, LeaseRefused, LeaseRequest, decode_frame,
|
||||
encode_frame,
|
||||
};
|
||||
use model_connector::PlanQuota;
|
||||
|
||||
const REGENERATE: &str = "LEASE_V2_REGENERATE";
|
||||
|
||||
fn fixtures() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/lease_v2")
|
||||
}
|
||||
|
||||
fn assert_frame(name: &str, message: &LeaseMessage) {
|
||||
let path = fixtures().join(name);
|
||||
let bytes = encode_frame(message);
|
||||
if std::env::var_os(REGENERATE).is_some() {
|
||||
std::fs::create_dir_all(fixtures()).unwrap();
|
||||
std::fs::write(&path, &bytes).unwrap();
|
||||
}
|
||||
let committed = std::fs::read(&path).unwrap_or_else(|_| panic!("{name} is missing"));
|
||||
assert_eq!(committed, bytes, "{name} is not what the types encode");
|
||||
assert_eq!(decode_frame(&committed).as_ref(), Some(message), "{name}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_request_frame_is_pinned() {
|
||||
assert_frame(
|
||||
"request.bin",
|
||||
&LeaseMessage::ClaudeLeaseRequest(LeaseRequest {
|
||||
expires_at_unix_ms: 1_793_307_000_000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_refusal_frame_is_pinned_with_its_kind_as_a_pascal_case_string() {
|
||||
let kinds = [
|
||||
("refusal_unknown_caller.bin", LeaseRefusalKind::UnknownCaller, "UnknownCaller"),
|
||||
("refusal_expired.bin", LeaseRefusalKind::Expired, "Expired"),
|
||||
("refusal_busy.bin", LeaseRefusalKind::Busy, "Busy"),
|
||||
(
|
||||
"refusal_provider_rejected.bin",
|
||||
LeaseRefusalKind::ProviderRejected,
|
||||
"ProviderRejected",
|
||||
),
|
||||
];
|
||||
for (name, kind, spelled) in kinds {
|
||||
let message = LeaseMessage::ClaudeLeaseRefusal(LeaseRefused {
|
||||
kind,
|
||||
detail: "the reason".to_string(),
|
||||
});
|
||||
assert_frame(name, &message);
|
||||
// The kind travels as the variant's name, which other runtimes read.
|
||||
let bytes = encode_frame(&message);
|
||||
let value: rmpv::Value = rmp_serde::from_slice(&bytes[4..]).unwrap();
|
||||
let rmpv::Value::Map(outer) = value else {
|
||||
panic!("a frame is a map")
|
||||
};
|
||||
let rmpv::Value::Map(inner) = &outer[1].1 else {
|
||||
panic!("the value is a map")
|
||||
};
|
||||
assert_eq!(inner[0].0.as_str(), Some("kind"));
|
||||
assert_eq!(inner[0].1.as_str(), Some(spelled));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_grant_with_both_usage_windows_is_pinned() {
|
||||
let window = |name: &str, permille, status: &str, resets| PlanQuota {
|
||||
window: name.to_string(),
|
||||
utilization_permille: Some(permille),
|
||||
resets_at_unix_ms: resets,
|
||||
status: status.to_string(),
|
||||
observed_at_unix_ms: 1_793_300_000_000,
|
||||
};
|
||||
assert_frame(
|
||||
"grant.bin",
|
||||
&LeaseMessage::ClaudeLeaseGrant(LeaseGranted {
|
||||
lease_id: "lease:0123456789abcdef0123456789abcdef".to_string(),
|
||||
plan_quota: vec![
|
||||
window("five_hour", 420, "allowed", 1_793_307_000_000),
|
||||
window("seven_day", 999, "allowed_warning", 1_793_800_000_000),
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue