sus 0.8.1 → 0.10.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: b15c28cff36f956e3847c3c843f5426efbe05d9ef8f52fbc9df200f7efa1bf12
4
- data.tar.gz: b6fe36ec97a8537b20b73d83d40aab8ebd7e3b60b9554493bf9e1ee2dd01bdf0
3
+ metadata.gz: 514f648fd1bd5831f2265f423e317d00d98bcc7a1b61077a67116712caa276a3
4
+ data.tar.gz: 65098f09cdbe0d6cfcd4e08041bdcadd5eb4737f387aadc238bcd9152fd678e3
5
5
  SHA512:
6
- metadata.gz: d3a1e05b190b0e590c8652356d13bdfe7bc84194b6da6c62d26a7d249d5c600966334f91db47c155db9e906a04832daa3f1d64426744acf830700962d2c3ab49
7
- data.tar.gz: 5325cbc73f4e0dea9808e379008df9ea603a005683d1b342f2950497cc24a751c7b0a4907db1397e92162bc5698b7d8abc528d47229cd09a9e590311b8d86063
6
+ metadata.gz: 534b1cbaff030deb16445d0c2a46059b664e8bbb207a2d4539eda3865b27ce219e58c1797b16f787e480564d54e0d1f3984d5c2d60ef70884030ef768ba41dc1
7
+ data.tar.gz: 347b61af35c3100524499c430b39bfac69576afd334c1abcf7030ca67fdabb318051f4b8fc5ad5d364da21335844ae9bfee8e36cbcadd2df075fdc8b62e26a1c
checksums.yaml.gz.sig CHANGED
Binary file
data/bin/sus CHANGED
@@ -4,13 +4,20 @@ require_relative '../lib/sus/config'
4
4
  config = Sus::Config.load
5
5
 
6
6
  require_relative '../lib/sus'
7
- filter = Sus::Filter.new
8
- assertions = Sus::Assertions.default(output: Sus::Output::Null.new)
7
+ registry = config.registry
9
8
 
10
- config.prepare(filter)
9
+ if config.verbose?
10
+ output = config.output
11
+ verbose = true
12
+ else
13
+ output = Sus::Output::Null.new
14
+ verbose = false
15
+ end
16
+
17
+ assertions = Sus::Assertions.default(output: output, verbose: verbose)
11
18
 
12
19
  config.before_tests(assertions)
13
- filter.call(assertions)
20
+ registry.call(assertions)
14
21
  config.after_tests(assertions)
15
22
 
16
23
  if assertions.failed.any?
data/bin/sus-parallel CHANGED
@@ -16,10 +16,9 @@ require 'etc'
16
16
  count = Etc.nprocessors
17
17
 
18
18
  loader = Thread.new do
19
- filter = Sus::Filter.new
20
- config.prepare(filter)
19
+ registry = config.registry
21
20
 
22
- filter.each do |child|
21
+ registry.each do |child|
23
22
  guard.synchronize{progress.expand}
24
23
  jobs << child
25
24
  end
@@ -2,13 +2,17 @@
2
2
  require_relative 'output'
3
3
  require_relative 'clock'
4
4
 
5
+ require_relative 'output/backtrace'
6
+
5
7
  module Sus
6
8
  class Assertions
7
9
  def self.default(**options)
8
- self.new(**options, verbose: true)
10
+ self.new(**options)
9
11
  end
10
12
 
11
- def initialize(target: nil, output: Output.buffered, inverted: false, isolated: false, measure: false, verbose: false)
13
+ def initialize(identity: nil, target: nil, output: Output.buffered, inverted: false, isolated: false, measure: false, verbose: false)
14
+ # In theory, the target could carry the identity of the assertion group, but it's not really necessary, so we just handle it explicitly and pass it into any nested assertions.
15
+ @identity = identity
12
16
  @target = target
13
17
  @output = output
14
18
  @inverted = inverted
@@ -50,7 +54,7 @@ module Sus
50
54
  attr :count
51
55
 
