verity 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/LICENSE +21 -0
- data/README.md +227 -0
- data/bin/benchmark +99 -0
- data/bin/test_all +43 -0
- data/bin/verity +82 -0
- data/lib/verity/assertions.rb +316 -0
- data/lib/verity/configuration.rb +129 -0
- data/lib/verity/fingerprint.rb +155 -0
- data/lib/verity/manifest.rb +462 -0
- data/lib/verity/reporter.rb +54 -0
- data/lib/verity/reporters/colored_dots.rb +59 -0
- data/lib/verity/reporters/composite_reporter.rb +44 -0
- data/lib/verity/reporters/documentation_reporter.rb +121 -0
- data/lib/verity/reporters/dots_reporter.rb +48 -0
- data/lib/verity/reporters/null_reporter.rb +11 -0
- data/lib/verity/reporters/parallel_summary_reporter.rb +51 -0
- data/lib/verity/reporters/test_reporter.rb +48 -0
- data/lib/verity/runner.rb +210 -0
- data/lib/verity/version.rb +5 -0
- data/lib/verity.rb +614 -0
- metadata +139 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 56eba3ab988df08937ded0d1a64257aef3e6b0377e89c205cb1142555cfe6022
|
|
4
|
+
data.tar.gz: 767c78b325581dbb233805be573efcf77a1ef6842d1ecb79c23e1036f0d18dd5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9d1afd58fb7e9eead47231b4c08ef719fcdbba846ed760fbc84238baceaca870c76bb160e678f1a3ff81c3ed40ed4fcc2460f7709bbe7fda911008b1704f26b3
|
|
7
|
+
data.tar.gz: 99325207bf1dbf549b2ee760d5e2528f8b2ae23e98930c3fe67123c18cf577fb3e8b572ef42ebcb8784f4acf13239516e6b45d2e5c9c468d68be5c6c893f4a83
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tad Thorley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Verity
|
|
2
|
+
|
|
3
|
+
Metadata-first Ruby tests: each case is a structured record (tags, timeouts, resource hints) backed by a SQLite manifest queue. The CLI loads discovery files, syncs them into the manifest, and runs tests — either on a single worker or across parallel forked processes that claim tests atomically from the queue.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby **≥ 3.3**
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "verity"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install from the repository root:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem build verity.gemspec && gem install verity-*.gem
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Running
|
|
24
|
+
|
|
25
|
+
From a checkout, after dependencies are available:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
./bin/verity
|
|
29
|
+
# or
|
|
30
|
+
bundle exec verity
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Positional arguments are treated as file paths or globs — only those files are loaded instead of the configured `test_globs`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
verity verity/models/user_test.rb
|
|
37
|
+
verity verity/models/*_test.rb verity/lib/auth_test.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each argument is resolved with `File.expand_path`, so relative paths work from any directory.
|
|
41
|
+
|
|
42
|
+
Use `--workers` / `-w` to run tests in parallel across forked processes:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
verity -w 4 # exactly 4 workers
|
|
46
|
+
verity -w cpus # one worker per CPU (Etc.nprocessors)
|
|
47
|
+
verity --workers 2 verity/ # combine with positional args
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Exit status is **0** if every claimed test passes, **1** otherwise (`exit` in `bin/verity` mirrors that). **2** is used for invalid CLI options or an invalid `--reporter` / `-r` value.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
verity --reporter dots
|
|
54
|
+
verity -r null
|
|
55
|
+
verity -r ./reporters/mine.rb:MyReporter
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
There is no `--version` flag. The current version is available programmatically as `Verity::VERSION`.
|
|
59
|
+
|
|
60
|
+
Built-in names are the same as for `Verity.build_reporter` (case-insensitive): `colored`, `colored_dots`, `documentation`, `doc`, `dots`, `null`, `none`, `silent`. Custom reporters: `path/to/file.rb:ClassName` (class must `include Verity::Reporter`); the file is `load`ed, then `ClassName.new` is called with no arguments.
|
|
61
|
+
|
|
62
|
+
`ColoredDotsReporter` (the default) prints green **.** / red **F** / yellow **E** when stdout is a TTY. Set `NO_COLOR` in the environment to disable; set `FORCE_COLOR` or `VERITY_FORCE_COLOR` to `"1"`, `"true"`, or `"yes"` (case-insensitive) to force color when not a TTY.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
Use `Verity.configure` before `Verity.run` (or ensure defaults match your layout):
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
Verity.configure do |c|
|
|
70
|
+
c.manifest_path = "verity/manifest.db" # default; path relative to cwd (ignored by git); or ":memory:" for single-process only
|
|
71
|
+
c.test_globs = ["verity/**/*_test.rb"] # default; set to your Verity discovery globs
|
|
72
|
+
# c.worker_count = :cpus # default; or a positive Integer, or "cpus" / :cpu / "cpu"
|
|
73
|
+
# c.reporter = Verity::Reporters::ColoredDotsReporter.new($stdout) # default
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **`test_globs`** — array of patterns passed to `Dir.glob`; merged and de-duplicated for **`test_files`**.
|
|
78
|
+
- **`manifest_path`** — SQLite database path (default **`verity/manifest.db`**), or `":memory:"` for an in-memory DB (only with **`worker_count` 1**).
|
|
79
|
+
- **`worker_count`** — number of parallel worker processes (`Integer` or decimal string), or **`:cpus`** / **`:cpu`** / **`"cpus"`** / **`"cpu"`** to use `Etc.nprocessors` (minimum **1**). Resolved at run time via **`Configuration#resolved_worker_count`**. Parallel runs need a **file** manifest (not `":memory:"`) and **`Kernel#fork`**.
|
|
80
|
+
- **`reporter`** — object that includes `Verity::Reporter` (default: `Verity::Reporters::ColoredDotsReporter` on `$stdout`). See **Custom reporters** below.
|
|
81
|
+
|
|
82
|
+
`Verity.run(worker_id: 0)` loads all `test_files`, migrates the manifest, replaces the `tests` table from the registry, then runs the manifest-driven runner for that worker.
|
|
83
|
+
|
|
84
|
+
`Verity.load_discovery!` only clears the registry and loads `test_files` (useful if you build your own harness). For each file it precomputes **fingerprints** with **Prism**: the hash covers the **block body** only (description and metadata changes do not change identity). **`Test#file`** and **`Test#line`** remain the **`test` call** location. If you `load` a file outside that path (no plan installed), fingerprints fall back to a line-based slug.
|
|
85
|
+
|
|
86
|
+
### Custom reporters
|
|
87
|
+
|
|
88
|
+
Implement {Verity::Reporter} and assign it on configuration. `Verity.run` and `Runner.new` (no `reporter:` keyword) use `Verity.configuration.reporter`. Built-ins live under `Verity::Reporters`:
|
|
89
|
+
|
|
90
|
+
| Class | Purpose |
|
|
91
|
+
|-------|---------|
|
|
92
|
+
| `ColoredDotsReporter` | Default — green/red/yellow dots with ANSI color (TTY-aware) |
|
|
93
|
+
| `DotsReporter` | Plain `.` / `F` / `E` dots, no color |
|
|
94
|
+
| `DocumentationReporter` | Prints group titles and test descriptions (outline style) |
|
|
95
|
+
| `NullReporter` | Discards all output (used internally for parallel child workers) |
|
|
96
|
+
| `TestReporter` | In-memory recorder for testing integrations (see below) |
|
|
97
|
+
| `CompositeReporter` | Delegates to multiple reporters |
|
|
98
|
+
| `ParallelSummaryReporter` | Emits the multi-worker summary block after parallel runs |
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
class MyReporter
|
|
102
|
+
include Verity::Reporter
|
|
103
|
+
|
|
104
|
+
def on_run_start(total:, worker_id:)
|
|
105
|
+
# total: expected number of examples for this worker (nil if unknown)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def on_test_complete(result:, worker_id:)
|
|
109
|
+
# See Verity::Runner::Result: :test, :status (:pass | :fail | :error), :error
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def on_run_finish(summary:, worker_id:)
|
|
113
|
+
# summary: :total, :passed, :failed, :errored, :skipped, :focus
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Optional: after Verity.run with worker_count > 1 (parent process only)
|
|
117
|
+
def on_parallel_complete(counts:, problem_rows:)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Verity.configure do |c|
|
|
122
|
+
c.reporter = MyReporter.new
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For a one-off run without changing global config, pass `Verity::Runner.new(reporter: MyReporter.new)`.
|
|
127
|
+
|
|
128
|
+
### TestReporter
|
|
129
|
+
|
|
130
|
+
`Verity::Reporters::TestReporter` records every callback in memory (no I/O), useful for testing integrations against the reporter protocol. It exposes four readers:
|
|
131
|
+
|
|
132
|
+
| Reader | Stores |
|
|
133
|
+
|--------|--------|
|
|
134
|
+
| `run_starts` | `[{ total:, worker_id: }, ...]` |
|
|
135
|
+
| `test_completes` | `[{ status:, worker_id: }, ...]` |
|
|
136
|
+
| `run_finishes` | `[{ summary:, worker_id: }, ...]` |
|
|
137
|
+
| `parallel_finishes` | `[{ counts:, problem_rows: }, ...]` |
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
reporter = Verity::Reporters::TestReporter.new
|
|
141
|
+
Verity.configure { |c| c.reporter = reporter }
|
|
142
|
+
Verity.run
|
|
143
|
+
reporter.test_completes.count { _1[:status] == :pass }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Grouping
|
|
147
|
+
|
|
148
|
+
Nest tests under titled sections with **`group`**. Each `test` registers with a **`group_path`** (array of titles) used for output and tooling; fingerprints and execution order are unchanged.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
group "Authentication", tags: [:integration] do
|
|
152
|
+
group "sessions", tags: [:focus] do
|
|
153
|
+
test "creates a session" do
|
|
154
|
+
# ...
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
group "WIP", tags: [:skip] do
|
|
160
|
+
test "not scheduled yet" do
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Tags on a **`group`** apply to **every nested `test`** (and inner groups): they are stored on each test as **`inherited_group_tags`** (outer groups first) and merged with the test’s own **`tags:`** for **`Verity.skipped?`**, **`Verity.focus_tag?`**, and **`Verity.effective_tags`**. **`:skip`** on any ancestor (or on the test) skips the example; **`:focus`** follows the same suite-wide rules as test-level **`:focus`**.
|
|
166
|
+
|
|
167
|
+
**`Verity::Reporters::DocumentationReporter`** prints new group titles when the path changes (indented like an outline). Dot reporters do not show groups. Custom reporters can read **`result.test.group_path`** and **`result.test.inherited_group_tags`**.
|
|
168
|
+
|
|
169
|
+
The group stack is cleared before each discovery file is loaded so a stray unclosed `group` in one file does not affect the next.
|
|
170
|
+
|
|
171
|
+
## Tags
|
|
172
|
+
|
|
173
|
+
- **`tags: [:skip]`** — The example is **not** enqueued in the manifest and does **not** run. It still appears in **`Verity::Registry.all`**. The summary line includes **`N skipped`** when `N > 0`. String `"skip"` in tags is treated the same (normalized with `to_sym`). A **`group`** may use **`tags: [:skip]`**; that applies to all nested tests (see **Grouping**).
|
|
174
|
+
- **`tags: [:focus]`** — If **any** non-skipped registered test has **`:focus`** (including via an enclosing **`group`**), **only** tests that have **`:focus`** in their effective tags are runnable (manifest + direct **`Runner#run`**). If every non-skipped test is focused, the filter does nothing (same as “all focused”). **`Skip` wins:** a test with both **`skip`** and **`focus`** is skipped. When focus narrows the suite, the summary ends with **`(focus)`**.
|
|
175
|
+
|
|
176
|
+
## `Verity::Test` fields
|
|
177
|
+
|
|
178
|
+
Each registered test is a `Data.define` struct with 11 fields:
|
|
179
|
+
|
|
180
|
+
| Field | Type | Description |
|
|
181
|
+
|-------|------|-------------|
|
|
182
|
+
| `fingerprint` | `String` | Stable identity hash derived from the block body via Prism AST |
|
|
183
|
+
| `description` | `String` | Human-readable name passed to `test "..."` |
|
|
184
|
+
| `tags` | `Array<Symbol>` | Tags declared on the test itself (e.g. `[:unit, :focus]`) |
|
|
185
|
+
| `timeout` | `Float`, `nil` | Optional per-test timeout in seconds |
|
|
186
|
+
| `requires` | `Array` | Declared dependency hints (e.g. `[:active_record]`) |
|
|
187
|
+
| `resources` | `Hash` | Extra keyword args from `test` (e.g. `{ tables: [:users] }`) |
|
|
188
|
+
| `file` | `String` | Absolute path of the file containing the `test` call |
|
|
189
|
+
| `line` | `Integer` | Line number of the `test` call |
|
|
190
|
+
| `fn` | `Proc` | The test body block |
|
|
191
|
+
| `group_path` | `Array<String>` | Nested `group` titles at registration time (outer first) |
|
|
192
|
+
| `inherited_group_tags` | `Array<Symbol>` | Tags accumulated from enclosing `group` blocks (outer first) |
|
|
193
|
+
|
|
194
|
+
## Repository layout (this project)
|
|
195
|
+
|
|
196
|
+
| Directory | Role |
|
|
197
|
+
|-----------|------|
|
|
198
|
+
| `test/` | Minitest for Verity internals |
|
|
199
|
+
| `spec/` | RSpec examples |
|
|
200
|
+
| `verity/` | Verity DSL files (default discovery glob targets `verity/**/*_test.rb`) |
|
|
201
|
+
| `lib/` | Gem implementation |
|
|
202
|
+
|
|
203
|
+
### Triple suite: compare, convert, and cross-check behavior
|
|
204
|
+
|
|
205
|
+
Integration scenarios are spelled out three ways on purpose:
|
|
206
|
+
|
|
207
|
+
| Layer | Paths | Audience |
|
|
208
|
+
|-------|-------|----------|
|
|
209
|
+
| **Dogfood DSL** | `verity/<topic>_test.rb` | Readers learning Verity (`test`, `group`, built-in assertions) |
|
|
210
|
+
| **Minitest** | `test/<topic>_test.rb` | Readers used to `@test`/assert style and class-based suites |
|
|
211
|
+
| **RSpec** | `spec/verity/<topic>_spec.rb` | Readers used to `describe`/`it` matchers |
|
|
212
|
+
|
|
213
|
+
Matching files share the **same basename** (`foo_test.rb` ↔ `foo_spec.rb`). Scenario titles are aligned so you can open two panes side by side when porting assertions or onboarding a team. Keeping all three suites green is deliberate **redundant proof** — the SQLite manifest and runner stay honest under different loaders and assertions.
|
|
214
|
+
|
|
215
|
+
Example triplet:
|
|
216
|
+
|
|
217
|
+
- [`verity/assertions_test.rb`](verity/assertions_test.rb)
|
|
218
|
+
- [`test/assertions_test.rb`](test/assertions_test.rb)
|
|
219
|
+
- [`spec/verity/assertions_spec.rb`](spec/verity/assertions_spec.rb)
|
|
220
|
+
|
|
221
|
+
## Design notes
|
|
222
|
+
|
|
223
|
+
See [verity-notes.md](verity-notes.md) for schema, fingerprints, and planned execution model.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT — see [LICENSE](LICENSE).
|
data/bin/benchmark
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "rbconfig"
|
|
6
|
+
require "bundler/setup"
|
|
7
|
+
|
|
8
|
+
root = File.expand_path("..", __dir__)
|
|
9
|
+
Dir.chdir(root) or abort("benchmark: cannot chdir to #{root}")
|
|
10
|
+
|
|
11
|
+
ruby = RbConfig.ruby
|
|
12
|
+
opts = { iterations: 1 }
|
|
13
|
+
OptionParser.new do |o|
|
|
14
|
+
o.banner = "Usage: benchmark [options]"
|
|
15
|
+
o.separator ""
|
|
16
|
+
o.separator "Runs the same three suites as bin/test_all and prints wall-clock timings."
|
|
17
|
+
o.separator ""
|
|
18
|
+
o.on("-n", "--iterations N", Integer, "Repeat each suite N times (default: 1)") do |n|
|
|
19
|
+
opts[:iterations] = n
|
|
20
|
+
end
|
|
21
|
+
o.on("-h", "--help", "Show this help") do
|
|
22
|
+
puts o
|
|
23
|
+
exit 0
|
|
24
|
+
end
|
|
25
|
+
end.parse!
|
|
26
|
+
|
|
27
|
+
bench_iterations = opts[:iterations]
|
|
28
|
+
abort "benchmark: iterations must be >= 1" if bench_iterations < 1
|
|
29
|
+
|
|
30
|
+
def fmt_sec(s)
|
|
31
|
+
format("%0.3f", s)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def summarize(samples)
|
|
35
|
+
return { min: samples.last, max: samples.last, mean: samples.last } if samples.size == 1
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
min: samples.min,
|
|
39
|
+
max: samples.max,
|
|
40
|
+
mean: samples.sum / samples.size
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def run_suite(iterations)
|
|
45
|
+
times = []
|
|
46
|
+
iterations.times do |i|
|
|
47
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
48
|
+
ok = yield
|
|
49
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
50
|
+
abort "benchmark: suite failed (iteration #{i + 1})" unless ok
|
|
51
|
+
|
|
52
|
+
times << (t1 - t0)
|
|
53
|
+
end
|
|
54
|
+
times
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
verity_bin = File.join(root, "bin", "verity")
|
|
58
|
+
results = []
|
|
59
|
+
|
|
60
|
+
results << [
|
|
61
|
+
"Dogfood (bin/verity)",
|
|
62
|
+
summarize(run_suite(bench_iterations) do
|
|
63
|
+
system(ruby, verity_bin)
|
|
64
|
+
end)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
minitest_files = Dir.glob("test/**/*_test.rb").sort
|
|
68
|
+
abort "benchmark: no files matched test/**/*_test.rb" if minitest_files.empty?
|
|
69
|
+
|
|
70
|
+
results << [
|
|
71
|
+
"Minitest (test/**/*_test.rb, #{minitest_files.size} files)",
|
|
72
|
+
summarize(run_suite(bench_iterations) do
|
|
73
|
+
minitest_files.all? do |path|
|
|
74
|
+
system(ruby, "-Ilib:test", path)
|
|
75
|
+
end
|
|
76
|
+
end)
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
results << [
|
|
80
|
+
"RSpec (spec/)",
|
|
81
|
+
summarize(run_suite(bench_iterations) do
|
|
82
|
+
system(ruby, "-S", "rspec", "--format", "progress")
|
|
83
|
+
end)
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
puts
|
|
87
|
+
puts "Benchmark: #{bench_iterations} iteration(s) per suite, monotonic wall clock (seconds)"
|
|
88
|
+
puts "-" * 72
|
|
89
|
+
results.each do |label, stat|
|
|
90
|
+
line = format("%-48s %7ss", label, fmt_sec(stat[:mean]))
|
|
91
|
+
line += format(" (min #{fmt_sec(stat[:min])} .. max #{fmt_sec(stat[:max])})") if bench_iterations > 1
|
|
92
|
+
|
|
93
|
+
puts line
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
total_mean = results.sum { |_, s| s[:mean] }
|
|
97
|
+
puts "-" * 72
|
|
98
|
+
puts format("%-48s %7ss", "Total (sum of suite means)", fmt_sec(total_mean))
|
|
99
|
+
puts
|
data/bin/test_all
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require "bundler/setup"
|
|
6
|
+
|
|
7
|
+
root = File.expand_path("..", __dir__)
|
|
8
|
+
Dir.chdir(root) or abort("test_all: cannot chdir to #{root}")
|
|
9
|
+
|
|
10
|
+
ruby = RbConfig.ruby
|
|
11
|
+
failed = false
|
|
12
|
+
|
|
13
|
+
def banner(title)
|
|
14
|
+
sep = "=" * 60
|
|
15
|
+
puts "\n#{sep}\n#{title}\n#{sep}\n"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
verity_bin = File.join(root, "bin", "verity")
|
|
19
|
+
|
|
20
|
+
banner "1/3 Dogfood — bin/verity (verity/**/*_test.rb)"
|
|
21
|
+
failed = true unless system(ruby, verity_bin)
|
|
22
|
+
|
|
23
|
+
banner "2/3 Minitest — test/**/*_test.rb"
|
|
24
|
+
minitest_files = Dir.glob("test/**/*_test.rb").sort
|
|
25
|
+
if minitest_files.empty?
|
|
26
|
+
warn "test_all: no files matched test/**/*_test.rb"
|
|
27
|
+
else
|
|
28
|
+
minitest_files.each do |path|
|
|
29
|
+
puts "\n--- #{path} ---"
|
|
30
|
+
failed = true unless system(ruby, "-Ilib:test", path)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
banner "3/3 RSpec — spec/"
|
|
35
|
+
failed = true unless system(ruby, "-S", "rspec", "--format", "documentation")
|
|
36
|
+
|
|
37
|
+
if failed
|
|
38
|
+
warn "\ntest_all: one or more suites failed"
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts "\ntest_all: all suites passed"
|
|
43
|
+
exit 0
|
data/bin/verity
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
require "verity"
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
OptionParser.new do |o|
|
|
11
|
+
o.banner = "Usage: verity [options] [file-or-glob ...]"
|
|
12
|
+
o.separator ""
|
|
13
|
+
o.separator "If file-or-glob arguments are given, only those paths are loaded."
|
|
14
|
+
o.separator "Use path/to/file.rb:LINE to run the test on that line or every test in the group opened there."
|
|
15
|
+
o.separator "Otherwise the configured default is used (see Verity::Configuration#test_globs)."
|
|
16
|
+
o.separator ""
|
|
17
|
+
o.on(
|
|
18
|
+
"-r",
|
|
19
|
+
"--reporter NAME",
|
|
20
|
+
"Output reporter: colored (default), colored_dots, dots, documentation, doc, null, none, silent;",
|
|
21
|
+
"or path/to/reporter.rb:ClassName for a custom class (include Verity::Reporter)."
|
|
22
|
+
) do |name|
|
|
23
|
+
Verity.configure { |c| c.reporter = Verity.build_reporter(name) }
|
|
24
|
+
end
|
|
25
|
+
o.on(
|
|
26
|
+
"-w",
|
|
27
|
+
"--workers COUNT",
|
|
28
|
+
"Parallel worker processes: a positive integer or cpus (one worker per CPU via Etc.nprocessors)."
|
|
29
|
+
) do |v|
|
|
30
|
+
Verity.configure { |c| c.worker_count = v.strip }
|
|
31
|
+
end
|
|
32
|
+
o.on("--order MODE", "Test order: random (default; use --seed to fix RNG) or fingerprint (sorted).") do |v|
|
|
33
|
+
mode = v.to_s.strip.downcase
|
|
34
|
+
unless %w[fingerprint random].include?(mode)
|
|
35
|
+
warn "verity: --order must be fingerprint or random (got #{v.inspect})"
|
|
36
|
+
exit 2
|
|
37
|
+
end
|
|
38
|
+
Verity.configure { |c| c.test_order = mode.to_sym }
|
|
39
|
+
end
|
|
40
|
+
o.on("--seed INTEGER", Integer, "RNG seed for shuffled order (printed only when auto-chosen).") do |n|
|
|
41
|
+
Verity.configure { |c| c.shuffle_seed = n }
|
|
42
|
+
end
|
|
43
|
+
o.on("-h", "--help", "Show this help") do
|
|
44
|
+
puts o
|
|
45
|
+
exit 0
|
|
46
|
+
end
|
|
47
|
+
end.parse!
|
|
48
|
+
|
|
49
|
+
if ARGV.any?
|
|
50
|
+
globs = []
|
|
51
|
+
filters = []
|
|
52
|
+
ARGV.each do |raw|
|
|
53
|
+
if (m = raw.match(/\A(.+):(\d+)\z/))
|
|
54
|
+
path_part = m[1]
|
|
55
|
+
line_part = Integer(m[2], 10)
|
|
56
|
+
abs = File.expand_path(path_part)
|
|
57
|
+
unless File.file?(abs)
|
|
58
|
+
warn "verity: #{raw.inspect} — line filter requires an existing file (got #{abs.inspect})"
|
|
59
|
+
exit 2
|
|
60
|
+
end
|
|
61
|
+
globs << abs
|
|
62
|
+
filters << [abs, line_part]
|
|
63
|
+
else
|
|
64
|
+
globs << File.expand_path(raw)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
Verity.configure do |c|
|
|
68
|
+
c.test_globs = globs.uniq
|
|
69
|
+
c.location_filters = filters unless filters.empty?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
73
|
+
warn "verity: #{e.message}"
|
|
74
|
+
exit 2
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
begin
|
|
78
|
+
exit(Verity.run ? 0 : 1)
|
|
79
|
+
rescue ArgumentError => e
|
|
80
|
+
warn "verity: #{e.message}"
|
|
81
|
+
exit 2
|
|
82
|
+
end
|