theorem 0.0.1 → 1.0.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 +4 -4
- data/bin/theorize +15 -2
- data/src/experiment.rb +19 -0
- data/src/harness.rb +23 -30
- data/src/stdout_reporter.rb +70 -0
- data/src/theorem/beaker.rb +66 -0
- data/src/theorem/completed_test.rb +27 -0
- data/src/theorem/harness.rb +57 -0
- data/src/theorem/hypothesis.rb +40 -0
- data/src/theorem/registry.rb +43 -0
- data/src/theorem/reporter.rb +28 -0
- data/src/theorem/test.rb +206 -0
- data/src/theorem.rb +61 -0
- metadata +29 -20
- data/src/hypothesis.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91647688dc2f743ee8d64461f26ee5f2265be617c82623a97e0cfd7cdec58aa8
|
4
|
+
data.tar.gz: 410086697fb9d6718c3f05a64cbac277b59fa4ced15cf679484b9eed11bb9edf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f66749de7a52cee707d0c149e7e9b273d05b46248a69e7acb4f3669d400e6a833fcac9d268eb0f87acd6f5b3cbc9d8c5bfaa836491d705c8666563ae9dfe6fe8
|
7
|
+
data.tar.gz: dc737567dc4303c362f5dcb3bfa6a75474981d6b355367b6d8d711c4ad5170c8f3e0c0cf252197f4007ef16f9e403234f75277559e3d9ff8ce908cc5faff94e9
|
data/bin/theorize
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
-
|
3
|
+
require 'slop'
|
4
|
+
require_relative '../src/theorem'
|
4
5
|
|
5
|
-
|
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.on '--help' do
|
13
|
+
puts o
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Theorem.run! opts
|
data/src/experiment.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Theorem
|
4
|
+
# shared examples
|
5
|
+
class Experiment
|
6
|
+
class << self
|
7
|
+
def test(name, &block)
|
8
|
+
@tests ||= []
|
9
|
+
@tests << { name: name, block: block }
|
10
|
+
end
|
11
|
+
|
12
|
+
def tests(_experiment_namespace: to_s, **opts)
|
13
|
+
@tests.map do |hash|
|
14
|
+
Control::Test.new(hash[:name], _experiment_namespace, **opts, &hash[:block])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/src/harness.rb
CHANGED
@@ -1,40 +1,33 @@
|
|
1
|
-
|
2
|
-
require 'extended_dir'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
module Theorem
|
6
|
-
module Hypothesis
|
7
|
-
on_completed_test do |test|
|
8
|
-
print test.failed? ? 'X' : '.'
|
9
|
-
end
|
10
|
-
|
11
|
-
# harness
|
12
|
-
class Harness
|
13
|
-
def self.locate_tests(dir)
|
14
|
-
ExtendedDir.require_all("./#{dir}")
|
15
|
-
|
16
|
-
Hypothesis.registry
|
17
|
-
end
|
3
|
+
require_relative './theorem/harness'
|
18
4
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
puts "Total tests #{total_count}"
|
5
|
+
# harness
|
6
|
+
module Theorem
|
7
|
+
# default test harness
|
8
|
+
module Harness
|
9
|
+
include Control::Harness
|
10
|
+
end
|
26
11
|
|
27
|
-
|
12
|
+
# module retry harness
|
13
|
+
module RetryHarness
|
14
|
+
include Control::Harness
|
28
15
|
|
29
|
-
|
16
|
+
on_run do |tests|
|
17
|
+
arr_of_tests = tests.map { |test| { test: test, index: 0 } }
|
30
18
|
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
final_results = []
|
20
|
+
arr_of_tests.each do |test|
|
21
|
+
test[:index] += 1
|
22
|
+
results = test[:test].run!
|
23
|
+
if results.any?(&:failed?) && test[:index] <= 3
|
24
|
+
puts "Retrying iteration: #{test[:index]}\n#{results.map(&:full_name).join("\n")}"
|
25
|
+
redo
|
34
26
|
end
|
35
|
-
|
36
|
-
exit failed_tests.any? ? 1 : 0
|
27
|
+
final_results.concat results
|
37
28
|
end
|
29
|
+
|
30
|
+
final_results
|
38
31
|
end
|
39
32
|
end
|
40
33
|
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
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Theorem
|
4
|
+
module Control
|
5
|
+
# test object for around hooks
|
6
|
+
class FlaskTest
|
7
|
+
def initialize(test, ctx)
|
8
|
+
@test = test
|
9
|
+
@ctx = ctx
|
10
|
+
end
|
11
|
+
|
12
|
+
def run!
|
13
|
+
@test.run!(@ctx)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# single use container
|
18
|
+
class Flask
|
19
|
+
attr_reader :state
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@state = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def run!(test, ctx, flask_test: FlaskTest.new(test, ctx))
|
26
|
+
ctx.instance_exec flask_test, &@state
|
27
|
+
nil
|
28
|
+
rescue Exception => error
|
29
|
+
Theorem.handle_exception(error)
|
30
|
+
|
31
|
+
error
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty?
|
35
|
+
@state.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepare(&block)
|
39
|
+
@state = block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# reusable container
|
44
|
+
class Beaker
|
45
|
+
def initialize
|
46
|
+
@state = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def run!(ctx)
|
50
|
+
ctx.instance_exec @state, ctx do |state, ctx|
|
51
|
+
state.each do |b|
|
52
|
+
ctx.instance_eval &b
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reverse_prepare(&block)
|
58
|
+
@state.unshift block
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare(&block)
|
62
|
+
@state << block
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Theorem
|
4
|
+
module Control
|
5
|
+
# error class
|
6
|
+
class CompletedTest
|
7
|
+
attr_reader :test, :error
|
8
|
+
|
9
|
+
def initialize(test, error = nil)
|
10
|
+
@test = test
|
11
|
+
@error = error
|
12
|
+
end
|
13
|
+
|
14
|
+
def full_name
|
15
|
+
test.full_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
test.name
|
20
|
+
end
|
21
|
+
|
22
|
+
def failed?
|
23
|
+
!@error.nil?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'completed_test'
|
4
|
+
require_relative 'beaker'
|
5
|
+
require_relative 'registry'
|
6
|
+
require_relative 'test'
|
7
|
+
|
8
|
+
module Theorem
|
9
|
+
module Control
|
10
|
+
# control hypothesis
|
11
|
+
module Hypothesis
|
12
|
+
def self.included(mod)
|
13
|
+
mod.define_singleton_method(:included) do |klass|
|
14
|
+
klass.define_singleton_method(:control) do
|
15
|
+
mod
|
16
|
+
end
|
17
|
+
|
18
|
+
klass.extend ClassMethods
|
19
|
+
klass.instance_eval do
|
20
|
+
@before_all ||= Beaker.new
|
21
|
+
@before_each ||= Beaker.new
|
22
|
+
@after_all ||= Beaker.new
|
23
|
+
@after_each ||= Beaker.new
|
24
|
+
@around = Flask.new
|
25
|
+
@tests = []
|
26
|
+
@completed_tests = []
|
27
|
+
@self = new
|
28
|
+
end
|
29
|
+
mod.add_to_registry(klass)
|
30
|
+
end
|
31
|
+
|
32
|
+
mod.const_set(:Beaker, Beaker)
|
33
|
+
mod.const_set(:Test, Test)
|
34
|
+
mod.const_set(:CompletedTest, CompletedTest)
|
35
|
+
mod.extend(Registry)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Theorem
|
4
|
+
module Control
|
5
|
+
module Registry
|
6
|
+
# beaker
|
7
|
+
def registry
|
8
|
+
@registry ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_to_registry(klass)
|
12
|
+
registry << klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_extra_event(&block)
|
16
|
+
@extra_events ||= []
|
17
|
+
@extra_events << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def extra_event_subscribers
|
21
|
+
@extra_events || []
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_completed_test(&block)
|
25
|
+
@completed_tests ||= []
|
26
|
+
@completed_tests << block
|
27
|
+
end
|
28
|
+
|
29
|
+
def completed_test_subscribers
|
30
|
+
@completed_tests || []
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_completed_suite(&block)
|
34
|
+
@completed_suites ||= []
|
35
|
+
@completed_suites << block
|
36
|
+
end
|
37
|
+
|
38
|
+
def completed_suite_subscribers
|
39
|
+
@completed_suites || []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
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
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './beaker'
|
3
|
+
|
4
|
+
module Theorem
|
5
|
+
module Control
|
6
|
+
# test new
|
7
|
+
class Test
|
8
|
+
def initialize(name, namespace, **opts, &block)
|
9
|
+
@name = name
|
10
|
+
@namespace = namespace
|
11
|
+
@block = block
|
12
|
+
@arguments = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :block, :name, :arguments, :namespace
|
16
|
+
|
17
|
+
def full_name
|
18
|
+
"#{namespace} #{name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!(ctx)
|
22
|
+
ctx.instance_exec self, **arguments, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# module
|
27
|
+
module ClassMethods
|
28
|
+
def inherited(klass)
|
29
|
+
klass.include(control)
|
30
|
+
klass.instance_exec self do |me|
|
31
|
+
@parent_before_all ||= []
|
32
|
+
@parent_before_all << me.before_all_beaker
|
33
|
+
|
34
|
+
@parent_before_each ||= []
|
35
|
+
@parent_before_each << me.before_each_beaker
|
36
|
+
|
37
|
+
@parent_after_each ||= []
|
38
|
+
@parent_after_each.unshift me.after_each_beaker
|
39
|
+
|
40
|
+
@parent_after_all ||= []
|
41
|
+
@parent_after_all.unshift me.after_all_beaker
|
42
|
+
end
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def before_all(&block)
|
47
|
+
@before_all.prepare(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def around(&block)
|
51
|
+
@around.prepare(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def before_each(&block)
|
55
|
+
@before_each.prepare(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_each(&block)
|
59
|
+
@after_each.reverse_prepare(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def after_all(&block)
|
63
|
+
@after_all.reverse_prepare(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def experiments(klass, **opts, &block)
|
67
|
+
obj = Class.new
|
68
|
+
obj.include(control)
|
69
|
+
obj.instance_eval &block if block
|
70
|
+
obj.instance_exec self, klass, opts do |consumer, experiment_klass, params|
|
71
|
+
@tests.concat experiment_klass.tests(_experiment_namespace: consumer.to_s, **params)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def tests
|
76
|
+
@tests
|
77
|
+
end
|
78
|
+
|
79
|
+
def before_all_beaker
|
80
|
+
@before_all
|
81
|
+
end
|
82
|
+
|
83
|
+
def before_each_beaker
|
84
|
+
@before_each
|
85
|
+
end
|
86
|
+
|
87
|
+
def after_each_beaker
|
88
|
+
@after_each
|
89
|
+
end
|
90
|
+
|
91
|
+
def after_all_beaker
|
92
|
+
@after_all
|
93
|
+
end
|
94
|
+
|
95
|
+
def test(name, &block)
|
96
|
+
@tests << Test.new(name, to_s, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def run!
|
100
|
+
test_case = new
|
101
|
+
|
102
|
+
before_failures = run_before_all_beakers(test_case)
|
103
|
+
if before_failures.any?
|
104
|
+
return before_failures
|
105
|
+
end
|
106
|
+
|
107
|
+
results = []
|
108
|
+
@tests.each do |test|
|
109
|
+
error ||= run_before_each_beakers(test_case)
|
110
|
+
error ||= run_test(test, test_case)
|
111
|
+
error ||= run_after_each_beakers(test_case)
|
112
|
+
|
113
|
+
completed_test = CompletedTest.new(test, error)
|
114
|
+
publish_test_completion(completed_test)
|
115
|
+
results << completed_test
|
116
|
+
end
|
117
|
+
|
118
|
+
after_failures = run_after_all_beakers(test_case)
|
119
|
+
if after_failures.any?
|
120
|
+
return after_failures
|
121
|
+
end
|
122
|
+
|
123
|
+
results
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def run_test(test, test_case)
|
129
|
+
if @around.empty?
|
130
|
+
begin
|
131
|
+
test.run!(test_case)
|
132
|
+
nil
|
133
|
+
rescue Exception => error
|
134
|
+
Theorem.handle_exception(error)
|
135
|
+
|
136
|
+
error
|
137
|
+
end
|
138
|
+
else
|
139
|
+
@around.run!(test, test_case)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def run_after_all_beakers(test_case)
|
144
|
+
@after_all.run!(test_case)
|
145
|
+
|
146
|
+
@parent_after_all&.each do |beaker|
|
147
|
+
beaker.run!(test_case)
|
148
|
+
end
|
149
|
+
|
150
|
+
[]
|
151
|
+
rescue Exception => error
|
152
|
+
Theorem.handle_exception(error)
|
153
|
+
|
154
|
+
@tests.map do |test|
|
155
|
+
CompletedTest.new(test, error)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def run_after_each_beakers(test_case)
|
160
|
+
@after_each.run!(test_case)
|
161
|
+
|
162
|
+
@parent_after_each&.each do |beaker|
|
163
|
+
beaker.run!(test_case)
|
164
|
+
end
|
165
|
+
nil
|
166
|
+
rescue Exception => error
|
167
|
+
Theorem.handle_exception(error)
|
168
|
+
|
169
|
+
error
|
170
|
+
end
|
171
|
+
|
172
|
+
def run_before_each_beakers(test_case)
|
173
|
+
@parent_before_each&.each do |beaker|
|
174
|
+
beaker.run!(test_case)
|
175
|
+
end
|
176
|
+
@before_each.run!(test_case)
|
177
|
+
nil
|
178
|
+
rescue Exception => error
|
179
|
+
Theorem.handle_exception(error)
|
180
|
+
|
181
|
+
error
|
182
|
+
end
|
183
|
+
|
184
|
+
def run_before_all_beakers(test_case)
|
185
|
+
@parent_before_all&.each do |beaker|
|
186
|
+
beaker.run!(test_case)
|
187
|
+
end
|
188
|
+
@before_all.run!(test_case)
|
189
|
+
[]
|
190
|
+
rescue Exception => error
|
191
|
+
Theorem.handle_exception(error)
|
192
|
+
|
193
|
+
@tests.map do |test|
|
194
|
+
CompletedTest.new(test, error)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def publish_test_completion(completed_test)
|
199
|
+
control.completed_test_subscribers.each do |subscriber|
|
200
|
+
subscriber.call(completed_test)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/src/theorem.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'theorem/hypothesis'
|
2
|
+
require_relative 'harness'
|
3
|
+
require_relative 'experiment'
|
4
|
+
require_relative 'stdout_reporter'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Theorem
|
8
|
+
# RSpec subclasses Exception, so the only way to catch them without a dependency is to catch Exception
|
9
|
+
def self.custom_exceptions
|
10
|
+
errors = []
|
11
|
+
if defined? RSpec::Expectations
|
12
|
+
errors.concat [RSpec::Expectations::ExpectationNotMetError, RSpec::Expectations::MultipleExpectationsNotMetError]
|
13
|
+
end
|
14
|
+
errors
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.handle_exception(error)
|
18
|
+
unless error.is_a?(StandardError) || custom_exceptions.include?(error.class)
|
19
|
+
raise error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run!(options)
|
24
|
+
options[:require].each do |file|
|
25
|
+
require file
|
26
|
+
end
|
27
|
+
|
28
|
+
raise StandardError, "Unknown Module: #{options[:module]}" unless defined? Object.const_get(options[:module])
|
29
|
+
raise StandardError, "Unknown Harness: #{options[:harness]}" unless defined? Object.const_get(options[:harness])
|
30
|
+
|
31
|
+
mod = Object.const_get(options[:module])
|
32
|
+
harness = Object.const_get options[:harness]
|
33
|
+
mod.include harness
|
34
|
+
|
35
|
+
options[:publisher].each do |publisher|
|
36
|
+
if defined? Object.const_get(publisher)
|
37
|
+
mod.include Object.const_get(publisher)
|
38
|
+
else
|
39
|
+
raise StandardError, "Unknown Publisher: #{publisher}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
mod.run!(options: options)
|
44
|
+
end
|
45
|
+
|
46
|
+
module JsonReporter
|
47
|
+
extend Control::Reporter
|
48
|
+
|
49
|
+
subscribe :on_completed_suite do |results|
|
50
|
+
results = results.map do |result|
|
51
|
+
{ name: result.full_name, failed: result.failed? }
|
52
|
+
end
|
53
|
+
puts results.to_json
|
54
|
+
puts "\n\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module Hypothesis
|
59
|
+
include Control::Hypothesis
|
60
|
+
end
|
61
|
+
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
|
4
|
+
version: 1.0.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-
|
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:
|
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:
|
26
|
+
version: 0.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: slop
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
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:
|
40
|
+
version: 4.9.2
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
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:
|
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:
|
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
|
83
|
+
description: simple and extensible test library toolkit
|
84
84
|
email: sean.christopher.gregory@gmail.com
|
85
85
|
executables:
|
86
86
|
- theorize
|
@@ -88,17 +88,26 @@ extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
90
|
- bin/theorize
|
91
|
+
- src/experiment.rb
|
91
92
|
- src/harness.rb
|
92
|
-
- src/
|
93
|
+
- src/stdout_reporter.rb
|
94
|
+
- src/theorem.rb
|
95
|
+
- src/theorem/beaker.rb
|
96
|
+
- src/theorem/completed_test.rb
|
97
|
+
- src/theorem/harness.rb
|
98
|
+
- src/theorem/hypothesis.rb
|
99
|
+
- src/theorem/registry.rb
|
100
|
+
- src/theorem/reporter.rb
|
101
|
+
- src/theorem/test.rb
|
93
102
|
homepage: https://rubygems.org/gems/theorem
|
94
103
|
licenses:
|
95
104
|
- MIT
|
96
105
|
metadata:
|
97
|
-
source_code_uri: https://
|
106
|
+
source_code_uri: https://gitlab.com/skinnyjames/theorem
|
98
107
|
post_install_message:
|
99
108
|
rdoc_options: []
|
100
109
|
require_paths:
|
101
|
-
-
|
110
|
+
- src
|
102
111
|
required_ruby_version: !ruby/object:Gem::Requirement
|
103
112
|
requirements:
|
104
113
|
- - ">="
|
@@ -113,5 +122,5 @@ requirements: []
|
|
113
122
|
rubygems_version: 3.2.31
|
114
123
|
signing_key:
|
115
124
|
specification_version: 4
|
116
|
-
summary: simple
|
125
|
+
summary: simple and extensible test library toolkit
|
117
126
|
test_files: []
|
data/src/hypothesis.rb
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Theorem
|
4
|
-
# entrypoint
|
5
|
-
module Hypothesis
|
6
|
-
# beaker
|
7
|
-
def self.registry
|
8
|
-
@registry ||= []
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.add_to_registry(klass)
|
12
|
-
registry << klass
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.on_completed_test(&block)
|
16
|
-
@completed_tests ||= []
|
17
|
-
@completed_tests << block
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.completed_test_subscribers
|
21
|
-
@completed_tests
|
22
|
-
end
|
23
|
-
|
24
|
-
class Beaker
|
25
|
-
def initialize
|
26
|
-
@state = []
|
27
|
-
end
|
28
|
-
|
29
|
-
def run!(ctx)
|
30
|
-
ctx.instance_exec @state, ctx do |state, ctx|
|
31
|
-
state.each do |b|
|
32
|
-
ctx.instance_eval &b
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def prepare(&block)
|
38
|
-
@state << block
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# error class
|
43
|
-
class CompletedTest
|
44
|
-
attr_reader :name, :error
|
45
|
-
|
46
|
-
def initialize(name, error = nil)
|
47
|
-
@name = name
|
48
|
-
@error = error
|
49
|
-
end
|
50
|
-
|
51
|
-
def failed?
|
52
|
-
!@error.nil?
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
# test
|
58
|
-
class Test
|
59
|
-
def initialize(name, beaker, &block)
|
60
|
-
@name = name
|
61
|
-
@block = block
|
62
|
-
end
|
63
|
-
|
64
|
-
attr_reader :block, :name
|
65
|
-
|
66
|
-
def run!(ctx)
|
67
|
-
ctx.instance_exec self, &block
|
68
|
-
nil
|
69
|
-
rescue Exception => ex
|
70
|
-
ex
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.included(klass)
|
75
|
-
klass.extend ClassMethods
|
76
|
-
klass.instance_eval do
|
77
|
-
@before_all ||= Beaker.new
|
78
|
-
@tests = []
|
79
|
-
@completed_tests = []
|
80
|
-
@self = new
|
81
|
-
end
|
82
|
-
Hypothesis.add_to_registry(klass)
|
83
|
-
end
|
84
|
-
|
85
|
-
# module
|
86
|
-
module ClassMethods
|
87
|
-
def inherited(klass)
|
88
|
-
klass.include(Hypothesis)
|
89
|
-
klass.instance_exec self do |me|
|
90
|
-
@parent_before_all ||= []
|
91
|
-
@parent_before_all << me.before_all_beaker
|
92
|
-
end
|
93
|
-
super
|
94
|
-
end
|
95
|
-
|
96
|
-
def before_all(&block)
|
97
|
-
@before_all.prepare(&block)
|
98
|
-
end
|
99
|
-
|
100
|
-
def tests
|
101
|
-
@tests
|
102
|
-
end
|
103
|
-
|
104
|
-
def before_all_beaker
|
105
|
-
@before_all
|
106
|
-
end
|
107
|
-
|
108
|
-
def test(name, &block)
|
109
|
-
@tests << Test.new(name, @before_all, &block)
|
110
|
-
end
|
111
|
-
|
112
|
-
def run!
|
113
|
-
test_case = new
|
114
|
-
@parent_before_all&.each do |beaker|
|
115
|
-
beaker.run!(test_case)
|
116
|
-
end
|
117
|
-
@before_all.run!(test_case)
|
118
|
-
results = []
|
119
|
-
@tests.each do |test|
|
120
|
-
error = test.run!(test_case)
|
121
|
-
completed_test = CompletedTest.new(test.name, error)
|
122
|
-
publish_test_completion(completed_test)
|
123
|
-
results << completed_test
|
124
|
-
end
|
125
|
-
results
|
126
|
-
end
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
def publish_test_completion(completed_test)
|
131
|
-
Hypothesis.completed_test_subscribers.each do |subscriber|
|
132
|
-
subscriber.call(completed_test)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|