theorem 1.1.0 → 1.2.2

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: 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