theorem 0.0.2 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2707643c05984ddc7bb49ef7f536fad90dc70ddc7fa058ec031f3945df8e4188
4
- data.tar.gz: 173b64d78b6de42218ebb90714c651a459e1ef377492840985ae8f5c0ae94a7f
3
+ metadata.gz: 561cfd58d73936664ce10aad722bab0fc21d97a28b421a5cc0e154bbf8f14bfa
4
+ data.tar.gz: 9e392733f121689bb139d908730ad2e7008f263e713d5193ede30d31f2309612
5
5
  SHA512:
6
- metadata.gz: e1f9df341383f804e7437a2193e2c9cd6978861d8dd3351533b7f14f059e3fd8e941e865c455325edcfa724da490549af329fbf5cd7fdd460333d1105fefeccc
7
- data.tar.gz: c9fd46000ef01e28b7f63f4c7512f599732004f129938085585b3725b8bf0abbea88961fea7048bdb5d8753dffb52b2228c4c0dacc24983668e2f3fdd2293193
6
+ metadata.gz: 7980418d3772ed5c314893f44cec5cd3cc166d3cf4e193580a0f1c65484e7020119c32db80ebba07e2a53e880e9f3ad5d83e45d6910dd3b3a08561642071abba
7
+ data.tar.gz: 25e8132963cab65e4d5822e15c1aa3dc5a8f28f225330a06f9a08bcff8d64a273bcdc86ed42035429e0bd1ac8e79676e0677061d898ba7ad460c781c6e16d7a4
data/bin/theorize CHANGED
@@ -1,7 +1,21 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
- require_relative '../src/harness'
3
+ require 'slop'
4
+ require_relative '../src/theorem'
4
5
 
5
- path = ARGV[0] || '.'
6
+ opts = Slop.parse do |o|
7
+ o.array '--require', 'files to require', delimiter: ','
8
+ o.string '-m', '--module', 'module (default: Theorem::Hypothesis)', default: 'Theorem::Hypothesis'
9
+ o.string '-h', '--harness', 'harness (default: Theorem::Harness)', default: 'Theorem::Harness'
10
+ o.array '-p', '--publisher', 'publishers (default: Theorem::StdoutReporter)', delimiter: ',', default: ['Theorem::StdoutReporter']
11
+ o.string '-d', '--directory', 'directory to load tests (default: ".")', default: '.'
12
+ o.array '-i', '--include', 'tags to specify what tests should be included (default: wont filter)', default: []
13
+ o.array '-e', '--exclude', 'tags the specify what tests should be excluded (default: wont filter)', default: []
14
+ o.string '--meta', 'string of metadata to pass to the harness', default: ''
15
+ o.on '--help' do
16
+ puts o
17
+ exit
18
+ end
19
+ end
6
20
 
7
- Theorem::Hypothesis::Harness.run! path
21
+ Theorem.run! opts.to_h
data/src/experiment.rb CHANGED
@@ -9,9 +9,9 @@ module Theorem
9
9
  @tests << { name: name, block: block }
10
10
  end
11
11
 
12
- def tests(**opts)
12
+ def tests(_experiment_namespace: to_s, **opts)
13
13
  @tests.map do |hash|
14
- Control::Test.new(hash[:name], to_s, **opts, &hash[:block])
14
+ Control::Test.new(hash[:name], _experiment_namespace, **opts, &hash[:block])
15
15
  end
16
16
  end
17
17
  end
data/src/harness.rb CHANGED
@@ -1,59 +1,41 @@
1
- require_relative './theorem'
2
- require 'extended_dir'
1
+ # frozen_string_literal: true
3
2
 
4
- # module
3
+ require_relative './theorem/harness'
4
+
5
+ # harness
5
6
  module Theorem
6
- # module
7
- module Hypothesis
8
- on_completed_test do |test|
9
- print test.failed? ? 'X' : '.'
10
- end
7
+ # default test harness
8
+ module Harness
9
+ include Control::Harness
11
10
 
12
- # harness
13
- class Harness
14
- def self.locate_tests(dir)
15
- ExtendedDir.require_all("./#{dir}")
11
+ load_tests do |options|
12
+ directory = options[:directory] || '.'
16
13
 
17
- Hypothesis.registry
18
- end
19
-
20
- def self.get_test_count(test_cases)
21
- test_cases.map do |test_case|
22
- test_case.tests.size
23
- end.inject(&:+)
24
- end
14
+ ExtendedDir.require_all("./#{directory}")
25
15
 
