theorem 1.1.0 → 1.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 561cfd58d73936664ce10aad722bab0fc21d97a28b421a5cc0e154bbf8f14bfa
4
- data.tar.gz: 9e392733f121689bb139d908730ad2e7008f263e713d5193ede30d31f2309612
3
+ metadata.gz: fa09d87e0b219e171e02728791f787a6ecd68a759e8a21143e85302361f68a1d
4
+ data.tar.gz: 739e1c4e6e19a3145d7c87c8ea688d10be70cce45735939e7f5d0bdf1817d385
5
5
  SHA512:
6
- metadata.gz: 7980418d3772ed5c314893f44cec5cd3cc166d3cf4e193580a0f1c65484e7020119c32db80ebba07e2a53e880e9f3ad5d83e45d6910dd3b3a08561642071abba
7
- data.tar.gz: 25e8132963cab65e4d5822e15c1aa3dc5a8f28f225330a06f9a08bcff8d64a273bcdc86ed42035429e0bd1ac8e79676e0677061d898ba7ad460c781c6e16d7a4
6
+ metadata.gz: 49ef938d77de1eacfd29a61c312acb396de525adb5c9b9699d3698cd31d8511c05665c560b55de61bc0fb9884024b32dbc61771cc0e44ae5267f26301cd46a18
7
+ data.tar.gz: 2fe7a59bec06dfd7b14c02e9245cdd0e9ca1ccf013f83254706cccd8e72a3aba9ac3048d9b0ed083b14f3d3e552c8cdd1b42ed75d24cc6ee67581740e5048f7f
data/src/harness.rb CHANGED
@@ -16,26 +16,4 @@ module Theorem
16
16
  filtered_registry(options)
17
17
  end
18
18
  end
19
-
20
- # module retry harness
21
- module RetryHarness
22
- include Control::Harness
23
-
24
- on_run do |tests|
25
- arr_of_tests = tests.map { |test| { test: test, index: 0 } }
26
-
27
- final_results = []
28
- arr_of_tests.each do |test|
29
- test[:index] += 1
30
- results = test[:test].run!
31
- if results.any?(&:failed?) && test[:index] <= 3
32
- puts "Retrying iteration: #{test[:index]}\n#{results.map(&:full_name).join("\n")}"
33
- redo
34
- end
35
- final_results.concat results
36
- end
37
-
38
- final_results
39
- end
40
- end
41
19
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../theorem/reporter'
4
+
5
+ module Theorem
6
+ module StringRefinements
7
+ refine String do
8
+ # colorization
9
+ def colorize(color_code)
10
+ "\e[#{color_code}m#{self}\e[0m"
11
+ end
12
+
13
+ def red
14
+ colorize(31)
15
+ end
16
+
17
+ def green
18
+ colorize(32)
19
+ end
20
+
21
+ def yellow
22
+ colorize(33)
23
+ end
24
+
25
+ def blue
26
+ colorize(34)
27
+ end
28
+
29
+ def pink
30
+ colorize(35)
31
+ end
32
+
33
+ def light_blue
34
+ colorize(36)
35
+ end
36
+ end
37
+ end
38
+
39
+ # Default Stdout reporter
40
+ module StdoutReporter
41
+ extend Control::Reporter
42
+ using StringRefinements
43
+
44
+ subscribe :test_finished do |test|
45
+ print test.failed? ? 'x'.red : '.'.green
46
+ end
47
+
48
+ subscribe :suite_finished do |results, duration|
49
+ puts "\n"
50
+ report_summary(results)
51
+
52
+ failed_tests = results.select(&:failed?)
53
+
54
+ report_failures(failed_tests)
55
+
56
+ puts "\nTotal time: #{duration} seconds"
57
+ puts "Total tests: #{results.size}\n"
58
+ end
59
+
60
+ def inflate_percentiles(tests)
61
+ sorted = tests.reject { |t| t.duration.nil? }.sort_by(&:duration)
62
+ tests.each_with_object([]) do |test, arr|
63
+ _, below = sorted.partition do |duration_test|
64
+ if test.duration.nil?
65
+ true
66
+ else
67
+ test.duration >= duration_test.duration
68
+ end
69
+ end
70
+ hash = {}
71
+ hash[:percentile] = (below.size.to_f / (sorted.size.to_f + 1)) * 100
72
+ hash[:test] = test
73
+ arr << hash
74
+ end
75
+ end
76
+
77
+ def report_summary(tests)
78
+ inflated = inflate_percentiles(tests)
79
+
80
+ top_20 = 80 / (100.0 * (tests.size + 1).to_f)
81
+ lowest_20 = 20 / (100.0 * (tests.size + 1).to_f)
82
+
83
+ inflated.each do |test|
84
+ icon = test[:test].failed? ? '❌'.red : '✓'.green
85
+ puts "#{icon} #{test[:test].full_name.blue} : #{duration(test)}"
86
+ end
87
+ end
88
+
89
+ def duration(test)
90
+ return 'Not run' if test[:test].duration.nil?
91
+
92
+ str = "#{format('%<num>0.10f', num: test[:test].duration)} seconds"
93
+ rank = test[:percentile]
94
+ if rank < 10
95
+ str.red
96
+ elsif rank > 90
97
+ str.green
98
+ elsif rank < 30
99
+ str.yellow
100
+ else
101
+ str
102
+ end
103
+ end
104
+
105
+ def report_failures(tests)
106
+ tests.each do |failure|
107
+ puts "\n\nFailure in #{failure.full_name}\nError: #{failure.error.message.to_s.red}\nBacktrace:\n------\n#{failure.error.backtrace.map(&:red).join("\n")}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -9,6 +9,10 @@ module Theorem
9
9
  @ctx = ctx
