lopata 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|