Testing

A rewrite is only worth it if nobody can tell from the HTML. Slim itself is the reference: wherever possible, a test renders through both engines and requires the same bytes.

Slim’s own suites

Slim’s test suite and its literate specification, TESTS.md, run against SlimX unchanged: 295 tests pass. The others test what SlimX leaves out on purpose, or Markdown output that depends on which library Tilt picks — and one pins a Slim bug, a = reported one line too low after a ruby: block.

Fuzzer

A seeded generator writes random templates from the grammar — nested tags, shortcuts, every attribute form, splats, control flow, blocks, comments, embedded engines, whitespace markers — and renders each through Slim and SlimX. The HTML must match, or both must fail with the same error. A second pass formats each template and requires the same HTML and an idempotent result.

The suite runs a fixed seed; CI adds 10,000 templates from a new seed on every run, and prints it when they disagree.

Golden snapshots

Nineteen templates cover the grammar, its errors and Tailwind class names. Next to each, a snapshot holds the rendered HTML, the Ruby compiled for Tilt and for Rails, the formatted source and the lint offenses: any change shows up in review. Whatever its snapshot says, each template must also:

  • render the HTML Slim renders, with and without html_safe, or fail with Slim’s message;
  • render the same through Rails and through Tilt;
  • compile to Ruby that parses, with every - and = on its template line;
  • format idempotently, to the same HTML, passing strict mode.

Real templates

6,097 templates from 30 open-source projects, pinned to a commit in corpus/manifest.yml. rake corpus:fetch downloads only their .slim files; they are never copied into the repository. For each one:

  • Slim and SlimX both compile it, or both refuse it;
  • SlimX’s Ruby parses, with every line where it belongs;
  • slimx fmt is idempotent, and refuses any change to the compiled Ruby;
  • the linter runs, and its offenses go to a report.

Downloaded templates are never rendered: rendering runs their Ruby. A production Rails app of about 1,400 templates, whose code we trust, is rendered through both engines against a stub that answers every helper, before and after formatting, with identical HTML.

rake corpus:diff BASE=main compares two revisions over the whole corpus — compiled Ruby, formatting, lint offenses — so a refactoring that changes nothing proves it.

Every commit

GitHub Actions runs the suite and Slim’s suites on every maintained Ruby (3.3, 3.4 and 4.0), ruby-head and macOS, the fuzzer with a new seed, and the corpus check; on a pull request, the corpus diff against its base too. It takes under a minute.

bundle exec rake test
FUZZ_SEED=42 FUZZ_CASES=20000 bundle exec ruby -Ilib -Itest test/fuzz_test.rb
bundle exec rake corpus:fetch corpus:check