Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b18e339
Fix Elixir 1.15-1.19 compatibility (register_test + runner result)
tomasz-tomczyk Jun 5, 2026
4bd2b31
Resolve But step keyword to the prior step type (cabbage-ex/cabbage#83)
tomasz-tomczyk Jun 5, 2026
f38d149
Add untagged-scenario regression coverage + fix misleading docs (cabb…
tomasz-tomczyk Jun 5, 2026
23e187f
Match plain string steps literally (cabbage-ex/cabbage#64)
tomasz-tomczyk Jun 5, 2026
61486a5
Build scenarios from Gherkin pickles instead of the legacy parser
tomasz-tomczyk Jun 5, 2026
d44c9d4
Depend on gherkin fork via git (master)
tomasz-tomczyk Jun 5, 2026
a6cc777
Vendor official Cucumber Expressions conformance testdata
tomasz-tomczyk Jun 5, 2026
63d82b4
Vendor cucumber/tag-expressions conformance corpus
tomasz-tomczyk Jun 5, 2026
8a7f8dc
Add spec-conformant Cabbage.TagExpression engine
tomasz-tomczyk Jun 5, 2026
8a64bb2
Add tag expression conformance scoreboard
tomasz-tomczyk Jun 5, 2026
f85c289
Add a conformant Cucumber Expressions engine
tomasz-tomczyk Jun 5, 2026
988c36f
Add Cucumber Expressions conformance scoreboard
tomasz-tomczyk Jun 5, 2026
fb8dd39
Match standard Cucumber Expressions in cabbage steps via the new engine
tomasz-tomczyk Jun 5, 2026
059f8fa
Merge branch 'expr-conformance'
tomasz-tomczyk Jun 5, 2026
975016f
Vendor Cucumber Compatibility Kit sample corpus
tomasz-tomczyk Jun 5, 2026
b944640
Add cucumber-messages emission subsystem (Cabbage.Messages)
tomasz-tomczyk Jun 5, 2026
f0f8d51
Add mix conformance.cck scoreboard and CCK harness
tomasz-tomczyk Jun 5, 2026
1673260
Model full result/exception state machine in the messages runner
tomasz-tomczyk Jun 5, 2026
3eee3f0
Grade CCK status/exception areas (CCK 15 -> 20)
tomasz-tomczyk Jun 5, 2026
2111fd7
Bump gherkin dep to parser-gaps; route markdown sources
tomasz-tomczyk Jun 5, 2026
397493f
Expose parameter_type_names/1 on the Cucumber Expression engine
tomasz-tomczyk Jun 5, 2026
38c78b8
Support custom + regex + undefined parameter types in the messages ru…
tomasz-tomczyk Jun 5, 2026
09796cc
Grade CCK parameter-types, regular-expression, unknown-parameter-type…
tomasz-tomczyk Jun 5, 2026
361a76f
Vendor CCK hook sample data (features, goldens, reference step defs)
tomasz-tomczyk Jun 5, 2026
8bb172b
Add hook subsystem to the cucumber-messages runner
tomasz-tomczyk Jun 5, 2026
7daeaf6
Grade the 9 hook CCK areas in the conformance harness
tomasz-tomczyk Jun 5, 2026
df8bb7f
Merge branch 'cck-hooks'
tomasz-tomczyk Jun 5, 2026
d00913a
Vendor CCK attachment sample data (features, goldens, reference step …
tomasz-tomczyk Jun 5, 2026
5763d80
Add attachments to the cucumber-messages runner
tomasz-tomczyk Jun 5, 2026
15c09b3
Grade CCK attachment areas in the conformance harness (CCK 33 -> 37)
tomasz-tomczyk Jun 5, 2026
6c2675f
Vendor CCK retry sample areas from pinned SHA
tomasz-tomczyk Jun 5, 2026
d0a435e
Add retry support to the cucumber-messages runner
tomasz-tomczyk Jun 5, 2026
ba196fb
Grade examples-tables-undefined CCK area
tomasz-tomczyk Jun 5, 2026
f1c4897
Bump gherkin dep for MDG descriptions; markdown CCK passes (42 -> 43)
tomasz-tomczyk Jun 5, 2026
467db81
Pin toolchain to Elixir 1.18.1-otp-27 / Erlang 27.0.1
tomasz-tomczyk Jun 5, 2026
e4e3a88
Make error-output assertions tolerant of Elixir 1.20 formatting
tomasz-tomczyk Jun 5, 2026
cb0ea71
Drop excoveralls and CircleCI tooling from mix config
tomasz-tomczyk Jun 5, 2026
a114974
Compare parameter regex by source for Elixir 1.19+
tomasz-tomczyk Jun 5, 2026
a4a6011
Add GitHub Actions CI across Elixir 1.18/1.19/1.20
tomasz-tomczyk Jun 5, 2026
c194715
Rewrite README for the modernized fork
tomasz-tomczyk Jun 5, 2026
ddaee82
Add @spec and docs to remaining public entry points
tomasz-tomczyk Jun 5, 2026
5348ac9
Address review findings: doc accuracy + fail-fast run/3 options
tomasz-tomczyk Jun 5, 2026
ca1d0b9
Decompose Cabbage.Messages.do_run_features into named sections
tomasz-tomczyk Jun 5, 2026
fceae96
Add opt-in compile-time ambiguity check to Cabbage.Feature (#88)
tomasz-tomczyk Jun 5, 2026
28e7ca7
Consolidate legacy {name:type} engine into one frozen mini-engine
tomasz-tomczyk Jun 5, 2026
eff708b
Format messages.ex and named_capture_expression_test.exs
tomasz-tomczyk Jun 5, 2026
9563004
Bump to 1.0.0 and note hex gherkin dep swap for release
tomasz-tomczyk Jun 7, 2026
62b8d60
Add 1.0.0 release docs: CHANGELOG, RELEASING, UPGRADING
tomasz-tomczyk Jun 7, 2026
57fccee
Add upstream MIT licenses for vendored conformance corpora
tomasz-tomczyk Jun 7, 2026
9e2b035
Thread scenario state as a value instead of an Agent
tomasz-tomczyk Jun 8, 2026
2de6688
Add Cabbage.Formatter to emit cucumber-messages from mix test
tomasz-tomczyk Jun 8, 2026
acdc7e8
Apply crit review cleanup for OG proposal
tomasz-tomczyk Jun 9, 2026
31748ee
Polish: two-runner signposting + small dedup (post-audit)
tomasz-tomczyk Jun 9, 2026
cb828fa
chore: retarget metadata to cabbage-ex/cabbage; point gherkin dep at …
tomasz-tomczyk Jun 9, 2026
09257a8
Accept string Cucumber Expressions in Cabbage.Feature step macros
tomasz-tomczyk Jun 10, 2026
def51ae
Add defstep + defand/defbut keyword-agnostic step macros
tomasz-tomczyk Jun 10, 2026
4bcd895
Add Cabbage.Steps module-builder and :import feature option
tomasz-tomczyk Jun 10, 2026
0420e0e
Expose step table/doc-string on the context under reserved keys
tomasz-tomczyk Jun 10, 2026
1d3ad9f
Audit step returns for the contract flip; add pending contract suite
tomasz-tomczyk Jun 10, 2026
b680ee5
Flip the step-return state contract to D3 (fail loud, bare-map replace)
tomasz-tomczyk Jun 10, 2026
fe79fca
Extract Cabbage.StepPattern.classify/2; Feature side adopts it
tomasz-tomczyk Jun 10, 2026
b1752bd
Add Cabbage.StepPattern.match/2; Feature side routes through it
tomasz-tomczyk Jun 10, 2026
5f10d89
Route Messages classification through Cabbage.StepPattern
tomasz-tomczyk Jun 10, 2026
c57cd1b
Docs coherence pass for the 1.0.0 API (T9)
tomasz-tomczyk Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
67 changes: 0 additions & 67 deletions .circleci/config.yml

This file was deleted.

76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: CI

on:
push:
pull_request:

permissions:
contents: read

jobs:
test:
name: Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
include:
- elixir: "1.18.1"
otp: "27.0.1"
- elixir: "1.19.5"
otp: "28.1"
- elixir: "1.20.1"
otp: "27.0.1"

env:
MIX_ENV: test

steps:
- uses: actions/checkout@v6

- name: Set up Elixir / OTP
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}
version-type: strict

- name: Restore deps and _build cache
uses: actions/cache@v5
with:
path: |
deps
_build
# The gherkin dependency is a git ref (no hash in mix.lock changes
# unless the pin moves), so the key folds in mix.lock + the BEAM
# versions and falls back to the latest partial match.
key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-

# Resolves the gherkin fork from GitHub over HTTPS (public repo).
- name: Fetch dependencies
run: mix deps.get

- name: Check formatting
run: mix format --check-formatted

- name: Compile (warnings as errors)
run: mix compile --warnings-as-errors

- name: Run tests
run: mix test

# The conformance.* mix tasks print scoreboards but always exit 0, so they
# are informational here. The hard gate is the tagged ExUnit conformance
# suite below, which asserts the expected passing-sample sets and fails the
# build on any regression.
- name: Conformance scoreboards (informational)
run: |
mix conformance.cck
mix conformance.tags
mix conformance.expressions

- name: Conformance gate (CCK 43/44, tags 64/64, expressions 115/115)
run: mix test --only conformance
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
erlang 27.0.1
elixir 1.18.1-otp-27
137 changes: 137 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,142 @@
# Changelog

## 1.0.0

Cabbage graduates to `1.0.0`. The internals are a ground-up rewrite onto a
spec-conformant Gherkin pipeline and a cucumber-messages runner, and the step
API is overhauled: keyword-agnostic step macros, shared `Cabbage.Steps`
libraries, string Cucumber Expressions, and an explicit step state contract.
Most existing 0.4.1 step modules still compile and run; the one behavioural
change to audit is the state contract (see Breaking). See
[UPGRADING.md](UPGRADING.md).

### Breaking

- **Elixir floor raised** from `~> 1.13` to `~> 1.18`. Cabbage now relies on
the built-in `JSON` module (Elixir 1.18+) and drops the `jason`/`excoveralls`
dependencies entirely.
- **Requires gherkin `~> 3.0`.** The runner consumes the rewritten gherkin
pipeline (`Gherkin.parse!/2` + `Gherkin.pickles/2`); gherkin `2.x` will not
work.
- **Non-standard "valued" tags are rejected.** Tags like `@timeout 100` are not
valid Gherkin and now raise a parse error. Use a plain tag (`@timeout`) plus
step/config logic, or split the value into its own construct.
- **Background steps now EXECUTE.** Previously `Background` steps were silently
broken and effectively skipped; they are now prepended to every scenario in
the feature (via pickle expansion), matching Cucumber semantics. Features that
relied on Background being a no-op may now run those steps.
- **Removed the non-standard `{name:type}` named-capture step sugar.** Patterns
like `defgiven "{count:int} rows", ...` were a cabbage-specific extension that
compiled to a named-capture regex (`(?<count>\d+)`) and bound `count` into the
step's `vars`. It is **not** part of the Cucumber Expressions spec and has been
dropped. Such a pattern now flows to the spec engine and raises an
`Undefined parameter type 'count:int'` compile error. Replace it with a regex
carrying a named capture — `~r/(?<count>\d+) rows/` — to keep the named `vars`
binding. (Spec expressions like `{int}` match, but bind positionally, not by
name.) See `UPGRADING.md`.
- **Internal struct modules renamed:** `Gherkin.Elements.*` →
`Cabbage.Feature.{Document, Scenario, Step}`. This only affects code that
pattern-matched on Cabbage/Gherkin internals; step-definition code is
unaffected.
- **Removed the internal state helpers** `Cabbage.Feature.Helpers.start_state/3`,
`fetch_state/2`, `update_state/3`, and `agent_name/2` (the module is
`@moduledoc false`). Step code never called these; only code reaching into
cabbage internals or relying on the per-scenario Agent process is affected.
- **Explicit step state contract; silent-keep removed.** A step's return value
now drives the context by an explicit rule: `:ok`/`nil` keep it, `{:ok, map}`
merges (delta-merge, the everyday form), a bare `map` **replaces** it, and
`{:error, reason}` or any non-conforming value **raises** (naming the step). The
`{:ok, map}` merge that every 0.4.1 step uses is unchanged, so most modules need
no edits. The behavioural change to audit: a step that previously returned a
stray non-conforming value and relied on the old contract *silently keeping*
state now raises — add an explicit `:ok` to such steps. Returning a struct (bare
or `{:ok, struct}`) also raises: the context must be a plain map. The reserved
context keys `:__table__`/`:__doc_string__` are stripped from threaded state.

### Changed

- **Scenario state is now threaded as a value, not held in an `Agent`.** The
`Feature` runner compiles each step to a `fn context -> next_context end` and
threads the scenario's context through them with a reduce. Previously state lived
in a per-scenario `Agent` registered under a global name.
- **`async: true` is now safe by construction.** The old global Agent name
(`cabbage_integration_test-<scenario>-<module>`) could collide and leak state
between same-named scenarios / re-runs; threaded state is per-invocation.
- **No more leaked processes.** The Agent was started and never stopped.

### Source-compatible

- Existing step definitions compile as-is — `use Cabbage.Feature, file: ...,
template: ...`, `defgiven`/`defwhen`/`defthen`, `import_feature/1`,
`import_steps/1`, `import_tags/1`, and `tag/2` are all retained — provided steps
return a contract-conforming value (`:ok`/`nil`/`{:ok, map}`/a map). See the state
contract under Breaking.

### Added

- **Keyword-agnostic step macros.** `defstep/3,4` is the canonical, keyword-neutral
step macro; matching is by **pattern only** (the `Given`/`When`/`Then` keyword
never gates a match). `defgiven`/`defwhen`/`defthen` are kept and `defand`/`defbut`
are added, all as readability aliases that register identically.
- **`Cabbage.Steps` — shared step libraries.** `use Cabbage.Steps` builds a module
of reusable step definitions that is *not* an ExUnit case and generates no tests.
Import it into a feature with `use Cabbage.Feature, ..., import: [Mod, ...]`. Local
steps win first-match on a same-pattern collision; imported steps follow in a
deterministic order; importing a non-step module raises a clear error; step
libraries compose transitively.
- **Reserved context keys for tables/doc strings.** A step's gherkin data table and
doc string are reachable from the context as `ctx.__table__` and
`ctx.__doc_string__` (empty list / empty string when absent), injected per step and
not threaded forward — the uniform path for Cucumber Expression steps.

- **Pickle-based execution.** Background prepending, `Rule` flattening, and
Scenario Outline expansion now come "for free" from the gherkin pickle
compiler instead of bespoke Cabbage logic.
- **Full Cucumber Expressions engine** (`{int}`, `{word}`, `{string}`,
custom/optional/alternation), validated at **115/115** against the upstream
cucumber-expressions conformance corpus.
- **String Cucumber Expression patterns in `Cabbage.Feature` step macros.**
`defgiven`/`defwhen`/`defthen` now accept a string Cucumber Expression
(`defgiven "I have {int} cucumbers", [count], state`) alongside `~r/regex/`.
The expression is matched through the engine above, so arguments arrive as a
positional list of *transformed* values (`{int}` -> integer, `{float}` ->
float, `{string}`/`{word}` -> string). Regex patterns are unchanged and still
bind a named-captures map. (Built-in parameter types only; there is no
public custom-parameter-type API for `Cabbage.Feature`.)
- **Tag Expressions engine** (`and`/`or`/`not`/parentheses), validated at
**64/64** against the upstream tag-expressions corpus.
- **Shared pattern core (`Cabbage.StepPattern`).** Pattern classification (regex
vs Cucumber Expression vs literal) and match extraction are factored into one
internal module used by both the `Cabbage.Feature` compile-time runner and the
`Cabbage.Messages` runtime runner, removing duplicated matching logic. (The two
execution paths remain separate; full convergence is a future release.)
- **cucumber-messages runner** (`Cabbage.Messages`) with hooks, attachments,
retry, and parameter-types support — validated at **43/44** against the
Cucumber Compatibility Kit (CCK) (the 44th asserts a run-level crash and is
deferred as `unsupported`).
- **Opt-in ambiguous-step handling** via `on_ambiguous_step:` in
`@feature_options` (default preserves prior last-match-wins behaviour).
- **`Cabbage.Formatter` — an ExUnit formatter that emits a cucumber-messages
NDJSON stream from a normal `mix test` run.** Enable it alongside the default
CLI formatter:

ExUnit.start(formatters: [ExUnit.CLIFormatter, Cabbage.Formatter])

For every `Cabbage.Feature` scenario it writes `meta`, per-feature
`source`/`gherkinDocument`/`pickle`, `testRunStarted`, and per-scenario
`testCase`/`testCaseStarted`/`testStepStarted`/`testStepFinished`/`testCaseFinished`,
closing with `testRunFinished` whose `success` flag reflects the run. Output
defaults to `cucumber-messages.ndjson` and is configurable via
`config :cabbage, messages_output: "path.ndjson"` or the formatter's
`:messages_output` option. The parser-side envelopes reuse the gherkin
dependency's `Gherkin.Message` builders.
- **Step results are scenario-level.** Because cabbage runs every step inside one
generated ExUnit test, each step is attributed the scenario's outcome uniformly
(all `PASSED`, all `FAILED`, or all `SKIPPED`); the run-level `success` flag is
exact. Granular per-step attribution is a planned follow-up.

See [UPGRADING.md](UPGRADING.md) for a 0.4.1 → 1.0.0 migration guide.

### v0.4.1

- Updated dependencies, remove dependency on retired `gherkin 1.6.1`
Expand Down
Loading