liquid-spec is both an acceptance suite and an implementation system for Liquid. It can verify an existing parser and renderer, but it is also designed to guide a human or coding agent from an empty project to a production-ready Liquid implementation, one observable behavior at a time.
The suite contains thousands of executable examples drawn from Shopify's reference implementation, a curated beginner ramp, parser-error matrices, Dawn theme fixtures, and production recordings. The adapter boundary works with any implementation strategy and, over JSON-RPC, any programming language.
A conventional conformance suite answers “is this implementation compatible?” liquid-spec also answers:
- What is the smallest useful behavior to implement next?
- Why does Liquid behave this way?
- Which earlier behavior did this change regress?
- How far has the implementation progressed from a text passthrough to production compatibility?
Every spec has a complexity from 0 to 1000. Foundation cases come first: empty input,
literal text, and object output. Variables, filters, control flow, loops, scopes,
partials, parser modes, compatibility quirks, and production recordings follow. The
runner reports the lowest-complexity failures as “Next best specs to work on” and
shows an implementation hint with each failure.
That turns the suite into an executable curriculum:
run liquid-spec
│
▼
read the first failure + hint ──► implement the general rule
▲ │
└──────── rerun every earlier spec ◄───┘
Complexity level cleared: 70 of 1000 means all exercised levels before the first
failing level are solid. It is deliberately more honest than a raw pass count: a toy
renderer may accidentally pass hundreds of empty-output cases without clearing the
beginner ramp.
liquid-spec gives an agent the ingredients that open-ended “implement Liquid” prompts normally lack:
- A stable, observable contract. Specs describe templates, input values, filesystems, expected output, and expected errors—not a required internal architecture. An AST interpreter, bytecode VM, compiler, or transpiler can follow the same path.
- A prerequisite-ordered search space. Complexity scoring reduces a language-sized task to a small next step. Later behavior is introduced after the semantics it builds on.
- Just-in-time implementation guidance. Curated failures include actionable hints,
and
liquid-spec docssupplies deeper implementer guides for values, grammar, scopes, filters, loops, partials, parsing, and quirks. - A tight verification loop. Every change is immediately checked against all earlier behavior. The agent cannot make apparent progress by silently trading one feature for another.
- Reference and production evidence.
liquid-spec tools eval --compareanswers ambiguous questions against Shopify/liquid, while integration tests and production recordings prevent a classroom-only implementation from looking complete. - Explicit scope. Feature gates distinguish portable Liquid, legacy parser modes, Ruby-specific behavior, and Shopify extensions. Unsupported features are visible debt, not noise hidden among failures.
- Machine-readable operation.
--json, name/suite filters, inspection, and focused eval commands let an agent gather precise evidence and iterate without scraping an unstructured test log.
liquid-spec init makes this workflow agent-ready. It generates disposable adapter
shims plus an AGENTS.md that explains the loop, hard rules, architecture advice,
protocol, feature gates, and documentation commands. Your Liquid package remains a
standalone library; the adapter exists only to let liquid-spec exercise it.
YAML behavior specs
(template, environment, filesystem, expected output/error)
│
▼
liquid-spec runner
│
compile + render calls
│
┌────────────┴────────────┐
▼ ▼
direct Ruby adapter JSON-RPC adapter
(TypeScript, Rust,
Go, Python, ...)
│ │
└────────────┬────────────┘
▼
your Liquid engine
For each case, liquid-spec compiles the source, renders it with the recorded environment and filesystem, then compares output or errors with the accepted behavior. The bridge is small enough that results describe the engine rather than the test integration.
Add to your Gemfile:
gem "liquid-spec", git: "https://github.com/Shopify/liquid-spec"Then run:
bundle installOr install directly from GitHub:
gem install specific_install
gem specific_install https://github.com/Shopify/liquid-spec# Generate both adapter choices and an agent implementation guide.
liquid-spec init
# Read the curriculum, then run the adapter for your language.
liquid-spec docs curriculum
liquid-spec run specs/adapter.rb # Ruby
liquid-spec run specs/adapter-jsonrpc.rb \
--command="your-liquid-jsonrpc-server" # any other languageThe first failure is the next lesson. Implement the behavior described by its hint and
rerun the same command. Re-running init leaves identical generated files alone; when a
target differs, it asks before overwriting and defaults to No.
The complete bootstrap is intentionally small. Start in an empty directory:
mkdir liquid-typescript
cd liquid-typescript
gem install specific_install
gem specific_install https://github.com/Shopify/liquid-spec
# Creates specs/adapter.rb, specs/adapter-jsonrpc.rb, and AGENTS.md.
# The JSON-RPC adapter is the bridge the TypeScript implementation will use.
liquid-spec init
codex -p "/goal Implement a full production-ready Liquid implementation in TypeScript. \
Read AGENTS.md first. Use specs/adapter-jsonrpc.rb only as the test bridge; build the \
engine as a standalone TypeScript library. Ask liquid-spec for guidance on the next \
steps, implement the general behavior behind each lowest-complexity failure, and rerun \
the suite after every change. Do not special-case specs or hide required behavior with \
missing_features. Keep going until liquid-spec reports Complexity level cleared: \
1000 of 1000 for every applicable suite."That is enough context because liquid-spec init wrote the detailed operating manual
into AGENTS.md. The agent can discover and use the whole feedback loop itself:
# After setting DEFAULT_COMMAND in specs/adapter-jsonrpc.rb to the TypeScript server:
liquid-spec run specs/adapter-jsonrpc.rb
liquid-spec tools inspect specs/adapter-jsonrpc.rb -n "the_failing_spec"
liquid-spec docs curriculum
liquid-spec docs core-abstractions
cat scratch.yml | liquid-spec tools eval specs/adapter-jsonrpc.rb --compare
liquid-spec run specs/adapter-jsonrpc.rb --jsonThe generated JSON-RPC adapter initially opts out of capabilities that cannot be carried
portably or have not been wired yet, such as Ruby-only values, standard test drops, and
Shopify extensions. Reaching 1000 of 1000 means every spec selected for the adapter
passes; a production target also requires reviewing missing_features and removing each
entry that belongs in that target. Use liquid-spec tools features to audit that scope.
For a human-driven implementation, use exactly the same loop: run, read the first hint,
implement, and rerun. liquid-spec docs json-rpc-protocol documents the four subprocess
methods (initialize, compile, render, and quit).
An adapter is a small Ruby file that tells liquid-spec how to use your implementation:
#!/usr/bin/env ruby
require "liquid/spec/cli/adapter_dsl"
# Load your implementation; ctx carries compiled state between callbacks.
LiquidSpec.setup do |ctx|
require "my_liquid"
end
# Declare what your adapter can't handle (default: run everything)
LiquidSpec.configure do |config|
config.missing_features = [:shopify_tags, :shopify_filters]
end
# Parse template source and retain the result in the adapter context.
LiquidSpec.compile do |ctx, source, options|
# options includes: :line_numbers, :error_mode
ctx[:template] = MyLiquid::Template.parse(source, **options)
end
# Render the template stored by compile or load_artifact.
LiquidSpec.render do |ctx, assigns, options|
# assigns = variables hash
# options includes: :registers, :strict_errors, :error_mode, :exception_renderer
ctx[:template].render(assigns, **options)
endThe options hash in render includes:
:registers- Hash with:file_systemand:template_factory:strict_errors- If true, raise errors; if false, render them inline:exception_renderer- Custom exception handler (optional)
Use liquid-spec init --jsonrpc my_adapter.rb when your Liquid engine is written in Rust, Go, Python, Node.js, or another language. The generated Ruby adapter launches your server as a subprocess and talks JSON-RPC over stdin/stdout.
Key setup points:
- Your server implements
initialize,compile,render, andquit. - Server debug logs go to stderr; stdout must contain only newline-delimited JSON-RPC messages.
- The adapter controls spec selection with
config.missing_features; server-reportedfeaturesare informational. - Minimal JSON-RPC adapters should usually opt out of unsupported or non-portable features such as
:drops,:ruby_types,:ruby_drops,:drop_class_output,:self_environment_shadowing,:binary_data, and:template_factory, plus Shopify-specific features. - Remove
:dropswhen the engine supports the standard test-drop library. Bidirectional runtime objects can use the protocol'sdrop_get,drop_call, anddrop_iteratecallbacks. - Read
docs/json-rpc-protocol.mdfor the exact message format and error-handling rules.
liquid-spec init --jsonrpc my_adapter.rb
liquid-spec run my_adapter.rb --command="./my-liquid-server"
liquid-spec run my_adapter.rb --json --list-passed > results.jsonSome Liquid implementations compile source once, store executable bytecode or
an equivalent compiled representation in a shared cache, and load those bytes
in application processes that never receive the source. compile and render
alone cannot measure that important production path: parsing again is not an
artifact-cache hit, while repeatedly rendering a resident template omits the
load and first-use costs.
If your implementation has such a persistent compiled format, declare both hooks:
# Called immediately after LiquidSpec.compile, before the template is rendered.
# Return the exact binary String you would put in memcache, a database, etc.
LiquidSpec.dump_artifact do |ctx|
ctx[:template].to_artifact
end
# Called with the artifact bytes and no source recompilation.
# Restore all adapter state expected by the regular LiquidSpec.render hook.
LiquidSpec.load_artifact do |ctx, bytes, _options|
ctx[:template] = MyLiquid::Artifact.load(bytes)
endThe contract is deliberately production-oriented:
dump_artifactreceives the state produced bycompileand must return a binary-safeString. It is invoked before any validation render, because rendering is allowed to mutate template runtime state.- The returned bytes must contain all immutable compile-time information needed to load the template without its source. Do not capture assigns, observed values, request objects, or render-time object shapes.
load_artifactmust leavectxin the same renderable state thatcompilewould. The source is unavailable. Its third argument is the runtime options Hash that the followingrenderhook will receive; most loaders ignore it.- Assigns, registers, and runtime filesystems are still supplied to the normal
renderhook for every call; they are not part of the artifact.
With both hooks, --bench validates the dump → load → render roundtrip, reports
raw artifact bytes and steady-state load diagnostics, and measures atomic source
compile + first-render and artifact-load + first-render workflows with 10
interleaved samples in the adapter process. Each sample invokes compile or load
before its first render, but process-level runtime, JIT, and global caches remain
warm. The harness intentionally does not include process startup or IPC.
--bench warns when either hook is missing because artifact size and
load+first-render results will be omitted. Implementations without a persistent
compiled format may leave the hooks absent; the warning documents that the
benchmark is then limited to source compile and resident render paths.
Projects can ship their own spec/benchmark suites alongside their adapter:
any ./specs/<name>/suite.yml directory in the invoking project is
discovered next to the gem's builtin suites and selected the same way
(-s <name>). Set timings: true in suite.yml to make it benchmarkable
with liquid-spec bench ADAPTER -s <name>, and default: false to keep it
out of regular runs.
| Suite | Tests | Description |
|---|---|---|
| basics | 941 | Essential Liquid features - start here! Ordered by complexity with implementation hints |
| liquid_ruby | 2,097 | Core Liquid specs from Shopify/liquid integration tests |
| liquid_ruby_lax | 121 | Lax-mode reference behavior |
| parser_errors | 1,905 | Strict parser error compatibility and mutation matrices |
| partials | 12 | Include/render focused compatibility specs and timings |
| benchmarks | 10 | Storefront, dynamic-partial, {% liquid %}, and Shopify-theme performance cases |
| shopify_production_recordings | 2,260 | Recorded behavior from Shopify's production Liquid compiler |
| shopify_theme_dawn | 26 | Real-world templates from Shopify Dawn theme |
If you're building a new Liquid implementation, start with the basics suite. It runs first and covers all fundamental features from the official Liquid documentation.
Specs are ordered by complexity so you can implement features progressively. The goal is a smooth ramp: a toy renderer should pass only the trivial first specs, then fail on a small, actionable next behavior.
| Complexity | Features |
|---|---|
| 0-1 | Empty template and literal passthrough |
| 5-20 | First object output, literal strings/numbers/booleans/nil |
| 30-50 | Variables, missing variables, very simple filters, assign |
| 55-65 | Basic if/else/unless and simple boolean composition |
| 70-100 | Gentle loops, comparisons, forloop basics, capture, simple case/when |
| 105-150 | Common filters/tags, comments/raw, interrupts, loop modifiers, whitespace control |
| 160-220 | Generated filter breadth, truthy/falsy edges, cycle/tablerow, first partials/filesystem |
| 230-400 | Long-tail standard behavior and parser/scope/filesystem edge cases |
| 500-900 | Mature compatibility: parser mutations, resource-limit accounting, recursion/deep nesting, date/time/Ruby quirks |
| 1000 | Production recordings and unscored specs |
Each non-trivial spec includes a detailed hint explaining how the feature should be implemented. If the first failure is surprising or unactionable, the spec probably needs a better hint or a higher complexity score.
Read Complexity level cleared, not just total passes. A naive adapter that always returns "" can accidentally pass many later specs whose expected output is empty, but its cleared complexity level should remain at 0. The complexity-level line tells you how far the implementation progressed through the ordered curriculum.
Suites marked as default run unless you select one explicitly with -s. Declare what
your adapter cannot handle to skip specs that require those capabilities:
LiquidSpec.configure do |config|
# Run everything (default — empty denylist)
config.missing_features = []
# Skip Shopify-specific specs (for adapters without Shopify extensions)
config.missing_features = [:shopify_tags, :shopify_objects, :shopify_filters]
endAfter the recorded ramp passes, generate nearby cases and compare them directly with Shopify/liquid:
liquid-spec tools mutate adapter.rb --around=for_loops --limit=100
liquid-spec tools fuzz adapter.rb --seed=1234 --rounds=500 --minimize
liquid-spec tools stress adapter.rb --depth=64 --repetitions=100mutate deterministically changes existing specs; fuzz reproducibly chains random
mutations; stress generates bounded valid nesting and repetition. Differences are saved
as runnable YAML regression specs by default. These commands cover whitespace controls,
literal boundaries, lookups, filters, conditionals, loop options, malformed block
structure, opaque bodies, Unicode, and newlines.
This is differential corpus mutation, not native coverage-guided fuzzing. See
liquid-spec docs adversarial for comparison semantics, seed selection, JSON output,
minimization, and how to curate a generated discovery into the permanent suite.
liquid-spec COMMAND [options]
Core commands:
liquid-spec init [FILE] Generate adapters and AGENTS.md
liquid-spec docs [NAME] Read implementer documentation
liquid-spec run ADAPTER Run the acceptance ramp
liquid-spec bench [ADAPTER] Benchmark implementations
Tool collection (`liquid-spec tools help`):
liquid-spec tools inspect ADAPTER Inspect matching specs in detail
liquid-spec tools eval ADAPTER Evaluate one YAML spec
liquid-spec tools matrix Compare adapters side-by-side
liquid-spec tools test Exercise bundled example adapters
liquid-spec tools features Audit feature tags and scope
liquid-spec tools report Analyze benchmark results
liquid-spec tools check Run every verifier
liquid-spec tools mutate ADAPTER Deterministic differential mutations
liquid-spec tools fuzz ADAPTER Seeded differential fuzz-style testing
liquid-spec tools stress ADAPTER Bounded structural stress
Run options:
-n, --name PATTERN Only run specs matching PATTERN
-s, --suite SUITE Run specific suite (liquid_ruby, benchmarks, etc.)
-c, --compare Compare output against reference liquid-ruby
-v, --verbose Show detailed output
-l, --list List available specs
--list-suites List available test suites
--max-failures N Stop after N failures (default: 5)
--no-max-failures Run all specs without stopping
--list-passed List specs that passed after the run (ramp/debug audits)
--json Output a single JSON summary (for tools)
--jsonl Output one JSON event per line (for benchmark streaming/tools)
-h, --help Show help
Examples:
liquid-spec run my_adapter.rb # Run all applicable specs
liquid-spec run my_adapter.rb -n for_tag # Run specs matching 'for_tag'
liquid-spec run my_adapter.rb -s liquid_ruby # Run only liquid_ruby suite
liquid-spec run my_adapter.rb --compare # Compare against reference
liquid-spec run my_adapter.rb --no-max-failures # See all failures
liquid-spec bench my_adapter.rb -n storefront # Benchmark adapter vs reference
liquid-spec tools check # Run every spec verifier
liquid-spec tools features # Audit supported feature scope
liquid-spec tools inspect my_adapter.rb -n "case" # Debug specific specs
liquid-spec tools mutate my_adapter.rb --around=for_loops
liquid-spec tools fuzz my_adapter.rb --seed=1234 --jsonWhen changing complexity scores or adding early specs, test the harness with intentionally bad adapters:
- an adapter that returns the template source unchanged
- an adapter that always returns
"" - an adapter that raises during compile or render
Use --list-passed to see accidental passes and --json for machine-readable analysis:
liquid-spec run /tmp/echo_adapter.rb --list-passed
liquid-spec run /tmp/empty_adapter.rb --json --list-passed > empty-results.jsonA source-echo adapter should only pass raw-text specs before failing on first object output. An always-empty adapter may pass many empty-output specs, so judge progress by Complexity level cleared (or JSON max_complexity_reached), not by total passes.
The matrix command runs specs across multiple adapters simultaneously and shows differences between implementations. This is useful for comparing behavior across different Liquid implementations or configurations.
liquid-spec tools matrix [options]
Options:
--all Run all default adapters from examples/
--adapters=LIST Comma-separated list of adapters
--reference=NAME Reference adapter (default: liquid_ruby)
-n, --name PATTERN Filter specs by name pattern
-s, --suite SUITE Spec suite to run
--max-failures N Stop after N differences (default: 10)
--no-max-failures Show all differences
-v, --verbose Show detailed output
Examples:
# Compare the default bundled adapters
liquid-spec tools matrix --all
# Compare specific adapters
liquid-spec tools matrix --adapters=liquid_ruby,liquid_ruby_lax
# Compare adapters on specific tests
liquid-spec tools matrix --adapters=liquid_ruby,my_adapter.rb -n truncateOutput shows which adapters produce different results for each spec:
Running 100 specs: ....F....F.. done
======================================================================
DIFFERENCES
======================================================================
----------------------------------------------------------------------
1. TruncateTest#test_truncate_with_custom_ellipsis
Template:
{{ text | truncate: 10, "..." }}
Adapters: liquid_ruby
Output:
"Hello w..."
Adapters: my_adapter
Output:
"Hello wo..."
======================================================================
liquid-spec includes a benchmark suite for measuring and comparing implementation performance. Benchmarks measure compile and render times separately, with statistical analysis including mean, standard deviation, and min/max ranges.
Run benchmarks against a single adapter to measure its performance:
liquid-spec bench examples/liquid_ruby.rb -n leaderboardAbbreviated output:
liquid_ruby — Benchmarks
Ruby 4.x (no-jit) │ 1 specs │ 5s/spec
Benchmark 1/1: liquid_tag_leaderboard
Parse (mean ± σ): 270µs ± 38µs [893 allocs, 9.3k runs]
Render (mean ± σ): 564µs ± 116µs [926 allocs, 4.4k runs]
Range (min … max): 530µs … 3.79ms [4.4k runs]
Cold (@1 / @10): 664µs / 618µs (1.2x vs warm)
1 passed
Benchmarks report parse/render distributions, allocation counts, cold-render behavior, and iteration totals. GC is disabled during timing to reduce measurement jitter.
Compare performance across different implementations using the core bench command:
liquid-spec bench --adapters=liquid_ruby,liquid_ruby_laxEach benchmark runs against all adapters. The command prints their measurements together,
then reports geometric-mean parse and render comparisons across common specs. When a
comparison mixes inline adapters with JSON-RPC adapters, bench warns that subprocess
and protocol overhead make the timings non-equivalent.
Benchmark 1/10: storefront_product_page
liquid_ruby Parse ... Render ...
liquid_ruby_lax Parse ... Render ...
→ liquid_ruby is 1.08x faster
──────────────────────────────────────────────────────────────────────
Comparison (10 common specs, reference: liquid_ruby)
Parse (geometric mean):
liquid_ruby is 1.05x faster than liquid_ruby_lax
Render (geometric mean):
liquid_ruby_lax ≈ liquid_ruby
The benchmark suite currently includes 10 realistic templates:
| Benchmark | Description |
|---|---|
bench_dynamic_partials |
Data-selected partials, loops, and three levels of nested includes |
bench_liquid_tag_inventory_report |
Inventory aggregation entirely inside a {% liquid %} block |
bench_liquid_tag_leaderboard |
Nested-loop ranking and formatting in {% liquid %} syntax |
bench_storefront_product_page |
Standard-Liquid product page with variants, reviews, and partials |
bench_storefront_collection_page |
Standard-Liquid collection browsing and product grids |
bench_storefront_cart_page |
Standard-Liquid cart totals, discounts, and line items |
bench_storefront_order_email |
Standard-Liquid transactional order email |
bench_storefront_cms_page |
Standard-Liquid content-management page |
shopify_theme_full_page |
Shopify-shaped Dream theme layout using portable Liquid |
shopify_theme_product_page |
Shopify-shaped Dream product page using portable Liquid |
Selecting these specs through liquid-spec run ADAPTER -s benchmarks checks correctness
without collecting timings; use liquid-spec bench for performance measurements.
Use liquid-spec bench --profile to generate StackProf profiles for detailed performance analysis:
# Single adapter profiling
liquid-spec bench examples/liquid_ruby.rb --profile
# Multi-adapter profiling
liquid-spec bench --adapters=liquid_ruby,liquid_ruby_lax --profileA single-adapter profile directory contains four dumps:
/tmp/liquid-spec-profile-20260710_155637/
├── compile_cpu.dump
├── compile_object.dump
├── render_cpu.dump
└── render_object.dump
Inspect one with stackprof /tmp/liquid-spec-profile-20260710_155637/render_cpu.dump.
In multi-adapter bench mode, each adapter gets a separate directory such as
/tmp/liquid-spec-profile-{timestamp}-liquid_ruby/; the command prints every path.
Profile types:
*_cpu.dump- CPU time profiles (where time is spent)*_object.dump- Object allocation profiles (where allocations happen)
The eval tool lets you quickly test individual templates. Specs are passed via YAML
on stdin or --spec=FILE; add --compare to compare with the reference liquid-ruby
implementation and fill omitted expectations:
liquid-spec tools eval examples/liquid_ruby.rb --compare <<EOF
name: upcase-test
complexity: 20
template: "{{ x | upcase }}"
expected: "HI"
environment:
x: hi
hint: "Test upcase filter on simple string variable"
EOFOutput:
upcase-test
Test upcase filter on simple string variable
Template: {{ x | upcase }}
Complexity: 20
✓ PASS (matches reference)
"HI"
Saved to: /tmp/liquid-spec-2026-01-02.yml
When using --compare, the expected field can be omitted—it will be filled from the reference implementation. If your implementation differs from the reference, you'll see a prominent message encouraging you to contribute the spec.
Specs are automatically saved to /tmp/liquid-spec-{date}.yml for easy contribution back to liquid-spec.
Default run output is intentionally concise: it shows the lowest-complexity failures
(the next specs to work on) and then one summary line.
Failing run:
$ liquid-spec run my_adapter.rb --max-failures 1
Next best specs to work on:
1) [c=5] object_string_literal
Template: "{{ 'world' }}"
Expected: "world"
Got: "{{ 'world' }}"
Hint: The {{ }} syntax outputs the value of an expression...
Complexity level cleared: 1 of 5, 2 passes, 1 failures.
Successful run (counts depend on selected suites/features):
$ liquid-spec run my_adapter.rb
Complexity level cleared: 1000 of 1000, 1234 passes, 0 failures, 12 skipped.
Congrats! All run specs passed.
Use -v for preamble, per-suite progress, and skipped-suite details.
See the examples/ directory:
liquid_ruby.rb- Standard Shopify/liquid gemliquid_ruby_lax.rb- Shopify/liquid configured for lax-mode compatibility specsliquid_ruby_shopify.rb- Shopify-flavored Liquid behaviorjson_rpc_ruby_liquid.rb- JSON-RPC adapter backed by Shopify/liquidliquid_c.rb/liquid_c_strict.rb- Legacy, explicit opt-in liquid-c examples; excluded from default adapter runsliquid_ruby_yjit.rb/liquid_ruby_zjit.rb- Ruby JIT benchmark variants
liquid-spec run examples/liquid_ruby.rbSpecs are YAML files with this structure:
- name: AssignTest#test_assign_with_filter
template: '{% assign foo = values | split: "," %}{{ foo[1] }}'
environment:
values: "foo,bar,baz"
expected: "bar"
complexity: 50
hint: |
The assign tag creates a variable. Filters can be used in the expression.Each spec defines:
- template - Liquid source to compile and render
- environment - Variables available during rendering
- expected - Expected output string
- complexity - Optional: ordering hint (lower = simpler, runs first; defaults to 1000 and must not exceed 1000)
- hint - Optional: implementation guidance for this feature
- error_mode - Optional:
lax,strict,strict2, or an array of compatible modes - filesystem - Optional: mock files for include/render tags
# Clone
git clone https://github.com/Shopify/liquid-spec.git
cd liquid-spec
# Install dependencies
bundle install
# Run every verifier (the Rake task is equivalent)
liquid-spec tools check
rake check
# Unit tests for liquid-spec itself
rake test
# Run specs against the Shopify/liquid reference adapter
bundle exec rake run
# Regenerate specs from Shopify/liquid source
# (requires ../liquid directory with Shopify/liquid checked out)
bundle exec rake generateThe rake generate task:
- Clones Shopify/liquid at the current version tag
- Patches its test suite to capture template/expected pairs
- Runs the tests and records every
assert_template_resultcall - Writes captured specs to
specs/liquid_ruby/
This ensures specs stay synchronized with the reference implementation.
MIT