10
10
  end
11
11
 
12
+ def name
13
+ @test.name
14
+ end
15
+
12
16
  def run!
13
17
  @test.run!(@ctx)
14
18
  end
@@ -54,6 +58,10 @@ module Theorem
54
58
  end
55
59
  end
56
60
 
61
+ def empty?
62
+ @state.empty?
63
+ end
64
+
57
65
  def reverse_prepare(&block)
58
66
  @state.unshift block
59
67
  end
@@ -4,11 +4,14 @@ module Theorem
4
4
  module Control
5
5
  # error class
6
6
  class CompletedTest
7
- attr_reader :test, :error
7
+ attr_reader :test, :duration
8
+ attr_accessor :error, :notary
8
9
 
9
- def initialize(test, error = nil)
10
+ def initialize(test, error = nil, notary:, duration: nil)
10
11
  @test = test
11
12
  @error = error
13
+ @notary = notary
14
+ @duration = duration
12
15
  end
13
16
 
14
17
  def full_name
@@ -11,9 +11,21 @@ module Theorem
11
11
  mod.define_singleton_method :included do |inner|
12
12
  inner.define_singleton_method :run! do |options: {}|
13
13
  tests = inner.instance_exec options, &mod.test_loader
14
+
15
+ inner.suite_started_subscribers.each do |subscriber|
16
+ subscriber.call tests.map(&:tests).flatten.map do |test|
17
+ { name: test.name, metadata: test.metadata }
18
+ end
19
+ end
20
+
21
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
22
  results = inner.instance_exec tests, options, &mod.run_loader
15
- inner.completed_suite_subscribers.each do |subscriber|
16
- subscriber.call(results)
23
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
+
25
+ duration = ending - starting
26
+
27
+ inner.suite_finished_subscribers.each do |subscriber|
28
+ subscriber.call(results, duration)
17
29
  end
18
30
  exit results.any?(&:failed?) ? 1 : 0
19
31
  end
@@ -22,19 +34,6 @@ module Theorem
22
34
 
23
35
  # harness helpers
24
36
  module ClassMethods
25
- DEFAULT_LOADER = ->(options) do
26
- directory = options[:directory] || '.'
27
-
28
- ExtendedDir.require_all("./#{directory}")
29
-
30
- registry
31
- end
32
-
33
- DEFAULT_RUNNER = ->(tests, options) do
34
- tests.each_with_object([]) do |test, memo|
35
- memo.concat test.run!
36
- end
37
- end
38
37
 
