Class: Sus::Assertions

Inherits:
Object
  • Object
show all
Defined in:
lib/sus/assertions.rb

Defined Under Namespace

Classes: Assert, Error

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(identity: nil, target: nil, output: Output.buffered, inverted: false, orientation: true, isolated: false, distinct: false, measure: false, verbose: false) ⇒ Assertions

Returns a new instance of Assertions.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/sus/assertions.rb', line 19

def initialize(identity: nil, target: nil, output: Output.buffered, inverted: false, orientation: true, isolated: false, distinct: false, measure: false, verbose: false)
	# 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.
	@identity = identity
	@target = target
	@output = output
	@inverted = inverted
	@orientation = orientation
	@isolated = isolated
	@distinct = distinct
	@verbose = verbose
	
	if measure
		@clock = Clock.start!
	else
		@clock = nil
	end
	
	@passed = Array.new
	@failed = Array.new
	@deferred = Array.new
	@skipped = Array.new
	@errored = Array.new
	
	@count = 0
end

Instance Attribute Details

#clockObject (readonly)

Returns the value of attribute clock.



71
72
73
# File 'lib/sus/assertions.rb', line 71

def clock
  @clock
end

#countObject (readonly)

The total number of assertions performed:



86
87
88
# File 'lib/sus/assertions.rb', line 86

def count
  @count
end

#deferredObject (readonly)

Nested assertions have been deferred.



80
81
82
# File 'lib/sus/assertions.rb', line 80

def deferred
  @deferred
end

#distinctObject (readonly)

Distinct is used to identify a set of assertions as a single statement for the purpose of user feedback. It’s used by top level ensure statements to ensure that error messages are captured and reported on those statements.



67
68
69
# File 'lib/sus/assertions.rb', line 67

def distinct
  @distinct
end

#erroredObject (readonly)

Returns the value of attribute errored.



83
84
85
# File 'lib/sus/assertions.rb', line 83

def errored
  @errored
end

#failedObject (readonly)

Nested assertions that have failed.



77
78
79
# File 'lib/sus/assertions.rb', line 77

def failed
  @failed
end

#identityObject (readonly)

The identity that is used to identify this set of assertions.



46
47
48
# File 'lib/sus/assertions.rb', line 46

def identity
  @identity
end

#invertedObject (readonly)

Whether this aset of assertions is inverted, i.e. the assertions are expected to fail relative to the parent. Used for grouping assertions and ensuring they are added to the parent passed/failed array correctly.



58
59
60
# File 'lib/sus/assertions.rb', line 58

def inverted
  @inverted
end

#isolatedObject (readonly)

