lopata 0.1.1 → 0.1.6
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/README.md +6 -2
- data/exe/lopata +1 -1
- data/lib/lopata.rb +54 -1
- data/lib/lopata/active_record.rb +99 -0
- data/lib/lopata/condition.rb +2 -3
- data/lib/lopata/configuration.rb +126 -0
- data/lib/lopata/environment.rb +36 -0
- data/lib/lopata/factory_bot.rb +36 -0
- data/lib/lopata/generators/app.rb +0 -2
- data/lib/lopata/generators/templates/Gemfile +0 -2
- data/lib/lopata/generators/templates/config/environments/qa.yml +0 -1
- data/lib/lopata/observers/backtrace_formatter.rb +103 -0
- data/lib/lopata/observers/base_observer.rb +17 -6
- data/lib/lopata/observers/console_output_observer.rb +63 -26
- data/lib/lopata/observers/web_logger.rb +57 -59
- data/lib/lopata/role.rb +46 -0
- data/lib/lopata/runner.rb +18 -17
- data/lib/lopata/scenario.rb +94 -34
- data/lib/lopata/scenario_builder.rb +62 -73
- data/lib/lopata/shared_step.rb +10 -4
- data/lib/lopata/step.rb +143 -44
- data/lib/lopata/version.rb +1 -1
- data/lib/lopata/world.rb +3 -1
- metadata +10 -10
- data/lib/lopata/config.rb +0 -101
- data/lib/lopata/generators/templates/.rspec +0 -3
- data/lib/lopata/generators/templates/spec/spec_helper.rb +0 -2
- data/lib/lopata/rspec/ar_dsl.rb +0 -38
- data/lib/lopata/rspec/dsl.rb +0 -39
- data/lib/lopata/rspec/role.rb +0 -75
data/lib/lopata/role.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Lopata
|
2
|
+
# @see Lopata::Configuration#role_descriptions
|
3
|
+
# @see Lopata::Configuration#default_role
|
4
|
+
module Role
|
5
|
+
# To be included in Lopata::Scenario
|
6
|
+
module Methods
|
7
|
+
def current_role
|
8
|
+
metadata[:as]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# To be included in Lopata::ScenarioBuilder
|
13
|
+
module DSL
|
14
|
+
def as(*args, &block)
|
15
|
+
@roles = args.flatten
|
16
|
+
@roles << Lopata::ScenarioBuilder::CalculatedValue.new(&block) if block_given?
|
17
|
+
@role_options = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def role_options
|
21
|
+
@role_options ||= build_role_options
|
22
|
+
end
|
23
|
+
|
24
|
+
def without_user
|
25
|
+
@without_user = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_role_options
|
29
|
+
return [] unless roles
|
30
|
+
[Lopata::ScenarioBuilder::Diagonal.new(:as, roles.map { |r| [Lopata.configuration.role_descriptions[r], r] })]
|
31
|
+
end
|
32
|
+
|
33
|
+
def roles
|
34
|
+
return false if @without_user
|
35
|
+
@roles ||= [Lopata.configuration.default_role].compact
|
36
|
+
end
|
37
|
+
|
38
|
+
def diagonals
|
39
|
+
super + role_options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Lopata::Scenario.include Lopata::Role::Methods
|
46
|
+
Lopata::ScenarioBuilder.prepend Lopata::Role::DSL
|
data/lib/lopata/runner.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require_relative 'generators/app'
|
3
|
-
require_relative 'config'
|
4
3
|
require_relative 'world'
|
5
4
|
require_relative 'loader'
|
6
5
|
require_relative '../lopata'
|
@@ -11,18 +10,14 @@ module Lopata
|
|
11
10
|
class Runner < Thor
|
12
11
|
desc 'test', 'Run tests'
|
13
12
|
option :env, default: :qa, aliases: 'e'
|
14
|
-
option :"no-log", type: :boolean, aliases: 'n'
|
15
|
-
option :focus, type: :boolean, aliases: 'f'
|
16
13
|
option :rerun, type: :boolean, aliases: 'r'
|
17
|
-
option :users, type: :array, aliases: 'u'
|
18
|
-
option :build, aliases: 'b'
|
19
14
|
option :keep, type: :boolean, aliases: 'k'
|
20
15
|
option :text, aliases: 't'
|
21
16
|
def test(*args)
|
22
17
|
configure_from_options
|
23
18
|
Lopata::Loader.load_shared_steps
|
24
19
|
Lopata::Loader.load_scenarios(*args)
|
25
|
-
world = Lopata
|
20
|
+
world = Lopata.world
|
26
21
|
world.start
|
27
22
|
world.scenarios.each { |s| s.run }
|
28
23
|
world.finish
|
@@ -38,17 +33,23 @@ module Lopata
|
|
38
33
|
|
39
34
|
no_commands do
|
40
35
|
def configure_from_options
|
41
|
-
Lopata
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
36
|
+
Lopata.configure do |c|
|
37
|
+
c.env = options[:env].to_sym
|
38
|
+
c.keep = options[:keep]
|
39
|
+
c.load_environment
|
40
|
+
c.run_before_start_hooks
|
41
|
+
end
|
42
|
+
add_text_filter(options[:text]) if options[:text]
|
43
|
+
add_rerun_filter if options[:rerun]
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_text_filter(text)
|
47
|
+
Lopata.configuration.filters << -> (scenario) { scenario.title.include?(text) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_rerun_filter
|
51
|
+
to_rerun = Lopata::Client.new.to_rerun
|
52
|
+
Lopata.configuration.filters << -> (scenario) { to_rerun.include?(scenario.title) }
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
data/lib/lopata/scenario.rb
CHANGED
@@ -3,55 +3,115 @@ 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 execution.let_methods.include?(method)
|
25
|
+
instance_exec(*args, &execution.let_methods[method])
|
26
|
+
elsif metadata.keys.include?(method)
|
27
|
+
metadata[method]
|
31
28
|
else
|
32
|
-
|
29
|
+
super
|
33
30
|
end
|
34
31
|
end
|
35
32
|
|
36
|
-
def
|
37
|
-
|
33
|
+
def respond_to_missing?(method, *)
|
34
|
+
execution.let_methods.include?(method) or metadata.keys.include?(method) or super
|
38
35
|
end
|
39
36
|
|
40
|
-
|
41
|
-
status
|
42
|
-
end
|
37
|
+
class Execution
|
38
|
+
attr_reader :scenario, :status, :steps, :title, :current_step
|
43
39
|
|
44
|
-
|
40
|
+
def initialize(title, options_title, metadata = {})
|
41
|
+
@title = [title, options_title].compact.reject(&:empty?).join(' ')
|
42
|
+
@metadata = metadata
|
43
|
+
@let_methods = {}
|
44
|
+
@status = :not_runned
|
45
|
+
@steps = []
|
46
|
+
@scenario = Lopata::Scenario.new(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def run
|
50
|
+
@status = :running
|
51
|
+
sort_steps
|
52
|
+
world.notify_observers(:scenario_started, self)
|
53
|
+
steps.each(&method(:run_step))
|
54
|
+
@status = steps.any?(&:failed?) ? :failed : :passed
|
55
|
+
world.notify_observers(:scenario_finished, self)
|
56
|
+
cleanup
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_step(step)
|
60
|
+
return if step.skipped?
|
61
|
+
@current_step = step
|
62
|
+
step.run(scenario)
|
63
|
+
skip_rest if step.failed? && step.skip_rest_on_failure?
|
64
|
+
@current_step = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def world
|
68
|
+
Lopata.world
|
69
|
+
end
|
45
70
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
71
|
+
def failed?
|
72
|
+
status == :failed
|
73
|
+
end
|
74
|
+
|
75
|
+
def sort_steps
|
76
|
+
@steps = steps.reject(&:teardown_group?) + steps.select(&:teardown_group?)
|
77
|
+
end
|
78
|
+
|
79
|
+
def skip_rest
|
80
|
+
steps.select { |s| s.status == :not_runned && !s.teardown? }.each(&:skip!)
|
81
|
+
end
|
82
|
+
|
83
|
+
def metadata
|
84
|
+
if current_step
|
85
|
+
@metadata.merge(current_step.metadata)
|
49
86
|
else
|
50
|
-
|
87
|
+
@metadata
|
51
88
|
end
|
52
89
|
end
|
53
90
|
|
54
|
-
def
|
55
|
-
|
91
|
+
def let_methods
|
92
|
+
if current_step
|
93
|
+
@let_methods.merge(current_step.let_methods)
|
94
|
+
else
|
95
|
+
@let_methods
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def let(method_name, &block)
|
100
|
+
# define_singleton_method method_name, &block
|
101
|
+
base =
|
102
|
+
if current_step && !current_step.groups.empty?
|
103
|
+
current_step.groups.last.let_methods
|
104
|
+
else
|
105
|
+
@let_methods
|
106
|
+
end
|
107
|
+
base[method_name] = block
|
56
108
|
end
|
57
|
-
|
109
|
+
|
110
|
+
def cleanup
|
111
|
+
@title = nil
|
112
|
+
@metadata = nil
|
113
|
+
@steps = nil
|
114
|
+
@scenario = nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Lopata::ScenarioBuilder
|
2
|
-
attr_reader :title, :common_metadata
|
2
|
+
attr_reader :title, :common_metadata, :options, :diagonals
|
3
|
+
attr_accessor :shared_step, :group
|
3
4
|
|
4
5
|
def self.define(title, metadata = {}, &block)
|
5
6
|
builder = new(title, metadata)
|
@@ -9,47 +10,35 @@ class Lopata::ScenarioBuilder
|
|
9
10
|
|
10
11
|
def initialize(title, metadata = {})
|
11
12
|
@title, @common_metadata = title, metadata
|
13
|
+
@diagonals = []
|
14
|
+
@options = []
|
12
15
|
end
|
13
16
|
|
14
17
|
def build
|
18
|
+
filters = Lopata.configuration.filters
|
15
19
|
option_combinations.each do |option_set|
|
16
20
|
metadata = common_metadata.merge(option_set.metadata)
|
17
|
-
scenario = Lopata::Scenario.new(title, option_set.title, metadata)
|
21
|
+
scenario = Lopata::Scenario::Execution.new(title, option_set.title, metadata)
|
18
22
|
|
19
|
-
|
20
|
-
next
|
21
|
-
step.pre_steps(scenario).each { |s| scenario.steps << s }
|
22
|
-
scenario.steps << step
|
23
|
+
unless filters.empty?
|
24
|
+
next unless filters.all? { |f| f[scenario] }
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
steps_with_hooks.each do |step|
|
28
|
+
next if step.condition && !step.condition.match?(scenario)
|
29
|
+
step.execution_steps(scenario).each { |s| scenario.steps << s }
|
27
30
|
end
|
28
31
|
|
29
32
|
world.scenarios << scenario
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
33
|
-
def as(*args, &block)
|
34
|
-
@roles = args.flatten
|
35
|
-
@roles << CalculatedValue.new(&block) if block_given?
|
36
|
-
@role_options = nil
|
37
|
-
end
|
38
|
-
|
39
|
-
def role_options
|
40
|
-
@role_options ||= build_role_options
|
41
|
-
end
|
42
|
-
|
43
36
|
def metadata(hash)
|
44
37
|
raise 'metadata expected to be a Hash' unless hash.is_a?(Hash)
|
45
38
|
@common_metadata ||= {}
|
46
39
|
@common_metadata.merge! hash
|
47
40
|
end
|
48
41
|
|
49
|
-
def without_user
|
50
|
-
@without_user = true
|
51
|
-
end
|
52
|
-
|
53
42
|
def skip_when(&block)
|
54
43
|
@skip_when = block
|
55
44
|
end
|
@@ -58,82 +47,72 @@ class Lopata::ScenarioBuilder
|
|
58
47
|
@skip_when && @skip_when.call(option_set)
|
59
48
|
end
|
60
49
|
|
61
|
-
%i{ setup action it teardown }.each do |name|
|
50
|
+
%i{ setup action it teardown verify context }.each do |name|
|
62
51
|
name_if = "%s_if" % name
|
63
52
|
name_unless = "%s_unless" % name
|
64
|
-
define_method name, ->(*args, &block) { add_step(name, *args, &block) }
|
65
|
-
define_method name_if, ->(condition, *args,
|
66
|
-
|
53
|
+
define_method name, ->(*args, **metadata, &block) { add_step(name, *args, metadata: metadata, &block) }
|
54
|
+
define_method name_if, ->(condition, *args, **metadata, &block) {
|
55
|
+
add_step(name, *args, metadata: metadata, condition: Lopata::Condition.new(condition), &block)
|
56
|
+
}
|
57
|
+
define_method name_unless, ->(condition, *args, **metadata, &block) {
|
58
|
+
add_step(name, *args, condition: Lopata::Condition.new(condition, positive: false), metadata: metadata, &block)
|
59
|
+
}
|
67
60
|
end
|
68
61
|
|
69
|
-
def add_step(method_name, *args, condition: nil, &block)
|
62
|
+
def add_step(method_name, *args, condition: nil, metadata: {}, &block)
|
70
63
|
step_class =
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
64
|
+
case method_name
|
65
|
+
when /^(setup|action|teardown|verify)/ then Lopata::ActionStep
|
66
|
+
when /^(context)/ then Lopata::GroupStep
|
67
|
+
else Lopata::Step
|
75
68
|
end
|
76
|
-
|
69
|
+
step = step_class.new(method_name, *args, condition: condition, shared_step: shared_step, &block)
|
70
|
+
step.metadata = metadata
|
71
|
+
steps << step
|
77
72
|
end
|
78
73
|
|
79
74
|
def steps
|
80
75
|
@steps ||= []
|
81
76
|
end
|
82
77
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
def add_step_as_is(method_name, *args, &block)
|
88
|
-
steps << Lopata::Step.new(method_name, *args) do
|
89
|
-
# do not convert args - symbols mean name of instance variable
|
90
|
-
# run_step method_name, *args, &block
|
91
|
-
instance_exec(&block) if block
|
78
|
+
def steps_with_hooks
|
79
|
+
s = []
|
80
|
+
unless Lopata.configuration.before_scenario_steps.empty?
|
81
|
+
s << Lopata::ActionStep.new(:setup, *Lopata.configuration.before_scenario_steps)
|
92
82
|
end
|
93
|
-
end
|
94
83
|
|
95
|
-
|
96
|
-
|
97
|
-
|
84
|
+
s += steps
|
85
|
+
|
86
|
+
unless Lopata.configuration.after_scenario_steps.empty?
|
87
|
+
s << Lopata::ActionStep.new(:teardown, *Lopata.configuration.after_scenario_steps)
|
98
88
|
end
|
99
|
-
end
|
100
89
|
|
101
|
-
|
102
|
-
return [] unless roles
|
103
|
-
[Diagonal.new(:as, roles.map { |r| [nil, r] })]
|
90
|
+
s
|
104
91
|
end
|
105
92
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
93
|
+
def let(method_name, &block)
|
94
|
+
steps << Lopata::Step.new(:let) do
|
95
|
+
execution.let(method_name, &block)
|
96
|
+
end
|
109
97
|
end
|
110
98
|
|
111
99
|
def option(metadata_key, variants)
|
112
|
-
options << Option.new(metadata_key, variants)
|
100
|
+
@options << Option.new(metadata_key, variants)
|
113
101
|
end
|
114
102
|
|
115
103
|
def diagonal(metadata_key, variants)
|
116
|
-
diagonals << Diagonal.new(metadata_key, variants)
|
117
|
-
end
|
118
|
-
|
119
|
-
def options
|
120
|
-
@options ||= []
|
121
|
-
end
|
122
|
-
|
123
|
-
def diagonals
|
124
|
-
@diagonals ||= []
|
104
|
+
@diagonals << Diagonal.new(metadata_key, variants)
|
125
105
|
end
|
126
106
|
|
127
107
|
def option_combinations
|
128
|
-
combinations = combine([OptionSet.new], options + diagonals
|
129
|
-
while !
|
130
|
-
combinations << OptionSet.new(*(options + diagonals
|
108
|
+
combinations = combine([OptionSet.new], options + diagonals)
|
109
|
+
while !diagonals.all?(&:complete?)
|
110
|
+
combinations << OptionSet.new(*(options + diagonals).map(&:next_variant))
|
131
111
|
end
|
132
112
|
combinations.reject { |option_set| skip?(option_set) }
|
133
113
|
end
|
134
114
|
|
135
115
|
def combine(source_combinations, rest_options)
|
136
|
-
# raise 'source_combinations cannot be empty' if source_combinations.blank?
|
137
116
|
return source_combinations if rest_options.empty?
|
138
117
|
combinations = []
|
139
118
|
current_option = rest_options.shift
|
@@ -146,7 +125,7 @@ class Lopata::ScenarioBuilder
|
|
146
125
|
end
|
147
126
|
|
148
127
|
def world
|
149
|
-
|
128
|
+
Lopata.world
|
150
129
|
end
|
151
130
|
|
152
131
|
# Набор вариантов, собранный для одного теста
|
@@ -187,10 +166,10 @@ class Lopata::ScenarioBuilder
|
|
187
166
|
end
|
188
167
|
|
189
168
|
class Variant
|
190
|
-
attr_reader :key, :title, :value
|
169
|
+
attr_reader :key, :title, :value, :option
|
191
170
|
|
192
|
-
def initialize(key, title, value)
|
193
|
-
@key, @title, @value = key, title, check_lambda_arity(value)
|
171
|
+
def initialize(option, key, title, value)
|
172
|
+
@option, @key, @title, @value = option, key, title, check_lambda_arity(value)
|
194
173
|
end
|
195
174
|
|
196
175
|
def metadata(option_set)
|
@@ -202,6 +181,10 @@ class Lopata::ScenarioBuilder
|
|
202
181
|
end
|
203
182
|
end
|
204
183
|
|
184
|
+
option.available_metadata_keys.each do |key|
|
185
|
+
data[key] = nil unless data.has_key?(key)
|
186
|
+
end
|
187
|
+
|
205
188
|
data.each do |key, v|
|
206
189
|
data[key] = v.calculate(option_set) if v.is_a? CalculatedValue
|
207
190
|
end
|
@@ -241,14 +224,15 @@ class Lopata::ScenarioBuilder
|
|
241
224
|
end
|
242
225
|
|
243
226
|
class Option
|
244
|
-
attr_reader :variants
|
227
|
+
attr_reader :variants, :key
|
245
228
|
def initialize(key, variants)
|
229
|
+
@key = key
|
246
230
|
@variants =
|
247
231
|
if variants.is_a? Hash
|
248
|
-
variants.map { |title, value| Variant.new(key, title, value) }
|
232
|
+
variants.map { |title, value| Variant.new(self, key, title, value) }
|
249
233
|
else
|
250
234
|
# Array of arrays of two elements
|
251
|
-
variants.map { |v| Variant.new(key, *v) }
|
235
|
+
variants.map { |v| Variant.new(self, key, *v) }
|
252
236
|
end
|
253
237
|
end
|
254
238
|
|
@@ -267,6 +251,11 @@ class Lopata::ScenarioBuilder
|
|
267
251
|
end
|
268
252
|
selected_variant
|
269
253
|
end
|
254
|
+
|
255
|
+
def available_metadata_keys
|
256
|
+
@available_metadata_keys ||= variants
|
257
|
+
.map(&:value).select { |v| v.is_a?(Hash) }.flat_map(&:keys).map { |k| "#{key}_#{k}".to_sym }.uniq
|
258
|
+
end
|
270
259
|
end
|
271
260
|
|
272
261
|
class Diagonal < Option
|