// test_generator.mbt — Deterministic test-case generator.
//
// A small LCG with a fixed seed produces the same sequence on every run and
// on every target, which is what makes the property tests reproducible.
// The generator produces realistic HTTP messages and signing inputs.
///|
/// A deterministic pseudo-random generator (LCG).
pub struct Prng {
mut state : Int64
}
///|
/// Constructs a generator from a seed.
pub fn Prng::new(seed : Int64) -> Prng {
{ state: seed }
}
///|
/// Advances the generator and returns a 32-bit pseudo-random value.
pub fn Prng::next_u32(self : Prng) -> Int {
self.state = self.state * 6364136223846793005L + 1442695040888963407L
let v = (self.state >> 33) & 0x7FFFFFFF
v.to_int()
}
///|
/// Returns a value in `0..bound`.
pub fn Prng::next_int(self : Prng, bound : Int) -> Int {
if bound <= 0 {
return 0
}
self.next_u32() % bound
}
///|
/// Returns a random boolean.
pub fn Prng::next_bool(self : Prng) -> Bool {
self.next_u32() % 2 == 0
}
///|
/// Picks a random element from a list.
pub fn Prng::pick(self : Prng, choices : Array[String]) -> String {
let i = self.next_int(choices.length())
choices[i]
}
///|
/// Picks one of several covered-component templates.
pub fn Prng::pick_components(self : Prng) -> Array[CoveredComponent] {
let choice = self.next_int(4)
match choice {
0 => [covered_derived(Method), covered_field("content-type")]
1 =>
[covered_derived(Method), covered_derived(Path), covered_field("x-tag")]
2 => [covered_field("content-type"), covered_derived(Query)]
3 =>
[
covered_derived(Method),
covered_derived(Path),
covered_field("content-type"),
covered_field("x-tag"),
]
_ => [covered_derived(Method)]
}
}
///|
/// Generates a request message for a property-test case.
pub fn Prng::generate_request(self : Prng, case : Int) -> RequestContext {
let methods = ["GET", "POST", "PUT", "PATCH", "DELETE"]
let paths = ["/", "/a", "/foo/bar", "/a%2Fb", "/path with space"]
let queries : Array[String?] = [
None,
Some(""),
Some("a=1"),
Some("a=1&b=2"),
Some("pet=dog"),
Some("x=%20"),
]
let method = self.pick(methods)
let path = self.pick(paths)
let query = queries[self.next_int(queries.length())]
let headers = OrderedHeaders::new()
let _ = headers.append("content-type", "application/json")
let _ = headers.append("x-tag", "case-" + case.to_string())
let _ = headers.append(
"x-extra",
"not-covered-" + self.next_int(100).to_string(),
)
let body : Bytes? = if self.next_bool() {
Some(@utf8.encode("body-" + case.to_string()))
} else {
None
}
match
RequestContext::new_default(
method, "https", "example.com", path, query, headers, body,
) {
Ok(r) => r
Err(_) =>
abort("property request construction failed at case " + case.to_string())
}
}
///|
/// Generates signing options for a property-test case.
pub fn Prng::generate_sign_options(
_self : Prng,
case : Int,
components : Array[CoveredComponent],
) -> SignOptions {
let keyid = "key-" + (case % 4).to_string()
let secret = "secret-" + case.to_string()
let key = make_test_key(keyid, secret)
let created = 1700000000L + Int64::from_int(case)
let params : SignatureParameters = {
created: Some(created),
expires: None,
keyid: Some(keyid),
alg: None,
nonce: None,
tag: None,
extensions: Array::new(),
}
{
label: "sig-prop",
components,
parameters: params,
algorithm: "hmac-sha256",
key,
}
}