ruby/spec is not a language specification, it is the shared test suite that keeps Ruby implementations honest
The Ruby Spec Suite aka ruby/spec
At a glance
- What is it?
- The Ruby Spec Suite describes and tests the behaviour of the Ruby language as executable code with a textual description attached to every example, run by MSpec, and its results are checked on every commit by MRI, JRuby, TruffleRuby, Opal and Artichoke. It deliberately does not aim to be an ISO style standard, and it synchronises with the implementations about once a month, which is the detail that matters when you read a failure.
- Who is it for?
- Use ruby/spec when you are implementing Ruby, porting it, or writing code that depends on subtle behaviour, because it is the only place where the implementations agree on what Ruby does down to the wording of a failure. Do not treat it as a language specification, since the project says it is not one and does not aim to become one.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A test suite that describes Ruby, not a standard
The Ruby Spec Suite, abbreviated ruby/spec, is a test suite for the behaviour of the Ruby programming language. The distinction in that last sentence is the whole point of the project.
The documentation is explicit that it is not a standardized specification like the ISO one, and does not aim to become one. Instead, it is a practical tool to describe and test the behaviour of Ruby with code. The specs are written with syntax similar to RSpec 2, and they are run with MSpec, the purpose-built framework for running this suite, whose project lives in a separate repository.
That framing is not a lack of ambition, it is a different goal. An ISO style specification describes a language in prose so that several independent implementations can be written against it. ruby/spec describes a language in executable examples so that existing implementations can be checked against each other. The second is far more useful in practice, because a disagreement between two Ruby implementations shows up as a failing example rather than as an argument in a document.
Every example carries a textual description, and the documentation lists three advantages of that. It is easier to understand the intent of the author, it documents how recent versions of Ruby should behave, and it helps Ruby implementations to agree on a common behaviour.
That third point is the one that makes ruby/spec an infrastructure project rather than a test corpus. The behaviour is not described once for the benefit of one implementation, it is described in a form that several implementations can be measured against.
Five areas of Ruby, from syntax to command line flags
The suite covers the language, the libraries, and the edges of the runtime, and the documentation lists five areas.
The language syntax lives in the language/ directory. The core library is in core/, and the standard library in library/. There is a directory for the C API for extensions, optional/capi, and one for the command line flags, command_line/.
The organisation differs by area, and the reason is instructive. The language specs are grouped by keyword, so you find everything about a given construct in one place, for example how a method definition behaves. The core and standard library specs are grouped by class and method, which is the shape a user of the library expects when they want to know what an object does.
The repository layout follows the same logic. Alongside those directories there is shared/ for code used by more than one area, fixtures/ for test data, a security/ directory, an optional/ directory that holds the C API specs, and the spec_helper.rb file that every example loads. There is a default.mspec file that defines the groups, a bin/ directory, a .mspec.constants file, rubocop configuration for the style of the spec code itself, a TODO file, and CONTRIBUTING.md for the rules on writing new examples.
That last detail, the rubocop configuration, matters more than it looks. The specs are a codebase that other codebases depend on, so their style has to be consistent, and the project enforces it with a linter rather than by convention. There is also a .rubocop_todo.yml, which is the usual way of recording the existing exceptions rather than pretending there are none.
The suite describes the behaviour of Ruby 3.3 and more recent versions. Every latest stable MRI release should pass all of the specs, and those runs happen in CI.
Running the suite locally takes two clones
The documented way to run the specs is short enough to do while you read this.
First clone the repository:
$ git clone https://github.com/ruby/spec.gitThen move into it and clone MSpec next to it, as a sibling directory:
$ git clone https://github.com/ruby/mspec.git ../mspecAnd run the suite:
$ ../mspec/bin/mspecThat executes all the specs using the executable named ruby on your current PATH, so the practical requirement is simply that a ruby you want to test is the one first on your PATH.
To run against a specific implementation, use the -t option. The argument is either a full path to the Ruby binary, or an executable found in PATH:
$ ../mspec/bin/mspec -t /path/to/some/bin/rubyThe selection options are the same shape. A single spec file can be passed by name:
$ ../mspec/bin/mspec core/kernel/kind_of_spec.rbA directory works too, and runs everything inside it:
$ ../mspec/bin/mspec core/kernelAnd the groups defined in default.mspec can be run by name, which is the fastest way to narrow a failure to one area:
$ ../mspec/bin/mspec :languageThere are similar commands for the core, library and capi groups. So the workflow when you are chasing a specific failure is to run the group first, then the directory, then the file, and each step is a single argument.
The licence is MIT, the repository is not archived, and the last push was on 2026-09-29. There are no GitHub releases, which is correct for a repository whose unit of distribution is a commit rather than a version.
Test the implementation's copy, not this repository
The documentation contains one instruction that is easy to skim past and expensive to ignore.
The specs are synchronized both ways around once a month by @andrykonchin between ruby/spec, MRI, JRuby and TruffleRuby, using a script from the MSpec repository. Each of those repositories has a full copy of the specs under spec/ruby to ease editing specs, and any of them can be used to add or edit specs, whichever is most convenient.
But for testing the development version of a Ruby implementation, the documentation says you should always test against that implementation's own copy of the specs under spec/ruby, because that is what the implementation tests against in its own CI.
The reason is stated in the same paragraph. This repository does not always contain the latest spec changes from MRI, since the synchronisation is monthly, and it does not contain tags, which are the specs marked as failing on a given Ruby implementation.
That second point deserves emphasis. Tags are how the suite records known deviations, a spec that a particular implementation is allowed to fail. This repository has no tags, so every spec here is expected to pass, and running this copy against a partially finished implementation gives you failures that the implementation's own CI would have filtered out. The failure you are looking at may be a known deviation rather than a bug.
So the practical sequence is: work in whichever copy is most convenient, but run in the implementation's copy. The run command assumes this, because it is written as a path relative to the implementation directory:
$ cd ruby_implementation/spec/ruby
# Add ../ruby_implementation/bin in PATH, or pass -t /path/to/bin/ruby
$ ../mspec/bin/mspecThe comment there explains the two ways to point at the interpreter, either add the implementation's bin directory to PATH, or pass the -t flag with a path to the binary.
Five implementations check the suite on every commit
The suite exists to make implementations agree, and the documentation names who is checking it.
ruby/spec is known to be tested on every commit in MRI on 30 platforms and 4 versions, in JRuby for both 1.7 and 9.x, in TruffleRuby, in Opal, and in Artichoke. MRI is the reference implementation and runs the suite across a wide platform matrix, and the others are implementations with very different architectures, a bytecode virtual machine, a JIT with a foreign function interface, a compiler to JavaScript, and a WebAssembly runtime.
That list is the reason to care. A spec that only MRI passes is a spec that describes MRI. A spec that a JavaScript compiling Ruby and a WebAssembly Ruby also pass is a spec that describes the language, and the fact that the suite is run on every commit of each of those projects means a regression is caught in the implementation that introduced it rather than in a downstream report.
It also explains the monthly synchronisation. The implementations hold their own copies so they can edit specs as part of their own development, and somebody has to reconcile those edits back into the shared repository. Doing that on a monthly cycle is a compromise between the speed at which an implementation can adjust a spec and the stability of a shared reference.
The practical consequence for a user of Ruby is subtle but real. When two implementations disagree about a behaviour, the outcome is usually a spec change rather than a bug fix, and the spec change travels through this monthly cycle before it reaches the other implementations. The suite is therefore a description of Ruby as agreed by implementors, not a promise about what a given release will do.
Leak checks and the top-level constant convention
Two details in the documentation are about hygiene rather than about Ruby semantics, and both are the kind of thing that makes a shared test corpus workable.
The first is the leak checking. A number of checks for various kinds of leaks, including file descriptors, temporary files, threads, subprocesses, ENV, ARGV, global encodings and top-level constants, can be enabled with the CHECK_LEAKS environment variable:
$ CHECK_LEAKS=true ../mspec/bin/mspecThis matters because the specs run in a single process. Without a leak check, a spec that leaves a file open or a thread running does not fail, it quietly poisons the specs that run after it, and the failure appears somewhere unrelated. Turning the check on is how you find out that the example you were reading, rather than the one you just added, is the problem.
The second is the convention for constants. New top-level constants should only be introduced when needed, or should follow the pattern of the class being tested plus Specs, such as a module named StringSpecs. Other constants used for testing should be nested under such a module. The repository has a .mspec.constants file, which is where that convention is enforced.
The reason for the rule is the same as the leak check. A spec file that defines a top-level constant pollutes the global namespace for every other spec in the process, and with thousands of files in the suite, the chance of a name collision grows with every addition.
Both of these are the sort of detail that only matters at the scale of a shared suite, and neither is visible from the outside except when something goes wrong.
Old Ruby versions are pinned commits, and the tags explain the differences
The suite is versioned by the behaviour it describes, and the documentation handles older versions with a list of pinned commits rather than with branches or tags of its own.
For each older Ruby release there is a Suite commit and a matching MSpec commit: Ruby 2.0.0-p647, 2.1.9, 2.2.10, 2.3.8, 2.4.10, 2.5.9, 2.6.10, 2.7.8, 3.0.7, 3.1.6 and 3.2.9. The 2.0.0 entry carries a note that it may encounter two failures.
Two commits are given for each version on purpose. The suite and the runner are pinned together, because MSpec is a separate project that evolves, and pairing a new runner with an old suite can produce failures that say nothing about the Ruby version under test.
This is a different model from a versioned library. There is no gem to pin and no semantic version to reason about; the unit of reference is a commit hash, which is exact but also unforgiving, since you cannot upgrade one piece without the other.
The current suite describes the behaviour of Ruby 3.3 and more recent versions, and the version list ends at 3.2.9. For anything older than that, the pinned commits are the reference and the current tree is the wrong thing to run.
The repository also carries a TODO file, a LICENSE with the MIT terms, and a CONTRIBUTING.md that describes the rules for writing examples, which is where you would look first if you wanted to contribute a spec rather than merely run one.
Editorial conclusion
Use ruby/spec when you are implementing Ruby, porting it, or writing code that depends on subtle behaviour, because it is the only place where the implementations agree on what Ruby does down to the wording of a failure. Do not treat it as a language specification, since the project says it is not one and does not aim to become one. Before you rely on it, check which Ruby version the specs you are reading describe, remember that the repository is synchronised monthly and may lag MRI, and run the suite against the implementation's own copy under spec/ruby rather than against this repository, as the documentation instructs.
Frequently asked questions
What is ruby/spec?
The Ruby Spec Suite is a test suite for the behaviour of the Ruby programming language, written in code with a textual description attached to every example. It is explicitly not an ISO style specification and does not aim to become one.
How do I run the Ruby specs locally?
Clone ruby/spec, clone MSpec as a sibling directory, and run ../mspec/bin/mspec from inside the spec directory, which uses the ruby executable on your PATH. Use the -t option with a path or a PATH executable to test a specific implementation.
Which Ruby implementations run the spec suite?
MRI on 30 platforms and 4 versions, JRuby for both 1.7 and 9.x, TruffleRuby, Opal and Artichoke. The documentation also states that every latest stable MRI release should pass all of the specs, and that those runs happen in CI.
Should I test against ruby/spec or against the implementation's own copy?
The documentation says to always test the development version of a Ruby implementation against that implementation's copy of the specs under spec/ruby, because that is what its own CI uses. This repository is synchronized monthly and does not carry the tags for specs that are known to fail on a given implementation.
How do I check the Ruby specs for leaks?
Set the CHECK_LEAKS environment variable to true when running mspec. The checks cover file descriptors, temporary files, threads, subprocesses, ENV, ARGV, global encodings and top-level constants.
How do I find the specs for older Ruby versions?
The documentation lists pinned Suite and MSpec commits for each release from Ruby 2.0.0-p647 through 3.2.9, for example a Suite commit and a matching MSpec commit for Ruby 2.7.8 and for 3.2.9. The current tree describes Ruby 3.3 and later.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ruby-spec)Was this useful?