52
56
  def inspect
53
- "\#<#{self.class} #{self.object_id} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred>"
57
+ "\#<#{self.class} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred>"
54
58
  end
55
59
 
56
60
  def total
@@ -58,8 +62,6 @@ module Sus
58
62
  end
59
63
 
60
64
  def print(output, verbose: @verbose)
61
- self
62
-
63
65
  if verbose && @target
64
66
  @target.print(output)
65
67
  output.write(": ")
@@ -107,21 +109,19 @@ module Sus
107
109
  end
108
110
 
109
111
  def assert(condition, message = nil)
110
- # sleep 0.5
111
-
112
112
  @count += 1
113
113
 
114
114
  if condition
115
115
  @passed << self
116
116
 
117
- @output.indented do
118
- @output.puts(:indent, :passed, pass_prefix, message || "assertion")
117
+ if @inverted || @verbose
118
+ @output.puts(:indent, :passed, pass_prefix, message || "assertion", Output::Backtrace.first(@identity))
119
119
  end
120
120
  else
121
121
  @failed << self
122
122
 
123
- @output.indented do
124
- @output.puts(:indent, :failed, fail_prefix, message || "assertion")
123
+ if !@inverted || @verbose
124
+ @output.puts(:indent, :failed, fail_prefix, message || "assertion", Output::Backtrace.first(@identity))
125
125
  end
126
126
  end
127
127
  end
@@ -138,37 +138,51 @@ module Sus
138
138
 
139
139
  # This resolves all deferred assertions in order.
140
140
  def resolve!
141
- while block = @deferred.shift
142
- block.call(self)
141
+ @output.indented do
142
+ while block = @deferred.shift
143
+ block.call(self)
144
+ end
143
145
  end
144
146
  end
145
147
 
146
148
  def fail(error)
147
149
  @failed << self
148
150
 
149
- @output.puts(:indent, :failed, fail_prefix, "Unhandled exception ", :value, error.class, ": ", error.message)
150
- error.backtrace.each do |line|
151
- @output.puts(:indent, line)
151
+ lines = error.message.split(/\r?\n/)
152
+
153
+ @output.puts(:indent, :error, fail_prefix, "Unhandled exception ", :value, error.class, ":", :reset, " ", lines.shift)
154
+
155
+ lines.each do |line|
156
+ @output.puts(:indent, "| ", line)
152
157
  end
158
+
159
+ @output.write(Output::Backtrace.for(error, @identity))
153
160
  end
154
161
 
155
- def nested(target, isolated: false, inverted: false, **options)
162
+ def nested(target, identity: @identity, isolated: false, inverted: false, **options)
156
163
  result = nil
157
- output = Output::Buffered.new
158
164
 
159
- output.write(:indent)
160
- target.print(output)
161
- output.puts
165
+ # Isolated assertions need to have buffered output so they can be replayed if they fail:
166
+ if isolated
167
+ output = @output.buffered
168
+ else
169
+ output = @output
170
+ end
171
+
172
+ output.puts(:indent, target)
162
173
 
163
- assertions = self.class.new(target: target, output: output, isolated: isolated, inverted: inverted, **options)
174
+ assertions = self.class.new(identity: identity, target: target, output: output, isolated: isolated, inverted: inverted, verbose: @verbose, **options)
164
175
 
165
- begin
166
- @clock&.start!
167
- result = yield(assertions)
168
- rescue StandardError => error
169
- assertions.fail(error)
170
- ensure
171
- @clock&.stop!
176
+ @clock&.start!
177
+
178
+ output.indented do
179
+ begin
180
+ result = yield(assertions)
181
+ rescue StandardError => error
182
+ assertions.fail(error)
183
+ ensure
184
+ @clock&.stop!
185
+ end
172
186
  end
173
187
 
174
188
  self.add(assertions)
@@ -178,13 +192,15 @@ module Sus
178
192
 
179
193
  def add(assertions)
180
194
  # If the assertions should be an isolated group, make sure any deferred assertions are resolved:
