Class: Scampi::Context

Inherits:
Object show all
Defined in:
lib/scampi/context.rb

Overview

A test context created by describe. Holds specs, hooks, and child contexts.

Operates in two phases:

  1. Register -- evaluates the block to discover it specs and nested children.
  2. Execute -- runs all registered specs in order, emitting TAP subtests.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, &block) ⇒ Context

Create a new context.



17
18
19
20
21
22
23
# File 'lib/scampi/context.rb', line 17

def initialize(name, &block)
  @name = name
  @block = block
  @items = []
  @before = []
  @after = []
end

Instance Attribute Details

#blockObject (readonly)

Returns the value of attribute block.



12
13
14
# File 'lib/scampi/context.rb', line 12

def block
  @block
end

#nameObject (readonly)

Returns the value of attribute name.



9
10
11
# File 'lib/scampi/context.rb', line 9

def name
  @name
end

#The block that defines this context's specs and children.(blockthatdefinesthiscontext's specs and children.) ⇒ Object (readonly)



12
# File 'lib/scampi/context.rb', line 12

attr_reader :block

#The context name (passed to `describe`).(contextname(passed to `describe`)) ⇒ Object (readonly)



9
# File 'lib/scampi/context.rb', line 9

attr_reader :name

Instance Method Details

#after(&block) ⇒ Object

Register an after hook that runs after each spec in this context.



98
# File 'lib/scampi/context.rb', line 98

def after(&block);  @items << [:after, block];  @after  << block; end

#before(&block) ⇒ Object

Register a before hook that runs before each spec in this context.



95
# File 'lib/scampi/context.rb', line 95

def before(&block); @items << [:before, block]; @before << block; end

#behaves_like(*names) ⇒ Object

Include shared context blocks by name.



103
104
105
# File 'lib/scampi/context.rb', line 103

def behaves_like(*names)
  names.each { |name| instance_eval(&Shared[name]) }
end

#change?(&block) ⇒ Boolean

Assert that the block changes the result of an expression.

Returns:

  • (Boolean)


209
# File 'lib/scampi/context.rb', line 209

def change?(&block) = lambda{}.change?(&block)

#countObject

Count total specs recursively across this context and its children.



40
41
42
43
44
45
46
47
48
# File 'lib/scampi/context.rb', line 40

def count
  @items.sum { |item|
    case item[0]
    when :spec  then 1
    when :child then item[1].count
    else 0
    end
  }
end

#describe(*args, &block) ⇒ Object

Create a nested child context (TAP subtest).

Methods defined on the parent context are copied to the child so helper methods remain accessible.



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/scampi/context.rb', line 188

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

#execute(indent = 0) ⇒ Object

Phase 2: run all registered specs in order, emitting TAP subtests.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/scampi/context.rb', line 54

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

#it(description, &block) ⇒ Object

Define a spec within this context.



110
111
112
113
114
# File 'lib/scampi/context.rb', line 110

def it(description, &block)
  return  unless description =~ RestrictName
  block ||= proc { should.flunk "not implemented" }
  @items << [:spec, description, block]
end

#raise?(*args, &block) ⇒ Boolean

Assert that the block raises an exception.

Returns:

  • (Boolean)


203
# File 'lib/scampi/context.rb', line 203

def raise?(*args, &block) = block.raise?(*args)

#registerObject

Phase 1: evaluate block to discover specs and children. Nothing is executed -- it and describe just queue items.



27
28
29
30
31
32
33
34
35
# File 'lib/scampi/context.rb', line 27

def register
  tap do
    if name =~ RestrictContext
      instance_eval(&block)
    else
      self
    end
  end
end

#run_requirement(description, spec, befores, afters, indent = 0, local_n = 1) ⇒ Object

Run a single spec with before/after hooks and emit TAP output.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/scampi/context.rb', line 136

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

#should(*args, &block) ⇒ Object

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.



119
120
121
122
123
124
125
# File 'lib/scampi/context.rb', line 119

def should(*args, &block)
  if Counter[:depth] == 0
    it('should ' + args.first, &block)
  else
    super(*args, &block)
  end
end

#throw?(*args, &block) ⇒ Boolean

Assert that the block throws a symbol.

Returns:

  • (Boolean)


206
# File 'lib/scampi/context.rb', line 206

def throw?(*args, &block) = block.throw?(*args)