39
38
  def load_tests(&block)
40
39
  @on_load_tests = block
@@ -45,11 +44,31 @@ module Theorem
45
44
  end
46
45
 
47
46
  def run_loader
48
- @on_run || DEFAULT_RUNNER
47
+ @on_run || default_runner
49
48
  end
50
49
 
51
50
  def test_loader
52
- @on_load_tests || DEFAULT_LOADER
51
+ @on_load_tests || default_loader
52
+ end
53
+
54
+ private
55
+
56
+ def default_loader
57
+ lambda do |options|
58
+ directory = options[:directory] || '.'
59
+
60
+ ExtendedDir.require_all("./#{directory}")
61
+
62
+ registry
63
+ end
64
+ end
65
+
66
+ def default_runner
67
+ lambda do |tests, options|
68
+ tests.each_with_object([]) do |test, memo|
69
+ memo.concat test.run!
70
+ end
71
+ end
53
72
  end
54
73
  end
55
74
  end
@@ -15,6 +15,16 @@ module Theorem
15
15
  mod
16
16
  end
17
17
 
18
+ klass.attr_reader :notary
19
+
20
+ klass.define_method :initialize do
21
+ @notary = Notation.new
22
+ end
23
+
24
+ klass.define_method :notate do |&block|
25
+ block.call(@notary)
26
+ end
27
+
18
28
  klass.extend ClassMethods
19
29
  klass.instance_eval do
20
30
  @before_all ||= Beaker.new
@@ -29,9 +39,9 @@ module Theorem
29
39
  mod.add_to_registry(klass)
30
40
  end
31
41
 
32
- mod.const_set(:Beaker, Beaker)
33
- mod.const_set(:Test, Test)
34
- mod.const_set(:CompletedTest, CompletedTest)
42
+ mod.const_set(:Beaker, Beaker) unless mod.const_defined?(:Beaker)
43
+ mod.const_set(:Test, Test) unless mod.const_defined?(:Test)
44
+ mod.const_set(:CompletedTest, CompletedTest) unless mod.const_defined?(:CompletedTest)
35
45
  mod.extend(Registry)
36
46
  end
37
47
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Theorem
4
+ module Control
5
+ class Notation
6
+ def initialize(state = {})
7
+ @state = state
8
+ end
9
+
10
+ def write(key, value)
11
+ @state[key] = value
12
+ end
13
+
14
+ def read(key)
15
+ @state[key]
16
+ end
17
+
18
+ def dump
19
+ @state
20
+ end
21
+
22
+ def merge(notary)
23
+ Notation.new(@state.merge(notary.dump))
24
+ end
25
+
26
+ def edit(key, &block)
27
+ data = read(key)
28
+ block.call(data)
29
+ write(key, data)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -28,31 +28,17 @@ module Theorem
28
28
  registry << klass
29
29
  end
30
30
 
31
- def on_extra_event(&block)
32
- @extra_events ||= []
33
- @extra_events << block
34
- end
35
-
36
- def extra_event_subscribers
37
- @extra_events || []
38
- end
39
-
40
- def on_completed_test(&block)
41
- @completed_tests ||= []
42
- @completed_tests << block
43
- end
31
+ %i[suite_started test_started test_finished suite_finished].each do |method|
32
+ define_method method do |&block|
33
+ instance_variable_set("@#{method}_subscribers", []) unless instance_variable_get("@#{method}_subscribers")
34
+ instance_variable_get("@#{method}_subscribers").append(block)
35
+ end
44
36
 
45
- def completed_test_subscribers
46
- @completed_tests || []
47
- end
37
+ define_method "#{method}_subscribers" do
38
+ return [] unless instance_variable_get("@#{method}_subscribers")
48
39
 
49
- def on_completed_suite(&block)
50
- @completed_suites ||= []
51
- @completed_suites << block
52
- end
53
-
54
- def completed_suite_subscribers
55
- @completed_suites || []
40
+ instance_variable_get("@#{method}_subscribers")
41
+ end
56
42
  end
57
43
  end
58
44
  end
data/src/theorem/test.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative './beaker'
3
+ require_relative 'notation'
3
4
 
