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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82f4b3d0f32345396cdbc66fc1d28d00d014f3576c8cf36e18eb355913fd34cd
4
- data.tar.gz: ac2241928677901a46a9e12fb980e7a787f9e43ede2c7bf862a0bb3277fe6527
3
+ metadata.gz: 2707643c05984ddc7bb49ef7f536fad90dc70ddc7fa058ec031f3945df8e4188
4
+ data.tar.gz: 173b64d78b6de42218ebb90714c651a459e1ef377492840985ae8f5c0ae94a7f
5
5
  SHA512:
6
- metadata.gz: 2233e6daab124321a5f83dda68b09b8159f1561afb5b9cb8c2f1b626170dce551ed1a833164fc2e58cc5a02a86f216b0be95057b4f8aee6b2c986c3928c10837
7
- data.tar.gz: 7b4432d63c2a4bdc6c18875b7fdecc27cb14412fcdbaf8d3867fd31863ff8173faa21d2e1b37db65848af72571e7656ebf5ce0a31ed8d21697f2412a9f052976
6
+ metadata.gz: e1f9df341383f804e7437a2193e2c9cd6978861d8dd3351533b7f14f059e3fd8e941e865c455325edcfa724da490549af329fbf5cd7fdd460333d1105fefeccc
7
+ data.tar.gz: c9fd46000ef01e28b7f63f4c7512f599732004f129938085585b3725b8bf0abbea88961fea7048bdb5d8753dffb52b2228c4c0dacc24983668e2f3fdd2293193
data/bin/theorize CHANGED
@@ -2,4 +2,6 @@
2
2
 
3
3
  require_relative '../src/harness'
4
4
 
5
- Theorem::Hypothesis::Harness.run! ARGV[0] || '.'
5
+ path = ARGV[0] || '.'
6
+
7
+ Theorem::Hypothesis::Harness.run! path
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 './hypothesis'
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.run!(dir)
20
- test_cases = locate_tests(dir)
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.map(&:run!).flatten
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.select(&:failed?)
32
- failed_tests.each do |failure|
33
- puts "Failure in #{failure.name}\nError: #{failure.error}\nBacktrace:\n------\n#{failure.error.backtrace.join("\n")}"
34
- end
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
@@ -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.1
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-05 00:00:00.000000000 Z
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/hypothesis.rb
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