Whether this set of assertions is isolated from the parent. This is used to ensure that any deferred assertions are competed before the parent is completed. This is used by ‘receive` assertions which are deferred until the user code of the test has completed.



64
65
66
# File 'lib/sus/assertions.rb', line 64

def isolated
  @isolated
end

#levelObject (readonly)

The nesting level of this set of assertions.



55
56
57
# File 'lib/sus/assertions.rb', line 55

def level
  @level
end

#orientationObject (readonly)

The absolute orientation of this set of assertions, i.e. whether the assertions are expected to pass or fail regardless of the parent. Used for correctly formatting the output.



61
62
63
# File 'lib/sus/assertions.rb', line 61

def orientation
  @orientation
end

#outputObject (readonly)

The output buffer used to capture output from the assertions.



52
53
54
# File 'lib/sus/assertions.rb', line 52

def output
  @output
end

#passedObject (readonly)

Nested assertions that have passed.



74
75
76
# File 'lib/sus/assertions.rb', line 74

def passed
  @passed
end

#skippedObject (readonly)

Returns the value of attribute skipped.



82
83
84
# File 'lib/sus/assertions.rb', line 82

def skipped
  @skipped
end

#targetObject (readonly)

The specific target of the assertions, e.g. the test case or nested test assertions.



49
50
51
# File 'lib/sus/assertions.rb', line 49

def target
  @target
end

#verboseObject (readonly)

Returns the value of attribute verbose.



69
70
71
# File 'lib/sus/assertions.rb', line 69

def verbose
  @verbose
end

Class Method Details

.default(**options) ⇒ Object



13
14
15
# File 'lib/sus/assertions.rb', line 13

def self.default(**options)
	self.new(**options)
end

Instance Method Details

#add(assertions) ⇒ Object

Add the child assertions which were nested to this instance.



321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/sus/assertions.rb', line 321

def add(assertions)
	# All child assertions should be resolved by this point:
	raise "Nested assertions must be fully resolved!" if assertions.deferred?
	
	if assertions.append?
		# If we are isolated, we merge all child assertions into the parent as a single entity:
		append!(assertions)
	else
		# Otherwise, we append all child assertions into the parent assertions:
		merge!(assertions)
	end
end

#assert(condition, message = nil) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/sus/assertions.rb', line 186

def assert(condition, message = nil)
	@count += 1
	
	identity = @identity&.scoped
	backtrace = Output::Backtrace.first(identity)
	assert = Assert.new(identity, backtrace, self)
	
	if condition
		@passed << assert
		@output.assert(condition, @orientation, message || "assertion passed", backtrace)
	else
		@failed << assert
		@output.assert(condition, @orientation, message || "assertion failed", backtrace)
	end
end

#defer(&block) ⇒ Object

Add deferred assertions.



237
238
239
# File 'lib/sus/assertions.rb', line 237

def defer(&block)
	@deferred << block
end

#deferred?Boolean

Whether there are any deferred assertions.

Returns:

  • (Boolean)


242
243
244
# File 'lib/sus/assertions.rb', line 242

def deferred?
	@deferred.any?
end

#each_failure(&block) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/sus/assertions.rb', line 202

def each_failure(&block)
	return to_enum(__method__) unless block_given?
	
	if self.failed? and @distinct
		return yield(self)
	end
	
	@failed.each do |assertions|
		assertions.each_failure(&block)
	end
	
	@errored.each do |assertions|
		assertions.each_failure(&block)
	end
end

#empty?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/sus/assertions.rb', line 140

def empty?
	@passed.empty? and @failed.empty? and @deferred.empty? and @skipped.empty? and @errored.empty?
end

#error!(error) ⇒ Object



276
277
278
279
280
281
282
283
# File 'lib/sus/assertions.rb', line 276

def error!(error)
	identity = @identity&.scoped(error.backtrace_locations)
	
	@errored << Error.new(identity, error)
	
	# TODO consider passing `identity`.
	@output.error(error, @identity)
end

#errored?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/sus/assertions.rb', line 158

def errored?
	@errored.any?
end

#failed?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/sus/assertions.rb', line 154

def failed?
	!self.passed?
end

#inform(message = nil) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
# File 'lib/sus/assertions.rb', line 224

def inform(message = nil)
	if message.nil? and block_given?
		begin
			message = yield
		rescue => error
			message = error.full_message
		end
	end
	
	@output.inform(message, @identity&.scoped)
end

#inspectObject



88
89
90
# File 'lib/sus/assertions.rb', line 88

def inspect
	"\#<#{self.class} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred #{@skipped.size} skipped #{@errored.size} errored>"
end

#messageObject



92
93
94
95
96
97
# File 'lib/sus/assertions.rb', line 92

def message
	{
		text: @output.string,
		location: @identity&.to_location
	}
end

#nested(target, identity: @identity, isolated: false, distinct: false, inverted: false, **options) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/sus/assertions.rb', line 285

def nested(target, identity: @identity, isolated: false, distinct: false, inverted: false, **options)
	result = nil
	
	# Isolated assertions need to have buffered output so they can be replayed if they fail:
	if isolated or distinct
		output = @output.buffered
	else
		output = @output
	end
	
	# Inverting a nested assertions causes the orientation to flip:
	if inverted
		orientation = !@orientation
	else
		orientation = @orientation
	end
	
	output.puts(:indent, target)
	
	assertions = self.class.new(identity: identity, target: target, output: output, isolated: isolated, inverted: inverted, orientation: orientation, distinct: distinct, verbose: @verbose, **options)
	
	output.indented do
		begin
			result = yield(assertions)
		rescue StandardError => error
			assertions.error!(error)
		end
	end
	
	# Some assertions are deferred until the end of the test, e.g. expecting a method to be called. This scope is managed by the {add} method. If there are no deferred assertions, then we can add the child assertions right away. Otherwise, we append the child assertions to our own list of deferred assertions. When an assertions instance is marked as `isolated`, it will force all deferred assertions to be resolved. It's also at this time, we should conclude measuring the duration of the test.
	assertions.resolve_into(self)
	
	return result
end

#passed?Boolean

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
# File 'lib/sus/assertions.rb', line 144

def passed?
	if @inverted
		# Inverted assertions:
		@failed.any? and @errored.empty?
	else
		# Normal assertions:
		@failed.empty? and @errored.empty?
	end
end


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/sus/assertions.rb', line 103

def print(output, verbose: @verbose)
	if verbose && @target
		@target.print(output)
		output.write(": ")
	end
	
	if @count.zero?
		output.write("0 assertions")
	else
		if @passed.any?
			output.write(:passed, @passed.size, " passed", :reset, " ")
		end
		
		if @failed.any?
			output.write(:failed, @failed.size, " failed", :reset, " ")
		end
		
		if @deferred.any?
			output.write(:deferred, @deferred.size, " deferred", :reset, " ")
		end
		
		if @skipped.any?
			output.write(:skipped, @skipped.size, " skipped", :reset, " ")
		end
		
		if @errored.any?
			output.write(:errored, @errored.size, " errored", :reset, " ")
		end
		
		output.write("out of ", self.total, " total (", @count, " assertions)")
	end
end

#puts(*message) ⇒ Object



136
137
138
# File 'lib/sus/assertions.rb', line 136

def puts(*message)
	@output.puts(:indent, *message)
end

#resolve!Object

This resolves all deferred assertions in order.



247
248
249
250
251
252
253
# File 'lib/sus/assertions.rb', line 247

def resolve!
	@output.indented do
		while block = @deferred.shift
			block.call(self)
		end
	end
end

#skip(reason) ⇒ Object



218
219
220
221
222
# File 'lib/sus/assertions.rb', line 218

def skip(reason)
	@output.skip(reason, @identity&.scoped)
	
	@skipped << self
end

#totalObject



99
100
101
# File 'lib/sus/assertions.rb', line 99

def total
	@passed.size + @failed.size + @deferred.size + @skipped.size + @errored.size
end