181
- if assertions.isolated
195
+ if assertions.isolated and assertions.deferred?
182
196
  assertions.resolve!
183
197
  end
184
198
 
185
199
  if assertions.deferred?
186
200
  self.defer do
201
+ output.puts(:indent, assertions.target)
187
202
  assertions.resolve!
203
+
188
204
  self.add!(assertions)
189
205
  end
190
206
  else
@@ -204,10 +220,6 @@ module Sus
204
220
  # Otherwise, we append all child assertions into the parent assertions:
205
221
  append!(assertions)
206
222
  end
207
-
208
- @output.indented do
209
- @output.append(assertions.output)
210
- end
211
223
  end
212
224
 
213
225
  def merge!(assertions)
@@ -216,17 +228,17 @@ module Sus
216
228
  if assertions.passed?
217
229
  @passed << assertions
218
230
 
219
- if @verbose
220
- @output.write(:indent, :passed, pass_prefix, :reset)
221
- self.print(@output, verbose: false)
222
- @output.puts
223
- end
231
+ # if @verbose
232
+ # @output.write(:indent, :passed, pass_prefix, :reset)
233
+ # self.print(@output, verbose: false)
234
+ # @output.puts
235
+ # end
224
236
  else
225
237
  @failed << assertions
226
238
 
227
- @output.write(:indent, :failed, fail_prefix, :reset)
228
- self.print(@output, verbose: false)
229
- @output.puts
239
+ # @output.write(:indent, :failed, fail_prefix, :reset)
240
+ # self.print(@output, verbose: false)
241
+ # @output.puts
230
242
  end
231
243
  end
232
244
 
@@ -236,10 +248,11 @@ module Sus
236
248
  @failed.concat(assertions.failed)
237
249
  @deferred.concat(assertions.deferred)
238
250
 
239
- if @verbose
240
- self.print(@output, verbose: false)
241
- @output.puts
242
- end
251
+ # if @verbose
252
+ # @output.write(:indent)
253
+ # self.print(@output, verbose: false)
254
+ # @output.puts
255
+ # end
243
256
  end
244
257
 
245
258
  def pass_prefix
data/lib/sus/base.rb CHANGED
@@ -2,9 +2,14 @@
2
2
  require_relative 'context'
3
3
 
4
4
  module Sus
5
+ # The base test case class. We need to be careful about what local state is stored.
5
6
  class Base
6
7
  def initialize(assertions)
7
- @assertions = assertions
8
+ @__assertions__ = assertions
9
+ end
10
+
11
+ def inspect
12
+ "\#<Sus::Base for #{self.class.description.inspect}>"
8
13
  end
9
14
 
10
15
  def before
@@ -22,19 +27,20 @@ module Sus
22
27
  end
23
28
 
24
29
  def assert(...)
25
- @assertions.assert(...)
30
+ @__assertions__.assert(...)
26
31
  end
27
32
 
28
33
  def refute(...)
29
- @assertions.refute(...)
34
+ @__assertions__.refute(...)
30
35
  end
31
36
  end
32
37
 
33
- def self.base(description = "base")
38
+ def self.base(description = nil)
34
39
  base = Class.new(Base)
40
+
35
41
  base.extend(Context)
36
42
  base.description = description
37
-
43
+
38
44
  return base
39
45
  end
40
46
  end
data/lib/sus/be.rb CHANGED
@@ -6,7 +6,9 @@ module Sus
6
6
  end
7
7
 
8
8
  def print(output)
9
- output.write("be ", :be, *@arguments.join(" "))
9
+ operation, *arguments = *@arguments
10
+
11
+ output.write("be ", :be, operation.to_s, :reset, " ", :variable, arguments.map(&:inspect).join, :reset)
10
12
  end
11
13
 
12
14
  def call(assertions, subject)
@@ -56,5 +58,9 @@ module Sus
56
58
  Be
57
59
  end
58
60
  end
61
+
62
+ def be_a(klass)
63
+ Be.new(:is_a?, klass)
64
+ end
59
65
  end
60
66
  end
data/lib/sus/be_within.rb CHANGED
@@ -7,7 +7,7 @@ module Sus
7
7
  end
8
8
 
9
9
  def print(output)
10
- output.write("be within ", :variable, @range)
10
+ output.write("be within ", :variable, @range, :reset)
11
11
  end
12
12
 
13
13
  def call(assertions, subject)
@@ -30,7 +30,7 @@ module Sus
30
30
  end
31
31
 
32
32
  def print(output)
33
- output.write("be within ", :variable, @tolerance)
33
+ output.write("be within ", :variable, @tolerance, :reset)
34
34
  end
35
35
 
36
36
  def call(assertions, subject)
data/lib/sus/config.rb CHANGED
@@ -21,6 +21,7 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  require_relative 'clock'
24
+ require_relative 'registry'
24
25
 
25
26
  module Sus
26
27
  class Config
@@ -34,7 +35,7 @@ module Sus
34
35
  end
35
36
  end
36
37
 
37
- def self.load(root: Dir.pwd, argv: ARGV)
38
+ def self.load(root: Dir.pwd, arguments: ARGV)
38
39
  derived = Class.new(self)
39
40
 
40
41
  if path = self.path(root)
@@ -43,13 +44,45 @@ module Sus
43
44
  derived.prepend(config)
44
45
  end
45
46
 
46
- return derived.new(root, argv)
47
+ options = {
48
+ verbose: !!arguments.delete('--verbose')
49
+ }
50
+
51
+ return derived.new(root, arguments, **options)
47
52
  end
48
53
 
49
- def initialize(root, paths)
54
+ def initialize(root, paths, verbose: false)
50
55
  @root = root
51
56
  @paths = paths
57
+ @verbose = verbose
58
+
52
59
  @clock = Clock.new
60
+
61
+ self.add_default_load_paths
62
+ end
63
+
64
+ def add_load_path(path)
65
+ path = ::File.expand_path(path, @root)
66
+
67
+ if ::File.directory?(path)
68
+ $LOAD_PATH.unshift(path)
69
+ end
70
+ end
71
+
72
+ def add_default_load_paths
73
+ add_load_path('lib')
74
+ add_load_path('fixtures')
75
+ end
76
+
77
+ attr :root
78
+ attr :paths
79
+
80
+ def verbose?
81
+ @verbose
82
+ end
83
+
84
+ def partial?
85
+ @paths.any?
53
86
  end
54
87
 
55
88
  def output
@@ -62,8 +95,15 @@ module Sus
62
95
  return Dir.glob(DEFAULT_TEST_PATTERN, base: @root)
63
96
  end
64
97
 
65
- def prepare(registry)
98
+ def registry
99
+ @registry ||= self.load_registry
100
+ end
101
+
102
+ def load_registry
103
+ registry = Sus::Registry.new
104
+
66
105
  if @paths&.any?
106
+ registry = Sus::Filter.new(registry)
67
107
  @paths.each do |path|
68
108
  registry.load(path)
69
109
  end
@@ -72,6 +112,8 @@ module Sus
72
112
  registry.load(path)
73
113
  end
74
114
  end
115
+
116
+ return registry
75
117
  end
76
118
 
77
119
  def before_tests(assertions)
@@ -81,6 +123,12 @@ module Sus
81
123
  def after_tests(assertions)
82
124
  @clock.stop!
83
125
 
126
+ self.print_summary(assertions)
127
+ end
128
+
129
+ protected
130
+
131
+ def print_summary(assertions)
84
132
  output = self.output
85
133
 
86
134
  assertions.print(output)
@@ -96,12 +144,6 @@ module Sus
96
144
  print_failed_assertions(assertions)
97
145
  end
98
146
 
99
- protected
100
-
101
- def partial?
102
- @paths.any?
103
- end
104
-
105
147
  def print_finished_statistics(assertions)
106
148
  duration = @clock.duration
107
149
  rate = assertions.count / duration