4
5
  module Theorem
5
6
  module Control
@@ -11,14 +12,19 @@ module Theorem
11
12
  @block = block
12
13
  @arguments = arguments
13
14
  @metadata = metadata
15
+ @notary = Notation.new
14
16
  end
15
17
 
16
- attr_reader :block, :name, :arguments, :namespace, :metadata
18
+ attr_reader :block, :name, :arguments, :namespace, :metadata, :notary
17
19
 
18
20
  def full_name
19
21
  "#{namespace} #{name}"
20
22
  end
21
23
 
24
+ def notate(&block)
25
+ block.call(notary)
26
+ end
27
+
22
28
  def run!(ctx)
23
29
  ctx.instance_exec self, **arguments, &block
24
30
  end
@@ -100,32 +106,67 @@ module Theorem
100
106
  def run!
101
107
  test_case = new
102
108
 
109
+ # run before all beakers to create state in test case
103
110
  before_failures = run_before_all_beakers(test_case)
111
+
104
112
  if before_failures.any?
113
+ before_failures.each do |failure|
114
+ publish_test_completion(failure)
115
+ end
105
116
  return before_failures
106
117
  end
107
118
 
119
+ # duplicate the before_all arrangement for the after all hook
120
+ duplicate_test_case = test_case.clone
121
+
108
122
  results = []
109
123
  @tests.each do |test|
124
+ test_start = clock_time
125
+
126
+ publish_test_start(test)
127
+
110
128
  error ||= run_before_each_beakers(test_case)
111
- error ||= run_test(test, test_case)
112
- error ||= run_after_each_beakers(test_case)
113
129
 
114
- completed_test = CompletedTest.new(test, error)
115
- publish_test_completion(completed_test)
130
+ before_test_case = test_case.clone
131
+ error ||= run_test(test, before_test_case)
132
+ error ||= run_after_each_beakers(before_test_case)
133
+
134
+ notary = test_case.notary.merge(test.notary)
135
+
136
+ duration = clock_time - test_start
137
+
138
+ completed_test = CompletedTest.new(test, error, duration: duration, notary: notary.dump)
139
+
140
+ # publish_early if there are no after_all beakers
141
+ publish_test_completion(completed_test) if @after_all.empty?
142
+
116
143
  results << completed_test
117
144
  end
118
145
 
119
- after_failures = run_after_all_beakers(test_case)
146
+ after_failures = run_after_all_beakers(results, duplicate_test_case)
147
+
120
148
  if after_failures.any?
149
+ after_failures.each do |failure|
150
+ publish_test_completion(failure)
151
+ end
121
152
  return after_failures
122
153
  end
123
154
 
155
+ results.each do |completed_test|
156
+ # merge any after_all notations
157
+ completed_test.notary.merge!(duplicate_test_case.notary.dump)
158
+ publish_test_completion(completed_test) unless @after_all.empty?
159
+ end
160
+
124
161
  results
125
162
  end
126
163
 
127
164
  private
128
165
 
166
+ def clock_time
167
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
168
+ end
169
+
129
170
  def run_test(test, test_case)
130
171
  if @around.empty?
131
172
  begin
@@ -141,7 +182,7 @@ module Theorem
141
182
  end
142
183
  end
143
184
 
144
- def run_after_all_beakers(test_case)
185
+ def run_after_all_beakers(results, test_case)
145
186
  @after_all.run!(test_case)
146
187
 
147
188
  @parent_after_all&.each do |beaker|
@@ -152,9 +193,12 @@ module Theorem
152
193
  rescue Exception => error
153
194
  Theorem.handle_exception(error)
154
195
 
155
- @tests.map do |test|
156
- CompletedTest.new(test, error)
196
+ results.each do |test|
197
+ test.error = error
198
+ test.notary = test_case.notary
157
199
  end
200
+
201
+ results
158
202
  end
159
203
 
160
204
  def run_after_each_beakers(test_case)
@@ -192,16 +236,21 @@ module Theorem
192
236
  Theorem.handle_exception(error)
193
237
 
194
238
  @tests.map do |test|
