kittest

package
v2.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package kittest is an in-memory test double for the kit authoring surface: a Room (plus Services/KV/config) you can drive from plain Go tests, with the sends, posts, and settle recorded for assertions.

r := kittest.NewRoom(kittest.Player("p1"), kittest.Player("p2"))
h := mygame.Game{}.NewRoom(r.Config(), r.Services())
h.OnStart(r)
h.OnJoin(r, r.Players[0])
h.OnInput(r, r.Players[0], kit.Input{Kind: kit.InputRune, Rune: ' '})
r.Advance(2 * time.Second) // move the room clock
h.OnWake(r)
frame := r.LastFrame(r.Players[0]) // assert on cells

The clock is virtual (starts at a fixed epoch, moves only via Advance) and the RNG is seeded, so tests are deterministic by default — mirroring the wasm room, not the native dev runner's wall clock.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Player

func Player(id string) kit.Player

Player builds a member Player with conventional fields from an id.

func String

func String(f *kit.Frame, row int) string

String renders a frame row as plain text — convenient in test failures.

Types

type Room

type Room struct {
	Players []kit.Player
	Cfg     kit.RoomConfig
	Clock   time.Time
	RNG     *rand.Rand

	Frames   map[string][]*kit.Frame // accountID -> sent frames (copies)
	Ended    *kit.Result
	Posted   []kit.Result
	InputCtx kit.InputContext
	Logs     []string

	KV         map[string]map[string][]byte // accountID -> key -> value
	KVRules    map[string]map[string]kit.MergeRule
	ConfigVals map[string][]byte

	// KVUnavailable simulates the production host's KV degradation — the ABI
	// gives the guest no error channel, so when the host's store fails it does
	// NOT surface a Go error; it degrades exactly like this:
	//
	//   - Get reports the key as missing (nil, false, nil)
	//   - Set and Delete return nil without persisting anything
	//
	// While true, the same happens here, and the backing maps keep whatever
	// they held (the outage is transient, not a wipe). Use it to test the
	// read-absent-reinit hazard: a game that does the natural
	// `Get → missing → initialize starting balance → Set` resets a player's
	// saved wallet during a store blip. Robust games treat "missing" wallet
	// reads conservatively (e.g. re-read on a later wake before overwriting,
	// or write with kit.MergeMax/kit.MergeSum so a blip-era write cannot
	// clobber the durable value at merge time).
	KVUnavailable bool
}

Room is the in-memory kit.Room double.

Example (KvUnavailable)

ExampleRoom_kvUnavailable shows the read-absent-reinit hazard: a wallet loader that treats "missing" as "new player" silently resets saved state during a store blip — kittest can now make that test fail before production does.

package main

import (
	"context"
	"fmt"
	"strconv"

	kit "github.com/shellcade/kit/v2"
	"github.com/shellcade/kit/v2/kittest"
)

func main() {
	ctx := context.Background()
	r := kittest.NewRoom(kittest.Player("p1"))
	store := r.Services().Accounts.For(r.Players[0]).Store()

	// The naive pattern: Get → missing → initialize starting balance → Set.
	loadWallet := func() int {
		v, ok, _ := store.Get(ctx, "balance")
		if !ok {
			store.Set(ctx, "balance", []byte("1000"), kit.MergeMax)
			return 1000
		}
		n, _ := strconv.Atoi(string(v))
		return n
	}

	store.Set(ctx, "balance", []byte("9500"), kit.MergeMax) // a veteran's wallet
	fmt.Println("healthy:", loadWallet())

	r.KVUnavailable = true // a transient store blip…
	fmt.Println("blip:   ", loadWallet())

	r.KVUnavailable = false               // …but here the reinit Set was dropped too, so the
	fmt.Println("after:  ", loadWallet()) // durable 9500 survives. In production
	// the blip can end between the Get and the Set — and then the 1000
	// overwrites 9500. Don't persist a starting balance from a missing read.

}
Output:
healthy: 9500
blip:    1000
after:   9500

func NewRoom

func NewRoom(players ...kit.Player) *Room

NewRoom returns a Room with the given members, a seeded RNG (seed 1), and a fixed-epoch virtual clock.

func (*Room) Advance

func (r *Room) Advance(d time.Duration)

Advance moves the virtual clock (use before OnWake to simulate time).

func (*Room) Config

func (r *Room) Config() kit.RoomConfig

func (*Room) Count

func (r *Room) Count() int

func (*Room) End

func (r *Room) End(res kit.Result)

func (*Room) Has

func (r *Room) Has(p kit.Player) bool

func (*Room) Identical

func (r *Room) Identical(f *kit.Frame)

func (*Room) LastFrame

func (r *Room) LastFrame(p kit.Player) *kit.Frame

LastFrame returns the most recent frame sent to p, or nil.

func (*Room) Log

func (r *Room) Log(msg string)

func (*Room) Members

func (r *Room) Members() []kit.Player

func (*Room) Now

func (r *Room) Now() time.Time

func (*Room) Post

func (r *Room) Post(res kit.Result)

func (*Room) Rand

func (r *Room) Rand() *rand.Rand

func (*Room) Send

func (r *Room) Send(p kit.Player, f *kit.Frame)

func (*Room) Services

func (r *Room) Services() kit.Services

func (*Room) SetInputContext

func (r *Room) SetInputContext(c kit.InputContext)

func (*Room) Settled

func (r *Room) Settled() bool

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL