duel 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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/AGENT_PROMPT.md +211 -0
  3. data/CHANGELOG.md +281 -0
  4. data/LICENSE +21 -0
  5. data/README.md +354 -0
  6. data/docs/README.md +23 -0
  7. data/docs/benchmarks.md +289 -0
  8. data/docs/ci.md +141 -0
  9. data/docs/configuration.md +429 -0
  10. data/docs/how-it-works.md +207 -0
  11. data/docs/json.md +306 -0
  12. data/docs/plugins.md +408 -0
  13. data/docs/troubleshooting.md +105 -0
  14. data/exe/duel +6 -0
  15. data/lib/duel/across.rb +48 -0
  16. data/lib/duel/baseline.rb +166 -0
  17. data/lib/duel/cli.rb +264 -0
  18. data/lib/duel/config.rb +343 -0
  19. data/lib/duel/context.rb +630 -0
  20. data/lib/duel/init.rb +211 -0
  21. data/lib/duel/json_report.rb +107 -0
  22. data/lib/duel/lock.rb +38 -0
  23. data/lib/duel/metric.rb +496 -0
  24. data/lib/duel/native.rb +162 -0
  25. data/lib/duel/process_sample.rb +144 -0
  26. data/lib/duel/profile.rb +116 -0
  27. data/lib/duel/rake_task.rb +25 -0
  28. data/lib/duel/resource.rb +209 -0
  29. data/lib/duel/rubies.rb +149 -0
  30. data/lib/duel/runner.rb +1114 -0
  31. data/lib/duel/stats.rb +73 -0
  32. data/lib/duel/summary.rb +237 -0
  33. data/lib/duel/templates/benchmark_duel.rb.erb +34 -0
  34. data/lib/duel/templates/duel.rb.erb +35 -0
  35. data/lib/duel/templates/perf.yml.erb +97 -0
  36. data/lib/duel/testing.rb +241 -0
  37. data/lib/duel/version.rb +5 -0
  38. data/lib/duel/worker.rb +211 -0
  39. data/lib/duel.rb +413 -0
  40. data/sig/duel/across.rbs +21 -0
  41. data/sig/duel/baseline.rbs +61 -0
  42. data/sig/duel/cli.rbs +95 -0
  43. data/sig/duel/config.rbs +181 -0
  44. data/sig/duel/context.rbs +387 -0
  45. data/sig/duel/init.rbs +97 -0
  46. data/sig/duel/json_report.rbs +47 -0
  47. data/sig/duel/lock.rbs +12 -0
  48. data/sig/duel/metric.rbs +351 -0
  49. data/sig/duel/native.rbs +98 -0
  50. data/sig/duel/process_sample.rbs +54 -0
  51. data/sig/duel/profile.rbs +64 -0
  52. data/sig/duel/rake_task.rbs +9 -0
  53. data/sig/duel/resource.rbs +106 -0
  54. data/sig/duel/rubies.rbs +45 -0
  55. data/sig/duel/runner.rbs +530 -0
  56. data/sig/duel/stats.rbs +26 -0
  57. data/sig/duel/summary.rbs +87 -0
  58. data/sig/duel/testing.rbs +91 -0
  59. data/sig/duel/version.rbs +3 -0
  60. data/sig/duel/worker.rbs +74 -0
  61. data/sig/duel.rbs +276 -0
  62. data/sig/manifest.yaml +4 -0
  63. metadata +124 -0
