scientist 1.1.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a444eefb69bf453db50912ba9a629edd0845f1b2591c36809f4329862188b82
4
- data.tar.gz: 28895c2df468c46c10d4b474ee9aa58b448514bb856c6f059112ad48d09bb97b
3
+ metadata.gz: f03841ae9bccb8d7b979971d5f6a38482f57bbc81af82d0a710479ccc3f16fbb
4
+ data.tar.gz: c0f81d696918e302462baf1bd918cd25179a99045d48082fb162e1fdb2fa2280
5
5
  SHA512:
6
- metadata.gz: 40c3e14294c377e70c86e03b50ac121da94b8e79e0b5f14a6844343a4f5b56b6fa340f66caa4671498e303978eea9ebd331ad7633aebd6b5901d970321f71342
7
- data.tar.gz: '092d3a37710ea538580205e284b31314eb298b5d10d910d2a36cff0b334ecbbd01c2b9311b35769eec7f308549f954c1a27084306f60232cd18bae24fd59e96d'
6
+ metadata.gz: f1680e6564a6e83b3dde55f6757f3e7c953a70b3d748aa0e7ca2a54b5a33a4b3a947a8f20aa52790a41e6a66e36627e4e851f627625c0aaf9fd4cf41b94ace8c
7
+ data.tar.gz: 75ba381053b73e9418ed2ab5608c70c92a55990c938a1c25f4324169ff560a8feea6827ef909e50b9fc3baf5a5eb49f3b7b26ed4e3f9799007b80a71c7543c86
data/.travis.yml CHANGED
@@ -3,12 +3,11 @@ language: ruby
3
3
  cache: bundler
4
4
  script: script/test
5
5
  rvm:
6
- - 1.9.3
7
- - 2.0.0
8
- - 2.1.8
9
- - 2.2.4
10
- - 2.3.0
11
- - 2.4.0
6
+ - 2.3.8
7
+ - 2.4.5
8
+ - 2.5.3
9
+ - 2.6.1
10
+ - truffleruby-head
12
11
  before_install: gem install bundler
13
12
  addons:
14
13
  apt:
data/README.md CHANGED
@@ -24,9 +24,9 @@ Wrap a `use` block around the code's original behavior, and wrap `try` around th
24
24
 
25
25
  * It decides whether or not to run the `try` block,
26
26
  * Randomizes the order in which `use` and `try` blocks are run,
27
- * Measures the durations of all behaviors,
27
+ * Measures the durations of all behaviors in seconds,
28
28
  * Compares the result of `try` to the result of `use`,
29
- * Swallows (but records) any exceptions raised in the `try` block, and
29
+ * Swallow and record exceptions raised in the `try` block when overriding `raised`, and
30
30
  * Publishes all this information.
31
31
 
32
32
  The `use` block is called the **control**. The `try` block is called the **candidate**.
@@ -62,7 +62,7 @@ class MyExperiment
62
62
 
63
63
  attr_accessor :name
64
64
 
65
- def initialize(name:)
65
+ def initialize(name)
66
66
  @name = name
67
67
  end
68
68
 
@@ -71,20 +71,21 @@ class MyExperiment
71
71
  true
72
72
  end
73
73
 
74
+ def raised(operation, error)
75
+ # see "In a Scientist callback" below
76
+ p "Operation '#{operation}' failed with error '#{error.inspect}'"
77
+ super # will re-raise
78
+ end
79
+
74
80
  def publish(result)
75
81
  # see "Publishing results" below
76
82
  p result
77
83
  end
78
84
  end
79
-
80
- # replace `Scientist::Default` as the default implementation
81
- module Scientist::Experiment
82
- def self.new(name)
83
- MyExperiment.new(name: name)
84
- end
85
- end
86
85
  ```
87
86
 
87
+ When `Scientist::Experiment` is included in a class, it automatically sets it as the default implementation via `Scientist::Experiment.set_default`. This `set_default` call is is skipped if you include `Scientist::Experiment` in a module.
88
+
88
89
  Now calls to the `science` helper will load instances of `MyExperiment`.
89
90
 
90
91
  ### Controlling comparison
@@ -206,6 +207,8 @@ class MyExperiment
206
207
  end
207
208
  ```
