org.llm4s.agent.testkit
Members list
Type members
Classlikes
The contract every org.llm4s.agent.graph.Checkpointer must meet, as a ScalaTest suite. Mix it into a spec, implement newCheckpointer, and every case below runs against your store:
The contract every org.llm4s.agent.graph.Checkpointer must meet, as a ScalaTest suite. Mix it into a spec, implement newCheckpointer, and every case below runs against your store:
class SqliteCheckpointerContractSpec extends AnyFlatSpec with CheckpointerContract:
protected def newCheckpointer(clock: java.time.Clock): Checkpointer =
SqliteCheckpointer.open(freshDatabaseFile(), clock)
The cases are the ones the Checkpointer Scaladoc states, in two groups:
- '''The store''', called directly: commits applied atomically or not at all; a new checkpoint accepted only over the latest (GraphError.CheckpointConflict) and pending writes only for it (GraphError.InvalidCommit); events numbered contiguously in commit order and never reused, also after a refused commit, compaction or
deleteThread; compaction and GraphError.ReplayUnavailable;deleteThread; checkpoints, pending writes and events read back as written; and claims and fencing - one live claim per thread, tokens strictly increasing, takeover once a claim has expired by the store's clock, renewal and release by the current token only, and every commit refused with GraphError.StaleClaim unless it carries the current token - including from many threads at once. - '''Two runtimes over one store''': a second GraphRuntime is refused a thread whose run is live in the first (GraphError.ThreadBusy naming the holder); a run that keeps renewing is not taken over; once a claim expires, the second runtime recovers the thread without re-running the first run's completed tasks, and the first run's later commits are refused, so it fails with GraphError.CheckpointWriteFailed and leaves nothing in the thread; and of several runtimes recovering one thread at once, exactly one is admitted.
Claim expiry is judged by the store's clock. By default the store under test must use the clock it is given for that (and may use it for nothing else), and the suite moves a ManualClock; it never waits for real time to pass a claim's ttl. A store that judges expiry by a clock it cannot be handed - a database server's now() - overrides advanceStoreClock to make claims expire as if that clock had moved (for example by moving every stored expiry back), and sets exactExpiry to false, so that the cases check expiry by its effect only.
A durable store also overrides reopen, so that the cases about claims and tokens surviving a restart - a process that closes the store and opens the same storage again - run against storage that was really closed. The default reopens nothing and hands the same instance back.
Each case gets a new store from newCheckpointer and uses its own thread ids; the store need not be empty of other threads.
Attributes
- Supertypes
-
trait OptionValuestrait EitherValuestrait Matcherstrait Explicitlytrait MatcherWordstrait Tolerancetrait AnyFlatSpecLiketrait Documentingtrait Alertingtrait Notifyingtrait Informingtrait CanVerbtrait MustVerbtrait ShouldVerbtrait TestRegistrationtrait TestSuitetrait Suitetrait Serializabletrait Assertionstrait TripleEqualstrait TripleEqualsSupportclass Objecttrait Matchableclass AnyShow all
A clock that moves only when told to, for driving claim expiry in a test: hand it to the store under test, and advance it past a claim's ttl to let another run take the thread over. Safe to read and move from any thread.
A clock that moves only when told to, for driving claim expiry in a test: hand it to the store under test, and advance it past a claim's ttl to let another run take the thread over. Safe to read and move from any thread.
Attributes
- Supertypes
-
class Clocktrait InstantSourceclass Objecttrait Matchableclass Any