26
- def self.report_failures(failed_tests)
27
- failed_tests.each do |failure|
28
- puts "❌ Failure in #{failure.full_name}\nError: #{failure.error}\nBacktrace:\n------\n#{failure.error.backtrace.join("\n")}"
29
- end
30
- end
31
-
32
- def self.report_passes(passing_tests)
33
- passing_tests.each do |pass|
34
- puts "✓ #{pass.full_name}"
35
- end
36
- end
16
+ filtered_registry(options)
17
+ end
18
+ end
37
19
 
38
- def self.run!(dir)
39
- test_cases = locate_tests(dir)
40
- total_count = get_test_count(test_cases)
20
+ # module retry harness
21
+ module RetryHarness
22
+ include Control::Harness
41
23
 
42
- puts "Total tests #{total_count}"
24
+ on_run do |tests|
25
+ arr_of_tests = tests.map { |test| { test: test, index: 0 } }
43
26
 
44
- results = test_cases.each_with_object([]) do |test_case, memo|
45
- memo.concat test_case.run!
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
46
34
  end
47
-
48
- puts "\n\nSummary\n-------"
49
-
50
- failed_tests, passed_tests = results.partition(&:failed?)
51
-
52
- report_passes(passed_tests)
53
- report_failures(failed_tests)
54
-
55
- exit failed_tests.any? ? 1 : 0
35
+ final_results.concat results
56
36
  end
37
+
38
+ final_results
57
39
  end
58
40
  end
59
41
  end
@@ -0,0 +1,70 @@
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
@@ -54,6 +54,10 @@ module Theorem
54
54
  end
55
55
  end
56
56
 
57
+ def reverse_prepare(&block)
58
+ @state.unshift block
59
+ end
60
+
57
61
  def prepare(&block)
58
62
  @state << block
59
63
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'hypothesis'
4
+ require 'extended_dir'
5
+ module Theorem
6
+ module Control
7
+ # control harness
8
+ module Harness
9
+ def self.included(mod)
10
+ mod.extend(ClassMethods)
11
+ mod.define_singleton_method :included do |inner|
12
+ inner.define_singleton_method :run! do |options: {}|
13
+ tests = inner.instance_exec options, &mod.test_loader
14
+ results = inner.instance_exec tests, options, &mod.run_loader
15
+ inner.completed_suite_subscribers.each do |subscriber|
16
+ subscriber.call(results)
17
+ end
18
+ exit results.any?(&:failed?) ? 1 : 0
19
+ end
20
+ end
21
+ end
22
+
23
+ # harness helpers
24
+ 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
+
39
+ def load_tests(&block)
40
+ @on_load_tests = block
41
+ end
42
+
43
+ def on_run(&block)
44
+ @on_run = block
45
+ end
46
+
47
+ def run_loader
48
+ @on_run || DEFAULT_RUNNER
49
+ end
50
+
51
+ def test_loader
52
+ @on_load_tests || DEFAULT_LOADER
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -19,6 +19,8 @@ module Theorem
19
19
  klass.instance_eval do
20
20
  @before_all ||= Beaker.new
21
21
  @before_each ||= Beaker.new
22
+ @after_all ||= Beaker.new
23
+ @after_each ||= Beaker.new
22
24
  @around = Flask.new
23
25
  @tests = []
24
26
  @completed_tests = []
@@ -8,10 +8,35 @@ module Theorem
8
8
  @registry ||= []
9
9
  end
10
10
 
11
+ def filtered_registry(options)
12
+ registry.each do |test_class|
13
+ if options[:include]&.any?
14
+ test_class.tests.select! do |test|
15
+ test.metadata[:tags]&.intersection(options[:include])&.any?
16
+ end
17
+ end
18
+
19
+ next unless options[:exclude]&.any?
20
+
21
+ test_class.tests.reject! do |test|
22
+ test.metadata[:tags]&.intersection(options[:include])&.any?
23
+ end
24
+ end
25
+ end
26
+
11
27
  def add_to_registry(klass)
12
28
  registry << klass
13
29
  end
14
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
+
15
40
  def on_completed_test(&block)
16
41
  @completed_tests ||= []
17
42
  @completed_tests << block
@@ -20,6 +45,15 @@ module Theorem
20
45
  def completed_test_subscribers
21
46
  @completed_tests || []
22
47
  end
48
+
49
+ def on_completed_suite(&block)
50
+ @completed_suites ||= []
51
+ @completed_suites << block
52
+ end
53
+
54
+ def completed_suite_subscribers
55
+ @completed_suites || []
56
+ end
23
57
  end
24
58
  end
