Class: BareTest::Suite

Inherits:
Object
  • Object
show all
Defined in:
lib/baretest/suite.rb

Overview

A Suite is a container for multiple assertions. You can give a suite a description, also a suite can contain setup and teardown blocks that are executed before (setup) and after (teardown) every assertion.

Suites can also be nested. Nested suites will inherit setup and teardown.

Constant Summary collapse

ValidOptions =

A list of valid options Suite::new accepts

[:skip, :requires, :use, :provides, :depends_on, :tags]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(description = nil, parent = nil, opts = nil, &block) ⇒ Suite

Create a new suite.

Arguments:

description

A string with a human readable description of this suite, preferably less than 60 characters and without newlines

parent

The suite that nests this suite. Ancestry plays a role in execution of setup and teardown blocks (all ancestors setups and teardowns are executed too).

opts

An additional options hash.

Keys the options hash accepts:

:skip

Skips the suite if true or a String is passed. If a String is passed, it is used as the reason.

:requires

A string or array of strings with requires that have to be done in order to run this suite. If a require fails, the assertions will all be skipped with reason “Missing dependency”.

:use

A symbol or array of symbols with components this suite should load prior to running.

:provides

A symbol or array of symbols with dependencies this suite resolves, see ‘depends_on’.

:depends_on

A symbol or array of symbols with dependencies of this suite, see ‘provides’.

:tags

A symbol or array of symbols, useful to run only suites having/not having specific tags

&block

The given block is instance evaled and can contain further definition of this assertion. See Suite#suite and Suite#assert.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/baretest/suite.rb', line 86

def initialize(description=nil, parent=nil, opts=nil, &block)
  @description = description
  @parent      = parent
  @suites      = [] # [["description", subsuite, skipped], ["description2", ...], ...] - see Array#assoc
  @assertions  = []
  @skipped     = false
  @setup       = {nil => []}
  @components  = []
  @teardown    = []
  @verification_exception_handlers = {}
  if @parent then
    @ancestors   = [self, *@parent.ancestors]
    @depends_on  = @parent.depends_on
    @tags        = @parent.tags
  else
    @ancestors   = [self]
    @depends_on  = []
    @tags        = []
  end
  @provides    = []
  @reason      = [] # skip reason
  if opts then
    raise ArgumentError, "Invalid option(s): #{(opts.keys - ValidOptions).inspect}" unless (opts.keys - ValidOptions).empty?
    skip, requires, use, provides, depends_on, tags = opts.values_at(*ValidOptions)
    skip(skip == true ? nil : skip) if skip
    use(*use) if use
    requires(*requires) if requires
    @depends_on |= Array(depends_on) if depends_on
    @provides   |= Array(provides) if provides
    @tags       |= Array(tags) if tags
  end
  instance_eval(&block) if block
end

Instance Attribute Details

#ancestorsObject (readonly)

An Array containing the suite itself (first element), then its direct parent suite, then that suite’s parent and so on



41
42
43
# File 'lib/baretest/suite.rb', line 41

def ancestors
  @ancestors
end

#assertionsObject (readonly)

All assertions in this suite



27
28
29
# File 'lib/baretest/suite.rb', line 27

def assertions
  @assertions
end

#depends_onObject (readonly)

All things this suite depends on, see Suite::new for more information



44
45
46
# File 'lib/baretest/suite.rb', line 44

def depends_on
  @depends_on
end

#descriptionObject (readonly)

This suites description. Toplevel suites usually don’t have a description.



34
35
36
# File 'lib/baretest/suite.rb', line 34

def description
  @description
end

#parentObject (readonly)

This suites direct parent. Nil if toplevel suite.



37
38
39
# File 'lib/baretest/suite.rb', line 37

def parent
  @parent
end

#providesObject (readonly)

All things this suite provides, see Suite::new for more information



47
48
49
# File 'lib/baretest/suite.rb', line 47

def provides
  @provides
end

#skippedObject (readonly)

