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.
@@ -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
@@ -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::Config.world
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::Config.ops = {
42
- focus: options[:focus],
43
- rerun: options[:rerun],
44
- users: options[:users],
45
- build: options[:build],
46
- env: options[:env],
47
- keep: options[:keep],
48
- text: options[:text]
49
- }
50
- Lopata::Config.init(options[:env])
51
- Lopata::Config.initialize_test
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
@@ -3,55 +3,115 @@ require 'rspec/expectations'
3
3
  class Lopata::Scenario
4
4
  include RSpec::Matchers
5
5
 
6
- attr_reader :title, :metadata, :steps, :status
6
+ attr_reader :execution
7
7
 
8
- def initialize(title, options_title, metadata = {})
9
- @title = [title, options_title].compact.reject(&:empty?).join(' ')
10
- @metadata = metadata
11
- @steps = []
12
- @status = :not_runned
8
+ def initialize(execution)
9
+ @execution = execution
13
10
  end
14
11
 
15
- def run
16
- @status = :running
17
- world.notify_observers(:scenario_started, self)
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 match_metadata?(metadata_key)
26
- case metadata_key
27
- when Hash
28
- metadata_key.keys.all? { |k| metadata[k] == metadata_key[k] }
29
- when Array
30
- metadata_key.map { |key| metadata[key] }.none?(&:nil?)
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
- metadata[metadata_key]
29
+ super
33
30
  end
34
31
  end
35
32
 
36
- def world
37
- @world ||= Lopata::Config.world
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
- def failed?
41
- status == :failed
42
- end
37
+ class Execution
38
+ attr_reader :scenario, :status, :steps, :title, :current_step
43
39
 
44
- private
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 method_missing(method, *args, &block)
47
- if metadata.keys.include?(method)
48
- metadata[method]
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
- super
87
+ @metadata
51
88
  end
52
89
  end
53
90
 
54
- def respond_to_missing?(method, *)
55
- metadata.keys.include?(method) or super
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
- end
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
- steps.each do |step|
20
- next if step.condition && !step.condition.match?(scenario)
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
- if Lopata::Config.after_scenario
26
- scenario.steps << Lopata::Step.new(:after_scenario, &Lopata::Config.after_scenario)
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, &block) { add_step(name, *args, condition: Lopata::Condition.new(condition), &block) }
66
- define_method name_unless, ->(condition, *args, &block) { add_step(name, *args, condition: Lopata::Condition.new(condition, positive: false), &block) }
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
- if method_name =~ /^(setup|action|teardown)/
72
- Lopata::ActionStep
73
- else
74
- Lopata::Step
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
- steps << step_class.new(method_name, *args, condition: condition, &block)
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 cleanup(*args, &block)
84
- add_step_as_is(:cleanup, *args, &block)
85
- end
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
- def let(method_name, &block)
96
- steps << Lopata::Step.new(nil) do
97
- define_singleton_method method_name, &block
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
- def build_role_options
102
- return [] unless roles
103
- [Diagonal.new(:as, roles.map { |r| [nil, r] })]
90
+ s
104
91
  end
105
92
 
106
- def roles
107
- return false if @without_user
108
- @roles ||= [Lopata::Config.default_role].compact
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 + role_options)
129
- while !(diagonals + role_options).all?(&:complete?)
130
- combinations << OptionSet.new(*(options + diagonals + role_options).map(&:next_variant))
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
- @world ||= Lopata::Config.world
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