theorem 0.0.1 → 0.0.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 +4 -4
- data/bin/theorize +3 -1
- data/src/experiment.rb +19 -0
- data/src/harness.rb +28 -9
- data/src/theorem/beaker.rb +62 -0
- data/src/theorem/completed_test.rb +27 -0
- data/src/theorem/hypothesis.rb +38 -0
- data/src/theorem/registry.rb +25 -0
- data/src/theorem/test.rb +152 -0
- data/src/theorem.rb +23 -0
- metadata +9 -3
- 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: 2707643c05984ddc7bb49ef7f536fad90dc70ddc7fa058ec031f3945df8e4188
|
4
|
+
data.tar.gz: 173b64d78b6de42218ebb90714c651a459e1ef377492840985ae8f5c0ae94a7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1f9df341383f804e7437a2193e2c9cd6978861d8dd3351533b7f14f059e3fd8e941e865c455325edcfa724da490549af329fbf5cd7fdd460333d1105fefeccc
|
7
|
+
data.tar.gz: c9fd46000ef01e28b7f63f4c7512f599732004f129938085585b3725b8bf0abbea88961fea7048bdb5d8753dffb52b2228c4c0dacc24983668e2f3fdd2293193
|
data/bin/theorize
CHANGED
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(**opts)
|
13
|
+
@tests.map do |hash|
|
14
|
+
Control::Test.new(hash[:name], to_s, **opts, &hash[:block])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/src/harness.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
require_relative './
|
1
|
+
require_relative './theorem'
|
2
2
|
require 'extended_dir'
|
3
3
|
|
4
4
|
# module
|
5
5
|
module Theorem
|
6
|
+
# module
|
6
7
|
module Hypothesis
|
7
8
|
on_completed_test do |test|
|
8
9
|
print test.failed? ? 'X' : '.'
|
@@ -16,22 +17,40 @@ module Theorem
|
|
16
17
|
Hypothesis.registry
|
17
18
|
end
|
18
19
|
|
19
|
-
def self.
|
20
|
-
test_cases
|
21
|
-
total_count = test_cases.map do |test_case|
|
20
|
+
def self.get_test_count(test_cases)
|
21
|
+
test_cases.map do |test_case|
|
22
22
|
test_case.tests.size
|
23
23
|
end.inject(&:+)
|
24
|
+
end
|
25
|
+
|
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
|
37
|
+
|
38
|
+
def self.run!(dir)
|
39
|
+
test_cases = locate_tests(dir)
|
40
|
+
total_count = get_test_count(test_cases)
|
24
41
|
|
25
42
|
puts "Total tests #{total_count}"
|
26
43
|
|
27
|
-
results = test_cases.
|
44
|
+
results = test_cases.each_with_object([]) do |test_case, memo|
|
45
|
+
memo.concat test_case.run!
|
46
|
+
end
|
28
47
|
|
29
48
|
puts "\n\nSummary\n-------"
|
30
49
|
|
31
|
-
failed_tests = results.
|
32
|
-
|
33
|
-
|
34
|
-
|
50
|
+
failed_tests, passed_tests = results.partition(&:failed?)
|
51
|
+
|
52
|
+
report_passes(passed_tests)
|
53
|
+
report_failures(failed_tests)
|
35
54
|
|
36
55
|
exit failed_tests.any? ? 1 : 0
|
37
56
|
end
|
@@ -0,0 +1,62 @@
|
|
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 prepare(&block)
|
58
|
+
@state << block
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
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,38 @@
|
|
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
|
+
@around = Flask.new
|
23
|
+
@tests = []
|
24
|
+
@completed_tests = []
|
25
|
+
@self = new
|
26
|
+
end
|
27
|
+
mod.add_to_registry(klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
mod.const_set(:Beaker, Beaker)
|
31
|
+
mod.const_set(:Test, Test)
|
32
|
+
mod.const_set(:CompletedTest, CompletedTest)
|
33
|
+
mod.extend(Registry)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,25 @@
|
|
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_completed_test(&block)
|
16
|
+
@completed_tests ||= []
|
17
|
+
@completed_tests << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def completed_test_subscribers
|
21
|
+
@completed_tests || []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/src/theorem/test.rb
ADDED
@@ -0,0 +1,152 @@
|
|
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
|
+
end
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def before_all(&block)
|
41
|
+
@before_all.prepare(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def around(&block)
|
45
|
+
@around.prepare(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def before_each(&block)
|
49
|
+
@before_each.prepare(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def experiments(klass, **opts, &block)
|
53
|
+
obj = Class.new
|
54
|
+
obj.include(control)
|
55
|
+
obj.instance_eval &block if block
|
56
|
+
obj.instance_exec klass, opts do |experiment_klass, params|
|
57
|
+
@tests.concat experiment_klass.tests(**params)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tests
|
62
|
+
@tests
|
63
|
+
end
|
64
|
+
|
65
|
+
def before_all_beaker
|
66
|
+
@before_all
|
67
|
+
end
|
68
|
+
|
69
|
+
def before_each_beaker
|
70
|
+
@before_each
|
71
|
+
end
|
72
|
+
|
73
|
+
def test(name, &block)
|
74
|
+
@tests << Test.new(name, to_s, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def run!
|
78
|
+
test_case = new
|
79
|
+
|
80
|
+
before_failures = run_before_all_beakers(test_case)
|
81
|
+
if before_failures.any?
|
82
|
+
return before_failures
|
83
|
+
end
|
84
|
+
|
85
|
+
results = []
|
86
|
+
@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)
|
91
|
+
|
92
|
+
completed_test = CompletedTest.new(test, error)
|
93
|
+
publish_test_completion(completed_test)
|
94
|
+
results << completed_test
|
95
|
+
end
|
96
|
+
results
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def run_test(test, test_case)
|
102
|
+
if @around.empty?
|
103
|
+
begin
|
104
|
+
test.run!(test_case)
|
105
|
+
nil
|
106
|
+
rescue Exception => error
|
107
|
+
Theorem.handle_exception(error)
|
108
|
+
|
109
|
+
error
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@around.run!(test, test_case)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_before_each_beakers(test_case)
|
117
|
+
@parent_before_each&.each do |beaker|
|
118
|
+
beaker.run!(test_case)
|
119
|
+
end
|
120
|
+
@before_each.run!(test_case)
|
121
|
+
[]
|
122
|
+
rescue Exception => error
|
123
|
+
Theorem.handle_exception(error)
|
124
|
+
|
125
|
+
@tests.map do |test|
|
126
|
+
CompletedTest.new(test, error)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def run_before_all_beakers(test_case)
|
131
|
+
@parent_before_all&.each do |beaker|
|
132
|
+
beaker.run!(test_case)
|
133
|
+
end
|
134
|
+
@before_all.run!(test_case)
|
135
|
+
[]
|
136
|
+
rescue Exception => error
|
137
|
+
Theorem.handle_exception(error)
|
138
|
+
|
139
|
+
@tests.map do |test|
|
140
|
+
CompletedTest.new(test, error)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def publish_test_completion(completed_test)
|
145
|
+
control.completed_test_subscribers.each do |subscriber|
|
146
|
+
subscriber.call(completed_test)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/src/theorem.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'theorem/hypothesis'
|
2
|
+
require_relative 'experiment'
|
3
|
+
|
4
|
+
module Theorem
|
5
|
+
# RSpec subclasses Exception, so the only way to catch them without a dependency is to catch Exception
|
6
|
+
def self.custom_exceptions
|
7
|
+
errors = []
|
8
|
+
if defined? RSpec::Expectations
|
9
|
+
errors.concat [RSpec::Expectations::ExpectationNotMetError, RSpec::Expectations::MultipleExpectationsNotMetError]
|
10
|
+
end
|
11
|
+
errors
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.handle_exception(error)
|
15
|
+
unless error.is_a?(StandardError) || custom_exceptions.include?(error.class)
|
16
|
+
raise error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Hypothesis
|
21
|
+
include Control::Hypothesis
|
22
|
+
end
|
23
|
+
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: 0.0.
|
4
|
+
version: 0.0.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-
|
11
|
+
date: 2022-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: extended_dir
|
@@ -88,8 +88,14 @@ 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/theorem.rb
|
94
|
+
- src/theorem/beaker.rb
|
95
|
+
- src/theorem/completed_test.rb
|
96
|
+
- src/theorem/hypothesis.rb
|
97
|
+
- src/theorem/registry.rb
|
98
|
+
- src/theorem/test.rb
|
93
99
|
homepage: https://rubygems.org/gems/theorem
|
94
100
|
licenses:
|
95
101
|
- MIT
|
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
|