lopata 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,34 @@
1
1
  module Lopata
2
2
  module Observers
3
+ # Lopata allows observe scenarios execution.
4
+ # All the observers are subclasses of Lopata::Observers::BaseObserver.
5
+ #
6
+ # @see Lopata::Observers::ConsoleOutputObserver for implementation example
3
7
  class BaseObserver
8
+ # Called before scenarios execution.
9
+ # All the scenarios are prepared at the moment, so it may be used to get number of scenarios
10
+ # via world.scenarios.count
11
+ #
12
+ # @param world [Lopata::World]
4
13
  def started(world)
5
14
  end
6
15
 
16
+ # Called after all scenarios execution.
17
+ # All the scenarios are finished at the moment, so it may be used for output statistics.
18
+ #
19
+ # @param world [Lopata::World]
7
20
  def finished(world)
8
21
  end
9
22
 
23
+ # Called before single scenario execution.
24
+ # @param world [Lopata::Scenario::Execution]
10
25
  def scenario_started(scenario)
11
26
  end
12
27
 
28
+ # Called after single scenario execution.
29
+ # @param world [Lopata::Scenario::Execution]
13
30
  def scenario_finished(scenario)
14
31
  end
15
-
16
- def step_started(step)
17
- end
18
-
19
- def step_finished(step)
20
- end
21
32
  end
22
33
  end
23
34
  end
@@ -2,26 +2,44 @@ require_relative 'backtrace_formatter'
2
2
 
3
3
  module Lopata
4
4
  module Observers
5
+ # @private
5
6
  class ConsoleOutputObserver < BaseObserver
7
+ extend Forwardable
8
+ # @private
9
+ attr_reader :output
10
+ # @private
11
+ def_delegators :output, :puts, :flush
12
+
13
+ def initialize
14
+ @output = $stdout
15
+ end
16
+
17
+ # @see Lopata::Observers::BaseObserver#finished
6
18
  def finished(world)
7
- total = world.scenarios.length
8
- statuses = world.scenarios.map(&:status)
9
- counts = statuses.uniq.map do |status|
10
- colored("%d %s", status) % [statuses.count { |s| s == status }, status]
19
+ total = statuses.values.inject(0, &:+)
20
+ counts = statuses.map do |status, count|
21
+ colored("%d %s", status) % [count, status]
11
22
  end
12
23
  details = counts.empty? ? "" : "(%s)" % counts.join(', ')
13
24
  puts "#{total} scenario%s %s" % [total == 1 ? '' : 's', details]
14
25
  end
15
26
 
27
+ # @see Lopata::Observers::BaseObserver#scenario_finished
16
28
  def scenario_finished(scenario)
17
29
  message = "#{scenario.title} #{bold(scenario.status.to_s.upcase)}"
18
30
  puts colored(message, scenario.status)
19
- return unless scenario.failed?
20
31
 
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
+ statuses[scenario.status] ||= 0
33
+ statuses[scenario.status] += 1
34
+
35
+ if scenario.failed?
36
+ scenario.steps.each do |step|
37
+ puts colored(" #{status_marker(step.status)} #{step.title}", step.status)
38
+ puts indent(4, backtrace_formatter.error_message(step.exception, include_backtrace: true)) if step.failed?
39
+ end
24
40
  end
41
+
42
+ flush
25
43
  end
26
44
 
27
45
  private
@@ -72,6 +90,10 @@ module Lopata
72
90
  def indent(cols, text)
73
91
  text.split("\n").map { |line| " " * cols + line }.join("\n")
74
92
  end
93
+
94
+ def statuses
95
+ @statuses ||= {}
96
+ end
75
97
  end
76
98
  end
77
99
  end
@@ -4,21 +4,18 @@ require_relative 'backtrace_formatter'
4
4
 
5
5
  module Lopata
6
6
  module Observers
7
+ # @private
7
8
  class WebLogger < BaseObserver
8
9
  def started(world)
