lopata 0.1.2 → 0.1.7

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,5 +1,7 @@
1
1
  module Lopata
2
+ # @private
2
3
  module Generators
4
+ # @private
3
5
  class App < Thor::Group
4
6
  include Thor::Actions
5
7
  argument :name
@@ -11,10 +13,8 @@ module Lopata
11
13
  def create_root_files
12
14
  template 'Lopatafile', "#{name}/Lopatafile"
13
15
  template 'Gemfile', "#{name}/Gemfile"
14
- template '.rspec', "#{name}/.rspec"
15
16
  template 'config/environments/qa.yml', "#{name}/config/environments/qa.yml"
16
17
  template 'config/initializers/capybara.rb', "#{name}/config/initializers/capybara.rb"
17
- template 'spec/spec_helper.rb', "#{name}/spec/spec_helper.rb"
18
18
  end
19
19
 
20
20
  def init_dirs
@@ -1,7 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
  gem 'selenium-webdriver'
3
- gem 'rspec', :require => 'spec'
4
- gem 'rake'
5
3
  gem 'activerecord', '~> 6.0.2'
6
4
  gem 'capybara', '~> 3.11.1'
7
5
  gem 'thor'
@@ -1,5 +1,4 @@
1
1
  url: http://yourapp.com
2
- name: QA
3
2
  # db:
4
3
  # adapter: postgresql
5
4
  # host: localhost
@@ -1,4 +1,5 @@
1
1
  module Lopata
2
+ # @private
2
3
  module Id
3
4
  extend self
4
5
 
@@ -1,3 +1,4 @@
1
+ # @private
1
2
  module Lopata::Loader
2
3
  extend self
3
4
 
@@ -20,6 +21,7 @@ module Lopata::Loader
20
21
  Dir["scenarios/**/*.rb"].each { |f| load File.expand_path(f) }
21
22
  end
22
23
 
24
+ # Loads all shared steps from predefined paths
23
25
  def load_shared_steps
24
26
  Dir["shared_steps/**/*rb"].each { |f| load File.expand_path(f) }
25
27
  end
@@ -1,3 +1,4 @@
1
+ # @private
1
2
  module Lopata::Observers
2
3
  autoload :BaseObserver, 'lopata/observers/base_observer.rb'
3
4
  autoload :ConsoleOutputObserver, 'lopata/observers/console_output_observer.rb'
@@ -1,11 +1,15 @@
1
1
  module Lopata
2
2
  module Observers
3
+ # @private
3
4
  # Based on RSpec::Core::BacktraceFormatter
5
+ #
6
+ # Provides ability to format backtrace and find source code by file and line number.
4
7
  class BacktraceFormatter
8
+ # @private
5
9
  attr_accessor :exclusion_patterns, :inclusion_patterns
6
10
 
7
11
  def initialize
8
- patterns = %w[ /lib\d*/ruby/ bin/ exe/lopata /lib/bundler/ /exe/bundle: ]
12
+ patterns = %w[ /lib\d*/ruby/ bin/ exe/lopata /lib/bundler/ /exe/bundle /\.rvm/ ]
9
13
  patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }
10
14
 
11
15
  @exclusion_patterns = [Regexp.union(*patterns)]
@@ -14,6 +18,10 @@ module Lopata
14
18
  inclusion_patterns << Regexp.new(Dir.getwd)
15
19
  end
16
20
 
21
+ # Filter backtrace.
22
+ #
23
+ # @param backtrace [Array<String>] exception backtrace
24
+ # @return [Array<String>] backtrace lines except ruby libraries, gems and executable files.
17
25
  def format(backtrace)
18
26
  return [] unless backtrace
19
27
  return backtrace if backtrace.empty?
@@ -28,6 +36,12 @@ module Lopata
28
36
  end
29
37
  end
30
38
 
39
+
40
+ # Extracts error message from excetion
41
+ #
42
+ # @param exception [Exception]
43
+ # @param include_backtrace [Boolean] whether to add formatted backtrace to output
44
+ # @return [String] error message from excetion, incuding source code line.
31
45
  def error_message(exception, include_backtrace: false)
32
46
  backtrace = format(exception.backtrace)
