lopata 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,27 +1,46 @@
1
1
  require_relative 'backtrace_formatter'
2
+ require 'forwardable'
2
3
 
3
4
  module Lopata
4
5
  module Observers
6
+ # @private
5
7
  class ConsoleOutputObserver < BaseObserver
8
+ extend Forwardable
9
+ # @private
10
+ attr_reader :output
11
+ # @private
12
+ def_delegators :output, :puts, :flush
13
+
14
+ def initialize
15
+ @output = $stdout
16
+ end
17
+
18
+ # @see Lopata::Observers::BaseObserver#finished
6
19
  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]
20
+ total = statuses.values.inject(0, &:+)
21
+ counts = statuses.map do |status, count|
22
+ colored("%d %s", status) % [count, status]
11
23
  end
12
24
  details = counts.empty? ? "" : "(%s)" % counts.join(', ')
13
25
  puts "#{total} scenario%s %s" % [total == 1 ? '' : 's', details]
14
26
  end
15
27
 
28
+ # @see Lopata::Observers::BaseObserver#scenario_finished
16
29
  def scenario_finished(scenario)
17
30
  message = "#{scenario.title} #{bold(scenario.status.to_s.upcase)}"
18
31
  puts colored(message, scenario.status)
19
- return unless scenario.failed?
20
32
 
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?
33
+ statuses[scenario.status] ||= 0
34
+ statuses[scenario.status] += 1
35
+
36
+ if scenario.failed?
37
+ scenario.steps.each do |step|
38
+ puts colored(" #{status_marker(step.status)} #{step.title}", step.status)
39
+ puts indent(4, backtrace_formatter.error_message(step.exception, include_backtrace: true)) if step.failed?
40
+ end
24
41
  end
42
+
43
+ flush
25
44
  end
26
45
 
27
46
  private
@@ -72,6 +91,10 @@ module Lopata
72
91
  def indent(cols, text)
73
92
  text.split("\n").map { |line| " " * cols + line }.join("\n")
74
93
  end
94
+
95
+ def statuses
96
+ @statuses ||= {}
97
+ end
75
98
  end
76
99
  end
77
100
  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