lopata 0.1.3 → 0.1.8
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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/README.md +6 -2
- data/exe/lopata +1 -1
- data/lib/lopata.rb +52 -2
- data/lib/lopata/active_record.rb +105 -5
- data/lib/lopata/condition.rb +2 -1
- data/lib/lopata/configuration.rb +126 -0
- data/lib/lopata/environment.rb +36 -0
- data/lib/lopata/factory_bot.rb +52 -16
- data/lib/lopata/generators/app.rb +2 -2
- data/lib/lopata/generators/templates/Gemfile +0 -2
- data/lib/lopata/generators/templates/config/environments/qa.yml +0 -1
- data/lib/lopata/id.rb +1 -0
- data/lib/lopata/loader.rb +2 -0
- data/lib/lopata/observers.rb +1 -0
- data/lib/lopata/observers/backtrace_formatter.rb +16 -2
- data/lib/lopata/observers/base_observer.rb +17 -6
- data/lib/lopata/observers/console_output_observer.rb +31 -8
- data/lib/lopata/observers/web_logger.rb +30 -33
- data/lib/lopata/role.rb +91 -0
- data/lib/lopata/runner.rb +18 -21
- data/lib/lopata/scenario.rb +57 -7
- data/lib/lopata/scenario_builder.rb +250 -68
- data/lib/lopata/shared_step.rb +2 -0
- data/lib/lopata/step.rb +27 -23
- data/lib/lopata/version.rb +2 -1
- data/lib/lopata/world.rb +8 -12
- metadata +7 -8
- data/lib/lopata/config.rb +0 -97
- data/lib/lopata/generators/templates/.rspec +0 -3
- data/lib/lopata/generators/templates/spec/spec_helper.rb +0 -2
- data/lib/lopata/rspec/dsl.rb +0 -39
- data/lib/lopata/rspec/role.rb +0 -74
@@ -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
|
data/lib/lopata/id.rb
CHANGED
data/lib/lopata/loader.rb
CHANGED
@@ -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
|
data/lib/lopata/observers.rb
CHANGED
@@ -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 =
|
8
|
-
|
9
|
-
|
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.
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
35
|
+
attr_reader :url, :project_code, :build_number
|
35
36
|
|
36
|
-
def initialize
|
37
|
-
|
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.
|
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(
|
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
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
108
|
-
|
109
|
-
def project_code
|
110
|
-
Lopata::Config.lopata_code
|
107
|
+
args
|
111
108
|
end
|
112
109
|
|
113
110
|
def error_message_for(step)
|
data/lib/lopata/role.rb
ADDED
@@ -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
|
data/lib/lopata/runner.rb
CHANGED
@@ -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
|
26
|
-
world.
|
22
|
+
world = Lopata.world
|
23
|
+
world.notify_observers(:started, world)
|
27
24
|
world.scenarios.each { |s| s.run }
|
28
|
-
world.
|
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
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
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
|
60
|
-
|
61
|
-
|
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
|