25
59
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Theorem
4
+ module Control
5
+ # reporter mixin
6
+ module Reporter
7
+ def self.extended(mod)
8
+ mod.extend(mod)
9
+ mod.define_singleton_method :included do |root|
10
+ mod.subscriptions.each do |subscription, handler|
11
+ mod.instance_exec root, subscription, handler do |root, sub, handle|
12
+ root.send(sub, &handle)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def subscribe(name, &block)
19
+ @subscriptions ||= {}
20
+ @subscriptions[name] = block
21
+ end
22
+
23
+ def subscriptions
24
+ @subscriptions
25
+ end
26
+ end
27
+ end
28
+ end
data/src/theorem/test.rb CHANGED
@@ -5,14 +5,15 @@ module Theorem
5
5
  module Control
6
6
  # test new
7
7
  class Test
8
- def initialize(name, namespace, **opts, &block)
8
+ def initialize(name, namespace, arguments: {}, **metadata, &block)
9
9
  @name = name
10
10
  @namespace = namespace
11
11
  @block = block
12
- @arguments = opts
12
+ @arguments = arguments
13
+ @metadata = metadata
13
14
  end
14
15
 
15
- attr_reader :block, :name, :arguments, :namespace
16
+ attr_reader :block, :name, :arguments, :namespace, :metadata
16
17
 
17
18
  def full_name
18
19
  "#{namespace} #{name}"
@@ -33,6 +34,12 @@ module Theorem
33
34
 
34
35
  @parent_before_each ||= []
35
36
  @parent_before_each << me.before_each_beaker
37
+
38
+ @parent_after_each ||= []
39
+ @parent_after_each.unshift me.after_each_beaker
40
+
41
+ @parent_after_all ||= []
42
+ @parent_after_all.unshift me.after_all_beaker
36
43
  end
37
44
  super
38
45
  end
@@ -49,12 +56,20 @@ module Theorem
49
56
  @before_each.prepare(&block)
50
57
  end
51
58
 
59
+ def after_each(&block)
60
+ @after_each.reverse_prepare(&block)
61
+ end
62
+
63
+ def after_all(&block)
64
+ @after_all.reverse_prepare(&block)
65
+ end
66
+
52
67
  def experiments(klass, **opts, &block)
53
68
  obj = Class.new
54
69
  obj.include(control)
55
70
  obj.instance_eval &block if block
56
- obj.instance_exec klass, opts do |experiment_klass, params|
57
- @tests.concat experiment_klass.tests(**params)
71
+ obj.instance_exec self, klass, opts do |consumer, experiment_klass, params|
72
+ @tests.concat experiment_klass.tests(_experiment_namespace: consumer.to_s, arguments: params)
58
73
  end
59
74
  end
60
75
 
@@ -70,8 +85,16 @@ module Theorem
70
85
  @before_each
71
86
  end
72
87
 
73
- def test(name, &block)
74
- @tests << Test.new(name, to_s, &block)
88
+ def after_each_beaker
89
+ @after_each
90
+ end
91
+
92
+ def after_all_beaker
93
+ @after_all
94
+ end
95
+
96
+ def test(name, **hargs, &block)
97
+ @tests << Test.new(name, to_s, **hargs, &block)
75
98
  end
76
99
 
77
100
  def run!
@@ -84,15 +107,20 @@ module Theorem
84
107
 
85
108
  results = []
86
109
  @tests.each do |test|
87
- before_each_failures = run_before_each_beakers(test_case)
88
- return before_each_failures if before_each_failures.any?
89
-
90
- error = run_test(test, test_case)
110
+ error ||= run_before_each_beakers(test_case)
111
+ error ||= run_test(test, test_case)
112
+ error ||= run_after_each_beakers(test_case)
91
113
 
92
114
  completed_test = CompletedTest.new(test, error)
93
115
  publish_test_completion(completed_test)
94
116
  results << completed_test
95
117
  end
118
+
119
+ after_failures = run_after_all_beakers(test_case)
120
+ if after_failures.any?
121
+ return after_failures
122
+ end
123
+
96
124
  results
97
125
  end
98
126
 
@@ -113,11 +141,13 @@ module Theorem
113
141
  end
114
142
  end
115
143
 
116
- def run_before_each_beakers(test_case)
117
- @parent_before_each&.each do |beaker|
144
+ def run_after_all_beakers(test_case)
145
+ @after_all.run!(test_case)
146
+
147
+ @parent_after_all&.each do |beaker|
118
148
  beaker.run!(test_case)
119
149
  end
120
- @before_each.run!(test_case)
150
+
121
151
  []
