Class: Spectre::RunContext

Inherits:
Object show all
Includes:
Delegate
Defined in:
lib/spectre.rb

Overview

The run context is passed to all specs and provides the DSL for assertions, expectations, logging, etc.

Constant Summary collapse

DEFAULT_ASYNC_NAME =

The default identifier of async blocks.

'default'
@@current =
nil
@@skip_count =
0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Delegate

#instance_eval, #instance_exec, #method_missing, #respond_to_missing?

Constructor Details

#initialize(engine, parent, type, bag = nil) ⇒ RunContext

Returns a new instance of RunContext.



819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
# File 'lib/spectre.rb', line 819

def initialize engine, parent, type, bag = nil
  @engine = engine
  @parent = parent
  @type = type
  @logs = []

  @threads = {}

  @name = parent.name

  # If the run type is an actual spec, the context
  # of the run is its parent +Specification+ 's parent.
  if type == :spec
    @context = parent.parent
  else
    # Otherwise the run context is the parent itself
    # This is the case when a setup or teardown block
    # is executet, which uses its own run context.
    @context = parent
    @name += "-#{type}"
  end

  @bag = OpenStruct.new(bag)

  @properties = {}

  @evaluations = []
  @error = nil
  @skipped = false

  @started = Time.now

  begin
    @@current = self
    yield self
  ensure
    @finished = Time.now
    @@current = nil
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Spectre::Delegate

Instance Attribute Details

#bagObject (readonly)

A variable bag to store values across setup, teardown, before, after and it blocks.

setup do
  bag.foo = 'bar'
end

it 'does something' do
  assert bag.foo.to be 'bar'
end


797
798
799
# File 'lib/spectre.rb', line 797

def bag
  @bag
end

#contextObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def context
  @context
end

#dataObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def data
  @data
end

#errorObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def error
  @error
end

#evaluationsObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def evaluations
  @evaluations
end

#finishedObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def finished
  @finished
end

#logsObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def logs
  @logs
end

#nameObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def name
  @name
end

#parentObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def parent
  @parent
end

#propertiesObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def properties
  @properties
end

#startedObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def started
  @started
end

#typeObject (readonly)

:stopdoc:



801
802
803
# File 'lib/spectre.rb', line 801

def type
  @type
end

Class Method Details

.currentObject

The current executing RunContext



815
816
817
# File 'lib/spectre.rb', line 815

def self.current
  @@current
end

Instance Method Details

#async(name = DEFAULT_ASYNC_NAME) ⇒ Object

Executes the given block asyncronously in a new thread. This thread can be awaited with await and the given name. You can start multiple threads with the same name. Using this name with await will wait for all threads to finish.

The last line in the async block will be returned as a result. and is available when await ing the threads.

async do
  info 'do some async stuff'

  'a result'
end

result = await


1063
1064
1065
1066
# File 'lib/spectre.rb', line 1063

def async(name = DEFAULT_ASYNC_NAME, &)
  @threads[name] ||= []
  @threads[name] << Thread.new(&)
end

#await(name = DEFAULT_ASYNC_NAME) ⇒ Object

returns

An Array of previously executed async blocks

with the same name.

Waits for the threads created with async to finish. Awaits all threads with the given name.



1075
1076
1077
1078
1079
1080
# File 'lib/spectre.rb', line 1075

def await name = DEFAULT_ASYNC_NAME
  return unless @threads.key? name

  threads = @threads.delete(name)
  threads.map(&:join)
end

#durationObject

returns

The duration of the previously executed measure block.

The duration optained by measure.



1042
1043
1044
# File 'lib/spectre.rb', line 1042

def duration
  @measured_duration
end

#execute(data) ⇒ Object

Executes the given block in the context of this RunContext. For internal use only. Do not execute within an it block.



864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
# File 'lib/spectre.rb', line 864

def execute(data, &)
  @data = data
  instance_exec(data.is_a?(Hash) ? OpenStruct.new(data) : data, &)
rescue AbortException
  # Do nothing. The run will be ended here
rescue Interrupt
  @skipped = true
  @engine.formatter.log(:debug, nil, :skipped, 'canceled by user')
  @engine.logger.info("#{@parent.desc} - canceled by user")
  raise Interrupt if (@@skip_count += 1) > 2
rescue StandardError => e
  @error = e
  @engine.formatter.log(:fatal, e.message, :error, e.class.name)
  @engine.logger.fatal("#{e.message}\n#{e.backtrace.join("\n")}")
end