9
- raise "Build number is not initailzed in Lopata::Config" unless Lopata::Config.build_number
10
- @client = Lopata::Client.new(Lopata::Config.build_number)
10
+ @client = Lopata::Client.new
11
11
  @client.start(world.scenarios.count)
12
+ @finished = 0
12
13
  end
13
14
 
14
15
  def scenario_finished(scenario)
15
- @client.add_attempt(scenario)
16
+ @finished += 1
17
+ @client.add_attempt(scenario, @finished)
16
18
  end
17
-
18
- # def example_pending(notification)
19
- # example = notification.example
20
- # @client.add_attempt(example, Lopata::PENDING, example.execution_result.pending_message)
21
- # end
22
19
  end
23
20
  end
24
21
 
@@ -27,27 +24,30 @@ module Lopata
27
24
  PENDING = 2
28
25
  SKIPPED = 5
29
26
 
27
+ # @private
30
28
  class Client
31
29
  include HTTParty
32
- base_uri Lopata::Config.lopata_host
33
30
 
34
- attr_accessor :build_number
31
+ attr_reader :url, :project_code, :build_number
35
32
 
36
- def initialize(build_number)
37
- @build_number = build_number
33
+ def initialize
34
+ params = Lopata.configuration.web_logging_params
35
+ raise "Web logging is not initailzed" unless params
36
+ @url = HTTParty.normalize_base_uri(params[:url])
37
+ @project_code = params[:project_code]
38
+ @build_number = params[:build_number]
38
39
  end
39
40
 
40
41
  def start(count)
41
42
  @launch_id = JSON.parse(post("/projects/#{project_code}/builds/#{build_number}/launches.json", body: {total: count}).body)['id']
42
43
  end
43
44
 
44
- def add_attempt(scenario)
45
+ def add_attempt(scenario, finished)
45
46
  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 }
47
+ steps = scenario.steps.map { |s| step_hash(s) }
48
+ request = { status: status, steps: steps, launch: { id: @launch_id, finished: finished } }
48
49
  test = test_id(scenario)
49
50
  post("/tests/#{test}/attempts.json", body: request)
50
- inc_finished
51
51
  end
52
52
 
53
53
  def step_hash(step)
@@ -82,32 +82,25 @@ module Lopata
82
82
 
83
83
  private
84
84
 
85
- def get_json(url)
86
- JSON.parse(self.class.get(url).body)
85
+ def get_json(path)
86
+ JSON.parse(self.class.get(path, base_uri: url).body)
87
87
  end
88
88
 
89
89
  def post(*args)
90
- self.class.post(*args)
90
+ self.class.post(*with_base_uri(args))
91
91
  end
92
92
 
93
93
  def patch(*args)
94
- self.class.patch(*args)
94
+ self.class.patch(*with_base_uri(args))
95
95
  end
96
96
 
97
- def inc_finished
98
- @finished ||= 0
99
- @finished += 1
100
- response = patch("/launches/#{@launch_id}",
101
- body: { finished: @finished }.to_json,
102
- headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' })
103
- if response.code == 404
104
- puts 'Launch has been cancelled. Exit.'
105
- exit!
97
+ def with_base_uri(args = [])
98
+ if args.last.is_a? Hash
99
+ args.last[:base_uri] = url
100
+ else
101
+ args << { base_uri: url }
106
102
  end
107
- end
108
-
109
- def project_code
110
- Lopata::Config.lopata_code
103
+ args
111
104
  end
112
105
 
113
106
  def error_message_for(step)
@@ -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,27 +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
- users: options[:users],
44
- build: options[:build],
45
- env: options[:env],
46
- keep: options[:keep],
47
- }
48
- Lopata::Config.init(options[:env])
49
- 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
50
42
  add_text_filter(options[:text]) if options[:text]
51
43
  add_rerun_filter if options[:rerun]
52
44
  end
53
45
 
54
46
  def add_text_filter(text)
55
- Lopata::Config.filters << -> (scenario) { scenario.title.include?(text) }
47
+ Lopata.configuration.filters << -> (scenario) { scenario.title.include?(text) }
56
48
  end
57
49
 