33
47
  source_line = extract_source_line(backtrace.first)
@@ -86,4 +100,4 @@ module Lopata
86
100
  end
87
101
  end
88
102
  end
89
- end
103
+ end
@@ -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 scenario [Lopata::Scenario::Execution]
10
25
  def scenario_started(scenario)
11
26
  end
12
27
 
28
+ # Called after single scenario execution.
29
+ # @param scenario [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,50 +4,54 @@ 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
 
22
+ # @private
25
23
  PASSED = 0
24
+ # @private
26
25
  FAILED = 1
26
+ # @private
27
27
  PENDING = 2
28
+ # @private
28
29
  SKIPPED = 5
29
30
 
31
+ # @private
30
32
  class Client
31
33
  include HTTParty
32
- base_uri Lopata::Config.lopata_host
33
34
 
34
- attr_accessor :build_number
35
+ attr_reader :url, :project_code, :build_number
35
36
 
36
- def initialize(build_number)
37
- @build_number = build_number
37
+ def initialize
38
+ params = Lopata.configuration.web_logging_params
39
+ raise "Web logging is not initailzed" unless params
40
+ @url = HTTParty.normalize_base_uri(params[:url])
41
+ @project_code = params[:project_code]
42
+ @build_number = params[:build_number]
38
43
  end
39
44
 
40
45
  def start(count)
41
46
  @launch_id = JSON.parse(post("/projects/#{project_code}/builds/#{build_number}/launches.json", body: {total: count}).body)['id']
42
47
  end
43
48
 
44
- def add_attempt(scenario)
49
+ def add_attempt(scenario, finished)
45
50
  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 }
51
+ steps = scenario.steps.map { |s| step_hash(s) }
52
+ request = { status: status, steps: steps, launch: { id: @launch_id, finished: finished } }
48
53
  test = test_id(scenario)
49
54
  post("/tests/#{test}/attempts.json", body: request)
50
- inc_finished
51
55
  end
52
56
 
53
57
  def step_hash(step)
@@ -82,32 +86,25 @@ module Lopata
82
86
 
83
87
  private
84
88
 
85
- def get_json(url)
86
- JSON.parse(self.class.get(url).body)
89
+ def get_json(path)
90
+ JSON.parse(self.class.get(path, base_uri: url).body)
87
91
  end
88
92
 
89
93
  def post(*args)
90
- self.class.post(*args)
94
+ self.class.post(*with_base_uri(args))
91
95
  end
92
96
 
93
97
  def patch(*args)
94
- self.class.patch(*args)
98
+ self.class.patch(*with_base_uri(args))
95
99
  end
96
100
 
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!
101
+ def with_base_uri(args = [])
102
+ if args.last.is_a? Hash
103
+ args.last[:base_uri] = url
104
+ else
105
+ args << { base_uri: url }
106
106
  end
107
- end
108
-
109
- def project_code
110
- Lopata::Config.lopata_code
107
+ args
111
108
  end
112
109
 
113
110
  def error_message_for(step)
