Class: Sus::Assertions

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

Overview

Represents a collection of test assertions and their results. Tracks passed, failed, skipped, and errored assertions.

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

Initialize a new assertions instance.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/sus/assertions.rb', line 31

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.



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

def clock
  @clock
end

#countObject (readonly)

Returns the value of attribute count.



103
104
105
# File 'lib/sus/assertions.rb', line 103

def count
  @count
end

#deferredObject (readonly)

Returns the value of attribute deferred.



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

def deferred
  @deferred
end

#distinctObject (readonly)

Returns the value of attribute distinct.



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

def distinct
  @distinct
end

#erroredObject (readonly)

Returns the value of attribute errored.



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

def errored
  @errored
end

#failedObject (readonly)

Returns the value of attribute failed.



91
92
93
# File 'lib/sus/assertions.rb', line 91

def failed
  @failed
end

#identityObject (readonly)

Returns the value of attribute identity.



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

def identity
  @identity
end

#invertedObject (readonly)

Returns the value of attribute inverted.



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

def inverted
  @inverted
end

#isolatedObject (readonly)

Returns the value of attribute isolated.



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

def isolated
  @isolated
end

#levelObject (readonly)

Returns the value of attribute level.



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

def level
  @level
end

#Nested assertions that have been deferred.(assertionsthathavebeendeferred.) ⇒ Object (readonly)



94
# File 'lib/sus/assertions.rb', line 94

attr :deferred

#Nested assertions that have been skipped.(assertionsthathavebeenskipped.) ⇒ Object (readonly)



97
# File 'lib/sus/assertions.rb', line 97

attr :skipped

#Nested assertions that have errored.(assertionsthathaveerrored.) ⇒ Object (readonly)



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

attr :errored

#Nested assertions that have failed.(assertionsthathavefailed.) ⇒ Object (readonly)



91
# File 'lib/sus/assertions.rb', line 91

attr :failed

#Nested assertions that have passed.(assertionsthathavepassed.) ⇒ Object (readonly)



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

attr :passed

#orientationObject (readonly)

Returns the value of attribute orientation.



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

def orientation
  @orientation
end

#outputObject (readonly)

Returns the value of attribute output.



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

def output
  @output
end

#passedObject (readonly)

Returns the value of attribute passed.



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

def passed
  @passed
end

#skippedObject (readonly)

Returns the value of attribute skipped.



97
98
99
# File 'lib/sus/assertions.rb', line 97

def skipped
  @skipped
end

#targetObject (readonly)

Returns the value of attribute target.



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

def target
  @target
end

#The nesting level of this set of assertions.(nestinglevelofthissetofassertions.) ⇒ Object (readonly)



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

attr :level

#The total number of assertions performed.(totalnumberofassertionsperformed.) ⇒ Object (readonly)



103
# File 'lib/sus/assertions.rb', line 103

attr :count

#verboseObject (readonly)

Returns the value of attribute verbose.



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

def verbose
  @verbose
end

Class Method Details

.default(**options) ⇒ Object

Create a new assertions instance with default options.



17
18
19
# File 'lib/sus/assertions.rb', line 17

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

Instance Method Details

#add(assertions) ⇒ Object

Add child assertions that were nested to this instance.



394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/sus/assertions.rb', line 394

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

Make an assertion about a condition.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/sus/assertions.rb', line 230

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 a deferred assertion that will be resolved later.



289
290
291
# File 'lib/sus/assertions.rb', line 289

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

#deferred?Boolean

Returns:

  • (Boolean)


294
295
296
# File 'lib/sus/assertions.rb', line 294

def deferred?
	@deferred.any?
end

#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.=(isusedtoidentifyasetofassertionsasasinglestatement) ⇒ Object



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

attr :distinct

#each_failure(&block) ⇒ Object

Iterate over all failures in this assertions instance.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/sus/assertions.rb', line 249

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)


166
167
168
# File 'lib/sus/assertions.rb', line 166

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

#error!(error) ⇒ Object

Record an error that occurred during test execution.



339
340
341
342
343
344
345
346
# File 'lib/sus/assertions.rb', line 339

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)


187
188
189
# File 'lib/sus/assertions.rb', line 187

def errored?
	@errored.any?
end

#failed?Boolean

Returns:

  • (Boolean)


182
183
184
# File 'lib/sus/assertions.rb', line 182

def failed?
	!self.passed?
end

#inform(message = nil) ⇒ Object

Print an informational message during test execution.



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

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



106
107
108
# File 'lib/sus/assertions.rb', line 106

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

#messageObject



111
112
113
114
115
116
# File 'lib/sus/assertions.rb', line 111

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

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

Create a nested set of assertions.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/sus/assertions.rb', line 357

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)


171
172
173
174
175
176
177
178
179
# File 'lib/sus/assertions.rb', line 171

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

Print a summary of the assertions to the output.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/sus/assertions.rb', line 126

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

Print a message to the output buffer.



161
162
163
# File 'lib/sus/assertions.rb', line 161

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

#resolve!Object

Resolve all deferred assertions in order.



299
300
301
302
303
304
305
# File 'lib/sus/assertions.rb', line 299

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

#skip(reason) ⇒ Object

Skip this set of assertions with a reason.



267
268
269
270
271
# File 'lib/sus/assertions.rb', line 267

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

#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.=(absoluteorientationofthissetofassertions, i.e.whethertheassertionsareexpectedtopass) ⇒ Object



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

attr :orientation

#The clock used to measure execution time, if measurement is enabled.=(clockusedtomeasureexecutiontime) ⇒ Object



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

attr :clock

#The identity that is used to identify this set of assertions.=(identitythatisusedtoidentifythissetofassertions. = (value)) ⇒ Object



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

attr :identity

#The output buffer used to capture output from the assertions.=(outputbufferusedtocaptureoutputfromtheassertions. = (value)) ⇒ Object



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

attr :output

#The specific target of the assertions, e.g. the test case or nested test assertions.=(specifictargetoftheassertions, e.g.thetest) ⇒ Object



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

attr :target

#totalObject



119
120
121
# File 'lib/sus/assertions.rb', line 119

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

#Whether this set 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.=(thissetofassertionsisinverted, i.e.theassertionsareexpectedtofailrelativetotheparent.Used) ⇒ Object



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

attr :inverted

#Whether this set of assertions is isolated from the parent. This is used to ensure that any deferred assertions are completed before the parent is completed. This is used by `receive` assertions which are deferred until the user code of the test has completed.=(thissetofassertionsisisolatedfromtheparent.Thisisusedto) ⇒ Object



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

attr :isolated

#Whether to output verbose information.=(tooutputverboseinformation. = (value)) ⇒ Object



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

attr :verbose