lopata 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/lopata/active_record.rb +36 -0
- data/lib/lopata/condition.rb +0 -2
- data/lib/lopata/config.rb +24 -28
- data/lib/lopata/factory_bot.rb +36 -0
- data/lib/lopata/observers/backtrace_formatter.rb +89 -0
- data/lib/lopata/observers/console_output_observer.rb +38 -23
- data/lib/lopata/observers/web_logger.rb +37 -32
- data/lib/lopata/rspec/dsl.rb +1 -1
- data/lib/lopata/rspec/role.rb +0 -1
- data/lib/lopata/runner.rb +12 -2
- data/lib/lopata/scenario.rb +64 -36
- data/lib/lopata/scenario_builder.rb +55 -23
- data/lib/lopata/shared_step.rb +10 -4
- data/lib/lopata/step.rb +141 -40
- data/lib/lopata/version.rb +1 -1
- data/lib/lopata.rb +4 -0
- metadata +7 -5
- data/lib/lopata/rspec/ar_dsl.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 996fe5f1c16b6fed5daadff4ce365a778c2f09d79562c221e497c541541e9012
|
4
|
+
data.tar.gz: 7200a98d6bead7fb3baed82bbdffbf1c6aca805d999a563f3ada78026344dfed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 037ccc6d687292186b752de6924a2f3373d0934569f9e2d01567cac1a4607aa33ce70f53473ad0d737d07cdb1da231bb8b526f6c24f8c2e73d494978dfc94179
|
7
|
+
data.tar.gz: 7c90f679400d64d7ab676807cea167947cee47114cf9ea8daed84c517888bf5e7e64b475c1841f9e6cb886cf08184ca3acedaf6dc5af68a3518e74e4130f91b7
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Lopata
|
2
|
+
module ActiveRecord
|
3
|
+
# To be included in Lopata::Scenario
|
4
|
+
module Methods
|
5
|
+
def cleanup(*objects)
|
6
|
+
return if Lopata::Config.ops[:keep]
|
7
|
+
objects.flatten.compact.each do |o|
|
8
|
+
begin
|
9
|
+
o.reload.destroy!
|
10
|
+
rescue ::ActiveRecord::RecordNotFound
|
11
|
+
# Already destroyed
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def reload(*objects)
|
17
|
+
objects.flatten.each(&:reload)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# To be included in Lopata::ScenarioBuilder
|
22
|
+
module DSL
|
23
|
+
def cleanup(*vars, &block)
|
24
|
+
unless vars.empty?
|
25
|
+
teardown do
|
26
|
+
cleanup vars.map { |v| instance_variable_get "@#{v}" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
teardown &block if block_given?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Lopata::Scenario.include Lopata::ActiveRecord::Methods
|
36
|
+
Lopata::ScenarioBuilder.include Lopata::ActiveRecord::DSL
|
data/lib/lopata/condition.rb
CHANGED
data/lib/lopata/config.rb
CHANGED
@@ -2,8 +2,8 @@ module Lopata
|
|
2
2
|
module Config
|
3
3
|
extend self
|
4
4
|
|
5
|
-
attr_accessor :build_number, :lopata_host, :lopata_code, :only_roles, :role_descriptions,
|
6
|
-
:default_role, :ops
|
5
|
+
attr_accessor :build_number, :lopata_host, :lopata_code, :only_roles, :role_descriptions,
|
6
|
+
:default_role, :ops
|
7
7
|
|
8
8
|
def init(env)
|
9
9
|
require 'yaml'
|
@@ -40,34 +40,15 @@ module Lopata
|
|
40
40
|
init_rspec_filters
|
41
41
|
end
|
42
42
|
|
43
|
-
def init_active_record
|
44
|
-
require 'lopata/rspec/ar_dsl'
|
45
|
-
::RSpec.configure do |c|
|
46
|
-
c.include Lopata::RSpec::AR::DSL
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
43
|
def init_lopata_logging(build)
|
51
44
|
self.build_number = build
|
52
45
|
require 'lopata/observers/web_logger'
|
53
46
|
add_observer Lopata::Observers::WebLogger.new
|
54
47
|
end
|
55
48
|
|
56
|
-
def init_rerun
|
57
|
-
::RSpec.configure do |c|
|
58
|
-
c.inclusion_filter = { full_description: build_rerun_filter_proc }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
49
|
def init_rspec_filters
|
63
50
|
filters = {}
|
64
51
|
filters[:focus] = true if ops[:focus]
|
65
|
-
if ops[:rerun]
|
66
|
-
filters[:full_description] = build_rerun_filter_proc
|
67
|
-
end
|
68
|
-
if ops[:text]
|
69
|
-
filters[:full_description] = ->(desc) { desc.include?(ops[:text]) }
|
70
|
-
end
|
71
52
|
unless filters.blank?
|
72
53
|
::RSpec.configure do |c|
|
73
54
|
c.inclusion_filter = filters
|
@@ -75,17 +56,28 @@ module Lopata
|
|
75
56
|
end
|
76
57
|
end
|
77
58
|
|
78
|
-
def build_rerun_filter_proc
|
79
|
-
to_rerun = Lopata::Client.new(Lopata::Config.build_number).to_rerun
|
80
|
-
Proc.new do |desc|
|
81
|
-
to_rerun.include?(desc)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
59
|
def before_start(&block)
|
86
60
|
@before_start = block
|
87
61
|
end
|
88
62
|
|
63
|
+
def before_scenario(*steps, &block)
|
64
|
+
before_scenario_steps.append(*steps) unless steps.empty?
|
65
|
+
before_scenario_steps.append(block) if block_given?
|
66
|
+
end
|
67
|
+
|
68
|
+
def before_scenario_steps
|
69
|
+
@before_scenario_steps ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
def after_scenario(*steps, &block)
|
73
|
+
after_scenario_steps.append(*steps) unless steps.empty?
|
74
|
+
after_scenario_steps.append(block) if block_given?
|
75
|
+
end
|
76
|
+
|
77
|
+
def after_scenario_steps
|
78
|
+
@after_scenario_steps ||= []
|
79
|
+
end
|
80
|
+
|
89
81
|
def initialize_test
|
90
82
|
@before_start.call if @before_start
|
91
83
|
end
|
@@ -94,6 +86,10 @@ module Lopata
|
|
94
86
|
@world ||= Lopata::World.new
|
95
87
|
end
|
96
88
|
|
89
|
+
def filters
|
90
|
+
@filters ||= []
|
91
|
+
end
|
92
|
+
|
97
93
|
def add_observer(observer)
|
98
94
|
world.observers << observer
|
99
95
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'active_record'
|
2
|
+
|
3
|
+
module Lopata
|
4
|
+
module FactoryBot
|
5
|
+
# To be included in Lopata::Scenario
|
6
|
+
module Methods
|
7
|
+
def create(*params)
|
8
|
+
cleanup_later ::FactoryBot.create(*params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_created(cls, params)
|
12
|
+
cleanup_later cls.where(params).take
|
13
|
+
end
|
14
|
+
|
15
|
+
def cleanup_later(object)
|
16
|
+
return nil unless object
|
17
|
+
@created_objects ||= []
|
18
|
+
@created_objects << object
|
19
|
+
object
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# To be included in Lopata::ScenarioBuilder
|
24
|
+
module DSL
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Lopata::Scenario.include Lopata::FactoryBot::Methods
|
30
|
+
Lopata::ScenarioBuilder.include Lopata::FactoryBot::DSL
|
31
|
+
|
32
|
+
Lopata.configure do |c|
|
33
|
+
c.after_scenario { cleanup @created_objects }
|
34
|
+
end
|
35
|
+
|
36
|
+
::FactoryBot.find_definitions unless Lopata::Config.readonly
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Lopata
|
2
|
+
module Observers
|
3
|
+
# Based on RSpec::Core::BacktraceFormatter
|
4
|
+
class BacktraceFormatter
|
5
|
+
attr_accessor :exclusion_patterns, :inclusion_patterns
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
patterns = %w[ /lib\d*/ruby/ bin/ exe/lopata /lib/bundler/ /exe/bundle: ]
|
9
|
+
patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }
|
10
|
+
|
11
|
+
@exclusion_patterns = [Regexp.union(*patterns)]
|
12
|
+
@inclusion_patterns = []
|
13
|
+
|
14
|
+
inclusion_patterns << Regexp.new(Dir.getwd)
|
15
|
+
end
|
16
|
+
|
17
|
+
def format(backtrace)
|
18
|
+
return [] unless backtrace
|
19
|
+
return backtrace if backtrace.empty?
|
20
|
+
|
21
|
+
backtrace.map { |l| backtrace_line(l) }.compact.
|
22
|
+
tap do |filtered|
|
23
|
+
if filtered.empty?
|
24
|
+
filtered.concat backtrace
|
25
|
+
filtered << ""
|
26
|
+
filtered << " Showing full backtrace because every line was filtered out."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def error_message(exception, include_backtrace: false)
|
32
|
+
backtrace = format(exception.backtrace)
|
33
|
+
source_line = extract_source_line(backtrace.first)
|
34
|
+
msg = ''
|
35
|
+
msg << "\n#{source_line}\n" if source_line
|
36
|
+
msg << "#{exception.class.name}: " unless exception.class.name =~ /RSpec/
|
37
|
+
msg << exception.message if exception.message
|
38
|
+
msg << "\n#{backtrace.join("\n")}\n" if include_backtrace
|
39
|
+
msg
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_source_line(backtrace_line)
|
43
|
+
file_and_line_number = backtrace_line.match(/(.+?):(\d+)(|:\d+)/)
|
44
|
+
return nil unless file_and_line_number
|
45
|
+
file_path, line_number = file_and_line_number[1..2]
|
46
|
+
return nil unless File.exist?(file_path)
|
47
|
+
lines = File.read(file_path).split("\n")
|
48
|
+
lines[line_number.to_i - 1]
|
49
|
+
end
|
50
|
+
|
51
|
+
def backtrace_line(line)
|
52
|
+
relative_path(line) unless exclude?(line)
|
53
|
+
end
|
54
|
+
|
55
|
+
def exclude?(line)
|
56
|
+
matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def matches?(patterns, line)
|
62
|
+
patterns.any? { |p| line =~ p }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Matches strings either at the beginning of the input or prefixed with a
|
66
|
+
# whitespace, containing the current path, either postfixed with the
|
67
|
+
# separator, or at the end of the string. Match groups are the character
|
68
|
+
# before and the character after the string if any.
|
69
|
+
#
|
70
|
+
# http://rubular.com/r/fT0gmX6VJX
|
71
|
+
# http://rubular.com/r/duOrD4i3wb
|
72
|
+
# http://rubular.com/r/sbAMHFrOx1
|
73
|
+
def relative_path_regex
|
74
|
+
@relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param line [String] current code line
|
78
|
+
# @return [String] relative path to line
|
79
|
+
def relative_path(line)
|
80
|
+
line = line.sub(relative_path_regex, "\\1.\\2".freeze)
|
81
|
+
line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
|
82
|
+
return nil if line == '-e:1'.freeze
|
83
|
+
line
|
84
|
+
rescue SecurityError
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'backtrace_formatter'
|
2
|
+
|
1
3
|
module Lopata
|
2
4
|
module Observers
|
3
5
|
class ConsoleOutputObserver < BaseObserver
|
@@ -11,24 +13,14 @@ module Lopata
|
|
11
13
|
puts "#{total} scenario%s %s" % [total == 1 ? '' : 's', details]
|
12
14
|
end
|
13
15
|
|
14
|
-
def step_finished(step)
|
15
|
-
@failed_steps << step if step.failed?
|
16
|
-
end
|
17
|
-
|
18
|
-
def scenario_started(scenario)
|
19
|
-
@failed_steps = []
|
20
|
-
end
|
21
|
-
|
22
16
|
def scenario_finished(scenario)
|
23
17
|
message = "#{scenario.title} #{bold(scenario.status.to_s.upcase)}"
|
24
18
|
puts colored(message, scenario.status)
|
19
|
+
return unless scenario.failed?
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
puts step.exception.backtrace.join("\n")
|
30
|
-
puts
|
31
|
-
end
|
21
|
+
scenario.steps_in_running_order.each do |step|
|
22
|
+
puts colored(" #{status_marker(step.status)} #{step.title}", step.status)
|
23
|
+
puts indent(4, backtrace_formatter.error_message(step.exception, include_backtrace: true)) if step.failed?
|
32
24
|
end
|
33
25
|
end
|
34
26
|
|
@@ -38,25 +30,48 @@ module Lopata
|
|
38
30
|
case status
|
39
31
|
when :failed then red(text)
|
40
32
|
when :passed then green(text)
|
33
|
+
when :skipped then cyan(text)
|
34
|
+
when :pending then yellow(text)
|
41
35
|
else text
|
42
36
|
end
|
43
37
|
end
|
44
38
|
|
45
|
-
|
46
|
-
|
39
|
+
{
|
40
|
+
red: 31,
|
41
|
+
green: 32,
|
42
|
+
cyan: 36,
|
43
|
+
yellow: 33,
|
44
|
+
bold: 1,
|
45
|
+
}.each do |color, code|
|
46
|
+
define_method(color) do |text|
|
47
|
+
wrap(text, code)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def wrap(text, code)
|
52
|
+
"\e[#{code}m#{text}\e[0m"
|
47
53
|
end
|
48
54
|
|
49
|
-
def
|
50
|
-
|
55
|
+
def backtrace_formatter
|
56
|
+
@backtrace_formatter ||= Lopata::Observers::BacktraceFormatter.new
|
51
57
|
end
|
52
58
|
|
53
|
-
def
|
54
|
-
|
59
|
+
def status_marker(status)
|
60
|
+
case status
|
61
|
+
when :failed then "[!]"
|
62
|
+
when :skipped then "[-]"
|
63
|
+
when :pending then "[?]"
|
64
|
+
else "[+]"
|
65
|
+
end
|
55
66
|
end
|
56
67
|
|
57
|
-
|
58
|
-
|
68
|
+
# Adds indent to text
|
69
|
+
# @param cols [Number] number of spaces to be added
|
70
|
+
# @param text [String] text to add indent
|
71
|
+
# @return [String] text with indent
|
72
|
+
def indent(cols, text)
|
73
|
+
text.split("\n").map { |line| " " * cols + line }.join("\n")
|
59
74
|
end
|
60
75
|
end
|
61
76
|
end
|
62
|
-
end
|
77
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
require 'json'
|
3
|
+
require_relative 'backtrace_formatter'
|
3
4
|
|
4
5
|
module Lopata
|
5
6
|
module Observers
|
@@ -11,46 +12,20 @@ module Lopata
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def scenario_finished(scenario)
|
14
|
-
|
15
|
-
backtrace = backtrace_for(scenario)
|
16
|
-
@client.add_attempt(scenario, Lopata::FAILED, error_message_for(scenario), backtrace)
|
17
|
-
else
|
18
|
-
@client.add_attempt(scenario, Lopata::PASSED)
|
19
|
-
end
|
15
|
+
@client.add_attempt(scenario)
|
20
16
|
end
|
21
17
|
|
22
18
|
# def example_pending(notification)
|
23
19
|
# example = notification.example
|
24
20
|
# @client.add_attempt(example, Lopata::PENDING, example.execution_result.pending_message)
|
25
21
|
# end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def error_message_for(scenario)
|
30
|
-
exception = scenario.steps.map(&:exception).compact.last
|
31
|
-
msg = ''
|
32
|
-
if exception
|
33
|
-
msg << "#{exception.class.name}: " unless exception.class.name =~ /RSpec/
|
34
|
-
msg << "#{exception.message.to_s}" if exception.message
|
35
|
-
end
|
36
|
-
(msg.length == 0) ? 'Empty message' : msg
|
37
|
-
end
|
38
|
-
|
39
|
-
def backtrace_for(scenario)
|
40
|
-
exception = scenario.steps.map(&:exception).compact.last
|
41
|
-
msg = ''
|
42
|
-
if exception
|
43
|
-
msg = exception.backtrace.join("\n")
|
44
|
-
msg << "\n"
|
45
|
-
end
|
46
|
-
msg
|
47
|
-
end
|
48
22
|
end
|
49
23
|
end
|
50
24
|
|
51
25
|
PASSED = 0
|
52
26
|
FAILED = 1
|
53
27
|
PENDING = 2
|
28
|
+
SKIPPED = 5
|
54
29
|
|
55
30
|
class Client
|
56
31
|
include HTTParty
|
@@ -66,15 +41,24 @@ module Lopata
|
|
66
41
|
@launch_id = JSON.parse(post("/projects/#{project_code}/builds/#{build_number}/launches.json", body: {total: count}).body)['id']
|
67
42
|
end
|
68
43
|
|
69
|
-
def add_attempt(scenario
|
44
|
+
def add_attempt(scenario)
|
45
|
+
status = scenario.failed? ? Lopata::FAILED : Lopata::PASSED
|
46
|
+
steps = scenario.steps_in_running_order.map { |s| step_hash(s) }
|
47
|
+
request = { status: status, steps: steps }
|
70
48
|
test = test_id(scenario)
|
71
|
-
request = { status: status}
|
72
|
-
request[:message] = msg if msg
|
73
|
-
request[:backtrace] = backtrace if backtrace
|
74
49
|
post("/tests/#{test}/attempts.json", body: request)
|
75
50
|
inc_finished
|
76
51
|
end
|
77
52
|
|
53
|
+
def step_hash(step)
|
54
|
+
hash = { status: step.status, title: step.title }
|
55
|
+
if step.failed?
|
56
|
+
hash[:message] = error_message_for(step)
|
57
|
+
hash[:backtrace] = backtrace_for(step)
|
58
|
+
end
|
59
|
+
hash
|
60
|
+
end
|
61
|
+
|
78
62
|
def test_id(scenario)
|
79
63
|
request = {
|
80
64
|
test: {
|
@@ -125,5 +109,26 @@ module Lopata
|
|
125
109
|
def project_code
|
126
110
|
Lopata::Config.lopata_code
|
127
111
|
end
|
112
|
+
|
113
|
+
def error_message_for(step)
|
114
|
+
if step.exception
|
115
|
+
backtrace_formatter.error_message(step.exception)
|
116
|
+
else
|
117
|
+
'Empty error message'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def backtrace_for(step)
|
122
|
+
msg = ''
|
123
|
+
if step.exception
|
124
|
+
msg = backtrace_formatter.format(step.exception.backtrace).join("\n")
|
125
|
+
msg << "\n"
|
126
|
+
end
|
127
|
+
msg
|
128
|
+
end
|
129
|
+
|
130
|
+
def backtrace_formatter
|
131
|
+
@backtrace_formatter ||= Lopata::Observers::BacktraceFormatter.new
|
132
|
+
end
|
128
133
|
end
|
129
134
|
end
|
data/lib/lopata/rspec/dsl.rb
CHANGED
data/lib/lopata/rspec/role.rb
CHANGED
@@ -20,7 +20,6 @@ module Lopata::RSpec::Role
|
|
20
20
|
else
|
21
21
|
Lopata::RSpec::Role.filter_roles(*names).each do |name|
|
22
22
|
example_group_class = describe role_description(name), :current_role => name do
|
23
|
-
instance_exec &Lopata::Config.after_as if Lopata::Config.after_as
|
24
23
|
define_method :current_role do
|
25
24
|
name
|
26
25
|
end
|
data/lib/lopata/runner.rb
CHANGED
@@ -40,15 +40,25 @@ module Lopata
|
|
40
40
|
def configure_from_options
|
41
41
|
Lopata::Config.ops = {
|
42
42
|
focus: options[:focus],
|
43
|
-
rerun: options[:rerun],
|
44
43
|
users: options[:users],
|
45
44
|
build: options[:build],
|
46
45
|
env: options[:env],
|
47
46
|
keep: options[:keep],
|
48
|
-
text: options[:text]
|
49
47
|
}
|
50
48
|
Lopata::Config.init(options[:env])
|
51
49
|
Lopata::Config.initialize_test
|
50
|
+
add_text_filter(options[:text]) if options[:text]
|
51
|
+
add_rerun_filter if options[:rerun]
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_text_filter(text)
|
55
|
+
Lopata::Config.filters << -> (scenario) { scenario.title.include?(text) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_rerun_filter
|
59
|
+
to_rerun = Lopata::Client.new(Lopata::Config.build_number).to_rerun
|
60
|
+
puts to_rerun
|
61
|
+
Lopata::Config.filters << -> (scenario) { to_rerun.include?(scenario.title) }
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
data/lib/lopata/scenario.rb
CHANGED
@@ -3,55 +3,83 @@ require 'rspec/expectations'
|
|
3
3
|
class Lopata::Scenario
|
4
4
|
include RSpec::Matchers
|
5
5
|
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :execution
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@metadata = metadata
|
11
|
-
@steps = []
|
12
|
-
@status = :not_runned
|
8
|
+
def initialize(execution)
|
9
|
+
@execution = execution
|
13
10
|
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
teardown_steps = []
|
19
|
-
@steps.reject(&:teardown?).each { |step| step.run(self) }
|
20
|
-
@steps.select(&:teardown?).each { |step| step.run(self) }
|
21
|
-
@status = @steps.all?(&:passed?) ? :passed : :failed
|
22
|
-
world.notify_observers(:scenario_finished, self)
|
12
|
+
# Marks current step as pending
|
13
|
+
def pending(message = nil)
|
14
|
+
execution.current_step.pending!(message)
|
23
15
|
end
|
24
16
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
17
|
+
def metadata
|
18
|
+
execution.metadata
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def method_missing(method, *args, &block)
|
24
|
+
if metadata.keys.include?(method)
|
25
|
+
metadata[method]
|
31
26
|
else
|
32
|
-
|
27
|
+
super
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
|
-
def
|
37
|
-
|
31
|
+
def respond_to_missing?(method, *)
|
32
|
+
metadata.keys.include?(method) or super
|
38
33
|
end
|
39
34
|
|
40
|
-
|
41
|
-
status
|
42
|
-
end
|
35
|
+
class Execution
|
36
|
+
attr_reader :scenario, :status, :steps, :title, :current_step
|
43
37
|
|
44
|
-
|
38
|
+
def initialize(title, options_title, metadata = {})
|
39
|
+
@title = [title, options_title].compact.reject(&:empty?).join(' ')
|
40
|
+
@metadata = metadata
|
41
|
+
@status = :not_runned
|
42
|
+
@steps = []
|
43
|
+
@scenario = Lopata::Scenario.new(self)
|
44
|
+
end
|
45
45
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
def run
|
47
|
+
@status = :running
|
48
|
+
world.notify_observers(:scenario_started, self)
|
49
|
+
steps_in_running_order.each(&method(:run_step))
|
50
|
+
@status = steps.any?(&:failed?) ? :failed : :passed
|
51
|
+
world.notify_observers(:scenario_finished, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_step(step)
|
55
|
+
return if step.skipped?
|
56
|
+
@current_step = step
|
57
|
+
step.run(scenario)
|
58
|
+
skip_rest if step.failed? && step.skip_rest_on_failure?
|
52
59
|
end
|
53
60
|
|
54
|
-
def
|
55
|
-
|
61
|
+
def world
|
62
|
+
@world ||= Lopata::Config.world
|
56
63
|
end
|
57
|
-
|
64
|
+
|
65
|
+
def failed?
|
66
|
+
status == :failed
|
67
|
+
end
|
68
|
+
|
69
|
+
def steps_in_running_order
|
70
|
+
steps.reject(&:teardown_group?) + steps.select(&:teardown_group?)
|
71
|
+
end
|
72
|
+
|
73
|
+
def skip_rest
|
74
|
+
steps.select { |s| s.status == :not_runned && !s.teardown? }.each(&:skip!)
|
75
|
+
end
|
76
|
+
|
77
|
+
def metadata
|
78
|
+
if current_step
|
79
|
+
@metadata.merge(current_step.metadata)
|
80
|
+
else
|
81
|
+
@metadata
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Lopata::ScenarioBuilder
|
2
2
|
attr_reader :title, :common_metadata
|
3
|
+
attr_accessor :shared_step, :group
|
3
4
|
|
4
5
|
def self.define(title, metadata = {}, &block)
|
5
6
|
builder = new(title, metadata)
|
@@ -12,18 +13,18 @@ class Lopata::ScenarioBuilder
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def build
|
16
|
+
filters = Lopata::Config.filters
|
15
17
|
option_combinations.each do |option_set|
|
16
18
|
metadata = common_metadata.merge(option_set.metadata)
|
17
|
-
scenario = Lopata::Scenario.new(title, option_set.title, metadata)
|
19
|
+
scenario = Lopata::Scenario::Execution.new(title, option_set.title, metadata)
|
18
20
|
|
19
|
-
|
20
|
-
next
|
21
|
-
step.pre_steps(scenario).each { |s| scenario.steps << s }
|
22
|
-
scenario.steps << step
|
21
|
+
unless filters.empty?
|
22
|
+
next unless filters.all? { |f| f[scenario] }
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
steps_with_hooks.each do |step|
|
26
|
+
next if step.condition && !step.condition.match?(scenario)
|
27
|
+
step.execution_steps(scenario).each { |s| scenario.steps << s }
|
27
28
|
end
|
28
29
|
|
29
30
|
world.scenarios << scenario
|
@@ -58,28 +59,49 @@ class Lopata::ScenarioBuilder
|
|
58
59
|
@skip_when && @skip_when.call(option_set)
|
59
60
|
end
|
60
61
|
|
61
|
-
%i{ setup action it teardown }.each do |name|
|
62
|
+
%i{ setup action it teardown verify context }.each do |name|
|
62
63
|
name_if = "%s_if" % name
|
63
64
|
name_unless = "%s_unless" % name
|
64
|
-
define_method name, ->(*args, &block) { add_step(name, *args, &block) }
|
65
|
-
define_method name_if, ->(condition, *args,
|
66
|
-
|
65
|
+
define_method name, ->(*args, **metadata, &block) { add_step(name, *args, metadata: metadata, &block) }
|
66
|
+
define_method name_if, ->(condition, *args, **metadata, &block) {
|
67
|
+
add_step(name, *args, metadata: metadata, condition: Lopata::Condition.new(condition), &block)
|
68
|
+
}
|
69
|
+
define_method name_unless, ->(condition, *args, **metadata, &block) {
|
70
|
+
add_step(name, *args, condition: Lopata::Condition.new(condition, positive: false), metadata: metadata, &block)
|
71
|
+
}
|
67
72
|
end
|
68
73
|
|
69
|
-
def add_step(method_name, *args, condition: nil, &block)
|
74
|
+
def add_step(method_name, *args, condition: nil, metadata: {}, &block)
|
70
75
|
step_class =
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
case method_name
|
77
|
+
when /^(setup|action|teardown|verify)/ then Lopata::ActionStep
|
78
|
+
when /^(context)/ then Lopata::GroupStep
|
79
|
+
else Lopata::Step
|
75
80
|
end
|
76
|
-
|
81
|
+
step = step_class.new(method_name, *args, condition: condition, shared_step: shared_step, group: group, &block)
|
82
|
+
step.metadata = metadata
|
83
|
+
steps << step
|
77
84
|
end
|
78
85
|
|
79
86
|
def steps
|
80
87
|
@steps ||= []
|
81
88
|
end
|
82
89
|
|
90
|
+
def steps_with_hooks
|
91
|
+
s = []
|
92
|
+
unless Lopata::Config.before_scenario_steps.empty?
|
93
|
+
s << Lopata::ActionStep.new(:setup, *Lopata::Config.before_scenario_steps)
|
94
|
+
end
|
95
|
+
|
96
|
+
s += steps
|
97
|
+
|
98
|
+
unless Lopata::Config.after_scenario_steps.empty?
|
99
|
+
s << Lopata::ActionStep.new(:teardown, *Lopata::Config.after_scenario_steps)
|
100
|
+
end
|
101
|
+
|
102
|
+
s
|
103
|
+
end
|
104
|
+
|
83
105
|
def cleanup(*args, &block)
|
84
106
|
add_step_as_is(:cleanup, *args, &block)
|
85
107
|
end
|
@@ -187,10 +209,10 @@ class Lopata::ScenarioBuilder
|
|
187
209
|
end
|
188
210
|
|
189
211
|
class Variant
|
190
|
-
attr_reader :key, :title, :value
|
212
|
+
attr_reader :key, :title, :value, :option
|
191
213
|
|
192
|
-
def initialize(key, title, value)
|
193
|
-
@key, @title, @value = key, title, check_lambda_arity(value)
|
214
|
+
def initialize(option, key, title, value)
|
215
|
+
@option, @key, @title, @value = option, key, title, check_lambda_arity(value)
|
194
216
|
end
|
195
217
|
|
196
218
|
def metadata(option_set)
|
@@ -202,6 +224,10 @@ class Lopata::ScenarioBuilder
|
|
202
224
|
end
|
203
225
|
end
|
204
226
|
|
227
|
+
option.available_metadata_keys.each do |key|
|
228
|
+
data[key] = nil unless data.has_key?(key)
|
229
|
+
end
|
230
|
+
|
205
231
|
data.each do |key, v|
|
206
232
|
data[key] = v.calculate(option_set) if v.is_a? CalculatedValue
|
207
233
|
end
|
@@ -241,14 +267,15 @@ class Lopata::ScenarioBuilder
|
|
241
267
|
end
|
242
268
|
|
243
269
|
class Option
|
244
|
-
attr_reader :variants
|
270
|
+
attr_reader :variants, :key
|
245
271
|
def initialize(key, variants)
|
272
|
+
@key = key
|
246
273
|
@variants =
|
247
274
|
if variants.is_a? Hash
|
248
|
-
variants.map { |title, value| Variant.new(key, title, value) }
|
275
|
+
variants.map { |title, value| Variant.new(self, key, title, value) }
|
249
276
|
else
|
250
277
|
# Array of arrays of two elements
|
251
|
-
variants.map { |v| Variant.new(key, *v) }
|
278
|
+
variants.map { |v| Variant.new(self, key, *v) }
|
252
279
|
end
|
253
280
|
end
|
254
281
|
|
@@ -267,6 +294,11 @@ class Lopata::ScenarioBuilder
|
|
267
294
|
end
|
268
295
|
selected_variant
|
269
296
|
end
|
297
|
+
|
298
|
+
def available_metadata_keys
|
299
|
+
@available_metadata_keys ||= variants
|
300
|
+
.map(&:value).select { |v| v.is_a?(Hash) }.flat_map(&:keys).map { |k| "#{key}_#{k}".to_sym }.uniq
|
301
|
+
end
|
270
302
|
end
|
271
303
|
|
272
304
|
class Diagonal < Option
|
data/lib/lopata/shared_step.rb
CHANGED
@@ -2,16 +2,15 @@ module Lopata
|
|
2
2
|
class SharedStep
|
3
3
|
attr_reader :name, :block
|
4
4
|
|
5
|
-
class
|
5
|
+
class NotFound < StandardError; end
|
6
6
|
|
7
7
|
def self.register(name, &block)
|
8
8
|
raise ArgumentError, "Comma is not allowed in shared step name: '%s'" % name if name =~ /,/
|
9
|
-
|
10
|
-
@shared_steps[name] = new(name, &block)
|
9
|
+
registry[name] = new(name, &block)
|
11
10
|
end
|
12
11
|
|
13
12
|
def self.find(name)
|
14
|
-
|
13
|
+
registry[name] or raise NotFound, "Shared step '%s' not found" % name
|
15
14
|
end
|
16
15
|
|
17
16
|
def initialize(name, &block)
|
@@ -24,8 +23,15 @@ module Lopata
|
|
24
23
|
|
25
24
|
def build_steps
|
26
25
|
builder = Lopata::ScenarioBuilder.new(name)
|
26
|
+
builder.shared_step = self
|
27
27
|
builder.instance_exec(&block)
|
28
28
|
builder.steps
|
29
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.registry
|
34
|
+
@shared_steps ||= {}
|
35
|
+
end
|
30
36
|
end
|
31
37
|
end
|
data/lib/lopata/step.rb
CHANGED
@@ -1,14 +1,118 @@
|
|
1
1
|
module Lopata
|
2
2
|
class Step
|
3
|
-
attr_reader :block, :
|
3
|
+
attr_reader :block, :args, :condition, :method_name, :shared_step, :group
|
4
|
+
# metadata overrien by the step.
|
5
|
+
attr_accessor :metadata
|
4
6
|
|
5
|
-
def initialize(method_name, *args, condition: nil, &block)
|
7
|
+
def initialize(method_name, *args, condition: nil, shared_step: nil, group: nil, &block)
|
6
8
|
@method_name = method_name
|
7
9
|
@args = args
|
8
|
-
@status = :not_started
|
9
10
|
@block = block
|
11
|
+
@shared_step = shared_step
|
12
|
+
@condition = condition
|
13
|
+
@group = group
|
14
|
+
initialized! if defined? initialized!
|
15
|
+
end
|
16
|
+
|
17
|
+
def title
|
18
|
+
base_title = args.first
|
19
|
+
base_title ||= shared_step && "#{method_name.capitalize} #{shared_step.name}" || "Untitled #{method_name}"
|
20
|
+
if group
|
21
|
+
"#{group.title}: #{base_title}"
|
22
|
+
else
|
23
|
+
base_title
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def execution_steps(scenario, groups: [])
|
28
|
+
return [] if condition && !condition.match?(scenario)
|
29
|
+
return [] unless block
|
30
|
+
[StepExecution.new(self, groups, &block)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Used for action, setup, teardown
|
35
|
+
class ActionStep < Step
|
36
|
+
def execution_steps(scenario, groups: [])
|
37
|
+
steps = []
|
38
|
+
return steps if condition && !condition.match?(scenario)
|
39
|
+
convert_args(scenario).each do |step|
|
40
|
+
if step.is_a?(String)
|
41
|
+
Lopata::SharedStep.find(step).steps.each do |shared_step|
|
42
|
+
next if shared_step.condition && !shared_step.condition.match?(scenario)
|
43
|
+
steps += shared_step.execution_steps(scenario, groups: groups)
|
44
|
+
end
|
45
|
+
elsif step.is_a?(Proc)
|
46
|
+
steps << StepExecution.new(self, groups, &step)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
steps << StepExecution.new(self, groups, &block) if block
|
50
|
+
steps.reject { |s| !s.block }
|
51
|
+
end
|
52
|
+
|
53
|
+
def separate_args(args)
|
54
|
+
args.map { |a| a.is_a?(String) && a =~ /,/ ? a.split(',').map(&:strip) : a }.flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
def convert_args(scenario)
|
58
|
+
flat_args = separate_args(args.flatten)
|
59
|
+
flat_args.map do |arg|
|
60
|
+
case arg
|
61
|
+
# trait symbols as link to metadata.
|
62
|
+
when Symbol then scenario.metadata[arg]
|
63
|
+
else
|
64
|
+
arg
|
65
|
+
end
|
66
|
+
end.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def title
|
70
|
+
if group
|
71
|
+
"%s: %s" % [group.title, method_name]
|
72
|
+
else
|
73
|
+
shared_step && "#{method_name.capitalize} #{shared_step.name}" || "Untitled #{method_name}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Used for context
|
79
|
+
class GroupStep < Step
|
80
|
+
|
81
|
+
def execution_steps(scenario, groups: [])
|
82
|
+
steps = []
|
83
|
+
return steps if condition && !condition.match?(scenario)
|
84
|
+
@steps.each do |step|
|
85
|
+
steps += step.execution_steps(scenario, groups: groups + [self])
|
86
|
+
end
|
87
|
+
steps.reject! { |s| !s.block }
|
88
|
+
steps.reject { |s| s.teardown_group?(self) } + steps.select { |s| s.teardown_group?(self) }
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Group step's block is a block in context of builder, not scenario. So hide the @block to not be used in scenario.
|
94
|
+
def initialized!
|
95
|
+
builder = Lopata::ScenarioBuilder.new(title)
|
96
|
+
builder.group = self
|
97
|
+
builder.instance_exec(&@block)
|
98
|
+
@steps = builder.steps
|
99
|
+
@block = nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class StepExecution
|
104
|
+
attr_reader :step, :status, :exception, :block, :pending_message, :groups
|
105
|
+
extend Forwardable
|
106
|
+
def_delegators :step, :title, :method_name
|
107
|
+
|
108
|
+
class PendingStepFixedError < StandardError; end
|
109
|
+
|
110
|
+
def initialize(step, groups, &block)
|
111
|
+
@step = step
|
112
|
+
@status = :not_runned
|
10
113
|
@exception = nil
|
11
|
-
@
|
114
|
+
@block = block
|
115
|
+
@groups = groups
|
12
116
|
end
|
13
117
|
|
14
118
|
def run(scenario)
|
@@ -16,16 +120,22 @@ module Lopata
|
|
16
120
|
world.notify_observers(:step_started, self)
|
17
121
|
begin
|
18
122
|
run_step(scenario)
|
19
|
-
|
123
|
+
if pending?
|
124
|
+
@status = :failed
|
125
|
+
raise PendingStepFixedError, 'Expected step to fail since it is pending, but it passed.'
|
126
|
+
else
|
127
|
+
@status = :passed
|
128
|
+
end
|
20
129
|
rescue Exception => e
|
21
|
-
@status = :failed
|
130
|
+
@status = :failed unless pending?
|
22
131
|
@exception = e
|
23
132
|
end
|
24
133
|
world.notify_observers(:step_finished, self)
|
25
134
|
end
|
26
135
|
|
27
136
|
def run_step(scenario)
|
28
|
-
|
137
|
+
return unless block
|
138
|
+
scenario.instance_exec(&block)
|
29
139
|
end
|
30
140
|
|
31
141
|
def world
|
@@ -40,47 +150,38 @@ module Lopata
|
|
40
150
|
status == :passed
|
41
151
|
end
|
42
152
|
|
43
|
-
def
|
44
|
-
|
153
|
+
def skipped?
|
154
|
+
status == :skipped
|
45
155
|
end
|
46
156
|
|
47
|
-
def
|
48
|
-
|
157
|
+
def skip!
|
158
|
+
@status = :skipped
|
49
159
|
end
|
50
|
-
end
|
51
160
|
|
52
|
-
|
53
|
-
|
54
|
-
def pre_steps(scenario)
|
55
|
-
steps = []
|
56
|
-
convert_args(scenario).each do |step|
|
57
|
-
if step.is_a?(String)
|
58
|
-
Lopata::SharedStep.find(step).steps.each do |shared_step|
|
59
|
-
steps += shared_step.pre_steps(scenario)
|
60
|
-
steps << shared_step
|
61
|
-
end
|
62
|
-
elsif step.is_a?(Proc)
|
63
|
-
steps << Lopata::Step.new(method_name, &step)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
steps
|
161
|
+
def pending?
|
162
|
+
status == :pending
|
67
163
|
end
|
68
164
|
|
69
|
-
def
|
70
|
-
|
165
|
+
def pending!(message = nil)
|
166
|
+
@status = :pending
|
167
|
+
@pending_message = message
|
71
168
|
end
|
72
169
|
|
73
|
-
def
|
74
|
-
|
75
|
-
flat_args.map do |arg|
|
76
|
-
case arg
|
77
|
-
# trait symbols as link to metadata.
|
78
|
-
when Symbol then scenario.metadata[arg]
|
79
|
-
else
|
80
|
-
arg
|
81
|
-
end
|
82
|
-
end.flatten
|
170
|
+
def teardown?
|
171
|
+
%i{ teardown cleanup }.include?(method_name)
|
83
172
|
end
|
84
173
|
|
174
|
+
def teardown_group?(group = nil)
|
175
|
+
teardown? && self.groups.last == group
|
176
|
+
end
|
177
|
+
|
178
|
+
def skip_rest_on_failure?
|
179
|
+
%i{ setup action }.include?(method_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Step metadata is a combination of metadata given for step and all contexts (groups) the step included
|
183
|
+
def metadata
|
184
|
+
([step] + groups).compact.inject({}) { |merged, part| merged.merge(part.metadata) }
|
185
|
+
end
|
85
186
|
end
|
86
|
-
end
|
187
|
+
end
|
data/lib/lopata/version.rb
CHANGED
data/lib/lopata.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lopata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Volochnev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '1.0'
|
83
|
-
description: Functional acceptance
|
83
|
+
description: Functional acceptance testing
|
84
84
|
email: alexey.volochnev@gmail.com
|
85
85
|
executables:
|
86
86
|
- lopata
|
@@ -90,8 +90,10 @@ files:
|
|
90
90
|
- README.md
|
91
91
|
- exe/lopata
|
92
92
|
- lib/lopata.rb
|
93
|
+
- lib/lopata/active_record.rb
|
93
94
|
- lib/lopata/condition.rb
|
94
95
|
- lib/lopata/config.rb
|
96
|
+
- lib/lopata/factory_bot.rb
|
95
97
|
- lib/lopata/generators/app.rb
|
96
98
|
- lib/lopata/generators/templates/.rspec
|
97
99
|
- lib/lopata/generators/templates/Gemfile
|
@@ -102,10 +104,10 @@ files:
|
|
102
104
|
- lib/lopata/id.rb
|
103
105
|
- lib/lopata/loader.rb
|
104
106
|
- lib/lopata/observers.rb
|
107
|
+
- lib/lopata/observers/backtrace_formatter.rb
|
105
108
|
- lib/lopata/observers/base_observer.rb
|
106
109
|
- lib/lopata/observers/console_output_observer.rb
|
107
110
|
- lib/lopata/observers/web_logger.rb
|
108
|
-
- lib/lopata/rspec/ar_dsl.rb
|
109
111
|
- lib/lopata/rspec/dsl.rb
|
110
112
|
- lib/lopata/rspec/role.rb
|
111
113
|
- lib/lopata/runner.rb
|
@@ -137,5 +139,5 @@ requirements: []
|
|
137
139
|
rubygems_version: 3.0.3
|
138
140
|
signing_key:
|
139
141
|
specification_version: 4
|
140
|
-
summary: lopata-0.1.
|
142
|
+
summary: lopata-0.1.2
|
141
143
|
test_files: []
|
data/lib/lopata/rspec/ar_dsl.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Lopata
|
2
|
-
module RSpec
|
3
|
-
module AR
|
4
|
-
module DSL
|
5
|
-
def self.included(base)
|
6
|
-
base.extend(ClassMethods)
|
7
|
-
end
|
8
|
-
|
9
|
-
def cleanup(*objects)
|
10
|
-
return if Lopata::Config.ops[:keep]
|
11
|
-
objects.flatten.compact.each do |o|
|
12
|
-
begin
|
13
|
-
o.reload.destroy!
|
14
|
-
rescue ActiveRecord::RecordNotFound
|
15
|
-
# Already destroyed
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def reload(*objects)
|
21
|
-
objects.flatten.each(&:reload)
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
module ClassMethods
|
26
|
-
def cleanup(*vars, &block)
|
27
|
-
unless vars.empty?
|
28
|
-
teardown do
|
29
|
-
cleanup vars.map { |v| instance_variable_get "@#{v}" }
|
30
|
-
end
|
31
|
-
end
|
32
|
-
teardown &block if block_given?
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|