#fail_with(message) ⇒ Object

deprecated

use report instead.

Raise a failure error. Using this method within an assert or expect block will report an error and aborts the run immediately.

assert 'something' do
  fail_with 'a detailed message'
end

Raises:



946
947
948
# File 'lib/spectre.rb', line 946

def fail_with message
  raise Failure, message
end

#group(desc) ⇒ Object

Groups code in a block. This is solely used for structuring tests and will not have effect on test reports.



1087
1088
1089
1090
1091
1092
# File 'lib/spectre.rb', line 1087

def group(desc, &)
  @engine.logger.correlate do
    @engine.logger.debug("group \"#{desc}\"")
    @engine.formatter.scope(desc, :group, &)
  end
end

#measureObject

Takes a block and measures the time of the execution. The duration in seconds is available through duration.

measure do
  info 'do some long running stuff'
  sleep 1
end

info "run duration was #{duration}"


1028
1029
1030
1031
1032
1033
1034
# File 'lib/spectre.rb', line 1028

def measure
  start_time = Time.now
  yield
  end_time = Time.now

  @measured_duration = end_time - start_time
end

#methodObject

:method: expect :args: desc, &

Expect a specific condition. If a block is given it creates an EvaluationContext and its methods are available. If a failure is reported within this block, the run will continue.

foo = 'bar'

expect foo.to be 'bar'

expect 'a certain condition to be true' do
  report 'it was not' if foo == 'bar'
end


915
916
917
918
919
920
921
# File 'lib/spectre.rb', line 915

%i[debug info warn].each do |method|
  define_method(method) do |message|
    message = message.to_s
    @engine.logger.send(method, message)
    @engine.formatter.log(method, message)
  end
end

#observe(desc) ⇒ Object

Observes the given block and catches all errors within this block. If errors or failures occur the test will not fail or aborted.

The result of the observation can be retreived through success?

observe do
  info 'do some stuff'
end

assert success?.to be true

observe do
  raise StandardError, 'Oops!'
end

assert success?.to be false


1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
# File 'lib/spectre.rb', line 1114

def observe desc
  @engine.formatter.log(:info, "observe #{desc}") do
    yield
    @success = true
    [:info, :ok, nil]
  rescue StandardError => e
    @success = false
    @engine.logger.warn("#{e.message}\n#{e.backtrace.join("\n")}")
    [:info, :warn, e.message]
  end
end

#property(**kwargs) ⇒ Object

Adds the given key-value arguments to the run report. Use this to add generated values during the run to the test report.

property key: 'value'


1013
1014
1015
# File 'lib/spectre.rb', line 1013

def property **kwargs
  @properties.merge!(kwargs)
end

#resourcesObject

Access the loaded resources (files). The path parameter is relative to the resource directory.

resources['path/to/some.txt']


931
932
933
# File 'lib/spectre.rb', line 931

def resources
  @engine.resources
end

#run(desc, with: nil) ⇒ Object Also known as: also

Run the mixin with the given name and parameters

run 'additional actions' do
  with some_param: 42,
       another_param: 'foo'
end


1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
# File 'lib/spectre.rb', line 1141

def run(desc, with: nil, &)
  @engine.formatter.scope(desc, :mixin) do
    raise "mixin \"#{desc}\" not found" unless @engine.mixins.key? desc

    mixin = @engine.mixins[desc]
    mixin.instance_eval(&) if block_given?

    @engine.logger.correlate do
      @engine.logger.debug("execute mixin \"#{desc}\"")
      result = mixin.run(self, with)
      return result.is_a?(Hash) ? OpenStruct.new(result) : result
    end
  end
end

#skip(message) ⇒ Object

Skip the run for this spec. This can be used to skip spec runs when a certain condition occurs.

skip 'subject is not yet ready to be tested' unless service_is_ready()

Raises:



1167
1168
1169
1170
1171
# File 'lib/spectre.rb', line 1167

def skip message
  @skipped = true
  @engine.logger.info("#{message} - canceled by user")
  raise AbortException
end

#statusObject

The status of the current run. One of :error, :failed, :skipped or :success



884
885
886
887
888
889
890
# File 'lib/spectre.rb', line 884

def status
  return :error if @error
  return :failed if @evaluations.any? { |x| x.failures.any? }
  return :skipped if @skipped

  :success
end

#success?Boolean

Returns the status of the pervious observe block

Returns:

  • (Boolean)


1129
1130
1131
# File 'lib/spectre.rb', line 1129

def success?
  @success.nil? || @success
end