Whether this suite has been manually skipped (either via Suite.new(…, :skip => reason) or via Suite#skip)



31
32
33
# File 'lib/baretest/suite.rb', line 31

def skipped
  @skipped
end

#suitesObject (readonly)

Nested suites, in the order of definition



24
25
26
# File 'lib/baretest/suite.rb', line 24

def suites
  @suites
end

#tagsObject (readonly)

All things this suite is tagged with, see Suite::new for more information



50
51
52
# File 'lib/baretest/suite.rb', line 50

def tags
  @tags
end

#verification_exception_handlersObject (readonly)

:nodoc:



52
53
54
# File 'lib/baretest/suite.rb', line 52

def verification_exception_handlers
  @verification_exception_handlers
end

Instance Method Details

#ancestry_componentsObject

All setup-components in the order of their definition and nesting (outermost first, innermost last)



253
254
255
# File 'lib/baretest/suite.rb', line 253

def ancestry_components
  @parent ? @parent.ancestry_components|@components : @components
end

#ancestry_setupObject

All setups in the order of their definition and nesting (outermost first, innermost last)



245
246
247
248
249
# File 'lib/baretest/suite.rb', line 245

def ancestry_setup
  @parent ? @parent.ancestry_setup.merge(@setup) { |k,v1,v2|
    v1+v2
  } : @setup
end

#ancestry_teardownObject

All teardowns in the order of their nesting (innermost first, outermost last)



258
259
260
# File 'lib/baretest/suite.rb', line 258

def ancestry_teardown
  ancestors.map { |suite| suite.teardown }.flatten
end

#assert(description = nil, opts = nil, &block) ⇒ Object Also known as: guard

Define an assertion. The block is supposed to return a trueish value (anything but nil or false).

See Assertion for more info.



341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/baretest/suite.rb', line 341

def assert(description=nil, opts=nil, &block)
  assertion = Assertion.new(self, description, opts, &block)
  if match = caller.first.match(/^(.*):(\d+)(?::.+)?$/) then
    file, line = match.captures
    file = File.expand_path(file)
    if File.exist?(file) then
      assertion.file = file
      assertion.line = line.to_i
    end
  end
  @assertions << assertion
end

#component_variant_countObject

Returns the number of possible setup variations. See #each_component_variant



300
301
302
# File 'lib/baretest/suite.rb', line 300

def component_variant_count
  ancestry_setup.values_at(*ancestry_components).inject(1) { |r,f| r*f.size }
end

#each_component_variantObject

Yields all possible permutations of setup components.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/baretest/suite.rb', line 305

def each_component_variant
  setups     = ancestry_setup
  components = ancestry_components
  base       = setups[nil]

  if components.empty?
    yield(base)
  else
    setup_in_order = setups.values_at(*components)
    maximums       = setup_in_order.map { |i| i.size }
    iterations     = maximums.inject { |r,f| r*f } || 0

    iterations.times do |i|
      process = maximums.map { |e| i,e=i.divmod(e); e }
      yield base+setup_in_order.zip(process).map { |variants, current|
        variants[current]
      }
    end
  end

  self
end

#first_component_variant {|setups| ... } ⇒ Object

Return only the first of all possible setup variation permutations.

Yields:

  • (setups)


329
330
331
332
333
334
335
# File 'lib/baretest/suite.rb', line 329

def first_component_variant
  setups, *comps = ancestry_setup.values_at(nil, *ancestry_components)
  setups = setups+comps.map { |comp| comp.first }
  yield(setups) if block_given?

  setups
end

#handle_verification_exceptions(*exception_classes, &block) ⇒ Object

Experimental Define handlers for specific exception classes. The handler gets the assertion, the phase and the exception yielded and is expected to return a BareTest::Status.



142
143
144
145
146
147
# File 'lib/baretest/suite.rb', line 142

def handle_verification_exceptions(*exception_classes, &block) # :nodoc:
  exception_classes.each do |exception_class|
    raise "Already registered a verification exception handler for class #{exception_class}" if @verification_exception_handlers[exception_class]
    @verification_exception_handlers[exception_class] = block
  end
end

#idObject

An ID, usable for persistence



121
122
123
# File 'lib/baretest/suite.rb', line 121

def id
  @id ||= ancestors.map { |suite| suite.description }.join("\f")
end

#inspectObject

:nodoc:



359
360
361
# File 'lib/baretest/suite.rb', line 359

def inspect #:nodoc:
  sprintf "#<%s:%08x %p>", self.class, object_id>>1, @description
end

#reason(opt = nil) ⇒ Object

The failure/error/skipping/pending reason. Returns nil if there’s no reason, a string otherwise Options:

:default

Reason to return if no reason is present

:separator

String used to separate multiple reasons

:indent

A String, the indentation to use. Prefixes every line.

:first_indent

A String, used to indent the first line only (replaces indent).



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/baretest/suite.rb', line 185

def reason(opt=nil)
  if opt then
    default, separator, indent, first_indent = 
      *opt.values_at(:default, :separator, :indent, :first_indent)
    reason = @reason
    reason = Array(default) if reason.empty? && default
    return nil if reason.empty?
    reason = reason.join(separator || "\n")
    reason = reason.gsub(/^/, indent) if indent
    reason = reason.gsub(/^#{Regexp.escape(indent)}/, first_indent) if first_indent
    reason
  else
    @reason.empty? ? nil : @reason.join("\n")
  end
end

#requires(*paths) ⇒ Object

Instruct this suite to require the given files. The suite is skipped if a file can’t be loaded.



151
152
153
154
155
156
157
158
159
# File 'lib/baretest/suite.rb', line 151

def requires(*paths)
  paths.each do |file|
    begin
      require file
    rescue LoadError => e
      skip("Missing source file: #{file} (#{e})")
    end
  end
end

#setup(component = nil, multiplexed = nil, &block) ⇒ Object

Define a setup block for this suite. The block will be ran before every assertion once, even for nested suites.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/baretest/suite.rb', line 264

def setup(component=nil, multiplexed=nil, &block)
  if component.nil? && block then
    @setup[nil] << ::BareTest::Setup.new(nil, nil, nil, block)
  elsif block then
    @components << component unless @setup.has_key?(component)
    @setup[component] ||= []

    case multiplexed
      when nil, String
        @setup[component] << ::BareTest::Setup.new(component, multiplexed, nil, block)
      when Array
        multiplexed.each do |substitute|
          @setup[component] << BareTest::Setup.new(component, substitute.to_s, substitute, block)
        end
      when Hash
        multiplexed.each do |substitute, value|
          @setup[component] << BareTest::Setup.new(component, substitute, value, block)
        end
      else
        raise TypeError, "multiplexed must be an instance of NilClass, String, Array or Hash, but #{multiplexed.class} given."
    end
  elsif component || multiplexed
    raise ArgumentError, "With component or multiplexed given, a block must be provided too."
  end

  @setup
end

#skip(reason = nil) ⇒ Object

Marks this suite as manually skipped.



167
168
169
170
171
# File 'lib/baretest/suite.rb', line 167

def skip(reason=nil)
  @skipped  = true
  @reason  |= reason ? Array(reason) : ['Manually skipped']
  true
end

#skipped?Boolean

Returns whether this assertion has been marked as manually skipped.

Returns:

  • (Boolean)


174
175
176
# File 'lib/baretest/suite.rb', line 174

def skipped?
  @skipped
end

#suite(description = nil, *args, &block) ⇒ Object

Define a nested suite.

Nested suites inherit setup & teardown methods. Also if an outer suite is skipped, all inner suites are skipped too.

See Suite::new - all arguments are passed to it verbatim, and self is added as parent.



208
209
210
211
212
213
214
215
216
217
# File 'lib/baretest/suite.rb', line 208

def suite(description=nil, *args, &block)
  suite          = self.class.new(description, self, *args, &block)
  existing_suite = @suites.assoc(description)
  if existing_suite then
    existing_suite.last.update(suite)
  else
    @suites << [description, suite]
  end
  suite
end

#tagged?(tags) ⇒ Boolean

Returns whether this suite has all the passed tags

Returns:

  • (Boolean)


162
163
164
# File 'lib/baretest/suite.rb', line 162

def tagged?(tags)
  (@tags-tags).empty?
end

#teardown(&block) ⇒ Object

Define a teardown block for this suite. The block will be ran after every assertion once, even for nested suites.



294
295
296
# File 'lib/baretest/suite.rb', line 294

def teardown(&block)
  block ? @teardown << block : @teardown
end

#to_sObject

:nodoc:



355
356
357
# File 'lib/baretest/suite.rb', line 355

def to_s #:nodoc:
  sprintf "%s %s", self.class, @description
end

#update(with_suite) ⇒ Object

Performs a recursive merge with the given suite.

Used to merge suites with the same description.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/baretest/suite.rb', line 222

def update(with_suite)
  assertions, setup, teardown, provides, depends_on, skipped, reason, suites =
    *with_suite.merge_attributes
  @assertions.concat(assertions)
  @setup.update(setup) do |k,v1,v2| v1+v2 end
  @teardown.concat(teardown)
  @provides   |= provides
  @depends_on |= depends_on
  @skipped   ||= skipped
  @reason.concat(reason)
  suites.each { |description, suite|
    if append_to = @suites.assoc(description) then
      append_to.last.update(suite)
    else
      @suites << [description, suite]
    end
  }

  self
end

#use(*components) ⇒ Object

Instruct this suite to use the given components. The suite is skipped if a component is not available.



127
128
129
130
131
132
133
134
135
136
# File 'lib/baretest/suite.rb', line 127

def use(*components)
  components.each do |name|
    component = BareTest.component(name)
    if component then
      instance_eval(&component)
    else
      skip("Missing component: #{name.inspect}")
    end
  end
end