208
209
 
210
+ Note that the `#clean` method will discard the previous cleaner block if you call it again. If for some reason you need to access the currently configured cleaner block, `Scientist::Experiment#cleaner` will return the block without further ado. _(This probably won't come up in normal usage, but comes in handy if you're writing, say, a custom experiment runner that provides default cleaners.)_
211
+
209
212
  ### Ignoring mismatches
210
213
 
211
214
  During the early stages of an experiment, it's possible that some of your code will always generate a mismatch for reasons you know and understand but haven't yet fixed. Instead of these known cases always showing up as mismatches in your metrics or analysis, you can tell an experiment whether or not to ignore a mismatch using the `ignore` method. You may include more than one block if needed:
@@ -254,7 +257,7 @@ class MyExperiment
254
257
 
255
258
  attr_accessor :name, :percent_enabled
256
259
 
257
- def initialize(name:)
260
+ def initialize(name)
258
261
  @name = name
259
262
  @percent_enabled = 100
260
263
  end
@@ -327,7 +330,7 @@ class MyExperiment
327
330
  }
328
331
  else
329
332
  {
330
- # see "Keeping it clean" below
333
+ # see "Keeping it clean" above
331
334
  :value => observation.cleaned_value
332
335
  }
333
336
  end
@@ -491,6 +494,22 @@ science "various-ways", run: "first-way" do |e|
491
494
  end