58
50
  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) }
51
+ to_rerun = Lopata::Client.new.to_rerun
52
+ Lopata.configuration.filters << -> (scenario) { to_rerun.include?(scenario.title) }
62
53
  end
63
54
  end
64
55
  end
@@ -21,7 +21,9 @@ class Lopata::Scenario
21
21
  private
22
22
 
23
23
  def method_missing(method, *args, &block)
24
- if metadata.keys.include?(method)
24
+ if execution.let_methods.include?(method)
25
+ instance_exec(*args, &execution.let_methods[method])
26
+ elsif metadata.keys.include?(method)
25
27
  metadata[method]
26
28
  else
27
29
  super
@@ -29,7 +31,7 @@ class Lopata::Scenario
29
31
  end
30
32
 
31
33
  def respond_to_missing?(method, *)
32
- metadata.keys.include?(method) or super
34
+ execution.let_methods.include?(method) or metadata.keys.include?(method) or super
33
35
  end
34
36
 
35
37
  class Execution
@@ -38,6 +40,7 @@ class Lopata::Scenario
38
40
  def initialize(title, options_title, metadata = {})
39
41
  @title = [title, options_title].compact.reject(&:empty?).join(' ')
40
42
  @metadata = metadata
43
+ @let_methods = {}
41
44
  @status = :not_runned
42
45
  @steps = []
43
46
  @scenario = Lopata::Scenario.new(self)
@@ -45,8 +48,9 @@ class Lopata::Scenario
45
48
 
46
49
  def run
47
50
  @status = :running
51
+ sort_steps
48
52
  world.notify_observers(:scenario_started, self)
49
- steps_in_running_order.each(&method(:run_step))
53
+ steps.each(&method(:run_step))
50
54
  @status = steps.any?(&:failed?) ? :failed : :passed
51
55
  world.notify_observers(:scenario_finished, self)
52
56
  cleanup
@@ -57,18 +61,19 @@ class Lopata::Scenario
57
61
  @current_step = step
58
62
  step.run(scenario)
59
63
  skip_rest if step.failed? && step.skip_rest_on_failure?
64
+ @current_step = nil
60
65
  end
61
66
 
62
67
  def world
63
- @world ||= Lopata::Config.world
68
+ Lopata.world
64
69
  end
65
70
 
66
71
  def failed?
67
72
  status == :failed
68
73
  end
69
74
 
70
- def steps_in_running_order
71
- steps.reject(&:teardown_group?) + steps.select(&:teardown_group?)
75
+ def sort_steps
76
+ @steps = steps.reject(&:teardown_group?) + steps.select(&:teardown_group?)
72
77
  end
73
78
 
74
79
  def skip_rest
@@ -83,6 +88,25 @@ class Lopata::Scenario
83
88
  end
84
89
  end
85
90
 
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
108
+ end
109
+
86
110
  def cleanup
87
111
  @title = nil
88
112
  @metadata = nil
@@ -1,5 +1,5 @@
1
1
  class Lopata::ScenarioBuilder
2
- attr_reader :title, :common_metadata
2
+ attr_reader :title, :common_metadata, :options, :diagonals
3
3
  attr_accessor :shared_step, :group
4
4
 
5
5
  def self.define(title, metadata = {}, &block)
@@ -10,10 +10,12 @@ class Lopata::ScenarioBuilder
10
10
 
11
11
  def initialize(title, metadata = {})
12
12
  @title, @common_metadata = title, metadata
13
+ @diagonals = []
14
+ @options = []
13
15
  end
14
16
 
15
17
  def build
16
- filters = Lopata::Config.filters
18
+ filters = Lopata.configuration.filters
17
19
  option_combinations.each do |option_set|
18
20
  metadata = common_metadata.merge(option_set.metadata)
19
21
  scenario = Lopata::Scenario::Execution.new(title, option_set.title, metadata)
@@ -31,26 +33,12 @@ class Lopata::ScenarioBuilder
31
33
  end
32
34
  end
33
35
 
