class Context
A test context created by describe. Holds specs, hooks, and child contexts.
Operates in two phases:
- Register -- evaluates the block to discover
itspecs and nested children. - Execute -- runs all registered specs in order, emitting TAP subtests.
Definitions
attr_reader :name
Signature
-
attribute
String The context name (passed to
describe).
attr_reader :block
Signature
-
attribute
Proc The block that defines this context's specs and children.
def initialize(name, &block)
Create a new context.
Signature
-
parameter
nameString The describe block's label.
Implementation
def initialize(name, &block)
@name = name
@block = block
@items = []
@before = []
@after = []
end
def register
Phase 1: evaluate block to discover specs and children.
Nothing is executed -- it and describe just queue items.
Implementation
def register
tap do
if name =~ RestrictContext
instance_eval(&block)
else
self
end
end
end
def count
Count total specs recursively across this context and its children.
Signature
-
returns
Integer
Implementation
def count
@items.sum { |item|
case item[0]
when :spec then 1
when :child then item[1].count
else 0
end
}
end
def execute(indent = 0)
Phase 2: run all registered specs in order, emitting TAP subtests.
Signature
-
parameter
indentInteger Nesting depth (0 = inside a top-level describe).
-
returns
Boolean Whether all specs and children passed.
Implementation
def execute(indent = 0)
prefix = " " * indent
inner = " " * (indent + 1)
plan = @items.count { |item| item[0] == :spec || item[0] == :child }
puts "#{prefix}# Subtest: #{@name}"
puts "#{inner}1..#{plan}"
befores = []
afters = []
local_n = 0
all_passed = true
@items.each { |item|
case item[0]
when :before
befores << item[1]
when :after
afters << item[1]
when :spec
Counter[:specifications] += 1
local_n += 1
passed = run_requirement(item[1], item[2], befores, afters, indent + 1, local_n)
all_passed = false unless passed
when :child
local_n += 1
child_passed = item[1].execute(indent + 1)
if child_passed
puts "#{inner}#{"ok".green} #{local_n} - #{item[1].name}"
else
puts "#{inner}#{"not ok".red} #{local_n} - #{item[1].name}"
end
all_passed = false unless child_passed
end
}
all_passed
end
def before(&block)
Register a before hook that runs before each spec in this context.
def after(&block)
Register an after hook that runs after each spec in this context.
def behaves_like(*names)
Include shared context blocks by name.
Signature
-
parameter
namesArray(String) Names of shared contexts to include.
Implementation
def behaves_like(*names)
names.each { |name| instance_eval(&Shared[name]) }
end
def it(description, &block)
Define a spec within this context.
Signature
-
parameter
descriptionString What this spec asserts.
Implementation
def it(description, &block)
return unless description =~ RestrictName
block ||= proc { should.flunk "not implemented" }
@items << [:spec, description, block]
end
def should(*args, &block)
When called at the context level (outside a spec body), acts as a
shortcut for it('should ...'). Inside a spec body, delegates to
the standard Object#should.
Implementation
def should(*args, &block)
if Counter[:depth] == 0
it('should ' + args.first, &block)
else
super(*args, &block)
end
end
def run_requirement(description, spec, befores, afters, indent = 0, local_n = 1)
Run a single spec with before/after hooks and emit TAP output.
Signature
-
parameter
descriptionString Spec description.
-
parameter
specProc The spec body.
-
parameter
beforesArray(Proc) Before hooks to run.
-
parameter
aftersArray(Proc) After hooks to run.
-
parameter
indentInteger TAP indentation depth.
-
parameter
local_nInteger Spec number within this context.
-
returns
Boolean Whether the spec passed.
Implementation
def run_requirement(description, spec, befores, afters, indent = 0, local_n = 1)
Scampi.handle_requirement(description, indent, local_n) do
begin
Counter[:depth] += 1
rescued = false
begin
befores.each { |block| instance_eval(&block) }
prev_req = Counter[:requirements]
instance_eval(&spec)
rescue Object => e
rescued = true
raise e
ensure
if Counter[:requirements] == prev_req and not rescued
raise Error.new(:missing,
"empty specification: #{@name} #{description}")
end
begin
afters.each { |block| instance_eval(&block) }
rescue Object => e
raise e unless rescued
end
end
rescue SystemExit, Interrupt
raise
rescue Object => e
ErrorLog << "#{e.class}: #{e.message}\n"
e.backtrace.find_all { |line| line !~ /bin\/scampi|\/scampi\.rb:\d+/ }.
each_with_index { |line, i|
ErrorLog << "\t#{line}#{i==0 ? ": #@name - #{description}" : ""}\n"
}
ErrorLog << "\n"
if e.kind_of? Error
Counter[e.count_as] += 1
e.count_as.to_s.upcase
else
Counter[:errors] += 1
"ERROR: #{e.class}"
end
else
""
ensure
Counter[:depth] -= 1
end
end
end
def describe(*args, &block)
Create a nested child context (TAP subtest).
Methods defined on the parent context are copied to the child so helper methods remain accessible.
Implementation
def describe(*args, &block)
context = Scampi::Context.new(args.join(' '), &block)
(parent_context = self).methods(false).each { |e|
(class << context; self; end).send(:define_method, e) { |*args2, &block2|
parent_context.send(e, *args2, &block2)
}
}
@before.each { |b| context.before(&b) }
@after.each { |b| context.after(&b) }
context.register
@items << [:child, context]
context
end
def raise?(*args, &block)
Assert that the block raises an exception.
def throw?(*args, &block)
Assert that the block throws a symbol.
def change?(&block)
Assert that the block changes the result of an expression.