Files
trueskill-tt/tests/trait_impls.rs
T
logaritmiskandClaude Opus 5 b553c630f5 refactor!: K comes first in History, HistoryBuilder and Joint
`K` is the one type parameter people change, and it was last. Naming a
history in a struct field meant writing all four to say one thing:

    struct Ladder { history: History<i64, ConstantDrift, NullObserver, String> }
    struct Analysis<'h> { joint: Joint<'h, i64, ConstantDrift, NullObserver, &'static str> }

Now:

    struct Ladder { history: History<String> }
    struct Analysis<'h> { joint: Joint<'h> }

`History<K, T, D, O>`, all four defaulted. Bounds may reference later
parameters, so `D: Drift<T> = ConstantDrift` is legal in third position.
`Joint` gains the same defaults, so `Joint<'h, String>` spells it.

72 call sites swapped, and the reorder makes most of them shorter: 18
now read `History<String>` and the `&'static str` ones read `History`.
The two turbofished builders shrink from
`HistoryBuilder::<Untimed, _, _, String>::new()` to
`HistoryBuilder::<String, Untimed>::new()`.

`Joint` keeps `O` structurally, defaulted rather than removed. #72 notes
it never touches the observer, which is true — but it borrows the whole
`&'h History<K, T, D, O>` and calls `History::resolve_terms`, so dropping
the parameter means either a view type or moving that method off
`History`. The default already buys the entire user-visible benefit,
which was the spelling.

Refs #72.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
2026-09-10 06:49:22 +02:00

97 lines
3.1 KiB
Rust

//! The traits a consumer needs on the public types, pinned so they cannot be
//! removed by accident.
//!
//! This is written from a consumer's position — deriving `Debug` on a struct
//! that *holds* a `History` — because that is the thing that failed. Asserting
//! `History: Debug` in isolation would not have caught the generic-bound half:
//! `Rating` derives `PartialEq`, but that is only usable if `D: PartialEq`, and
//! the crate's own only `Drift` impl did not satisfy it.
use trueskill_tt::{
ConstantDrift, ConvergenceOptions, ConvergenceReport, Event, GameOptions, Gaussian, History,
HistoryBuilder, InferenceError, Member, Outcome, Rating, Team,
};
/// The reported failure, verbatim: a consumer holding a history in app state.
#[derive(Debug)]
#[allow(
dead_code,
reason = "held only so `derive(Debug)` has something to render"
)]
struct App {
history: History,
}
#[test]
fn a_struct_holding_a_history_can_derive_debug() {
let app = App {
history: History::default(),
};
let rendered = format!("{app:?}");
// Summarising, not a dump of every skill store — the same choice `Joint`'s
// manual `Debug` makes about its n² factorisation.
assert!(rendered.contains("competitors"), "{rendered}");
assert!(rendered.contains("time_slices"), "{rendered}");
assert!(
!rendered.contains("SkillStore"),
"History's Debug should summarise, not dump: {rendered}"
);
}
#[test]
fn history_builder_is_debug_and_clone() {
let b: HistoryBuilder = History::builder();
let cloned = b.clone();
assert!(!format!("{cloned:?}").is_empty());
}
#[test]
fn config_and_input_value_types_are_comparable() {
assert_eq!(ConstantDrift::new(0.1), ConstantDrift::new(0.1));
assert_ne!(ConstantDrift::new(0.1), ConstantDrift::new(0.2));
assert_eq!(ConvergenceOptions::default(), ConvergenceOptions::default());
assert_eq!(GameOptions::default(), GameOptions::default());
// `Rating: PartialEq` is only reachable through `D: PartialEq`.
assert_eq!(Rating::<i64, ConstantDrift>::default(), Rating::default());
assert_ne!(
Rating::default(),
Rating::<i64, ConstantDrift>::default().with_drift_scale(2.0)
);
assert_eq!(Member::new("a"), Member::new("a"));
assert_ne!(Member::new("a"), Member::new("b"));
assert_eq!(
Team::with_members([Member::new("a")]),
Team::with_members([Member::new("a")])
);
let event = || Event {
time: 1,
teams: [
Team::with_members([Member::new("a")]),
Team::with_members([Member::new("b")]),
]
.into_iter()
.collect(),
outcome: Outcome::winner(0, 2),
};
assert_eq!(event(), event());
assert_eq!(Gaussian::default(), Gaussian::default());
}
#[test]
fn a_history_is_send_and_sync_and_default() {
fn assert_send_sync<X: Send + Sync>() {}
assert_send_sync::<History>();
assert_send_sync::<InferenceError>();
let mut h = History::default();
let report: ConvergenceReport = h.converge().expect("an empty history converges");
assert_eq!(report, report.clone());
}