lopata 0.1.5 → 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.
@@ -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
  # Набор вариантов, собранный для одного теста