195
- CompletedTest.new(test, error)
239
+ CompletedTest.new(test, error, notary: test_case.notary)
196
240
  end
197
241
  end
198
242
 
199
243
  def publish_test_completion(completed_test)
200
- control.completed_test_subscribers.each do |subscriber|
244
+ control.test_finished_subscribers.each do |subscriber|
201
245
  subscriber.call(completed_test)
202
246
  end
203
247
  end
204
248
 
249
+ def publish_test_start(test)
250
+ control.test_started_subscribers.each do |subscriber|
251
+ subscriber.call(test)
252
+ end
253
+ end
205
254
  end
206
255
  end
207
256
  end
data/src/theorem.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  require_relative 'theorem/hypothesis'
2
2
  require_relative 'harness'
3
3
  require_relative 'experiment'
4
- require_relative 'stdout_reporter'
5
-
4
+ require_relative 'publishers/stdout_reporter'
6
5
  require_relative 'theorem/harness'
7
6
  require 'json'
8
7
 
@@ -45,18 +44,6 @@ module Theorem
45
44
  mod.run!(options: options)
46
45
  end
47
46
 
48
- module JsonReporter
49
- extend Control::Reporter
50
-
51
- subscribe :on_completed_suite do |results|
52
- results = results.map do |result|
53
- { name: result.full_name, failed: result.failed? }
54
- end
55
- puts results.to_json
56
- puts "\n\n"
57
- end
58
- end
59
-
60
47
  module Hypothesis
61
48
  include Control::Hypothesis
62
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theorem
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Gregory
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-09 00:00:00.000000000 Z
11
+ date: 2022-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: extended_dir
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-cobertura
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: simple and extensible test library toolkit
84
112
  email: sean.christopher.gregory@gmail.com
85
113
  executables:
@@ -90,12 +118,13 @@ files:
90
118
  - bin/theorize
91
119
  - src/experiment.rb
92
120
  - src/harness.rb
93
- - src/stdout_reporter.rb
121
+ - src/publishers/stdout_reporter.rb
94
122
  - src/theorem.rb
95
123
  - src/theorem/beaker.rb
96
124
  - src/theorem/completed_test.rb
97
125
  - src/theorem/harness.rb
98
126
  - src/theorem/hypothesis.rb
127
+ - src/theorem/notation.rb
99
128
  - src/theorem/registry.rb
100
129
  - src/theorem/reporter.rb
101
130
  - src/theorem/test.rb
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './theorem/reporter'
4
-
5
- module Theorem
6
- module StringRefinements
7
- refine String do
8
- # colorization
9
- def colorize(color_code)
10
- "\e[#{color_code}m#{self}\e[0m"
11
- end
12
-
13
- def red
14
- colorize(31)
15
- end
16
-
17
- def green
18
- colorize(32)
19
- end
20
-
21
- def yellow
22
- colorize(33)
23
- end
24
-
25
- def blue
26
- colorize(34)
27
- end
28
-
29
- def pink
30
- colorize(35)
31
- end
32
-
33
- def light_blue
34
- colorize(36)
35
- end
36
- end
37
- end
38
-
39
- # Default Stdout reporter
40
- module StdoutReporter
41
- extend Control::Reporter
42
- using StringRefinements
43
-
44
- subscribe :on_completed_test do |test|
45
- print test.failed? ? 'x'.red : '.'.green
46
- end
47
-
48
- subscribe :on_completed_suite do |results|
49
- puts "\n"
50
- report_summary(results)
51
-
52
- failed_tests = results.select(&:failed?)
53
-
54
- report_failures(failed_tests)
55
- end
56
-
57
- def report_summary(tests)
58
- tests.each do |test|
59
- icon = test.failed? ? '❌'.red : '✓'.green
60
- puts "#{icon} #{test.full_name.blue}"
61
- end
62
- end
63
-
64
- def report_failures(tests)
65
- tests.each do |failure|
66
- puts "Failure in #{failure.full_name}\nError: #{failure.error.message.to_s.red}\nBacktrace:\n------\n#{failure.error.backtrace.map(&:red).join("\n")}"
67
- end
68
- end
69
- end
70
- end