34
- def as(*args, &block)
35
- @roles = args.flatten
36
- @roles << CalculatedValue.new(&block) if block_given?
37
- @role_options = nil
38
- end
39
-
40
- def role_options
41
- @role_options ||= build_role_options
42
- end
43
-
44
36
  def metadata(hash)
45
37
  raise 'metadata expected to be a Hash' unless hash.is_a?(Hash)
46
38
  @common_metadata ||= {}
47
39
  @common_metadata.merge! hash
48
40
  end
49
41
 
50
- def without_user
51
- @without_user = true
52
- end
53
-
54
42
  def skip_when(&block)
55
43
  @skip_when = block
56
44
  end
@@ -89,73 +77,42 @@ class Lopata::ScenarioBuilder
89
77
 
90
78
  def steps_with_hooks
91
79
  s = []
92
- unless Lopata::Config.before_scenario_steps.empty?
93
- s << Lopata::ActionStep.new(:setup, *Lopata::Config.before_scenario_steps)
80
+ unless Lopata.configuration.before_scenario_steps.empty?
81
+ s << Lopata::ActionStep.new(:setup, *Lopata.configuration.before_scenario_steps)
94
82
  end
95
83
 
96
84
  s += steps
97
85
 
98
- unless Lopata::Config.after_scenario_steps.empty?
99
- s << Lopata::ActionStep.new(:teardown, *Lopata::Config.after_scenario_steps)
86
+ unless Lopata.configuration.after_scenario_steps.empty?
87
+ s << Lopata::ActionStep.new(:teardown, *Lopata.configuration.after_scenario_steps)
100
88
  end
101
89
 
102
90
  s
103
91
  end
104
92
 
105
- def cleanup(*args, &block)
106
- add_step_as_is(:cleanup, *args, &block)
107
- end
108
-
109
- def add_step_as_is(method_name, *args, &block)
110
- steps << Lopata::Step.new(method_name, *args) do
111
- # do not convert args - symbols mean name of instance variable
112
- # run_step method_name, *args, &block
113
- instance_exec(&block) if block
114
- end
115
- end
116
-
117
93
  def let(method_name, &block)
118
- steps << Lopata::Step.new(nil) do
119
- define_singleton_method method_name, &block
94
+ steps << Lopata::Step.new(:let) do
95
+ execution.let(method_name, &block)
120
96
  end
121
97
  end
122
98
 
123
- def build_role_options
124
- return [] unless roles
125
- [Diagonal.new(:as, roles.map { |r| [nil, r] })]
126
- end
127
-
128
- def roles
129
- return false if @without_user
130
- @roles ||= [Lopata::Config.default_role].compact
131
- end
132
-
133
99
  def option(metadata_key, variants)
134
- options << Option.new(metadata_key, variants)
100
+ @options << Option.new(metadata_key, variants)
135
101
  end
136
102
 
137
103
  def diagonal(metadata_key, variants)
138
- diagonals << Diagonal.new(metadata_key, variants)
139
- end
140
-
141
- def options
142
- @options ||= []
143
- end
144
-
145
- def diagonals
146
- @diagonals ||= []
104
+ @diagonals << Diagonal.new(metadata_key, variants)
147
105
  end
148
106
 
149
107
  def option_combinations
150
- combinations = combine([OptionSet.new], options + diagonals + role_options)
151
- while !(diagonals + role_options).all?(&:complete?)
152
- 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))
153
111
  end
154
112
  combinations.reject { |option_set| skip?(option_set) }
155
113
  end
156
114
 
157
115
  def combine(source_combinations, rest_options)
158
- # raise 'source_combinations cannot be empty' if source_combinations.blank?
159
116
  return source_combinations if rest_options.empty?
160
117
  combinations = []
161
118
  current_option = rest_options.shift
@@ -168,7 +125,7 @@ class Lopata::ScenarioBuilder
168
125
  end
169
126
 
170
127
  def world
171
- @world ||= Lopata::Config.world
128
+ Lopata.world
172
129
  end
173
130
 
174
131
  # Набор вариантов, собранный для одного теста