122
152
  rescue Exception => error
123
153
  Theorem.handle_exception(error)
@@ -127,6 +157,31 @@ module Theorem
127
157
  end
128
158
  end
129
159
 
160
+ def run_after_each_beakers(test_case)
161
+ @after_each.run!(test_case)
162
+
163
+ @parent_after_each&.each do |beaker|
164
+ beaker.run!(test_case)
165
+ end
166
+ nil
167
+ rescue Exception => error
168
+ Theorem.handle_exception(error)
169
+
170
+ error
171
+ end
172
+
173
+ def run_before_each_beakers(test_case)
174
+ @parent_before_each&.each do |beaker|
175
+ beaker.run!(test_case)
176
+ end
177
+ @before_each.run!(test_case)
178
+ nil
179
+ rescue Exception => error
180
+ Theorem.handle_exception(error)
181
+
182
+ error
183
+ end
184
+
130
185
  def run_before_all_beakers(test_case)
131
186
  @parent_before_all&.each do |beaker|
132
187
  beaker.run!(test_case)
data/src/theorem.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require_relative 'theorem/hypothesis'
2
+ require_relative 'harness'
2
3
  require_relative 'experiment'
4
+ require_relative 'stdout_reporter'
5
+
6
+ require_relative 'theorem/harness'
7
+ require 'json'
3
8
 
4
9
  module Theorem
5
10
  # RSpec subclasses Exception, so the only way to catch them without a dependency is to catch Exception
@@ -17,6 +22,41 @@ module Theorem
17
22
  end
18
23
  end
19
24
 
25
+ def self.run!(options)
26
+ options[:require].each do |file|
27
+ require file
28
+ end
29
+
30
+ raise StandardError, "Unknown Module: #{options[:module]}" unless defined? Object.const_get(options[:module])
31
+ raise StandardError, "Unknown Harness: #{options[:harness]}" unless defined? Object.const_get(options[:harness])
32
+
33
+ mod = Object.const_get(options[:module])
34
+ harness = Object.const_get options[:harness]
35
+ mod.include harness
36
+
37
+ options[:publisher].each do |publisher|
38
+ if defined? Object.const_get(publisher)
39
+ mod.include Object.const_get(publisher)
40
+ else
41
+ raise StandardError, "Unknown Publisher: #{publisher}"
42
+ end
43
+ end
44
+
45
+ mod.run!(options: options)
46
+ end
47
+
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
+
20
60
  module Hypothesis
21
61
  include Control::Hypothesis
22
62
  end
metadata CHANGED
@@ -1,45 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theorem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.1.0
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-08 00:00:00.000000000 Z
11
+ date: 2022-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: extended_dir
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 0.1.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 0.1.1
27
27
  - !ruby/object:Gem::Dependency
28
- name: rbs
28
+ name: slop
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
33
+ version: 4.9.2
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 4.9.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec-expectations
42
+ name: rbs
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rubocop
56
+ name: rspec-expectations
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: watir
70
+ name: rubocop
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description: simple. testing. ah.
83
+ description: simple and extensible test library toolkit
84
84
  email: sean.christopher.gregory@gmail.com
85
85
  executables:
86
86
  - theorize
@@ -90,21 +90,24 @@ files:
90
90
  - bin/theorize
91
91
  - src/experiment.rb
92
92
  - src/harness.rb
93
+ - src/stdout_reporter.rb
93
94
  - src/theorem.rb
94
95
  - src/theorem/beaker.rb
95
96
  - src/theorem/completed_test.rb
97
+ - src/theorem/harness.rb
96
98
  - src/theorem/hypothesis.rb
97
99
  - src/theorem/registry.rb
100
+ - src/theorem/reporter.rb
98
101
  - src/theorem/test.rb
99
102
  homepage: https://rubygems.org/gems/theorem
100
103
  licenses:
101
104
  - MIT
102
105
  metadata:
103
- source_code_uri: https://github.com/skinnyjames/theorem
106
+ source_code_uri: https://gitlab.com/skinnyjames/theorem
104
107
  post_install_message:
105
108
  rdoc_options: []
106
109
  require_paths:
107
- - lib
110
+ - src
108
111
  required_ruby_version: !ruby/object:Gem::Requirement
109
112
  requirements:
110
113
  - - ">="
@@ -119,5 +122,5 @@ requirements: []
119
122
  rubygems_version: 3.2.31
120
123
  signing_key:
121
124
  specification_version: 4
122
- summary: simple. testing. ah.
125
+ summary: simple and extensible test library toolkit
123
126
  test_files: []