theorem 0.0.1 → 0.0.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: 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