492
495
  ```
493
496
 
497
+ #### Providing fake timing data
498
+
499
+ If you're writing tests that depend on specific timing values, you can provide canned durations using the `fabricate_durations_for_testing_purposes` method, and Scientist will report these in `Scientist::Observation#duration` instead of the actual execution times.
500
+
501
+ ```ruby
502
+ science "absolutely-nothing-suspicious-happening-here" do |e|
503
+ e.use { ... } # "control"
504
+ e.try { ... } # "candidate"
505
+ e.fabricate_durations_for_testing_purposes( "control" => 1.0, "candidate" => 0.5 )
506
+ end
507
+ ```
508
+
509
+ `fabricate_durations_for_testing_purposes` takes a Hash of duration values, keyed by behavior names. (By default, Scientist uses `"control"` and `"candidate"`, but if you override these as shown in [Trying more than one thing](#trying-more-than-one-thing) or [No control, just candidates](#no-control-just-candidates), use matching names here.) If a name is not provided, the actual execution time will be reported instead.
510
+
511
+ _Like `Scientist::Experiment#cleaner`, this probably won't come up in normal usage. It's here to make it easier to test code that extends Scientist._
512
+
494
513
  ### Without including Scientist
495
514
 
496
515
  If you need to use Scientist in a place where you aren't able to include the Scientist module, you can call `Scientist.run`:
@@ -504,17 +523,23 @@ end
504
523
 
505
524
  ## Hacking
506
525
 
507
- Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are installed automatically. Science requires Ruby 1.9 or newer.
526
+ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs the unit tests. All development dependencies are installed automatically. Scientist requires Ruby 2.3 or newer.
527
+
528
+ ## Wrappers
529
+
530
+ - [RealGeeks/lab_tech](https://github.com/RealGeeks/lab_tech) is a Rails engine for using this library by controlling, storing, and analyzing experiment results with ActiveRecord.
508
531
 
509
532
  ## Alternatives
510
533
 
511
534
  - [daylerees/scientist](https://github.com/daylerees/scientist) (PHP)
512
- - [github/scientist.net](https://github.com/github/scientist.net) (.NET)
535
+ - [scientistproject/scientist.net](https://github.com/scientistproject/Scientist.net) (.NET)
513
536
  - [joealcorn/laboratory](https://github.com/joealcorn/laboratory) (Python)
514
537
  - [rawls238/Scientist4J](https://github.com/rawls238/Scientist4J) (Java)
515
538
  - [tomiaijo/scientist](https://github.com/tomiaijo/scientist) (C++)
516
539
  - [trello/scientist](https://github.com/trello/scientist) (node.js)
517
540
  - [ziyasal/scientist.js](https://github.com/ziyasal/scientist.js) (node.js, ES6)
541
+ - [TrueWill/tzientist](https://github.com/TrueWill/tzientist) (node.js, TypeScript)
542
+ - [TrueWill/paleontologist](https://github.com/TrueWill/paleontologist) (Deno, TypeScript)
518
543
  - [yeller/laboratory](https://github.com/yeller/laboratory) (Clojure)
519
544
  - [lancew/Scientist](https://github.com/lancew/Scientist) (Perl 5)
520
545
  - [lancew/ScientistP6](https://github.com/lancew/ScientistP6) (Perl 6)
@@ -523,7 +548,9 @@ Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs t
523
548
  - [calavera/go-scientist](https://github.com/calavera/go-scientist) (Go)
524
549
  - [jelmersnoeck/experiment](https://github.com/jelmersnoeck/experiment) (Go)
525
550
  - [spoptchev/scientist](https://github.com/spoptchev/scientist) (Kotlin / Java)
526
-
551
+ - [junkpiano/scientist](https://github.com/junkpiano/scientist) (Swift)
552
+ - [serverless scientist](http://serverlessscientist.com/) (AWS Lambda)
553
+ - [fightmegg/scientist](https://github.com/fightmegg/scientist) (TypeScript, Browser / Node.js)
527
554
 
528
555
  ## Maintainers
529
556
 
data/doc/changelog.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changes
2
2
 
3
+ ## v1.3.0 (2 April 2019)
4
+
5
+ - New: Drop support for ruby <2.3
6
+ - Fix: Build new strings instead of modifying frozen ones
7
+ - New: Add an accessor for the configured clean block
8
+ - New: Add a hook to use fabricated durations instead of actual timing data.
9
+
10
+ ## v1.2.0 (5 July 2018)
11
+
12
+ - New: Use monotonic clock for duration calculations
13
+ - New: Drop support for ruby <2.1 to support monotonic clock
14
+ - New: Run CI on Ruby 2.5
15
+
16
+ ## v1.1.2 (9 May 2018)
17
+
18
+ - New: Add `raise_with` option to allow for custom mismatch errors to be raised
19
+
20
+ ## v1.1.1 (6 February 2018)
21
+
22
+ - Fix: default experiment no longer runs all `try` paths
23
+ - New: Add `Scientist.run` module method for running experiments when an included module isn't available
24
+ - New: Add [Noise and error rates](https://github.com/github/scientist#noise-and-error-rates) to `README.md`
25
+
3
26
  ## v1.1.0 (29 August 2017)
4
27
 
5
28
  - New: [Specify which exception types to rescue](https://github.com/github/scientist#in-candidate-code)
@@ -9,16 +9,26 @@ module Scientist::Experiment
9
9
  # If this is nil, raise_on_mismatches class attribute is used instead.
10
10
  attr_accessor :raise_on_mismatches
11
11
 
12
- # Create a new instance of a class that implements the Scientist::Experiment
13
- # interface.
14
- #
15
- # Override this method directly to change the default implementation.
12
+ def self.included(base)
13
+ self.set_default(base) if base.instance_of?(Class)
14
+ base.extend RaiseOnMismatch
15
+ end
16
+
17
+ # Instantiate a new experiment (using the class given to the .set_default method).
16
18
  def self.new(name)
17
- Scientist::Default.new(name)
19
+ (@experiment_klass || Scientist::Default).new(name)
20
+ end
21
+
22
+ # Configure Scientist to use the given class for all future experiments
23
+ # (must implement the Scientist::Experiment interface).
24
+ #
25
+ # Called automatically when new experiments are defined.
26
+ def self.set_default(klass)
27
+ @experiment_klass = klass
18
28
  end
19
29
 
20
30
  # A mismatch, raised when raise_on_mismatches is enabled.
21
- class MismatchError < StandardError
31
+ class MismatchError < Exception
22
32
  attr_reader :name, :result
23
33
 
24
34
  def initialize(name, result)
@@ -41,10 +51,10 @@ module Scientist::Experiment
41
51
  def format_observation(observation)
42
52
  observation.name + ":\n" +
43
53
  if observation.raised?
44
- observation.exception.inspect.prepend(" ") + "\n" +
45
- observation.exception.backtrace.map { |line| line.prepend(" ") }.join("\n")
54
+ lines = observation.exception.backtrace.map { |line| " #{line}" }.join("\n")
55
+ " #{observation.exception.inspect}" + "\n" + lines
46
56
  else
47
- observation.cleaned_value.inspect.prepend(" ")
57
+ " #{observation.cleaned_value.inspect}"
48
58
  end
49
59
  end
50
60
  end
@@ -67,10 +77,6 @@ module Scientist::Experiment
67
77
  end
68
78
  end
69
79
 
70
- def self.included(base)
71
- base.extend RaiseOnMismatch
72
- end
73
-
74
80
  # Define a block of code to run before an experiment begins, if the experiment
75
81
  # is enabled.
76
82
  #
@@ -96,6 +102,13 @@ module Scientist::Experiment
96
102
  @_scientist_cleaner = block
97
103
  end
98
104
 
105
+ # Accessor for the clean block, if one is available.
106
+ #
107
+ # Returns the configured block, or nil.
108
+ def cleaner
109
+ @_scientist_cleaner
110
+ end
111
+
99
112
  # Internal: Clean a value with the configured clean block, or return the value
100
113
  # if no clean block is configured.
101
114
  #
@@ -209,16 +222,7 @@ module Scientist::Experiment
209
222
  @_scientist_before_run.call
210
223
  end
211
224
 
212
- observations = []
213
-
214
- behaviors.keys.shuffle.each do |key|
215
- block = behaviors[key]
216
- observations << Scientist::Observation.new(key, self, &block)
217
- end
218
-
219
- control = observations.detect { |o| o.name == name }
220
-
221
- result = Scientist::Result.new self, observations, control
225
+ result = generate_result(name)
222
226
 
223
227
  begin
224
228
  publish(result)
@@ -234,11 +238,9 @@ module Scientist::Experiment
234
238
  end
235
239
  end
236
240
 
237
- if control.raised?
238
- raise control.exception
239
- else
240
- control.value
241
- end
241
+ control = result.control
242
+ raise control.exception if control.raised?
243
+ control.value
242
244
  end
243
245
 
244
246
  # Define a block that determines whether or not the experiment should run.
@@ -290,4 +292,24 @@ module Scientist::Experiment
290
292
  !!raise_on_mismatches
291
293
  end
292
294
  end
295
+
296
+ # Provide predefined durations to use instead of actual timing data.
297
+ # This is here solely as a convenience for developers of libraries that extend Scientist.
298
+ def fabricate_durations_for_testing_purposes(fabricated_durations = {})
299
+ @_scientist_fabricated_durations = fabricated_durations
300
+ end
301
+
302
+ # Internal: Generate the observations and create the result from those and the control.
303
+ def generate_result(name)
304
+ observations = []
305
+
306
+ behaviors.keys.shuffle.each do |key|
307
+ block = behaviors[key]
308
+ fabricated_duration = @_scientist_fabricated_durations && @_scientist_fabricated_durations[key]
309
+ observations << Scientist::Observation.new(key, self, fabricated_duration: fabricated_duration, &block)
310
+ end
311
+
312
+ control = observations.detect { |o| o.name == name }
313
+ Scientist::Result.new(self, observations, control)
314
+ end
293
315
  end
@@ -8,9 +8,6 @@ class Scientist::Observation
8
8
  # The experiment this observation is for
9
9
  attr_reader :experiment
10
10
 
11
- # The instant observation began.
12
- attr_reader :now
13
-
14
11
  # The String name of the behavior.
15
12
  attr_reader :name
16
13
 
@@ -23,18 +20,19 @@ class Scientist::Observation
23
20
  # The Float seconds elapsed.
24
21
  attr_reader :duration
25
22
 
26
- def initialize(name, experiment, &block)
23
+ def initialize(name, experiment, fabricated_duration: nil, &block)
27
24
  @name = name
28
25
  @experiment = experiment
29
- @now = Time.now
30
26
 
27
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) unless fabricated_duration
31
28
  begin
32
29
  @value = block.call
33
30
  rescue *RESCUES => e
34
31
  @exception = e
35
32
  end
36
33
 
37
- @duration = (Time.now - @now).to_f
34
+ @duration = fabricated_duration ||
35
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) - starting
38
36
 
39
37
  freeze
40
38
  end
@@ -1,3 +1,3 @@
1
1
  module Scientist
2
- VERSION = "1.1.2"
2
+ VERSION = "1.6.0"
3
3
  end
data/scientist.gemspec CHANGED
@@ -10,6 +10,8 @@ Gem::Specification.new do |gem|
10
10
  gem.homepage = "https://github.com/github/scientist"
11
11
  gem.license = "MIT"
12
12
 
13
+ gem.required_ruby_version = '>= 2.3'
14
+
13
15
  gem.files = `git ls-files`.split($/)
14
16
  gem.executables = []
15
17
  gem.test_files = gem.files.grep(/^test/)
data/script/test CHANGED
@@ -4,7 +4,7 @@
4
4
  set -e
5
5
 
6
6
  cd $(dirname "$0")/..
7
- script/bootstrap && ruby -I lib \
7
+ script/bootstrap && bundle exec ruby -I lib \
8
8
  -e 'require "bundler/setup"' \
9
9
  -e 'require "coveralls"; Coveralls.wear!{ add_filter ".bundle" }' \
10
10
  -e 'require "minitest/autorun"' \
@@ -2,6 +2,9 @@ describe Scientist::Experiment do
2
2
  class Fake
3
3
  include Scientist::Experiment
4
4
 
5
+ # Undo auto-config magic / preserve default behavior of Scientist::Experiment.new
6
+ Scientist::Experiment.set_default(nil)
7
+
5
8
  def initialize(*args)
6
9
  end
7
10
 
@@ -28,6 +31,24 @@ describe Scientist::Experiment do
28
31
  @ex = Fake.new
29
32
  end
30
33
 
34
+ it "sets the default on inclusion" do
35
+ klass = Class.new do
36
+ include Scientist::Experiment
37
+
38
+ def initialize(name)
39
+ end
40
+ end
41
+
42
+ assert_kind_of klass, Scientist::Experiment.new("hello")
43
+
44
+ Scientist::Experiment.set_default(nil)
45
+ end
46
+
47
+ it "doesn't set the default on inclusion when it's a module" do
48
+ Module.new { include Scientist::Experiment }
49
+ assert_kind_of Scientist::Default, Scientist::Experiment.new("hello")
50
+ end
51
+
31
52
  it "has a default implementation" do
32
53
  ex = Scientist::Experiment.new("hello")
33
54
  assert_kind_of Scientist::Default, ex
@@ -239,6 +260,13 @@ describe Scientist::Experiment do
239
260
  assert_equal 10, @ex.clean_value(10)
240
261
  end
241
262
 
263
+ it "provides the clean block when asked for it, in case subclasses wish to override and provide defaults" do
264
+ assert_nil @ex.cleaner
265
+ cleaner = ->(value) { value.upcase }
266
+ @ex.clean(&cleaner)
267
+ assert_equal cleaner, @ex.cleaner
268
+ end
269
+
242
270
  it "calls the configured clean block with a value when configured" do
243
271
  @ex.clean do |value|
244
272
  value.upcase
@@ -437,6 +465,20 @@ describe Scientist::Experiment do
437
465
  assert_raises(Scientist::Experiment::MismatchError) { @ex.run }
438
466
  end
439
467
 
468
+ it "allows MismatchError to bubble up through bare rescues" do
469
+ Fake.raise_on_mismatches = true
470
+ @ex.use { "control" }
471
+ @ex.try { "candidate" }
472
+ runner = -> {
473
+ begin
474
+ @ex.run
475
+ rescue
476
+ # StandardError handled
477
+ end
478
+ }
479
+ assert_raises(Scientist::Experiment::MismatchError) { runner.call }
480
+ end
481
+
440
482
  describe "#raise_on_mismatches?" do
441
483
  it "raises when there is a mismatch if the experiment instance's raise on mismatches is enabled" do
442
484
  Fake.raise_on_mismatches = false
@@ -525,7 +567,7 @@ candidate:
525
567
  assert_equal " \"value\"", lines[2]
526
568
  assert_equal "candidate:", lines[3]
527
569
  assert_equal " #<RuntimeError: error>", lines[4]
528
- assert_match %r( test/scientist/experiment_test.rb:\d+:in `block), lines[5]
570
+ assert_match %r(test/scientist/experiment_test.rb:\d+:in `block), lines[5]
529
571
  end
530
572
  end
531
573
  end
@@ -559,4 +601,32 @@ candidate:
559
601
  refute before, "before_run should not have run"
560
602
  end
561
603
  end
604
+
605
+ describe "testing hooks for extending code" do
606
+ it "allows a user to provide fabricated durations for testing purposes" do
607
+ @ex.use { true }
608
+ @ex.try { true }
609
+ @ex.fabricate_durations_for_testing_purposes( "control" => 0.5, "candidate" => 1.0 )
610
+
611
+ @ex.run
612
+
613
+ cont = @ex.published_result.control
614
+ cand = @ex.published_result.candidates.first
615
+ assert_in_delta 0.5, cont.duration, 0.01
616
+ assert_in_delta 1.0, cand.duration, 0.01
617
+ end
618
+
619
+ it "returns actual durations if fabricated ones are omitted for some blocks" do
620
+ @ex.use { true }
621
+ @ex.try { sleep 0.1; true }
622
+ @ex.fabricate_durations_for_testing_purposes( "control" => 0.5 )
623
+
624
+ @ex.run
625
+
626
+ cont = @ex.published_result.control
627
+ cand = @ex.published_result.candidates.first
628
+ assert_in_delta 0.5, cont.duration, 0.01
629
+ assert_in_delta 0.1, cand.duration, 0.01
630
+ end
631
+ end
562
632
  end
@@ -56,27 +56,29 @@ describe Scientist do
56
56
  obj = Object.new
57
57
  obj.extend(Scientist)
58
58
 
59
- result = obj.science "test", run: "first-way" do |e|
60
- experiment = e
59
+ behaviors_executed = []
61
60
 
62
- e.try("first-way") { true }
63
- e.try("second-way") { true }
61
+ result = obj.science "test", run: "first-way" do |e|
62
+ e.try("first-way") { behaviors_executed << "first-way" ; true }
63
+ e.try("second-way") { behaviors_executed << "second-way" ; true }
64
64
  end
65
65
 
66
66
  assert_equal true, result
67
+ assert_equal [ "first-way" ], behaviors_executed
67
68
  end
68
69
 
69
70
  it "runs control when there is a nil named test" do
70
71
  obj = Object.new
71
72
  obj.extend(Scientist)
72
73
 
73
- result = obj.science "test", nil do |e|
74
- experiment = e
74
+ behaviors_executed = []
75
75
 
76
- e.use { true }
77
- e.try("second-way") { true }
76
+ result = obj.science "test", nil do |e|
77
+ e.use { behaviors_executed << "control" ; true }
78
+ e.try("second-way") { behaviors_executed << "second-way" ; true }
78
79
  end
79
80
 
80
81
  assert_equal true, result
82
+ assert_equal [ "control" ], behaviors_executed
81
83
  end
82
84
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scientist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
@@ -9,10 +9,10 @@ authors:
9
9
  - Rick Bradley
10
10
  - Jesse Toth
11
11
  - Nathan Witmer
12
- autorequire:
12
+ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2018-05-09 00:00:00.000000000 Z
15
+ date: 2021-03-08 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: minitest
@@ -80,7 +80,7 @@ homepage: https://github.com/github/scientist
80
80
  licenses:
81
81
  - MIT
82
82
  metadata: {}
83
- post_install_message:
83
+ post_install_message:
84
84
  rdoc_options: []
85
85
  require_paths:
86
86
  - lib
@@ -88,16 +88,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
88
  requirements:
89
89
  - - ">="
90
90
  - !ruby/object:Gem::Version
91
- version: '0'
91
+ version: '2.3'
92
92
  required_rubygems_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  requirements: []
98
- rubyforge_project:
99
- rubygems_version: 2.7.3
100
- signing_key:
98
+ rubygems_version: 3.1.4
99
+ signing_key:
101
100
  specification_version: 4
102
101
  summary: Carefully test, measure, and track refactored code.
103
102
  test_files: