yard_example_runner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.commitlintrc.yml +38 -0
- data/.github/workflows/continuous-integration.yml +47 -0
- data/.github/workflows/enforce_conventional_commits.yml +27 -0
- data/.github/workflows/release.yml +52 -0
- data/.gitignore +28 -0
- data/.husky/commit-msg +1 -0
- data/.markdownlint.yml +25 -0
- data/.release-please-config.json +35 -0
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +27 -0
- data/AI_POLICY.md +26 -0
- data/CHANGELOG.md +21 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +237 -0
- data/GOVERNANCE.md +103 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +23 -0
- data/MAINTAINERS.md +16 -0
- data/README.md +516 -0
- data/Rakefile +59 -0
- data/bin/setup +13 -0
- data/lib/yard/cli/run_examples.rb +297 -0
- data/lib/yard_example_runner/example/comparison.rb +250 -0
- data/lib/yard_example_runner/example/constant_sandbox.rb +123 -0
- data/lib/yard_example_runner/example/evaluator.rb +145 -0
- data/lib/yard_example_runner/example.rb +360 -0
- data/lib/yard_example_runner/expectation.rb +23 -0
- data/lib/yard_example_runner/rake.rb +92 -0
- data/lib/yard_example_runner/version.rb +7 -0
- data/lib/yard_example_runner.rb +134 -0
- data/package.json +12 -0
- data/rake_tasks/cucumber.rake +9 -0
- data/rake_tasks/gem_tasks.rake +12 -0
- data/rake_tasks/markdownlint.rake +6 -0
- data/rake_tasks/rubocop.rake +5 -0
- data/rake_tasks/yard.rake +39 -0
- data/yard_example_runner.gemspec +43 -0
- metadata +251 -0
data/GOVERNANCE.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @markup markdown
|
|
3
|
+
# @title Governance
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Governance
|
|
7
|
+
|
|
8
|
+
This document explains how we steward the project with a light, principles-first
|
|
9
|
+
approach: enable trusted people, minimize dormant access, and keep decisions
|
|
10
|
+
transparent.
|
|
11
|
+
|
|
12
|
+
## Roles
|
|
13
|
+
|
|
14
|
+
A **Maintainer** is a trusted leader with write access who stewards the project's
|
|
15
|
+
health and direction. Responsibilities center on triage, review, merge, and helping
|
|
16
|
+
the community stay unblocked.
|
|
17
|
+
|
|
18
|
+
A **Project Lead** is a maintainer with additional administrative scope (repo Admin,
|
|
19
|
+
org Owner). They handle settings, secrets, access, and tie-breaks when needed.
|
|
20
|
+
|
|
21
|
+
## Becoming a Maintainer
|
|
22
|
+
|
|
23
|
+
Maintainers invite contributors who consistently ship, review, and model our values
|
|
24
|
+
to become maintainers. Anyone can nominate themselves or others in an issue or via a
|
|
25
|
+
private note. Current maintainers discuss nominations (see [Decision
|
|
26
|
+
Making](#decision-making)) with a focus on contribution quality, alignment with
|
|
27
|
+
project goals, and communication style.
|
|
28
|
+
|
|
29
|
+
## Access Principles
|
|
30
|
+
|
|
31
|
+
- Stewardship: Maintainer access exists to keep the project healthy and responsive.
|
|
32
|
+
- Least privilege: Elevated access is temporary and kept only while it’s needed.
|
|
33
|
+
- Continuity: Dormant access is paused to protect the project and unblock
|
|
34
|
+
contributors.
|
|
35
|
+
- Respect: Status changes are transparent, reversible, and acknowledge past
|
|
36
|
+
contributions.
|
|
37
|
+
|
|
38
|
+
## How We Apply Them
|
|
39
|
+
|
|
40
|
+
- Staying active: Maintainers keep elevated access while participating (shipping,
|
|
41
|
+
reviewing, triaging, or governance).
|
|
42
|
+
- When access is paused: If there’s no project activity for about a year, we’ll check
|
|
43
|
+
in. If we don’t hear back after a short window, we move the maintainer to Emeritus
|
|
44
|
+
and pause Owner/Admin/Write/package access (including CODEOWNERS entries).
|
|
45
|
+
- Coming back: Emeritus maintainers can be re-added quickly after a brief period of
|
|
46
|
+
renewed participation to refresh context.
|
|
47
|
+
- Recognition: Emeritus maintainers remain listed to honor prior contributions.
|
|
48
|
+
|
|
49
|
+
Access changes are communicated openly (e.g., PRs or issues) and reflected in
|
|
50
|
+
[MAINTAINERS.md](MAINTAINERS.md).
|
|
51
|
+
|
|
52
|
+
## Decision Making
|
|
53
|
+
|
|
54
|
+
Decisions are usually made by consensus among the active maintainers. If consensus
|
|
55
|
+
cannot be reached, the decision is made by a majority vote. If a vote results in a
|
|
56
|
+
tie, the Project Lead has the final say.
|
|
57
|
+
|
|
58
|
+
## Continuity
|
|
59
|
+
|
|
60
|
+
The project must be able to ship releases and respond to security issues even if
|
|
61
|
+
individual maintainers become unavailable.
|
|
62
|
+
|
|
63
|
+
### RubyGems Ownership
|
|
64
|
+
|
|
65
|
+
RubyGems ownership (the ability to push new gem versions) is granted to a subset of
|
|
66
|
+
active maintainers—typically the Project Lead and at least one other maintainer—to
|
|
67
|
+
balance security with continuity. Not all maintainers require RubyGems access.
|
|
68
|
+
|
|
69
|
+
RubyGems owners follow the same activity principles as other elevated access: if an
|
|
70
|
+
owner becomes inactive, their ownership is paused alongside other permissions.
|
|
71
|
+
|
|
72
|
+
### Minimum Thresholds
|
|
73
|
+
|
|
74
|
+
To avoid single points of failure:
|
|
75
|
+
|
|
76
|
+
- At least two active maintainers should have RubyGems ownership for the
|
|
77
|
+
`yard_example_runner` gem.
|
|
78
|
+
- At least two active maintainers should have GitHub org Owner or repo Admin access.
|
|
79
|
+
|
|
80
|
+
If thresholds drop below these levels, remaining maintainers should prioritize
|
|
81
|
+
onboarding or re-activating someone to restore redundancy.
|
|
82
|
+
|
|
83
|
+
### Access Audits
|
|
84
|
+
|
|
85
|
+
Periodically (at least annually), maintainers review access across all systems:
|
|
86
|
+
|
|
87
|
+
- GitHub organization membership and roles
|
|
88
|
+
- GitHub repository admin/write permissions
|
|
89
|
+
- RubyGems gem ownership
|
|
90
|
+
- GitHub Actions release automation: PATs/OIDC tokens (e.g., `AUTO_RELEASE_TOKEN`
|
|
91
|
+
scope), environment protection rules/approvers for RubyGems deployments, and any
|
|
92
|
+
OIDC trust configuration
|
|
93
|
+
|
|
94
|
+
The Project Lead (or a delegated maintainer) schedules and drives this review so
|
|
95
|
+
continuity checks do not slip.
|
|
96
|
+
|
|
97
|
+
Audits ensure access reflects current activity and that continuity thresholds are
|
|
98
|
+
met.
|
|
99
|
+
|
|
100
|
+
## Code of Conduct
|
|
101
|
+
|
|
102
|
+
All maintainers and contributors must adhere to the project's [Code of
|
|
103
|
+
Conduct](CODE_OF_CONDUCT.md).
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2016 Alex Rodionov
|
|
2
|
+
Copyright (c) 2026 James Couball
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
7
|
+
a copy of this software and associated documentation files (the
|
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
12
|
+
the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be
|
|
15
|
+
included in all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/MAINTAINERS.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @markup markdown
|
|
3
|
+
# @title Maintainers
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Maintainers
|
|
7
|
+
|
|
8
|
+
See [GOVERNANCE.md](GOVERNANCE.md) for the definition of the maintainer role.
|
|
9
|
+
|
|
10
|
+
## Active Maintainers
|
|
11
|
+
|
|
12
|
+
- [James Couball](https://github.com/jcouball) (Project Lead)
|
|
13
|
+
|
|
14
|
+
## Emeritus Maintainers
|
|
15
|
+
|
|
16
|
+
- None currently.
|
data/README.md
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
# @markup markdown
|
|
3
|
+
# @title README
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# yard_example_runner
|
|
7
|
+
|
|
8
|
+
[](https://badge.fury.io/rb/yard_example_runner)
|
|
10
|
+
|
|
11
|
+
`YardExampleRunner` is a YARD plugin that automatically parses `@example` tags in
|
|
12
|
+
your documentation and executes them as tests. It ensures code examples remain
|
|
13
|
+
accurate and serve as a living, executable specification.
|
|
14
|
+
|
|
15
|
+
This project is derived from [yard-doctest](https://github.com/p0deje/yard-doctest),
|
|
16
|
+
created by [Alex Rodionov](https://github.com/p0deje) and contributors.
|
|
17
|
+
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Basic usage](#basic-usage)
|
|
20
|
+
- [Advanced usage](#advanced-usage)
|
|
21
|
+
- [Asserting raised exceptions](#asserting-raised-exceptions)
|
|
22
|
+
- [Shared example context](#shared-example-context)
|
|
23
|
+
- [Hooks](#hooks)
|
|
24
|
+
- [Skipping examples](#skipping-examples)
|
|
25
|
+
- [Using matchers (optional)](#using-matchers-optional)
|
|
26
|
+
- [RSpec matchers](#rspec-matchers)
|
|
27
|
+
- [Minitest matchers](#minitest-matchers)
|
|
28
|
+
- [Custom matchers](#custom-matchers)
|
|
29
|
+
- [Rake](#rake)
|
|
30
|
+
- [Contributing](#contributing)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Add `yard_example_runner` as a development dependency:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle add yard_example_runner --group development
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or add it manually to your Gemfile and run `bundle install`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
gem 'yard_example_runner', group: :development
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Basic usage
|
|
47
|
+
|
|
48
|
+
Consider a simple geometry library:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
lib/
|
|
52
|
+
rectangle.rb
|
|
53
|
+
circle.rb
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each file contains a class with documented examples:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# rectangle.rb
|
|
60
|
+
class Rectangle
|
|
61
|
+
# @example
|
|
62
|
+
# Rectangle.shape_name #=> 'rectangle'
|
|
63
|
+
def self.shape_name
|
|
64
|
+
'rectangle'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(width, height)
|
|
68
|
+
@width = width
|
|
69
|
+
@height = height
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @example Unit square
|
|
73
|
+
# rect = Rectangle.new(1, 1)
|
|
74
|
+
# rect.area #=> 1
|
|
75
|
+
#
|
|
76
|
+
# @example Standard rectangle
|
|
77
|
+
# rect = Rectangle.new(4, 5)
|
|
78
|
+
# rect.area #=> 20
|
|
79
|
+
#
|
|
80
|
+
# @example Non-integer dimensions
|
|
81
|
+
# rect = Rectangle.new(2.5, 4.0)
|
|
82
|
+
# rect.area #=> 10.0
|
|
83
|
+
def area
|
|
84
|
+
@width * @height
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# circle.rb
|
|
91
|
+
class Circle
|
|
92
|
+
# @example
|
|
93
|
+
# Circle.shape_name #=> 'rectangle'
|
|
94
|
+
def self.shape_name
|
|
95
|
+
'circle'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @example Unit circle
|
|
99
|
+
# circle = Circle.new(1)
|
|
100
|
+
# circle.area.round(4) #=> 3.1416
|
|
101
|
+
def initialize(radius)
|
|
102
|
+
@radius = radius
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def area
|
|
106
|
+
Math::PI * @radius**2
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
First, tell YARD to automatically load `yard_example_runner` by adding it as a plugin
|
|
112
|
+
in your `.yardopts`:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# .yardopts
|
|
116
|
+
--plugin yard_example_runner
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Next, create a test helper that loads everything your examples need to run. It serves
|
|
120
|
+
a similar purpose to `spec_helper.rb` in RSpec or `test_helper.rb` in Minitest:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
touch example_runner_helper.rb
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# example_runner_helper.rb
|
|
128
|
+
require 'lib/rectangle'
|
|
129
|
+
require 'lib/circle'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Now run your examples:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
$ bundle exec yard run-examples
|
|
136
|
+
Run options: --seed 5974
|
|
137
|
+
|
|
138
|
+
# Running:
|
|
139
|
+
|
|
140
|
+
..F...
|
|
141
|
+
|
|
142
|
+
Finished in 0.015488s, 387.3967 runs/s, 387.3967 assertions/s.
|
|
143
|
+
|
|
144
|
+
1) Failure:
|
|
145
|
+
Circle.shape_name#test_0001_ [lib/circle.rb:3]:
|
|
146
|
+
Expected: "rectangle"
|
|
147
|
+
Actual: "circle"
|
|
148
|
+
|
|
149
|
+
6 runs, 6 assertions, 1 failures, 0 errors, 0 skips
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `Circle.shape_name` example contains a copy-paste error. Correct it and run the
|
|
153
|
+
command again:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
$ sed -i.bak "s/#=> 'rectangle'/#=> 'circle'/" lib/circle.rb
|
|
157
|
+
$ bundle exec yard run-examples
|
|
158
|
+
Run options: --seed 51966
|
|
159
|
+
|
|
160
|
+
# Running:
|
|
161
|
+
|
|
162
|
+
......
|
|
163
|
+
|
|
164
|
+
Finished in 0.002712s, 2212.3894 runs/s, 2212.3894 assertions/s.
|
|
165
|
+
|
|
166
|
+
6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The `#=>` operator is an equality assertion: the expression on the left is the actual
|
|
170
|
+
value, the value on the right is the expected value, and they are compared using Ruby's
|
|
171
|
+
case equality operator (`===`). This means the right-hand side can be a plain value,
|
|
172
|
+
a regular expression, a range, or a matcher object (such as an RSpec, Minitest, or
|
|
173
|
+
custom matcher) — each evaluated according to its own `===` or `matches?` semantics
|
|
174
|
+
rather than simple `==` equality.
|
|
175
|
+
|
|
176
|
+
A single example can contain multiple assertions:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class Rectangle
|
|
180
|
+
# @example
|
|
181
|
+
# small = Rectangle.new(1, 2)
|
|
182
|
+
# small.area #=> 2
|
|
183
|
+
# large = Rectangle.new(3, 4)
|
|
184
|
+
# large.area #=> 12
|
|
185
|
+
def area
|
|
186
|
+
@width * @height
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This runs as a single test with multiple assertions:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
$ bundle exec yard run-examples lib/rectangle.rb
|
|
195
|
+
# ...
|
|
196
|
+
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Examples without any assertions are still executed to verify that no exceptions are
|
|
200
|
+
raised:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
class Rectangle
|
|
204
|
+
# @example
|
|
205
|
+
# rect = Rectangle.new(2, 3)
|
|
206
|
+
# rect.area
|
|
207
|
+
def area
|
|
208
|
+
@width * @height
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
$ bundle exec yard run-examples lib/rectangle.rb
|
|
215
|
+
# ...
|
|
216
|
+
1 runs, 0 assertions, 0 failures, 0 errors, 0 skips
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Test execution is delegated to [minitest](https://github.com/minitest/minitest). Each
|
|
220
|
+
example is registered as an `it` block within a dynamically generated
|
|
221
|
+
`Minitest::Spec` subclass.
|
|
222
|
+
|
|
223
|
+
## Advanced usage
|
|
224
|
+
|
|
225
|
+
### Asserting raised exceptions
|
|
226
|
+
|
|
227
|
+
To assert that an example raises an exception, use `raise` on the right-hand side of
|
|
228
|
+
`#=>`, specifying the exception class and message:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
class Calculator
|
|
232
|
+
# @example
|
|
233
|
+
# divide(1, 0) #=> raise ZeroDivisionError, "divided by 0"
|
|
234
|
+
def divide(one, two)
|
|
235
|
+
one / two
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The raised exception is matched by comparing a string containing its class name and
|
|
241
|
+
message. The message in the assertion must **exactly** match the message raised at
|
|
242
|
+
runtime.
|
|
243
|
+
|
|
244
|
+
For more flexible exception matching — such as matching by class only or using a
|
|
245
|
+
regex on the message — see [RSpec matchers](#rspec-matchers), which supports
|
|
246
|
+
`raise_error`.
|
|
247
|
+
|
|
248
|
+
### Shared example context
|
|
249
|
+
|
|
250
|
+
Shared example context is about making objects and methods available to examples. The
|
|
251
|
+
`example_runner_helper.rb` file introduced in [Basic usage](#basic-usage) is loaded
|
|
252
|
+
before examples execute. Place shared helper methods there (or in
|
|
253
|
+
`support/example_runner_helper.rb`, `spec/example_runner_helper.rb`, or
|
|
254
|
+
`test/example_runner_helper.rb`).
|
|
255
|
+
|
|
256
|
+
Use hooks to set shared instance-variable state for examples:
|
|
257
|
+
|
|
258
|
+
For instance, if an example references an object without constructing it:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
class Rectangle
|
|
262
|
+
# @example Area of a shared rectangle
|
|
263
|
+
# rect.area #=> 20
|
|
264
|
+
def area
|
|
265
|
+
@width * @height
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Running this will fail because `rect` is not defined in the example:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
$ bundle exec yard run-examples
|
|
274
|
+
# ...
|
|
275
|
+
1) Error:
|
|
276
|
+
Rectangle#area#test_0001_Area of a shared rectangle:
|
|
277
|
+
NameError: undefined local variable or method `rect' for Object:Class
|
|
278
|
+
# ...
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Define `rect` as a memoized method in `example_runner_helper.rb` to make it available
|
|
282
|
+
across all examples:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
# example_runner_helper.rb
|
|
286
|
+
require 'lib/rectangle'
|
|
287
|
+
require 'lib/circle'
|
|
288
|
+
|
|
289
|
+
def rect
|
|
290
|
+
@rect ||= Rectangle.new(4, 5)
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Hooks
|
|
295
|
+
|
|
296
|
+
Hooks are lifecycle callbacks that run around each example, providing setup and
|
|
297
|
+
teardown behavior. They are defined in `example_runner_helper.rb` using
|
|
298
|
+
`YardExampleRunner.configure`:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
YardExampleRunner.configure do |runner|
|
|
302
|
+
runner.before do
|
|
303
|
+
# Runs before each example.
|
|
304
|
+
# Evaluated in the same context as the example,
|
|
305
|
+
# so instance variables are shared.
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
runner.after do
|
|
309
|
+
# Runs after each example.
|
|
310
|
+
# Also evaluated in the same context as the example.
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
runner.after_run do
|
|
314
|
+
# Runs once after all examples have finished.
|
|
315
|
+
# Evaluated in a separate context; instance variables
|
|
316
|
+
# from individual examples are not accessible here.
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Hooks can be scoped to a specific class, method, or named example by passing a
|
|
322
|
+
qualifier string:
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
YardExampleRunner.configure do |runner|
|
|
326
|
+
runner.before('MyClass') do
|
|
327
|
+
# Runs before every example in `MyClass` and its methods
|
|
328
|
+
# (e.g. `MyClass.foo`, `MyClass#bar`)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
runner.after('MyClass#foo') do
|
|
332
|
+
# Runs after every example for `MyClass#foo`
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
runner.before('MyClass#foo@Example one') do
|
|
336
|
+
# Runs before only the example named "Example one" in `MyClass#foo`
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Skipping examples
|
|
342
|
+
|
|
343
|
+
Examples can be excluded from a run by passing a class or method qualifier to
|
|
344
|
+
`runner.skip` in `example_runner_helper.rb`. The qualifier is matched as a substring
|
|
345
|
+
of the example's class/method path, so skipping a class also skips all of its
|
|
346
|
+
methods:
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
YardExampleRunner.configure do |runner|
|
|
350
|
+
runner.skip 'MyClass' # skips all examples in `MyClass` and its methods
|
|
351
|
+
runner.skip 'MyClass#foo' # skips all examples for `MyClass#foo` only
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Note that `skip` matches against the class/method path only. Skipping by named
|
|
356
|
+
example (e.g. `MyClass#foo@Example one`) is not supported; use a scoped
|
|
357
|
+
[hook](#hooks) to conditionally skip at that level of granularity.
|
|
358
|
+
|
|
359
|
+
### Using matchers (optional)
|
|
360
|
+
|
|
361
|
+
The right-hand side of `#=>` supports any object that implements `matches?`. Matchers
|
|
362
|
+
that also implement `failure_message` (or `failure_message_for_should`) produce better
|
|
363
|
+
failure output. This includes [RSpec
|
|
364
|
+
matchers](https://rspec.info/documentation/3.12/rspec-expectations/),
|
|
365
|
+
[minitest-matchers](https://github.com/wojtekmach/minitest-matchers) or
|
|
366
|
+
[minitest-matchers_vaccine](https://github.com/rmm5t/minitest-matchers_vaccine), and
|
|
367
|
+
any custom matcher you write yourself.
|
|
368
|
+
|
|
369
|
+
#### RSpec matchers
|
|
370
|
+
|
|
371
|
+
Add `rspec-expectations` as a development dependency:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
gem 'rspec-expectations', group: :development
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Include `RSpec::Matchers` in `example_runner_helper.rb`:
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
# example_runner_helper.rb
|
|
381
|
+
require 'rspec/expectations'
|
|
382
|
+
require 'rspec/matchers'
|
|
383
|
+
|
|
384
|
+
YardExampleRunner::Example.include RSpec::Matchers
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
##### Value matchers
|
|
388
|
+
|
|
389
|
+
Matchers like `eq`, `be_within`, `a_kind_of`, and `include` are compared against the
|
|
390
|
+
evaluated actual value:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
class Calculator
|
|
394
|
+
# @example
|
|
395
|
+
# Calculator.pi #=> be_within(0.01).of(3.14)
|
|
396
|
+
def self.pi
|
|
397
|
+
Math::PI
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# @example
|
|
401
|
+
# Calculator.describe(42) #=> a_kind_of(String) & match(/positive/)
|
|
402
|
+
def self.describe(n)
|
|
403
|
+
n > 0 ? "positive number" : "non-positive number"
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
##### Block matchers
|
|
409
|
+
|
|
410
|
+
Matchers like `raise_error`, `change`, and `output` receive the actual expression as
|
|
411
|
+
a callable block, so the matcher can invoke it and inspect side-effects:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
class Calculator
|
|
415
|
+
# @example
|
|
416
|
+
# Calculator.divide(1, 0) #=> raise_error(ZeroDivisionError)
|
|
417
|
+
#
|
|
418
|
+
# @example with message pattern
|
|
419
|
+
# Calculator.divide(1, 0) #=> raise_error(ZeroDivisionError, /divided/)
|
|
420
|
+
def self.divide(a, b)
|
|
421
|
+
a / b
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The expected side of `#=>` is evaluated outside the documented class's namespace,
|
|
427
|
+
so matchers like `include` are never shadowed by Ruby's built-in `Module#include`.
|
|
428
|
+
|
|
429
|
+
#### Minitest matchers
|
|
430
|
+
|
|
431
|
+
If you prefer to stay within the Minitest ecosystem, gems like
|
|
432
|
+
[minitest-matchers](https://github.com/wojtekmach/minitest-matchers) and
|
|
433
|
+
[minitest-matchers_vaccine](https://github.com/rmm5t/minitest-matchers_vaccine)
|
|
434
|
+
can be used alongside `yard_example_runner`. Add one as a development dependency:
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
gem 'minitest-matchers_vaccine', group: :development
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
If you use `minitest-matchers_vaccine`, require it in `example_runner_helper.rb`:
|
|
441
|
+
|
|
442
|
+
```ruby
|
|
443
|
+
# example_runner_helper.rb
|
|
444
|
+
require 'minitest/matchers_vaccine'
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`yard_example_runner` does not require including a matcher module on
|
|
448
|
+
`YardExampleRunner::Example`. Any matcher object that follows the `matches?` /
|
|
449
|
+
`failure_message` protocol is recognised automatically.
|
|
450
|
+
|
|
451
|
+
#### Custom matchers
|
|
452
|
+
|
|
453
|
+
Any object that responds to `matches?` works as a matcher.
|
|
454
|
+
No external dependencies are required:
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
# example_runner_helper.rb
|
|
458
|
+
class BePositive
|
|
459
|
+
def matches?(actual)
|
|
460
|
+
@actual = actual
|
|
461
|
+
actual > 0
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def failure_message
|
|
465
|
+
"expected #{@actual} to be positive"
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def be_positive
|
|
470
|
+
BePositive.new
|
|
471
|
+
end
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
class Counter
|
|
476
|
+
# @example
|
|
477
|
+
# Counter.total #=> be_positive
|
|
478
|
+
def self.total
|
|
479
|
+
42
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Block matchers additionally respond to `supports_block_expectations?` returning
|
|
485
|
+
`true`, which tells `yard_example_runner` to wrap the actual expression in a proc
|
|
486
|
+
before passing it to `matches?`.
|
|
487
|
+
|
|
488
|
+
### Rake
|
|
489
|
+
|
|
490
|
+
A Rake task is available for integrating example runs into your build pipeline:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
# Rakefile
|
|
494
|
+
require 'yard_example_runner/rake'
|
|
495
|
+
|
|
496
|
+
YardExampleRunner::RakeTask.new do |task|
|
|
497
|
+
task.run_examples_opts = %w[-v]
|
|
498
|
+
task.pattern = 'lib/**/*.rb'
|
|
499
|
+
end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
bundle exec rake yard:run-examples
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Contributing
|
|
507
|
+
|
|
508
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution workflow and requirements.
|
|
509
|
+
|
|
510
|
+
Minimum expectations:
|
|
511
|
+
|
|
512
|
+
1. Fork the repository and create a feature branch.
|
|
513
|
+
2. Run `bin/setup` to install development dependencies.
|
|
514
|
+
3. Make your changes and ensure `bundle exec rake` passes.
|
|
515
|
+
4. Use [Conventional Commits](https://www.conventionalcommits.org/) for all commits.
|
|
516
|
+
5. Open a pull request with a clear description of the change.
|