@@ -0,0 +1,91 @@
1
+ module Lopata
2
+ # Adds ability to run scenarios by given user roles
3
+ #
4
+ # @example Usage
5
+ # require 'lopata/role'
6
+ # Lopata.configure do |c|
7
+ # c.role_descriptions = {
8
+ # user: 'User',
9
+ # admin: 'Admin'
10
+ # }
11
+ # c.default_role = :user
12
+ # c.before_scenaro 'setup user'
13
+ # end
14
+ # Lopata.shared_step 'setup user' do
15
+ # setup { @user = User.create(role: current_role) if current_role }
16
+ # cleanup :user
17
+ # end
18
+ # Lopata.define 'login' do
19
+ # # will generate 2 scenarios, one for :user and one for :admin
20
+ # as :user, :admin
21
+ # action 'login'
22
+ # # verify the user is logged in
23
+ # end
24
+ #
25
+ # @see Lopata::Configuration#role_descriptions
26
+ # @see Lopata::Configuration#default_role
27
+ module Role
28
+ # To be included in Lopata::Scenario
29
+ module Methods
30
+ # @return [Symbol, nil] user role for current scenario or nil, if scenario is running without user.
31
+ def current_role
32
+ metadata[:as]
33
+ end
34
+ end
35
+
36
+ # To be included in Lopata::ScenarioBuilder
37
+ module DSL
38
+ # Enumerate the roles the scenario to be runned under.
39
+ # If not invoked the default role only will be used.
40
+ #
41
+ # The scenario should be set to use the role via before_scenario step using #current_role param.
42
+ #
43
+ # @param args [Array<Symbol>] list of roles the scenario to be runned with.
44
+ # @param block [Block] the block to calculate role from scenario metadata.
45
+ def as(*args, &block)
46
+ @roles = args.flatten
47
+ @roles << Lopata::ScenarioBuilder::CalculatedValue.new(&block) if block_given?
48
+ @role_options = nil
49
+ end
50
+
51
+ # @private
52
+ def role_options
53
+ @role_options ||= build_role_options
54
+ end
55
+
56
+ # Marks scenario to be runned without user.
57
+ #
58
+ # @example
59
+ # Lopata.define 'scenario withou user' do
60
+ # without_user
61
+ # it 'does not define the user' do
62
+ # expect(current_role).to be_nil
63
+ # end
64
+ # end
65
+ def without_user
66
+ @without_user = true
67
+ end
68
+
69
+ # @private
70
+ def build_role_options
71
+ return [] unless roles
72
+ [Lopata::ScenarioBuilder::Diagonal.new(:as, roles.map { |r| [Lopata.configuration.role_descriptions[r], r] })]
73
+ end
74
+
75
+ # @private
76
+ def roles
77
+ return false if @without_user
78
+ @roles ||= [Lopata.configuration.default_role].compact
79
+ end
80
+
81
+ # @private
82
+ def diagonals
83
+ super + role_options
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ Lopata::Scenario.include Lopata::Role::Methods
90
+ # Prepend the module to overload #diagonals method
91
+ 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'
@@ -8,24 +7,22 @@ require_relative 'observers'
8
7
  require_relative 'condition'
9
8
 
10
9
  module Lopata
10
+ # @private
11
11
  class Runner < Thor
12
12
  desc 'test', 'Run tests'
13
13
  option :env, default: :qa, aliases: 'e'
14
- option :"no-log", type: :boolean, aliases: 'n'
15
- option :focus, type: :boolean, aliases: 'f'
16
14
  option :rerun, type: :boolean, aliases: 'r'
17
- option :users, type: :array, aliases: 'u'
18
- option :build, aliases: 'b'
19
15
  option :keep, type: :boolean, aliases: 'k'
20
16
  option :text, aliases: 't'
21
17
  def test(*args)
18
+ trap_interrupt
22
19
  configure_from_options
23
20
  Lopata::Loader.load_shared_steps
24
21
  Lopata::Loader.load_scenarios(*args)
25
- world = Lopata::Config.world
26
- world.start
22
+ world = Lopata.world
23
+ world.notify_observers(:started, world)
27
24
  world.scenarios.each { |s| s.run }
28
- world.finish
25
+ world.notify_observers(:finished, world)
29
26
  end
30
27
 
31
28
  default_task :test
@@ -38,27 +35,27 @@ module Lopata
38
35
 
39
36
  no_commands do
40
37
  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
38
+ Lopata.configure do |c|
39
+ c.env = options[:env].to_sym
40
+ c.keep = options[:keep]
41
+ c.load_environment
42
+ c.run_before_start_hooks
43
+ end
50
44
  add_text_filter(options[:text]) if options[:text]
51
45
  add_rerun_filter if options[:rerun]
52
46
  end
53
47
 
54
48
  def add_text_filter(text)
55
- Lopata::Config.filters << -> (scenario) { scenario.title.include?(text) }
49
+ Lopata.configuration.filters << -> (scenario) { scenario.title.include?(text) }
56
50
  end
57
51
 
58
52
  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) }
53
+ to_rerun = Lopata::Client.new.to_rerun
54
+ Lopata.configuration.filters << -> (scenario) { to_rerun.include?(scenario.title) }
55
+ end
56
+
57
+ def trap_interrupt
58
+ trap('INT') { exit!(1) }
62
59
  end
63
60
  end
64
61
  end