data/lib/sus/expect.rb CHANGED
@@ -42,9 +42,9 @@ module Sus
42
42
  class Base
43
43
  def expect(subject = nil, **options, &block)
44
44
  if block_given?
45
- Expect.new(@assertions, block, **options)
45
+ Expect.new(@__assertions__, block, **options)
46
46
  else
47
- Expect.new(@assertions, subject, **options)
47
+ Expect.new(@__assertions__, subject, **options)
48
48
  end
49
49
  end
50
50
  end
data/lib/sus/file.rb CHANGED
@@ -7,9 +7,7 @@ Sus::TOPLEVEL_CLASS_EVAL = ->(klass, path){klass.class_eval(::File.read(path), p
7
7
  module Sus
8
8
  module File
9
9
  extend Context
10
-
11
- attr_accessor :path
12
-
10
+
13
11
  def self.extended(base)
14
12
  base.children = Hash.new
15
13
  end
@@ -24,6 +22,10 @@ module Sus
24
22
 
25
23
  return base
26
24
  end
25
+
26
+ def print(output)
27
+ output.write("file ", :path, self.identity)
28
+ end
27
29
  end
28
30
 
29
31
  module Context
data/lib/sus/filter.rb CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  module Sus
3
2
  class Filter
4
3
  class Index
@@ -30,7 +29,7 @@ module Sus
30
29
  end
31
30
  end
32
31
 
33
- def initialize(registry: Registry.new)
32
+ def initialize(registry = Registry.new)
34
33
  @registry = registry
35
34
  @index = nil
36
35
  @keys = Array.new
data/lib/sus/it.rb CHANGED
@@ -22,11 +22,11 @@ module Sus
22
22
 
23
23
  def print(output)
24
24
  self.superclass.print(output)
25
- output.write(" it ", :it, self.description, :reset, " ", self.identity.to_s)
25
+ output.write(" it ", :it, self.description, :reset, " ", :identity, self.identity.to_s, :reset)
26
26
  end
27
27
 
28
28
  def call(assertions)
29
- assertions.nested(self, isolated: true, measure: true) do |assertions|
29
+ assertions.nested(self, identity: self.identity, isolated: true, measure: true) do |assertions|
30
30
  instance = self.new(assertions)
31
31
 
32
32
  instance.around do
data/lib/sus/let.rb CHANGED
@@ -4,13 +4,13 @@ require_relative 'context'
4
4
  module Sus
5
5
  module Context
6
6
  def let(name, &block)
7
- ivar = :"@#{name}"
7
+ instance_variable = :"@#{name}"
8
8
 
9
9
  self.define_method(name) do
10
- if value = self.instance_variable_get(ivar)
11
- return value
10
+ if self.instance_variable_defined?(instance_variable)
11
+ return self.instance_variable_get(instance_variable)
12
12
  else
13
- self.instance_variable_set(ivar, self.instance_exec(&block))
13
+ self.instance_variable_set(instance_variable, self.instance_exec(&block))
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module Sus
24
+ module Output
25
+ # Print out a backtrace relevant to the given test identity if provided.
26
+ class Backtrace
27
+ def self.first(identity = nil)
28
+ # This implementation could be a little more efficient.
29
+ self.new(caller_locations(1), identity&.path, 1)
30
+ end
31
+
32
+ def self.for(exception, identity = nil)
33
+ self.new(exception.backtrace_locations, identity&.path)
34
+ end
35
+
36
+ def initialize(stack, root = nil, limit = nil)
37
+ @stack = stack
38
+ @root = root
39
+ @limit = limit
40
+ end
41
+
42
+ def filter(root = @root)
43
+ if @root
44
+ stack = @stack.select do |frame|
45
+ frame.path.start_with?(@root)
46
+ end
47
+ else
48
+ stack = @stack
49
+ end
50
+
51
+ if @limit
52
+ stack = stack.take(@limit)
53
+ end
54
+
55
+ return stack
56
+ end
57
+
58
+ def print(output)
59
+ if @limit == 1
60
+ filter.each do |frame|
61
+ output.write " ", :path, frame.path, :line, ":", frame.lineno
62
+ end
63
+ else
64
+ output.indented do
65
+ filter.each do |frame|
66
+ output.puts :indent, :path, frame.path, :line, ":", frame.lineno, :reset, " ", frame.label
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -27,32 +27,55 @@ module Sus
27
27
  # Styled output output.
28
28
  module Output
29
29
  class Buffered
30
- def initialize
31
- @output = Array.new
30
+ def initialize(tee = nil)
31
+ @chunks = Array.new
32
+ @tee = tee
33
+ end
34
+
35
+ attr :chunks
36
+ attr :tee
37
+
38
+ def inspect
39
+ if @tee
40
+ "\#<#{self.class.name} #{@chunks.size} chunks -> #{@tee.class}>"
41
+ else
42
+ "\#<#{self.class.name} #{@chunks.size} chunks>"
43
+ end
44
+ end
45
+
46
+ def buffered
47
+ self.class.new(self)
32
48
  end
33
49
 
34
50
  attr :output
35
51
 
36
52
  def each(&block)
37
- @output.each(&block)
53
+ @chunks.each(&block)
38
54
  end
39
55
 
40
56
  def append(buffer)
41
- @output.concat(buffer.output)
57
+ @chunks.concat(buffer.output)
58
+ @tee&.append(buffer)
42
59
  end
43
60
 
44
61
  def string
45
62
  io = StringIO.new
46
- Text.new(io).append(@output)
63
+ Text.new(io).append(@chunks)
47
64
  return io.string
48
65
  end
49
66
 
67
+ INDENT = [:indent].freeze
68
+
50
69
  def indent
51
- @output << [:indent]
70
+ @chunks << INDENT
71
+ @tee&.indent
52
72
  end
53
73
 
74
+ OUTDENT = [:outdent].freeze
75
+
54
76
  def outdent
55
- @output << [:outdent]
77
+ @chunks << OUTDENT
78
+ @tee&.outdent
56
79
  end
57
80
 
58
81
  def indented
@@ -63,11 +86,13 @@ module Sus
63
86
  end
64
87
 
65
88
  def write(*arguments)
66
- @output << [:write, *arguments]
89
+ @chunks << [:write, *arguments]
90
+ @tee&.write(*arguments)
67
91
  end
68
92
 
69
93
  def puts(*arguments)
70
- @output << [:puts, *arguments]
94
+ @chunks << [:puts, *arguments]
95
+ @tee&.puts(*arguments)
71
96
  end
72
97
  end
73
98
  end
@@ -30,6 +30,12 @@ module Sus
30
30
  def initialize
31
31
  end
32
32
 
33
+ def buffered
34
+ Buffered.new(nil)
35
+ end
36
+
37
+ attr :options
38
+
33
39
  def append(buffer)
34
40
  end
35
41
 
@@ -29,12 +29,17 @@ module Sus
29
29
  class Text
30
30
  def initialize(io)
31
31
  @io = io
32
+
32
33
  @styles = {reset: self.reset}
33
34
 
34
35
  @indent = String.new
35
36
  @styles[:indent] = @indent
36
37
  end
37
38
 
39
+ def buffered
40
+ Buffered.new(self)
41
+ end
42
+
38
43
  def append(buffer)
39
44
  buffer.each do |operation|
40
45
  self.public_send(*operation)
data/lib/sus/output.rb CHANGED
@@ -23,14 +23,19 @@ module Sus
23
23
 
24
24
  output[:context] = output.style(nil, nil, :bold)
25
25
 
26
- output[:describe] = output.style(:cyan, nil, :bold)
26
+ output[:describe] = output.style(:cyan)
27
27
  output[:it] = output.style(:cyan)
28
28
  output[:with] = output.style(:cyan)
29
29
 
30
30
  output[:variable] = output.style(:blue, nil, :bold)
31
31
 
32
- output[:passed] = output.style(:green, nil, :bold)
33
- output[:failed] = output.style(:red, nil, :bold)
32
+ output[:path] = output.style(:yellow)
33
+ output[:line] = output.style(:yellow)
34
+ output[:identity] = output.style(:yellow)
35
+
36
+ output[:passed] = output.style(:green)
37
+ output[:failed] = output.style(:red)
38
+ output[:error] = output.style(:red)
34
39
 
35
40
  return output
36
41
  end
data/lib/sus/receive.rb CHANGED
@@ -9,7 +9,7 @@ module Sus
9
9
  @base = base
10
10
  @method = method
11
11
 
12
- @times = Once.new
12
+ @times = Times.new
13
13
  @arguments = nil
14
14
  @options = nil
15
15
  @block = nil
@@ -35,6 +35,18 @@ module Sus
35
35
  return self
36
36
  end
37
37
 
38
+ def once
39
+ @times = Times.new(Be.new(:==, 1))
40
+ end
41
+
42
+ def twice
43
+ @times = Times.new(Be.new(:==, 2))
44
+ end
45
+
46
+ def with_call_count(predicate)
47
+ @times = Times.new(predicate)
48
+ end
49
+
38
50
  def and_return(*returning)
39
51
  if returning.size == 1
40
52
  @returning = returning.first
@@ -72,8 +84,10 @@ module Sus
72
84
  end
73
85
  end
74
86
 
75
- assertions.defer do
76
- @times.call(assertions, called)
87
+ if @times
88
+ assertions.defer do
89
+ @times.call(assertions, called)
90
+ end
77
91
  end
78
92
  end
79
93
  end
@@ -126,14 +140,20 @@ module Sus
126
140
  end
127
141
  end
128
142
 
129
- class Once
143
+ class Times
144
+ ONCE = Be.new(:==, 1)
145
+
146
+ def initialize(condition = ONCE)
147
+ @condition = condition
148
+ end
149
+
130
150
  def print(output)
131
- output.write("once")
151
+ output.write("with call count ", @condition)
132
152
  end
133
153
 
134
154
  def call(assertions, subject)
135
155
  assertions.nested(self) do |assertions|
136
- Expect.new(assertions, subject).to(Be == 1)
156
+ Expect.new(assertions, subject).to(@condition)
137
157
  end
138
158
  end
139
159
  end
data/lib/sus/registry.rb CHANGED
@@ -16,12 +16,16 @@ require_relative 'let'
16
16
  module Sus
17
17
  class Registry
18
18
  # Create a top level scope with self as the instance:
19
- def initialize(base = Sus.base("test registry"))
19
+ def initialize(base = Sus.base(self))
20
20
  @base = base
21
21
  end
22
22
 
23
23
  attr :base
24
24
 
25
+ def print(output)
26
+ output.write("Test Registry")
27
+ end
28
+
25
29
  def load(path)
26
30
  @base.file(path)
27
31
  end
@@ -64,7 +64,7 @@ module Sus
64
64
  end
65
65
 
66
66
  def print(output)
67
- output.write("respond to ", :variable, @method.to_s)
67
+ output.write("respond to ", :variable, @method.to_s, :reset)
68
68
  end
69
69
 
70
70
  def call(assertions, subject)
data/lib/sus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sus
4
- VERSION = "0.8.1"
4
+ VERSION = "0.10.0"
5
5
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,7 +37,7 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2022-08-18 00:00:00.000000000 Z
40
+ date: 2022-08-21 00:00:00.000000000 Z
41
41
  dependencies: []
42
42
  description:
43
43
  email:
@@ -69,6 +69,7 @@ files:
69
69
  - lib/sus/let.rb
70
70
  - lib/sus/mock.rb
71
71
  - lib/sus/output.rb
72
+ - lib/sus/output/backtrace.rb
72
73
  - lib/sus/output/bar.rb
73
74
  - lib/sus/output/buffered.rb
74
75
  - lib/sus/output/lines.rb
metadata.gz.sig CHANGED
Binary file