gamekit-coffeemachine is a gamification toolkit for the Coffee Machine kata. It provides language-agnostic tools—a Progress Runner and a CLI utility—that communicate with kata implementations (in Java, C++, Python, etc.) via an inter-process text protocol over Unix pipes. The actual kata implementations live in a separate companion repository (kata-coffeemachine).
- Owner: Murex
- Language: Go (1.26+)
- License: MIT
- Module path:
github.com/murex/gamekit-coffeemachine
gamekit-coffeemachine/
├── main.go # Entry point — launches the CLI runner (cli.Run)
├── go.mod / go.sum # Go module definition (Go 1.26, uses tool directives)
├── Makefile # Build, test, lint, release targets
├── .goreleaser.yml # GoReleaser v2 config for cross-platform releases
├── .golangci.yml # Linter config (golangci-lint v2 with revive, gosec, etc.)
├── progress-runner.sh # Shell script to build & run progress tests against an implementation
│
├── cli/
│ └── runner.go # CLI utility: interactive terminal to send commands to an implementation
│
├── process/
│ ├── coffee_machine_process.go # Process lifecycle: spawn, run, send messages, read responses
│ ├── coffee_machine_process_test.go # Tests for process management
│ ├── coffee_machine_message.go # Message types (instructions) sent over the protocol
│ └── coffee_machine_message_test.go # Tests for message formatting
│
├── progress/
│ ├── progress_test.go # TestMain + Test_Progress: orchestrates all iteration tests
│ ├── iteration/
│ │ ├── test_runner.go # Generic TestRunner: runs test cases sequentially per iteration
│ │ └── test_helper.go # Assertion helpers (AssertDrinkIsServed, etc.)
│ ├── iteration1/ # Drink instructions, sugar, stick
│ ├── iteration2/ # Payment handling
│ ├── iteration3/ # Extra hot drinks, orange juice
│ ├── iteration4/ # Drink quantity reporting, total amount
│ └── iteration5/ # Empty tank handling, mail notifications
│
├── ref/
│ ├── drinks.go # Drink definitions (Coffee, Tea, Chocolate, OrangeJuice) with prices
│ ├── liquids.go # Liquid types (Water, Milk)
│ ├── tank_status.go # Tank statuses (Full, Empty)
│ ├── amount.go # AmountRegexp: locale-agnostic money formatting for assertions
│ └── amount_test.go
│
├── settings/
│ ├── build_info.go # Build metadata (version, os, arch, commit, date, author) set via ldflags
│ └── build_info_test.go
│
├── dev-doc/
│ ├── README.md # Index of developer documentation
│ ├── inter-process-text-protocol.md # Protocol specification (critical reference)
│ ├── build.md
│ ├── release.md
│ ├── debug.md
│ ├── quality-metrics.md
│ └── add-language.md
│
├── .github/
│ ├── workflows/
│ │ ├── go.yml # CI: build & test on macOS, Ubuntu, Windows
│ │ ├── golangci_lint.yml # CI: linting
│ │ └── go_releaser.yml # CI: release pipeline
│ ├── CODEOWNERS
│ └── dependabot.yml
│
├── bin/ # Build output directory (gitignored, populated by `make build`)
├── dist/ # GoReleaser output directory (gitignored)
├── CONTRIBUTING.md
├── LICENSE.md
└── README.md
Both tools communicate with kata implementations through a text-based protocol over stdin/stdout pipes. The toolkit spawns a child process (the language implementation's run.sh) and exchanges line-delimited messages.
Protocol messages (defined in process/coffee_machine_message.go):
| Instruction | Direction | Purpose | Response type |
|---|---|---|---|
iteration |
→ | Query which iteration is implemented | Single line |
restart |
→ | Reset implementation state | Single line |
shutdown |
→ | Terminate the implementation process | Single line |
make-drink |
→ | Request a drink instruction | Single line |
print-report |
→ | Request a sales report | Multi-line (ends with END-OF-REPORT) |
set-tank |
→ | Set liquid tank status (empty/full) | Single line |
dump-mailbox |
→ | Dump notification mailbox | Multi-line (ends with END-OF-MAILBOX) |
The process.P struct manages the child process lifecycle and message exchange with a 5-second response timeout.
The progress runner (progress/) is built as a compiled Go test binary (via go test -c). It:
- Spawns the language implementation process.
- Queries the implementation's current iteration number.
- Runs black-box tests for iterations 1–5, skipping iterations beyond what the implementation reports.
- Sends
restartbetween test cases to reset state. - Sends
shutdownon teardown.
Each iteration sub-package (iteration1/ through iteration5/) defines test cases covering specific kata requirements:
- Iteration 1: Drink instructions, sugar, stick
- Iteration 2: Payment (enough/not enough money)
- Iteration 3: Extra hot drinks, orange juice
- Iteration 4: Drink quantity tracking, total revenue reporting
- Iteration 5: Empty liquid tanks, email notifications
The CLI (cli/runner.go) is an interactive terminal tool for manually sending protocol commands to an implementation. It reads user input from stdin, forwards it to the implementation process, and displays responses. Primarily used for debugging protocol integration when adding new language support.
Contains domain constants shared across the codebase:
- Drinks: Coffee (€0.60, code
C), Tea (€0.40, codeT), Chocolate (€0.50, codeH), Orange Juice (€0.60, codeO) - Liquids: Water, Milk
- Tank statuses: Full, Empty
- AmountRegexp: Generates locale-agnostic regex patterns for monetary amounts (handles both
.and,decimal separators)
All commands use the Makefile. The environment variable CGO_ENABLED=0 is set globally.
| Command | Description |
|---|---|
make build |
Builds all binaries into bin/: cli, progress-runner, gotestsum, test2json |
make test |
Runs all unit tests via gotestsum |
make lint |
Runs golangci-lint (config in .golangci.yml) |
make modernize |
Runs gopls modernize analysis |
make vet |
Runs go vet |
make tidy |
Runs go mod tidy |
make deps |
Updates all dependencies |
make prepare |
Full pre-commit pipeline: deps → tidy → lint → modernize → build → test |
make clean |
Removes bin/, dist/, _test_results/ |
make release |
Creates a release via GoReleaser |
make snapshot |
Creates a snapshot release (no publish) |
./progress-runner.sh <path-to-language-implementation> [-v|-vv]
This script builds the toolkit, then runs the progress runner binary against the specified implementation directory. The last directory component of the path is used as the language name. Results are written to _test_results/ as JUnit XML.
The LANG_IMPL_PATH environment variable is used internally to pass the implementation path to the progress runner process.
Build version, OS, architecture, commit, date, and author are injected at build time via -ldflags into the settings package variables. These are displayed when the tools start.
The project enforces strict linting via golangci-lint with the following notable rules:
- Max function length: 25 lines (revive
function-length) - Max line length: 120 characters (revive), 200 characters (lll)
- Max arguments per function: 4
- Cognitive complexity limit: 13
- Cyclomatic complexity limit: 16 (gocyclo)
- Revive rules are disabled for test files (
_test.go) - Security scanning via
gosec
Three GitHub Actions workflows run on push/PR:
go.yml— Build and test on macOS, Ubuntu, and Windows (Go 1.26)golangci_lint.yml— Lint checksgo_releaser.yml— Cross-platform release (Linux/macOS/Windows, amd64/arm64)
- Unit tests use
testify(assertandrequirepackages). - The progress runner test binary uses
TestMainfor setup/teardown of the implementation process. - Each iteration test runner sends a
restartmessage before each test case. - Test assertions use regex patterns to validate drink maker command format (e.g.,
C:1:0for coffee with 1 sugar and a stick).
README.md— High-level overview and architecture diagramdev-doc/inter-process-text-protocol.md— Protocol specification (essential for understanding message flow)process/coffee_machine_message.go— All message types and their wire formatprocess/coffee_machine_process.go— Process lifecycle and message exchange logicprogress/progress_test.go— How iterations are orchestratedref/drinks.go— Domain constants (drink names, prices, command codes)