@@ -0,0 +1,181 @@
1
+ module Duel
2
+ # What Duel knows about a project (.duel.rb changes what doesn't follow
3
+ # from the conventions).
4
+ class Config
5
+ # A prepare block (see #prepare): given a Resource for a version or a
6
+ # worker, it returns the environment variables it gets.
7
+ type prepare = ^(Resource resource) -> Hash[String, String?]
8
+
9
+ # The Ruby each version runs on (see #ruby): nil for the runner's, its
10
+ # version file's, a path, or what a block makes of the version's tree.
11
+ type ruby = :version_file | String | ^(String root) -> String | nil
12
+
13
+ @root: String
14
+ @benchmarks: String
15
+ @watch: Array[String]
16
+ @library: String | :conventional | nil
17
+ @build: String | :conventional | nil
18
+ @main_branch: String?
19
+ @release_tags: String
20
+ @prepares: Hash[Symbol, Array[prepare]]
21
+ @ruby: ruby
22
+ @metrics: Array[singleton(Metric)]
23
+ @in_workers: Array[^(Stops stops) -> void]
24
+ @hooks: Hash[Symbol, Array[hook]]
25
+ @used: Hash[Symbol, Used]
26
+ @measures: Hash[String, Hash[Symbol, untyped]]
27
+ @loader: (^(String version_root) -> void)?
28
+
29
+ # Where the project is (and its config file).
30
+ attr_reader root: String
31
+
32
+ # A glob, from the root, of the files defining benchmarks.
33
+ attr_accessor benchmarks: String
34
+
35
+ # Paths, from the root, whose changes a comparison is about.
36
+ attr_accessor watch: Array[String]
37
+
38
+ # The library to require from each version, or nil for none.
39
+ attr_writer library (): String?
40
+
41
+ # A shell command that builds a checked-out tree, or nil for none.
42
+ attr_writer build (): String?
43
+
44
+ # The branch a comparison defaults to (nil: the one origin/HEAD names).
45
+ attr_accessor main_branch: String?
46
+
47
+ # The glob release tags match (for `--base last-release`).
48
+ attr_accessor release_tags: String
49
+
50
+ # The Ruby each version runs on: nil for the runner's own,
51
+ # :version_file for the one its version file names, a path, or a block
52
+ # given the version's tree.
53
+ attr_accessor ruby: ruby
54
+
55
+ # What #prepare was given, by scope.
56
+ # @api private
57
+ attr_reader prepares: Hash[Symbol, Array[prepare]]
58
+
59
+ # The metrics #metric added, in order.
60
+ attr_reader metrics: Array[singleton(Metric)]
61
+
62
+ # What #in_worker was given.
63
+ # @api private
64
+ attr_reader in_workers: Array[^(Stops stops) -> void]
65
+
66
+ # What the hooks were given, by name.
67
+ # @api private
68
+ attr_reader hooks: Hash[Symbol, Array[hook]]
69
+
70
+ # Each measure's settings changed, by name.
71
+ # @api private
72
+ attr_reader measures: Hash[String, Hash[Symbol, untyped]]
73
+
74
+ # A configuration of the conventions, for the project at +root+.
75
+ def initialize: (?String root) -> void
76
+
77
+ # What #prepare prepares: each version, or each worker.
78
+ # @api private
79
+ SCOPES: Array[Symbol]
80
+
81
+ # The library: the gem the project's only .gemspec names, unless set.
82
+ %a{pure} def library: () -> String?
83
+
84
+ # The build command: `bundle exec rake compile` for a project with
85
+ # ext/ and a Rakefile, unless set.
86
+ %a{pure} def build: () -> String?
87
+
88
+ # How a worker loads one version of the code, from the tree at the
89
+ # root it's given, instead of the convention.
90
+ def load: () { (String version_root) -> void } -> void
91
+
92
+ # Loads the version at +version_root+.
93
+ # @api private
94
+ def load_into: (String version_root) -> void
95
+
96
+ # Uses a plugin, by its name: requires duel/+name+, and configures Duel
97
+ # for it with +options+, once. Returns what that returned.
98
+ def use: (Symbol name, **untyped options) -> untyped
99
+
100
+ # The plugins used, by name, in the order they were first used.
101
+ def plugins: () -> Array[Symbol]
102
+
103
+ # A plugin as #use installed it.
104
+ # @api private
105
+ class Used
106
+ @options: Hash[Symbol, untyped]
107
+ @value: untyped
108
+
109
+ attr_reader options: Hash[Symbol, untyped]
110
+
111
+ attr_accessor value: untyped
112
+
113
+ def initialize: (Hash[Symbol, untyped] options) -> void
114
+
115
+ # What using it again with +given+ options returns, or fails.
116
+ def again: (Symbol name, Hash[Symbol, untyped] given) -> untyped
117
+ end
118
+
119
+ # Prepares each version (once for a run, in the runner, before any of
120
+ # its workers start), or each worker (as it starts). Returns the
121
+ # environment the version's workers, or the worker, get.
122
+ def prepare: (Symbol scope) { (Resource resource) -> Hash[String, String?] } -> void
123
+
124
+ # Adds a metric to every benchmark: a Duel::Metric subclass, or
125
+ # running totals the block reads (see Snapshot), one by name (with its
126
+ # settings) or several by their settings. Returns the metric: running
127
+ # totals' takes what explains them (.details), typed by what the block
128
+ # read with them, C.
129
+ def metric: (singleton(Metric) klass) -> singleton(Metric)
130
+ # (Totals alone, each a T: no context.)
131
+ | [T < Metric::value] (String name, **untyped settings) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
132
+ | [T < Metric::value] (Hash[String, Metric::settings] measures) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
133
+ | [T < Metric::value] (**Metric::settings measures) { () -> Snapshot::read_totals[T] } -> Snapshot::_Explained[T, nil]
134
+ # (Totals and their context, C.)
135
+ | [T < Metric::value, C] (String name, **untyped settings) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
136
+ | [T < Metric::value, C] (Hash[String, Metric::settings] measures) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
137
+ | [T < Metric::value, C] (**Metric::settings measures) { () -> [Snapshot::read_totals[T], C] } -> Snapshot::_Explained[T, C]
138
+
139
+ # Runs the block in each worker, once its version and the benchmarks
140
+ # have loaded and before its metrics are made. Returns what it
141
+ # returns, in the worker.
142
+ def in_worker: [T] () { (Stops worker) -> T } -> InWorker[T]
143
+
144
+ # The configuration's hooks (see Duel::HOOKS), for every benchmark,
145
+ # run where they were written, before its file's and its own.
146
+ def setup: () { (?Definition benchmark) -> void } -> void
147
+
148
+ def teardown: () { (?Definition benchmark) -> void } -> void
149
+
150
+ def before_sample: () { (?Definition benchmark) -> void } -> void
151
+
152
+ def around_iteration: () { (Iteration iteration) -> void } -> void
153
+
154
+ def after_iterations: () { () -> void } -> void
155
+
156
+ # Changes a measure's settings, by its name (skip: true leaves it out).
157
+ def measure: (String name, **untyped settings) -> void
158
+
159
+ # Whether a measure is skipped (see #measure).
160
+ def skip?: (String name) -> bool
161
+
162
+ # The files that define benchmarks, in order.
163
+ # @api private
164
+ def benchmark_files: () -> Array[String]
165
+
166
+ private
167
+
168
+ def add_hook: (Symbol name, Proc? block) -> void
169
+ end
170
+
171
+ # Silences warnings about redefined constants, while loading a version
172
+ # over files of another's (see Config#load_into).
173
+ # @api private
174
+ module QuietRedefinition : Warning
175
+ REDEFINITION: Regexp
176
+
177
+ def self.during: [T] () { () -> T } -> T
178
+
179
+ def warn: (String message, ?category: Symbol?) -> nil
180
+ end
181
+ end
@@ -0,0 +1,387 @@
1
+ module Duel
2
+ # What a benchmark's run is measured by: a Sample, or a Trial,
3
+ # MemorySample or ProfileSample.
4
+ # @api private
5
+ interface _Sample
6
+ def start: () -> void
7
+
8
+ def stop: () -> void
9
+
10
+ # Runs the block with measuring paused, and returns what it does.
11
+ def pause: [T] () { () -> T } -> T
12
+
13
+ # Runs the block (around_iteration hooks, around each iteration)
14
+ # paused, and returns what it does.
15
+ def between: [T] () { () -> T } -> T
16
+
17
+ # Runs the block measured, inside #between's, and returns what it
18
+ # does.
19
+ def unpause: [T] () { () -> T } -> T
20
+ end
21
+
22
+ # What a benchmark's blocks run in (its file's hooks, and its own,
23
+ # too): its state lives in instance variables. Its
24
+ # methods are the two a benchmark calls, and the helpers that files of
25
+ # benchmarks define.
26
+ class Context
27
+ @__duel_harness: Harness
28
+
29
+ # @api private
30
+ def initialize: (Harness harness) -> void
31
+
32
+ # Runs the block the first time the benchmark runs: its trial, or its
33
+ # first peak memory sample in a worker that ran none.
34
+ def once: () { () -> void } -> void
35
+
36
+ # Runs the block with measuring paused, and returns what it does.
37
+ def untimed: [T] () { () -> T } -> T
38
+
39
+ # Fails if a helper has the name of a method Duel gives benchmarks.
40
+ # @api private
41
+ def self.check: () -> void
42
+ end
43
+
44
+ # A benchmark as a worker runs it: its Context, its hooks, its trial
45
+ # and its samples.
46
+ # @api private
47
+ class Harness
48
+ # Nothing left by pausing and resuming.
49
+ NO_LEAK: Hash[String, Float]
50
+
51
+ # The iterations each calibration sample pauses and resumes.
52
+ STRETCHES: Integer
53
+
54
+ # The iterations of each sample that learns what around_iteration
55
+ # hooks add to the metrics costly to read.
56
+ HOOKED: Integer
57
+
58
+ # The benchmark's body (or a hook), as a method of the context's, or
59
+ # a hook of the configuration's.
60
+ interface _Bound
61
+ def call: (?) -> untyped
62
+
63
+ def arity: () -> Integer
64
+ end
65
+
66
+ @definition: Definition
67
+ @context: Context
68
+ @setup: Array[_Bound]
69
+ @teardown: Array[_Bound]
70
+ @before_sample: Array[_Bound]
71
+ @after: Array[_Bound]
72
+ @body: _Bound
73
+ # Whether its block takes the number of iterations.
74
+ @given_n: bool
75
+ @iteration: Iteration?
76
+ @trial: bool
77
+ @sample: _Sample?
78
+ # Whether its once blocks have run (false: they run in the sample
79
+ # running, a worker's first for peak memory, and nil: no trial yet).
80
+ @once_done: bool?
81
+ @leak: Hash[String, Float]?
82
+
83
+ attr_reader definition: Definition
84
+
85
+ attr_reader context: Context
86
+
87
+ def initialize: (Definition definition, ?Hash[Symbol, Array[hook]] hooks) -> void
88
+
89
+ def once: () { () -> void } -> void
90
+
91
+ def untimed: [T] () { () -> T } -> T
92
+
93
+ # Runs its setup hooks.
94
+ def prepare: () -> void
95
+
96
+ # Runs its teardown hooks.
97
+ def finish: () -> void
98
+
99
+ # Runs the benchmark once, measuring nothing, with its once blocks.
100
+ def trial: () -> void
101
+
102
+ # Runs one sample for its peak memory (its once blocks too, if there
103
+ # was no trial).
104
+ def run_memory: (Integer n, MemorySample sample) -> void
105
+
106
+ # Runs one sample of +n+ iterations, measured by +sample+.
107
+ def run: (Integer n, _Sample sample) -> void
108
+
109
+ # Whether it runs inside around_iteration hooks.
110
+ def around?: () -> bool
111
+
112
+ # A Sample of +n+ iterations of it, measured by +metrics+.
113
+ def sample: (Array[Metric] metrics, n: Integer, ?counting: bool) -> Sample
114
+
115
+ # What a benchmark with around_iteration hooks leaves in each
116
+ # measure, an iteration, that isn't the iteration's own.
117
+ def leak: (Array[Metric] metrics) -> Hash[String, Float]
118
+
119
+ # Runs one iteration, measured, then its after_iterations hooks.
120
+ def iteration: () -> untyped
121
+
122
+ private
123
+
124
+ # The hooks +name+d: the configuration's, then its file's and its own.
125
+ def hooks: (Symbol name, Hash[Symbol, Array[hook]] hooks) -> Array[_Bound]
126
+
127
+ # +hook+, given the benchmark, if it takes it.
128
+ def given: (_Bound hook) -> _Bound
129
+
130
+ # What pausing and resuming leaves in each measure, an iteration.
131
+ def paused: (Array[Metric] metrics) -> Hash[String, Float]
132
+
133
+ # What the around_iteration hooks add to each measure (of metrics
134
+ # costly to read), an iteration.
135
+ def hooked: (Array[Metric] metrics) -> Hash[String, Float]
136
+
137
+ # Each measure's least in +runs+, over +stretches+.
138
+ def least: (Array[Hash[String, Metric::value]] runs, Integer stretches) -> Hash[String, Float]
139
+
140
+ def iterate: (Integer n, _Sample sample) -> void
141
+
142
+ def bound: (^(?) [self: Context] -> untyped block) -> _Bound
143
+ end
144
+
145
+ # An iteration of a benchmark with around_iteration hooks, which a hook
146
+ # is given to run.
147
+ class Iteration
148
+ @harness: Harness
149
+ @hook: Harness::_Bound
150
+ @inner: Iteration?
151
+ @ran: bool
152
+ # What its iteration did, the last time it ran.
153
+ @done: untyped
154
+
155
+ # @api private
156
+ def initialize: (Harness harness, Harness::_Bound hook, Iteration? inner) -> void
157
+
158
+ # Runs the iteration, measured, and returns what it does.
159
+ def call: () -> untyped
160
+
161
+ # Runs its hook, given it, and returns what its iteration did.
162
+ # @api private
163
+ def run: () -> untyped
164
+ end
165
+
166
+ # One measured stretch of a benchmark: every metric's measurement of it,
167
+ # and (CRuby) about how many bytes it allocated. A metric's #start_sample gets
168
+ # it: its #benchmark and #n are the metric's to read, and the rest is the
169
+ # worker's.
170
+ class Sample
171
+ # Zero bytes allocated: what JRuby tells.
172
+ # @api private
173
+ NO_HEAP: ^() -> Integer
174
+
175
+ # Bytes allocated so far, roughly (zero on JRuby).
176
+ # @api private
177
+ HEAP: ^() -> Integer
178
+
179
+ # Collects the garbage left so far, then holds off collecting.
180
+ # @api private
181
+ QUIET_GC: ^() -> void
182
+
183
+ @benchmark: Definition
184
+ @n: Integer
185
+ @counting: bool
186
+ @metrics: Array[Metric]
187
+ # Those paused around each iteration: not those costly to read.
188
+ @paused: Array[Metric]
189
+ @timed: Hash[String, Metric::value]
190
+ @counted: Hash[String, Metric::value]
191
+ @details: Hash[String, String]
192
+ @started: true?
193
+ @stopped: true?
194
+ @heap: Integer
195
+
196
+ # What a metric learns of the sample: its benchmark and its iterations.
197
+ attr_reader benchmark: Definition
198
+
199
+ attr_reader n: Integer
200
+
201
+ # @api private
202
+ attr_reader started: true?
203
+
204
+ # @api private
205
+ attr_reader stopped: true?
206
+
207
+ # Each timed measure's total, by name.
208
+ # @api private
209
+ attr_reader timed: Hash[String, Metric::value]
210
+
211
+ # Each counted measure's total, by name (when counting).
212
+ # @api private
213
+ attr_reader counted: Hash[String, Metric::value]
214
+
215
+ # Bytes allocated (once it's stopped).
216
+ # @api private
217
+ attr_reader heap: Integer
218
+
219
+ # What metrics said of it (Metric#details), by measure.
220
+ # @api private
221
+ attr_reader details: Hash[String, String]
222
+
223
+ # @api private
224
+ def initialize: (Definition benchmark, Array[Metric] metrics, n: Integer, ?counting: bool, ?around: bool) -> void
225
+
226
+ # @api private
227
+ def start: () -> void
228
+
229
+ # @api private
230
+ def stop: () -> void
231
+
232
+ # Runs the block with the metrics paused, and returns what it does.
233
+ # @api private
234
+ def pause: [T] () { () -> T } -> T
235
+
236
+ # Runs the block (around_iteration hooks) with the metrics paused, but those
237
+ # costly to read, and returns what it does.
238
+ # @api private
239
+ def between: [T] () { () -> T } -> T
240
+
241
+ # Runs the block measured, inside #between's, and returns what it
242
+ # does.
243
+ # @api private
244
+ def unpause: [T] () { () -> T } -> T
245
+
246
+ # Takes +leak+ away from each measure, once an iteration.
247
+ # @api private
248
+ def less: (Hash[String, Float] leak) -> void
249
+
250
+ private
251
+
252
+ # Keeps what a metric says of the sample, by measure.
253
+ def explain: (Metric metric) -> void
254
+
255
+ # Files a metric's measurement (or several, by name) by kind.
256
+ def record: (Metric metric, Metric::measurement value) -> void
257
+
258
+ # Adds +measured+ to +name+'s total, of its kind.
259
+ def file: (Hash[String, Metric::kind] kinds, String name, Metric::value? measured) -> void
260
+ end
261
+
262
+ # A sample of what around_iteration hooks add to the metrics costly to
263
+ # read: all but the iterations measured.
264
+ # @api private
265
+ class Hooked
266
+ @sample: Sample
267
+
268
+ def initialize: (Sample sample) -> void
269
+
270
+ def start: () -> void
271
+
272
+ def stop: () -> void
273
+
274
+ # Runs the block (an untimed one, inside an iteration): paused already.
275
+ def pause: [T] () { () -> T } -> T
276
+
277
+ # Runs the block (the around_iteration hooks): measured.
278
+ def between: [T] () { () -> T } -> T
279
+
280
+ # Runs the block (an iteration) paused.
281
+ def unpause: [T] () { () -> T } -> T
282
+ end
283
+
284
+ # A run of a benchmark that measures nothing: to see that it runs.
285
+ # @api private
286
+ class Trial
287
+ @started: true?
288
+ @stopped: true?
289
+
290
+ attr_reader started: true?
291
+
292
+ attr_reader stopped: true?
293
+
294
+ def start: () -> true
295
+
296
+ def stop: () -> true
297
+
298
+ def pause: [T] () { () -> T } -> T
299
+
300
+ def between: [T] () { () -> T } -> T
301
+
302
+ def unpause: [T] () { () -> T } -> T
303
+ end
304
+
305
+ # A stretch of a benchmark run for its peak memory.
306
+ # @api private
307
+ class MemorySample
308
+ @started: true?
309
+ @stopped: true?
310
+ # Resident memory where it started, plus what paused blocks left.
311
+ @base: Integer
312
+ # The most above it the watcher saw, outside paused blocks.
313
+ @peak: Integer
314
+ @paused: bool?
315
+ # Resident memory when it last stopped watching.
316
+ @unwatched: Integer
317
+ @watcher: Thread
318
+
319
+ attr_reader started: true?
320
+
321
+ attr_reader stopped: true?
322
+
323
+ # Bytes (once it's stopped).
324
+ attr_reader peak: Integer
325
+
326
+ def start: () -> void
327
+
328
+ def stop: () -> void
329
+
330
+ # Runs the block unwatched: what it leaves resident isn't counted.
331
+ def pause: [T] () { () -> T } -> T
332
+
333
+ # Runs the block (around_iteration hooks) unwatched, as #pause does.
334
+ def between: [T] () { () -> T } -> T
335
+
336
+ # Runs the block watched, inside #between's.
337
+ def unpause: [T] () { () -> T } -> T
338
+
339
+ private
340
+
341
+ def unwatched: () -> void
342
+
343
+ def watched: () -> void
344
+
345
+ def resident: () -> Integer
346
+
347
+ def watch: () -> void
348
+ end
349
+
350
+ # A stretch of a benchmark profiled with StackProf, the garbage
351
+ # collector held off, and untimed blocks and around_iteration hooks'
352
+ # own work left out.
353
+ # @api private
354
+ class ProfileSample
355
+ # Microseconds between samples.
356
+ INTERVAL: Integer
357
+
358
+ # What StackProf samples by here (see .mode).
359
+ MODE: :cpu | :wall
360
+
361
+ @started: true?
362
+ @stopped: true?
363
+
364
+ # What StackProf samples by on +platform+: CPU time, but elapsed time
365
+ # on macOS.
366
+ def self.mode: (?String platform) -> (:cpu | :wall)
367
+
368
+ attr_reader started: true?
369
+
370
+ attr_reader stopped: true?
371
+
372
+ def start: () -> void
373
+
374
+ def stop: () -> void
375
+
376
+ # Runs the block (an untimed one, or around_iteration hooks' work) left
377
+ # out of the profile.
378
+ def pause: [T] () { () -> T } -> T
379
+
380
+ # Runs the block (around_iteration hooks) in #pause.
381
+ def between: [T] () { () -> T } -> T
382
+
383
+ # Runs the block (an iteration, inside its hooks) profiled,
384
+ # inside #pause's.
385
+ def unpause: [T] () { () -> T } -> T
386
+ end
387
+ end
data/sig/duel/init.rbs ADDED
@@ -0,0 +1,97 @@
1
+ module Duel
2
+ # `duel init`: sets Duel up in a gem or an app that isn't a Rails app.
3
+ # @api private
4
+ class Init
5
+ USAGE: String
6
+
7
+ TEMPLATES: String
8
+
9
+ # What's added to a Rakefile.
10
+ RAKE_TASK: String
11
+
12
+ # What's added to a Gemfile, for profiles of regressions.
13
+ STACKPROF: String
14
+
15
+ # A Gemfile's development group.
16
+ DEVELOPMENT: Regexp
17
+
18
+ @argv: Array[String]
19
+ @out: _Output
20
+ @err: _Output
21
+ @root: String
22
+ @force: bool?
23
+ @dry_run: bool?
24
+ @stackprof: bool?
25
+
26
+ def initialize: (Array[String] argv, ?out: _Output, ?err: _Output, ?root: String) -> void
27
+
28
+ # Returns the exit status: 0 set up, 1 a Rails app, 2 a bad option.
29
+ def run: () -> Integer
30
+
31
+ # The files it writes, from the project's root, to what's in them.
32
+ def files: () -> Hash[String, String]
33
+
34
+ private
35
+
36
+ def parser: () -> OptionParser
37
+
38
+ # Says to use duel-rails's generator instead.
39
+ def rails: () -> Integer
40
+
41
+ # Writes +content+ to +path+, unless it exists, and says what it did.
42
+ def write: (String path, String content) -> void
43
+
44
+ # Adds Duel's Rake task to the Rakefile.
45
+ def add_rake_task: () -> void
46
+
47
+ # Adds StackProf to the Gemfile: in its development group, or at its end.
48
+ def add_stackprof: () -> void
49
+
50
+ # The Gemfile with StackProf last in its development group, or nil.
51
+ def in_development_group: (String text) -> String?
52
+
53
+ # What goes between a file's text and what's added at its end.
54
+ def gap: (String text) -> String
55
+
56
+ # The Gemfile with StackProf at its end.
57
+ def at_the_end: (String text) -> String
58
+
59
+ def said: (String what, String path) -> void
60
+
61
+ # What to do next, numbered, after a blank line.
62
+ def next_steps: () -> String
63
+
64
+ def render: (String name) -> String
65
+
66
+ # The library the gemspec names, or nil.
67
+ def library: () -> String?
68
+
69
+ # The library, without a gemspec: lib/'s only Ruby file, or nil.
70
+ def guess: () -> String?
71
+
72
+ # What each version's workers require.
73
+ def required: () -> String?
74
+
75
+ def benchmark_file: () -> String
76
+
77
+ def gemfile?: () -> bool
78
+
79
+ def ext: () -> bool
80
+
81
+ def version_file: () -> bool
82
+
83
+ # The Ruby the workflow sets up, or none.
84
+ def ruby_version: () -> String?
85
+
86
+ # The root of the project's git repository.
87
+ def repository: () -> String
88
+
89
+ # The project's directory, from its repository's root, or nil.
90
+ def working_directory: () -> String?
91
+
92
+ def workflow_name: () -> String
93
+
94
+ # What marks the workflow's comment on a pull request.
95
+ def marker: () -> String
96
+ end
97
+ end
@@ -0,0 +1,47 @@
1
+ module Duel
2
+ # A run's report as JSON, for a program to read.
3
+ # @api private
4
+ class JSONReport
5
+ # A value in the report: plain JSON types.
6
+ type value = String | Integer | Float | bool | nil | Array[value] | Hash[String, value]
7
+
8
+ # The report, by key.
9
+ type report = Hash[String, value]
10
+
11
+ # Its schema's version.
12
+ SCHEMA: Integer
13
+
14
+ @runner: Runner
15
+ @results: Array[Result]
16
+ @settings: Settings
17
+ @exit: Integer
18
+ @versions: Hash[String, Hash[String, String | bool | nil]]
19
+
20
+ # A run that didn't compare: +status+, its +exit+ status, and what it
21
+ # said.
22
+ def self.note: (String status, Integer exit, String message, Settings settings) -> report
23
+
24
+ # What every report starts with.
25
+ def self.head: (String status, Integer exit, String? message, Settings settings) -> report
26
+
27
+ def initialize: (Runner runner, Array[Result] results, settings: Settings, exit: Integer, baseline: Hash[String, String?],
28
+ candidate: Hash[String, String | bool | nil]) -> void
29
+
30
+ def to_h: () -> report
31
+
32
+ private
33
+
34
+ def benchmark: (Result result) -> report
35
+
36
+ # Each timed measure, by metric.
37
+ def timed: (Result result) -> Hash[String, report]
38
+
39
+ # Each counted measure, by metric.
40
+ def counted: (Result result) -> Hash[String, report]
41
+
42
+ def peak_memory: (Result result) -> report?
43
+
44
+ # The verdicts across the board, by metric.
45
+ def across: () -> Hash[String, report]
46
+ end
47
+ end