moto 0.0.33 → 0.0.40
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cli.rb +5 -12
- data/lib/clients/website.rb +4 -4
- data/lib/forward_context_methods.rb +23 -20
- data/lib/parser.rb +21 -20
- data/lib/reporting/listeners/console.rb +3 -3
- data/lib/reporting/listeners/console_dots.rb +2 -2
- data/lib/reporting/test_reporter.rb +1 -1
- data/lib/runner/test_generator.rb +165 -0
- data/lib/runner/test_provider.rb +69 -0
- data/lib/runner/test_runner.rb +65 -0
- data/lib/runner/thread_context.rb +146 -0
- data/lib/test/base.rb +12 -9
- data/lib/test_logging.rb +2 -2
- data/lib/version.rb +1 -1
- metadata +6 -6
- data/lib/runner.rb +0 -62
- data/lib/test_generator.rb +0 -112
- data/lib/thread_context.rb +0 -162
- data/lib/thread_pool.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b5492188f2eeb8cc2270194377f7da621456203
|
4
|
+
data.tar.gz: 95f4afebe273fdcf4a6a5482457f0193aee3dc6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f0d02d6cffea6babcb5126ae4e0c538a8600fce9617e6566a2709dbd582e3de82b54eeb47f414166aa19fce854022966a2b36c703071df84861b061e9052bcb
|
7
|
+
data.tar.gz: 4d636279703d273404c25ca98c6c83a74676da594daf0d33ee6fc87854a743743cc5cc5a935b3fe8519dec776046527ffcb114f1d0acb44ae4192694dbc282d5
|
data/lib/cli.rb
CHANGED
@@ -16,9 +16,9 @@ require_relative './empty_listener'
|
|
16
16
|
require_relative './forward_context_methods'
|
17
17
|
require_relative './test_logging'
|
18
18
|
require_relative './runner_logging'
|
19
|
-
require_relative './runner'
|
20
|
-
require_relative './thread_context'
|
21
|
-
require_relative './
|
19
|
+
require_relative './runner/test_runner'
|
20
|
+
require_relative './runner/thread_context'
|
21
|
+
require_relative './runner/test_generator'
|
22
22
|
require_relative './test/base'
|
23
23
|
require_relative './page'
|
24
24
|
require_relative './version'
|
@@ -28,7 +28,6 @@ require_relative './reporting/listeners/console'
|
|
28
28
|
require_relative './reporting/listeners/console_dots'
|
29
29
|
require_relative './reporting/listeners/junit_xml'
|
30
30
|
require_relative './reporting/listeners/webui'
|
31
|
-
require_relative './test_generator'
|
32
31
|
require_relative './exceptions/moto'
|
33
32
|
require_relative './exceptions/test_skipped'
|
34
33
|
require_relative './exceptions/test_forced_failure'
|
@@ -39,7 +38,6 @@ module Moto
|
|
39
38
|
class Cli
|
40
39
|
def self.run(argv)
|
41
40
|
test_paths_absolute = []
|
42
|
-
test_classes = []
|
43
41
|
|
44
42
|
unless argv[ :tests ].nil?
|
45
43
|
argv[ :tests ].each do |dir_name|
|
@@ -76,14 +74,9 @@ module Moto
|
|
76
74
|
initializer.init
|
77
75
|
end
|
78
76
|
|
79
|
-
|
80
|
-
test_paths_absolute.each do |test_path|
|
81
|
-
test_classes << tg.generate(test_path)
|
82
|
-
end
|
83
|
-
|
84
|
-
@test_reporter = Moto::Reporting::TestReporter.new(argv[:listeners], argv[:config], argv[:name])
|
77
|
+
test_reporter = Moto::Reporting::TestReporter.new(argv[:listeners], argv[:config], argv[:name])
|
85
78
|
|
86
|
-
runner = Moto::Runner.new(
|
79
|
+
runner = Moto::Runner::TestRunner.new(test_paths_absolute, argv[:environments], argv[:config], test_reporter)
|
87
80
|
runner.run
|
88
81
|
end
|
89
82
|
|
data/lib/clients/website.rb
CHANGED
@@ -17,11 +17,11 @@ module Moto
|
|
17
17
|
|
18
18
|
def start_run
|
19
19
|
# TODO: make session driver configurable
|
20
|
-
if context.
|
21
|
-
Capybara.default_selector = context.
|
20
|
+
if context.moto_app_config[:capybara][:default_selector]
|
21
|
+
Capybara.default_selector = context.moto_app_config[:capybara][:default_selector]
|
22
22
|
end
|
23
23
|
|
24
|
-
Thread.current['capybara_session'] = Capybara::Session.new(context.
|
24
|
+
Thread.current['capybara_session'] = Capybara::Session.new(context.moto_app_config[:capybara][:default_driver])
|
25
25
|
@pages = {}
|
26
26
|
end
|
27
27
|
|
@@ -53,7 +53,7 @@ module Moto
|
|
53
53
|
private
|
54
54
|
|
55
55
|
def register_grid_driver
|
56
|
-
grid_config = context.
|
56
|
+
grid_config = context.moto_app_config[:capybara][:grid]
|
57
57
|
return if grid_config.nil?
|
58
58
|
if grid_config[:capabilities].nil?
|
59
59
|
capabilities = Selenium::WebDriver::Remote::Capabilities.firefox
|
@@ -1,21 +1,24 @@
|
|
1
|
-
module Moto
|
2
|
-
module ForwardContextMethods
|
3
|
-
|
4
|
-
def client(name)
|
5
|
-
@context.client(name)
|
6
|
-
end
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
1
|
+
module Moto
|
2
|
+
module ForwardContextMethods
|
3
|
+
# Access client object instance for given class name.
|
4
|
+
def client(name)
|
5
|
+
@context.client(name)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Write message to test execution log file. See Ruby Logger class for details.
|
9
|
+
def logger
|
10
|
+
@context.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
# Read const value specific for current environment from `config/const.yml` file
|
14
|
+
def const(key)
|
15
|
+
@context.const(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Access currently executed test
|
19
|
+
def test
|
20
|
+
@context.test
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
21
24
|
end
|
data/lib/parser.rb
CHANGED
@@ -7,7 +7,7 @@ require_relative '../lib/app_generator'
|
|
7
7
|
module Moto
|
8
8
|
|
9
9
|
class Parser
|
10
|
-
|
10
|
+
|
11
11
|
def self.run(argv)
|
12
12
|
begin
|
13
13
|
|
@@ -26,7 +26,7 @@ module Moto
|
|
26
26
|
puts e.backtrace.join("\n")
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def self.run_parse(argv)
|
31
31
|
|
32
32
|
# TODO: fix this dumb verification of current working directory.
|
@@ -34,11 +34,11 @@ module Moto
|
|
34
34
|
msg = "Config file (config/moto.rb) not present.\n"
|
35
35
|
msg << 'Does current working directory contain Moto application?'
|
36
36
|
raise msg
|
37
|
-
end
|
37
|
+
end
|
38
38
|
|
39
39
|
require 'bundler/setup'
|
40
40
|
Bundler.require
|
41
|
-
|
41
|
+
|
42
42
|
# Default options
|
43
43
|
options = {}
|
44
44
|
options[:listeners] = []
|
@@ -46,33 +46,33 @@ module Moto
|
|
46
46
|
options[:config] = eval(File.read("#{MotoApp::DIR}/config/moto.rb"))
|
47
47
|
options[:environments] = []
|
48
48
|
options[:name] = ''
|
49
|
-
|
49
|
+
|
50
50
|
# Parse arguments
|
51
51
|
# TODO eval ?
|
52
52
|
# TODO const
|
53
|
-
# TODO
|
53
|
+
# TODO listeners should be consts - not strings
|
54
54
|
OptionParser.new do |opts|
|
55
|
-
opts.on('-t', '--tests Tests', Array)
|
56
|
-
opts.on('-g', '--tags Tags', Array)
|
57
|
-
opts.on('-l', '--listeners Listeners', Array)
|
55
|
+
opts.on('-t', '--tests Tests', Array) { |v| options[:tests ] = v }
|
56
|
+
opts.on('-g', '--tags Tags', Array) { |v| options[:tags ] = v }
|
57
|
+
opts.on('-l', '--listeners Listeners', Array) { |v| options[:listeners] = v }
|
58
58
|
opts.on('-e', '--environments Environment', Array) { |v| options[:environments] = v }
|
59
|
-
opts.on('-c', '--const Const')
|
60
|
-
opts.on('-n', '--name Name')
|
61
|
-
opts.on('-f', '--config Config')
|
59
|
+
opts.on('-c', '--const Const') { |v| options[:const] = v }
|
60
|
+
opts.on('-n', '--name Name') { |v| options[:name] = v }
|
61
|
+
opts.on('-f', '--config Config') { |v| options[:config].deep_merge!( eval( File.read(v) ) ) }
|
62
62
|
end.parse!
|
63
63
|
|
64
64
|
if options[:name].empty?
|
65
65
|
options[:name] = evaluate_name(options[:tags], options[:tests])
|
66
66
|
end
|
67
67
|
|
68
|
-
if options[ :config ][ :moto ][ :
|
68
|
+
if options[ :config ][ :moto ][ :test_runner ][ :mandatory_environment ] && options[ :environments ].empty?
|
69
69
|
puts 'Environment is mandatory for this project.'
|
70
70
|
exit 1
|
71
71
|
end
|
72
72
|
|
73
73
|
return options
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
def self.evaluate_name(tags, tests)
|
77
77
|
tags ||= ''
|
78
78
|
tests ||= ''
|
@@ -104,19 +104,20 @@ module Moto
|
|
104
104
|
|
105
105
|
return options
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
def self.show_help
|
109
109
|
puts """
|
110
110
|
Moto (#{Moto::VERSION}) CLI Help:
|
111
111
|
moto --version Display current version
|
112
112
|
|
113
113
|
moto run:
|
114
|
-
-t, --tests
|
115
|
-
|
116
|
-
-
|
117
|
-
|
114
|
+
-t, --tests = Tests to be executed.
|
115
|
+
For eg. Tests\Failure\Failure.rb should be passed as Tests::Failure
|
116
|
+
-l, --listeners = Reporters to be used.
|
117
|
+
Defaults are Moto::Listeners::ConsoleDots, Moto::Listeners::JunitXml
|
118
118
|
-e, --environment etc etc
|
119
119
|
|
120
|
+
|
120
121
|
moto generate:
|
121
122
|
-t, --test = Path and name of the test to be created.
|
122
123
|
Examples:
|
@@ -133,6 +134,6 @@ module Moto
|
|
133
134
|
You have been warned.
|
134
135
|
"""
|
135
136
|
end
|
136
|
-
|
137
|
+
|
137
138
|
end
|
138
139
|
end
|
@@ -4,7 +4,7 @@ module Moto
|
|
4
4
|
class Console < Base
|
5
5
|
|
6
6
|
def start_run
|
7
|
-
|
7
|
+
# puts 'START'
|
8
8
|
end
|
9
9
|
|
10
10
|
def end_run(run_status)
|
@@ -18,11 +18,11 @@ module Moto
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def start_test(test_status)
|
21
|
-
|
21
|
+
# puts test_status.name
|
22
22
|
end
|
23
23
|
|
24
24
|
def end_test(test_status)
|
25
|
-
puts "\t#{test_status.
|
25
|
+
puts "\n#{test_status.name}\n\t#{test_status.results.last.message}"
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
@@ -28,7 +28,7 @@ module Moto
|
|
28
28
|
puts 'ERRORS: '
|
29
29
|
run_status.tests_error.each do |test_status|
|
30
30
|
puts test_status.name
|
31
|
-
puts test_status.results.last.message
|
31
|
+
puts "\t" + test_status.results.last.message
|
32
32
|
puts ''
|
33
33
|
end
|
34
34
|
end
|
@@ -38,7 +38,7 @@ module Moto
|
|
38
38
|
puts 'SKIPPED: '
|
39
39
|
run_status.tests_skipped.each do |test_status|
|
40
40
|
puts test_status.name
|
41
|
-
puts test_status.results.last.message
|
41
|
+
puts "\t" + test_status.results.last.message
|
42
42
|
puts ''
|
43
43
|
end
|
44
44
|
end
|
@@ -12,7 +12,7 @@ module Moto
|
|
12
12
|
def initialize(listeners, config, custom_run_name)
|
13
13
|
|
14
14
|
if listeners.empty?
|
15
|
-
config[:moto][:
|
15
|
+
config[:moto][:test_runner][:default_listeners].each do |listener_class_name|
|
16
16
|
listeners << listener_class_name
|
17
17
|
end
|
18
18
|
else
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module MotoApp
|
2
|
+
module Tests
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
module Moto
|
7
|
+
module Runner
|
8
|
+
class TestGenerator
|
9
|
+
|
10
|
+
def initialize(environments)
|
11
|
+
@internal_counter = 0
|
12
|
+
@environments = environments
|
13
|
+
end
|
14
|
+
|
15
|
+
# Method returns an array of test instances that represent all variants (parameter sets from test's config).
|
16
|
+
# Example: A test with no config file will be returned as an array with single Moto::Test::Base in it.
|
17
|
+
# Example: A test with a config file and 2 sets of parameters there will be returned as array with two elements.
|
18
|
+
#
|
19
|
+
# @param [String] test_path_absolute Path to the test that is about to be instantiated.
|
20
|
+
# @return [Array] An array of [Moto::Test::Base] decendants
|
21
|
+
# each entry is a Test with set of parameters injected
|
22
|
+
def get_test_with_variants(test_path_absolute)
|
23
|
+
test_path_absolute ? variantize(test_path_absolute) : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Converts test's path to an array of Moto::Base::Test instances that represent all test variants (params, envs)
|
27
|
+
#
|
28
|
+
# *IMPORTANT*
|
29
|
+
# Config files with ruby code will be evaluated thus if you use any classes in them
|
30
|
+
# they must be required prior to that. That might be done in overloaded app's initializer.
|
31
|
+
#
|
32
|
+
# @param [String] test_path_absolute Path to the file with test
|
33
|
+
# @return [Array] array of already initialized test's variants
|
34
|
+
def variantize(test_path_absolute)
|
35
|
+
variants = []
|
36
|
+
|
37
|
+
@environments.each do |env|
|
38
|
+
params_path = test_path_absolute.sub(/\.rb\z/, '')
|
39
|
+
|
40
|
+
if File.exists?(params_path)
|
41
|
+
begin
|
42
|
+
params_all = eval(File.read(params_path))
|
43
|
+
rescue Exception => e
|
44
|
+
puts "Parameters error:\n\t File: #{params_path}\n\t Error: #{e.message}\n\n"
|
45
|
+
end
|
46
|
+
else
|
47
|
+
params_all = [{}]
|
48
|
+
end
|
49
|
+
|
50
|
+
params_all.each_with_index do |params, params_index|
|
51
|
+
|
52
|
+
# Filtering out param sets that are specific to certain envs
|
53
|
+
unless params['__env'].nil?
|
54
|
+
allowed_envs = params['__env'].is_a?(String) ? [params['__env']] : params['__env']
|
55
|
+
next unless allowed_envs.include? env
|
56
|
+
end
|
57
|
+
|
58
|
+
test = generate(test_path_absolute)
|
59
|
+
test.init(env, params, params_index, @internal_counter)
|
60
|
+
test.log_path = "#{test.dir}/logs/#{test.name.gsub(/[^0-9A-Za-z.\-]/, '_')}.log"
|
61
|
+
@internal_counter += 1
|
62
|
+
variants << test
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
variants
|
67
|
+
end
|
68
|
+
private :variantize
|
69
|
+
|
70
|
+
# assuming that target file includes only content of method 'run' and some magic comments
|
71
|
+
def generate(test_path_absolute)
|
72
|
+
method_body = File.read(test_path_absolute) + "\n"
|
73
|
+
|
74
|
+
full_code = !!method_body.match(/^#\s*FULL_CODE\s+/)
|
75
|
+
|
76
|
+
if full_code
|
77
|
+
generate_for_full_class_code(test_path_absolute)
|
78
|
+
else
|
79
|
+
generate_for_run_body(test_path_absolute, method_body)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private :generate
|
83
|
+
|
84
|
+
# Generates test instances, based on fully defined class file
|
85
|
+
# @return [Moto::Test::Base]
|
86
|
+
def generate_for_full_class_code(test_path_absolute)
|
87
|
+
require test_path_absolute
|
88
|
+
|
89
|
+
test_object = nil
|
90
|
+
|
91
|
+
# Checking if it's possible to create test based on provided path. In case something is wrong with
|
92
|
+
# modules structure in class itself Moto::Test::Base will be instantized with raise injected into its run()
|
93
|
+
# so we can have proper reporting and summary even if the test doesn't execute.
|
94
|
+
begin
|
95
|
+
class_name = test_path_absolute.gsub("#{MotoApp::DIR}/", 'moto_app/').camelize.chomp('.rb').constantize
|
96
|
+
test_object = class_name.new
|
97
|
+
rescue NameError
|
98
|
+
class_name = Moto::Test::Base
|
99
|
+
test_object = class_name.new
|
100
|
+
|
101
|
+
class << test_object
|
102
|
+
attr_accessor :custom_name
|
103
|
+
|
104
|
+
def run
|
105
|
+
raise "ERROR: Invalid module structure: #{custom_name}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
test_object.custom_name = test_path_absolute.gsub("#{MotoApp::DIR}/", 'moto_app/').camelize.chomp('.rb')
|
110
|
+
end
|
111
|
+
|
112
|
+
test_object.static_path = test_path_absolute
|
113
|
+
test_object.evaled = false
|
114
|
+
test_object
|
115
|
+
end
|
116
|
+
private :generate_for_full_class_code
|
117
|
+
|
118
|
+
def create_module_tree(root_module, next_modules)
|
119
|
+
return root_module if next_modules.empty?
|
120
|
+
next_module_name = next_modules.shift
|
121
|
+
if root_module.const_defined?(next_module_name.to_sym)
|
122
|
+
m = root_module.const_get(next_module_name.to_sym)
|
123
|
+
else
|
124
|
+
m = Module.new
|
125
|
+
root_module.const_set(next_module_name.to_sym, m)
|
126
|
+
end
|
127
|
+
create_module_tree(m, next_modules)
|
128
|
+
end
|
129
|
+
private :create_module_tree
|
130
|
+
|
131
|
+
# Generates test instances, based on a text file with ruby code that has to be injected into run() method
|
132
|
+
# @return [Moto::Test::Base]
|
133
|
+
def generate_for_run_body(test_path_absolute, method_body)
|
134
|
+
base = Moto::Test::Base
|
135
|
+
base_class_string = method_body.match(/^#\s*BASE_CLASS:\s(\S+)/)
|
136
|
+
unless base_class_string.nil?
|
137
|
+
base_class_string = base_class_string[1].strip
|
138
|
+
|
139
|
+
a = base_class_string.underscore.split('/')
|
140
|
+
base_test_path = a[1..-1].join('/')
|
141
|
+
|
142
|
+
require "#{MotoApp::DIR}/#{base_test_path}"
|
143
|
+
base = base_class_string.constantize
|
144
|
+
end
|
145
|
+
|
146
|
+
# MotoApp::Tests::Login::Short
|
147
|
+
consts = test_path_absolute.camelize.split('Tests::')[1].split('::')
|
148
|
+
consts.pop
|
149
|
+
class_name = consts.pop
|
150
|
+
|
151
|
+
m = create_module_tree(MotoApp::Tests, consts)
|
152
|
+
cls = Class.new(base)
|
153
|
+
m.const_set(class_name.to_sym, cls)
|
154
|
+
|
155
|
+
test_object = cls.new
|
156
|
+
test_object.instance_eval("def run\n #{method_body} \n end")
|
157
|
+
test_object.static_path = test_path_absolute
|
158
|
+
test_object.evaled = true
|
159
|
+
test_object
|
160
|
+
end
|
161
|
+
private :generate_for_run_body
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require_relative 'test_generator'
|
3
|
+
|
4
|
+
module Moto
|
5
|
+
module Runner
|
6
|
+
# Thread safe provider of test instances
|
7
|
+
class TestProvider
|
8
|
+
|
9
|
+
# @param [Array] test_paths_absolute
|
10
|
+
# @param [Array] environments Array
|
11
|
+
# @param [Integer] test_repeats
|
12
|
+
def initialize(test_paths_absolute, environments, test_repeats)
|
13
|
+
super()
|
14
|
+
@test_repeats = test_repeats
|
15
|
+
@current_test_repeat = 1
|
16
|
+
@queue = Queue.new
|
17
|
+
@test_paths_absolute = test_paths_absolute
|
18
|
+
@test_generator = TestGenerator.new(environments)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use this to retrieve tests safely in multithreaded environment
|
22
|
+
def get_test
|
23
|
+
create_tests
|
24
|
+
@queue.pop
|
25
|
+
end
|
26
|
+
|
27
|
+
# Pushes new tests to the queue if possible and the queue is already empty
|
28
|
+
def create_tests
|
29
|
+
if @queue.empty?
|
30
|
+
|
31
|
+
test_variants = @test_generator.get_test_with_variants(get_test_path)
|
32
|
+
|
33
|
+
if test_variants
|
34
|
+
test_variants.each do |test|
|
35
|
+
@queue.push(test)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
private :create_tests
|
42
|
+
|
43
|
+
# Returns path to the test while supporting the number of repeats specified by the user
|
44
|
+
# return [String] Path to the test
|
45
|
+
def get_test_path
|
46
|
+
|
47
|
+
if @current_test_repeat == 1
|
48
|
+
@test_path = @test_paths_absolute.shift
|
49
|
+
end
|
50
|
+
|
51
|
+
if @current_test_repeat == @test_repeats
|
52
|
+
@current_test_repeat = 1
|
53
|
+
else
|
54
|
+
@current_test_repeat += 1
|
55
|
+
end
|
56
|
+
|
57
|
+
@test_path
|
58
|
+
end
|
59
|
+
private :get_test_path
|
60
|
+
|
61
|
+
# Number of threads waiting for a job
|
62
|
+
def num_waiting
|
63
|
+
@queue.num_waiting
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../reporting/test_reporter'
|
2
|
+
require_relative './test_provider'
|
3
|
+
|
4
|
+
module Moto
|
5
|
+
module Runner
|
6
|
+
class TestRunner
|
7
|
+
|
8
|
+
attr_reader :logger
|
9
|
+
attr_reader :config
|
10
|
+
attr_reader :test_reporter
|
11
|
+
|
12
|
+
def initialize(test_paths_absolute, environments, config, test_reporter)
|
13
|
+
@test_paths_absolute = test_paths_absolute
|
14
|
+
@config = config
|
15
|
+
@test_reporter = test_reporter
|
16
|
+
|
17
|
+
# TODO: initialize logger from config (yml or just ruby code)
|
18
|
+
@logger = Logger.new(File.open("#{MotoApp::DIR}/moto.log", File::WRONLY | File::APPEND | File::CREAT))
|
19
|
+
|
20
|
+
@environments = environments.empty? ? environments << :__default : environments
|
21
|
+
end
|
22
|
+
|
23
|
+
def running_thread_count
|
24
|
+
Thread.list.select {|thread| thread.status == "run"}.count
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
test_provider = TestProvider.new(@test_paths_absolute, @environments, @config[:moto][:test_runner][:test_repeats])
|
29
|
+
threads_max = @config[:moto][:test_runner][:thread_count] || 1
|
30
|
+
|
31
|
+
# remove log/screenshot files from previous execution
|
32
|
+
@test_paths_absolute.each do |test_path|
|
33
|
+
FileUtils.rm_rf("#{File.dirname(test_path)}/logs")
|
34
|
+
end
|
35
|
+
|
36
|
+
@test_reporter.report_start_run
|
37
|
+
|
38
|
+
# Create as many threads as we're allowed by the config file.
|
39
|
+
# test_provider.get_test - will invoke Queue.pop thus putting the thread to sleep
|
40
|
+
# once there is no more work.
|
41
|
+
(1..threads_max).each do |index|
|
42
|
+
Thread.new do
|
43
|
+
Thread.current[:id] = index
|
44
|
+
loop do
|
45
|
+
tc = ThreadContext.new(@config, test_provider.get_test, @test_reporter)
|
46
|
+
tc.run
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Waiting for all threads to run out of work so we can end the application
|
52
|
+
loop do
|
53
|
+
if test_provider.num_waiting == threads_max
|
54
|
+
break
|
55
|
+
end
|
56
|
+
|
57
|
+
sleep 1
|
58
|
+
end
|
59
|
+
|
60
|
+
@test_reporter.report_end_run
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Moto
|
5
|
+
module Runner
|
6
|
+
class ThreadContext
|
7
|
+
|
8
|
+
# all resources specific for single thread will be initialized here. E.g. browser session
|
9
|
+
attr_reader :logger
|
10
|
+
attr_reader :test
|
11
|
+
attr_reader :moto_app_config
|
12
|
+
|
13
|
+
def initialize(config, test, test_reporter)
|
14
|
+
@config = config
|
15
|
+
@test = test
|
16
|
+
@clients = {}
|
17
|
+
@test.context = self
|
18
|
+
@test_reporter = test_reporter
|
19
|
+
|
20
|
+
# TODO: temporary fix
|
21
|
+
Thread.current['context'] = self
|
22
|
+
|
23
|
+
# TODO: temporary fix
|
24
|
+
@moto_app_config = {}
|
25
|
+
Dir.glob('config/*.yml').each do |f|
|
26
|
+
@moto_app_config.deep_merge! YAML.load_file(f)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def client(name)
|
31
|
+
return @clients[name] if @clients.key? name
|
32
|
+
|
33
|
+
name_app = 'MotoApp::Lib::Clients::' + name
|
34
|
+
name_moto = 'Moto::Clients::' + name
|
35
|
+
|
36
|
+
c = try_client(name_app, "#{MotoApp::DIR}/")
|
37
|
+
unless c.nil?
|
38
|
+
@clients[name] = c
|
39
|
+
return c
|
40
|
+
end
|
41
|
+
|
42
|
+
c = try_client(name_moto, "#{Moto::DIR}/lib")
|
43
|
+
unless c.nil?
|
44
|
+
@clients[name] = c
|
45
|
+
return c
|
46
|
+
end
|
47
|
+
raise "Could not find client class for name #{name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def try_client(name, dir)
|
51
|
+
begin
|
52
|
+
a = name.underscore.split('/')
|
53
|
+
client_path = a[1..-1].join('/')
|
54
|
+
require "#{dir}/#{client_path}"
|
55
|
+
client_const = name.constantize
|
56
|
+
instance = client_const.new(self)
|
57
|
+
instance.init
|
58
|
+
instance.start_run
|
59
|
+
instance.start_test(@test)
|
60
|
+
return instance
|
61
|
+
rescue Exception => e
|
62
|
+
# puts e
|
63
|
+
# puts e.backtrace
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def const(key)
|
69
|
+
key = key.to_s
|
70
|
+
key = "#{@test.env.to_s}.#{key}" if @test.env != :__default
|
71
|
+
code = if key.include? '.'
|
72
|
+
"@moto_app_config#{key.split('.').map { |a| "['#{a}']" }.join('')}"
|
73
|
+
else
|
74
|
+
"@moto_app_config['#{key}']"
|
75
|
+
end
|
76
|
+
begin
|
77
|
+
v = eval code
|
78
|
+
raise if v.nil?
|
79
|
+
rescue
|
80
|
+
raise "There is no const defined for key: #{key}. Environment: #{ (@test.env == :__default) ? '<none>' : @test.env }"
|
81
|
+
end
|
82
|
+
v
|
83
|
+
end
|
84
|
+
|
85
|
+
def run
|
86
|
+
max_attempts = @config[:moto][:thread_context][:max_attempts] || 1
|
87
|
+
sleep_time = @config[:moto][:thread_context][:sleep_before_attempt] || 0
|
88
|
+
|
89
|
+
log_directory = File.dirname(@test.log_path)
|
90
|
+
if !File.directory?(log_directory)
|
91
|
+
FileUtils.mkdir_p(log_directory)
|
92
|
+
end
|
93
|
+
|
94
|
+
@logger = Logger.new(File.open(@test.log_path, File::WRONLY | File::TRUNC | File::CREAT))
|
95
|
+
@logger.level = @config[:moto][:thread_context][:log_level] || Logger::DEBUG
|
96
|
+
|
97
|
+
# Reporting: start_test
|
98
|
+
@test_reporter.report_start_test(@test.status)
|
99
|
+
|
100
|
+
(1..max_attempts).each do |attempt|
|
101
|
+
|
102
|
+
@clients.each_value { |c| c.start_test(@test) }
|
103
|
+
@test.before
|
104
|
+
@logger.info "Start: #{@test.name} attempt #{attempt}/#{max_attempts}"
|
105
|
+
|
106
|
+
begin
|
107
|
+
@test.run_test
|
108
|
+
rescue Exceptions::TestForcedPassed, Exceptions::TestForcedFailure, Exceptions::TestSkipped => e
|
109
|
+
@logger.info(e.message)
|
110
|
+
rescue Exception => e
|
111
|
+
@logger.error("#{e.class.name}: #{e.message}")
|
112
|
+
@logger.error(e.backtrace.join("\n"))
|
113
|
+
@clients.each_value { |c| c.handle_test_exception(@test, e) }
|
114
|
+
end
|
115
|
+
|
116
|
+
@test.after
|
117
|
+
@clients.each_value { |c| c.end_test(@test) }
|
118
|
+
|
119
|
+
@logger.info("Result: #{@test.status.results.last.code}")
|
120
|
+
|
121
|
+
# test should have another attempt only in case of an error
|
122
|
+
# pass, skip and fail statuses end attempts
|
123
|
+
if @test.status.results.last.code != Moto::Test::Result::ERROR
|
124
|
+
break
|
125
|
+
end
|
126
|
+
|
127
|
+
# don't go to sleep in the last attempt
|
128
|
+
if attempt < max_attempts
|
129
|
+
sleep sleep_time
|
130
|
+
end
|
131
|
+
|
132
|
+
end # Make another attempt
|
133
|
+
|
134
|
+
# Close and flush stream to file
|
135
|
+
@logger.close
|
136
|
+
|
137
|
+
# Reporting: end_test
|
138
|
+
@test_reporter.report_end_test(@test.status)
|
139
|
+
|
140
|
+
@clients.each_value { |c| c.end_run }
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/test/base.rb
CHANGED
@@ -27,10 +27,10 @@ module Moto
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Initializes test to be executed with specified params and environment
|
30
|
-
def init(env, params, params_index)
|
30
|
+
def init(env, params, params_index, global_index)
|
31
31
|
@env = env
|
32
32
|
@params = params
|
33
|
-
@name = generate_name(params_index)
|
33
|
+
@name = generate_name(params_index, global_index)
|
34
34
|
|
35
35
|
@status = Moto::Test::Status.new
|
36
36
|
@status.name = @name
|
@@ -42,16 +42,19 @@ module Moto
|
|
42
42
|
# Generates name of the test based on its properties:
|
43
43
|
# - number/name of currently executed configuration run
|
44
44
|
# - env
|
45
|
-
def generate_name(params_index)
|
45
|
+
def generate_name(params_index, global_index)
|
46
|
+
simple_class_name = self.class.to_s.demodulize
|
47
|
+
|
46
48
|
if @env == :__default
|
47
|
-
return "#{
|
48
|
-
return "#{
|
49
|
-
return "#{
|
49
|
+
return "#{simple_class_name}_#{global_index}" if @params.empty?
|
50
|
+
return "#{simple_class_name}_#{@params[:__name]}_#{global_index}" if @params.key?(:__name)
|
51
|
+
return "#{simple_class_name}_P#{params_index}_#{global_index}" unless @params.key?(:__name)
|
50
52
|
else
|
51
|
-
return "#{
|
52
|
-
return "#{
|
53
|
-
return "#{
|
53
|
+
return "#{simple_class_name}_#{@env}_##{global_index}" if @params.empty?
|
54
|
+
return "#{simple_class_name}_#{@env}_#{@params[:__name]}_#{global_index}" if @params.key?(:__name)
|
55
|
+
return "#{simple_class_name}_#{@env}_P#{params_index}_#{global_index}" unless @params.key?(:__name)
|
54
56
|
end
|
57
|
+
|
55
58
|
self.class.to_s
|
56
59
|
end
|
57
60
|
private :generate_name
|
data/lib/test_logging.rb
CHANGED
@@ -36,10 +36,10 @@ module Moto
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
|
-
@context.
|
39
|
+
@context.test.logger.debug("ENTER >>> #{self.class.name}::#{__callee__}(#{args})") unless skip_logging
|
40
40
|
result = send original_method, *args
|
41
41
|
# Below is the hack to properly escape binary data (if any manages to make it to logs)
|
42
|
-
@context.
|
42
|
+
@context.test.logger.debug("LEAVE <<< #{self.class.name}::#{__callee__} => #{[result].to_s[1..-2]}") unless skip_logging
|
43
43
|
result
|
44
44
|
end
|
45
45
|
@added = false
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: moto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.40
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bartek Wilczek
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2016-04-
|
14
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -104,15 +104,15 @@ files:
|
|
104
104
|
- lib/reporting/listeners/webui.rb
|
105
105
|
- lib/reporting/run_status.rb
|
106
106
|
- lib/reporting/test_reporter.rb
|
107
|
-
- lib/runner.rb
|
107
|
+
- lib/runner/test_generator.rb
|
108
|
+
- lib/runner/test_provider.rb
|
109
|
+
- lib/runner/test_runner.rb
|
110
|
+
- lib/runner/thread_context.rb
|
108
111
|
- lib/runner_logging.rb
|
109
112
|
- lib/test/base.rb
|
110
113
|
- lib/test/result.rb
|
111
114
|
- lib/test/status.rb
|
112
|
-
- lib/test_generator.rb
|
113
115
|
- lib/test_logging.rb
|
114
|
-
- lib/thread_context.rb
|
115
|
-
- lib/thread_pool.rb
|
116
116
|
- lib/version.rb
|
117
117
|
homepage: https://github.com/bwilczek/moto
|
118
118
|
licenses:
|
data/lib/runner.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require_relative './reporting/test_reporter'
|
2
|
-
|
3
|
-
module Moto
|
4
|
-
class Runner
|
5
|
-
|
6
|
-
attr_reader :logger
|
7
|
-
attr_reader :environments
|
8
|
-
attr_reader :assert
|
9
|
-
attr_reader :config
|
10
|
-
attr_reader :name
|
11
|
-
attr_reader :test_reporter
|
12
|
-
|
13
|
-
def initialize(tests, environments, config, test_reporter)
|
14
|
-
@tests = tests
|
15
|
-
@config = config
|
16
|
-
@thread_pool = ThreadPool.new(my_config[:thread_count] || 1)
|
17
|
-
@test_reporter = test_reporter
|
18
|
-
|
19
|
-
# TODO: initialize logger from config (yml or just ruby code)
|
20
|
-
# @logger = Logger.new(STDOUT)
|
21
|
-
@logger = Logger.new(File.open("#{MotoApp::DIR}/moto.log", File::WRONLY | File::APPEND | File::CREAT))
|
22
|
-
# @logger.level = Logger::WARN
|
23
|
-
|
24
|
-
# TODO: validate envs, maybe no-env should be supported as well?
|
25
|
-
environments << :__default if environments.empty?
|
26
|
-
@environments = environments
|
27
|
-
end
|
28
|
-
|
29
|
-
# TODO: Remake
|
30
|
-
# @return [Hash] hash with config
|
31
|
-
def my_config
|
32
|
-
caller_path = caller.first.to_s.split(/:\d/)[0]
|
33
|
-
keys = []
|
34
|
-
if caller_path.include? MotoApp::DIR
|
35
|
-
caller_path.sub!( "#{MotoApp::DIR}/lib/", '' )
|
36
|
-
keys << 'moto_app'
|
37
|
-
elsif caller_path.include? Moto::DIR
|
38
|
-
caller_path.sub!( "#{Moto::DIR}/lib/", '' )
|
39
|
-
keys << 'moto'
|
40
|
-
end
|
41
|
-
caller_path.sub!('.rb', '')
|
42
|
-
keys << caller_path.split('/')
|
43
|
-
keys.flatten!
|
44
|
-
eval "@config#{keys.map{|k| "[:#{k}]" }.join('')}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def run
|
48
|
-
@test_reporter.report_start_run
|
49
|
-
|
50
|
-
@tests.each do |test|
|
51
|
-
@thread_pool.schedule do
|
52
|
-
tc = ThreadContext.new(self, test, @test_reporter)
|
53
|
-
tc.run
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
@thread_pool.shutdown
|
58
|
-
@test_reporter.report_end_run
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
data/lib/test_generator.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
module MotoApp
|
2
|
-
module Tests
|
3
|
-
end
|
4
|
-
end
|
5
|
-
|
6
|
-
module Moto
|
7
|
-
class TestGenerator
|
8
|
-
|
9
|
-
def initialize(app_dir)
|
10
|
-
@app_dir = app_dir
|
11
|
-
end
|
12
|
-
|
13
|
-
# assuming that target file includes full valid ruby class
|
14
|
-
def create(class_name)
|
15
|
-
class_name = 'MotoApp::Tests::'+class_name
|
16
|
-
a = class_name.underscore.split('/')
|
17
|
-
test_path = (a[1..20]+[a[-1]]).join('/')
|
18
|
-
|
19
|
-
# TODO: check if this path and constant exists
|
20
|
-
require "#{MotoApp::DIR}/#{test_path}"
|
21
|
-
test_const = class_name.safe_constantize
|
22
|
-
test_const.new
|
23
|
-
end
|
24
|
-
|
25
|
-
def create_module_tree(root_module, next_modules)
|
26
|
-
return root_module if next_modules.empty?
|
27
|
-
next_module_name = next_modules.shift
|
28
|
-
if root_module.const_defined?(next_module_name.to_sym)
|
29
|
-
m = root_module.const_get(next_module_name.to_sym)
|
30
|
-
else
|
31
|
-
m = Module.new
|
32
|
-
root_module.const_set(next_module_name.to_sym, m)
|
33
|
-
end
|
34
|
-
create_module_tree(m, next_modules)
|
35
|
-
end
|
36
|
-
|
37
|
-
# assuming that target file includes only content of method 'run' and some magic comments
|
38
|
-
def generate(test_path_absolute)
|
39
|
-
method_body = File.read(test_path_absolute) + "\n"
|
40
|
-
|
41
|
-
full_code = !! method_body.match( /^#\s*FULL_CODE\s+/ )
|
42
|
-
|
43
|
-
if full_code
|
44
|
-
generate_for_full_class_code(test_path_absolute)
|
45
|
-
else
|
46
|
-
generate_for_run_body(test_path_absolute, method_body)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Generates objects with tests that are supposed to be executed in this run.
|
51
|
-
def generate_for_full_class_code(test_path_absolute)
|
52
|
-
require test_path_absolute
|
53
|
-
|
54
|
-
test_object = nil
|
55
|
-
|
56
|
-
# Checking if it's possible to create test based on provided path. In case something is wrong with
|
57
|
-
# modules structure in class itself Moto::Test::Base will be instantized with raise injected into its run()
|
58
|
-
# so we can have proper reporting and summary even if the test doesn't execute.
|
59
|
-
begin
|
60
|
-
class_name = test_path_absolute.gsub("#{MotoApp::DIR}/",'moto_app/').camelize.chomp('.rb').constantize
|
61
|
-
test_object = class_name.new
|
62
|
-
rescue NameError
|
63
|
-
class_name = Moto::Test::Base
|
64
|
-
test_object = class_name.new
|
65
|
-
|
66
|
-
class << test_object
|
67
|
-
attr_accessor :custom_name
|
68
|
-
|
69
|
-
def run
|
70
|
-
raise "ERROR: Invalid module structure: #{custom_name}"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
test_object.custom_name = test_path_absolute.gsub("#{MotoApp::DIR}/",'moto_app/').camelize.chomp('.rb')
|
75
|
-
end
|
76
|
-
|
77
|
-
test_object.static_path = test_path_absolute
|
78
|
-
test_object.evaled = false
|
79
|
-
test_object
|
80
|
-
end
|
81
|
-
|
82
|
-
def generate_for_run_body(test_path_absolute, method_body)
|
83
|
-
base = Moto::Test::Base
|
84
|
-
base_class_string = method_body.match( /^#\s*BASE_CLASS:\s(\S+)/ )
|
85
|
-
unless base_class_string.nil?
|
86
|
-
base_class_string = base_class_string[1].strip
|
87
|
-
|
88
|
-
a = base_class_string.underscore.split('/')
|
89
|
-
base_test_path = a[1..-1].join('/')
|
90
|
-
|
91
|
-
require "#{MotoApp::DIR}/#{base_test_path}"
|
92
|
-
base = base_class_string.constantize
|
93
|
-
end
|
94
|
-
|
95
|
-
# MotoApp::Tests::Login::Short
|
96
|
-
consts = test_path_absolute.camelize.split('Tests::')[1].split('::')
|
97
|
-
consts.pop
|
98
|
-
class_name = consts.pop
|
99
|
-
|
100
|
-
m = create_module_tree(MotoApp::Tests, consts)
|
101
|
-
cls = Class.new(base)
|
102
|
-
m.const_set(class_name.to_sym, cls)
|
103
|
-
|
104
|
-
test_object = cls.new
|
105
|
-
test_object.instance_eval( "def run\n #{method_body} \n end" )
|
106
|
-
test_object.static_path = test_path_absolute
|
107
|
-
test_object.evaled = true
|
108
|
-
test_object
|
109
|
-
end
|
110
|
-
|
111
|
-
end
|
112
|
-
end
|
data/lib/thread_context.rb
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
|
-
module Moto
|
4
|
-
class ThreadContext
|
5
|
-
|
6
|
-
# all resources specific for single thread will be initialized here. E.g. browser session
|
7
|
-
attr_reader :runner
|
8
|
-
attr_reader :logger
|
9
|
-
attr_reader :current_test
|
10
|
-
|
11
|
-
def initialize(runner, test, test_reporter)
|
12
|
-
@runner = runner
|
13
|
-
@test = test
|
14
|
-
@clients = {}
|
15
|
-
@test.context = self
|
16
|
-
@test_reporter = test_reporter
|
17
|
-
|
18
|
-
#TODO temporary fix
|
19
|
-
Thread.current['context']= self
|
20
|
-
|
21
|
-
@config = {}
|
22
|
-
Dir.glob("config/*.yml").each do |f|
|
23
|
-
@config.deep_merge! YAML.load_file(f)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def client(name)
|
28
|
-
return @clients[name] if @clients.key? name
|
29
|
-
|
30
|
-
name_app = 'MotoApp::Lib::Clients::' + name
|
31
|
-
name_moto = 'Moto::Clients::' + name
|
32
|
-
|
33
|
-
c = try_client(name_app, "#{MotoApp::DIR}/")
|
34
|
-
unless c.nil?
|
35
|
-
@clients[name] = c
|
36
|
-
return c
|
37
|
-
end
|
38
|
-
|
39
|
-
c = try_client(name_moto, "#{Moto::DIR}/lib")
|
40
|
-
unless c.nil?
|
41
|
-
@clients[name] = c
|
42
|
-
return c
|
43
|
-
end
|
44
|
-
raise "Could not find client class for name #{name}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def try_client(name, dir)
|
48
|
-
begin
|
49
|
-
a = name.underscore.split('/')
|
50
|
-
client_path = a[1..-1].join('/')
|
51
|
-
require "#{dir}/#{client_path}"
|
52
|
-
client_const = name.constantize
|
53
|
-
instance = client_const.new(self)
|
54
|
-
instance.init
|
55
|
-
instance.start_run
|
56
|
-
instance.start_test(@current_test)
|
57
|
-
return instance
|
58
|
-
rescue Exception => e
|
59
|
-
# puts e
|
60
|
-
# puts e.backtrace
|
61
|
-
return nil
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def const(key)
|
66
|
-
key = key.to_s
|
67
|
-
key = "#{@current_test.env.to_s}.#{key}" if @current_test.env != :__default
|
68
|
-
code = if key.include? '.'
|
69
|
-
"@config#{key.split('.').map { |a| "['#{a}']" }.join('')}"
|
70
|
-
else
|
71
|
-
"@config['#{key}']"
|
72
|
-
end
|
73
|
-
begin
|
74
|
-
v = eval code
|
75
|
-
raise if v.nil?
|
76
|
-
rescue
|
77
|
-
raise "There is no const defined for key: #{key}. Environment: #{ (@current_test.env == :__default) ? '<none>' : @current_test.env }"
|
78
|
-
end
|
79
|
-
v
|
80
|
-
end
|
81
|
-
|
82
|
-
def run
|
83
|
-
# remove log/screenshot files from previous execution
|
84
|
-
Dir.glob("#{@test.dir}/*.{log,png}").each { |f| File.delete f }
|
85
|
-
max_attempts = @runner.my_config[:max_attempts] || 1
|
86
|
-
sleep_time = @runner.my_config[:sleep_before_attempt] || 0
|
87
|
-
|
88
|
-
@runner.environments.each do |env|
|
89
|
-
params_all = [{}]
|
90
|
-
|
91
|
-
# RB Config files
|
92
|
-
params_path = "#{@test.dir}/#{@test.filename}"
|
93
|
-
params_all = eval(File.read(params_path)) if File.exists?(params_path)
|
94
|
-
|
95
|
-
params_all.each_with_index do |params, params_index|
|
96
|
-
|
97
|
-
# Filtering out param sets that are specific to certain envs
|
98
|
-
unless params['__env'].nil?
|
99
|
-
allowed_envs = params['__env'].is_a?(String) ? [params['__env']] : params['__env']
|
100
|
-
next unless allowed_envs.include? env
|
101
|
-
end
|
102
|
-
|
103
|
-
@test.init(env, params, params_index)
|
104
|
-
# @test.log_path = "#{@test.dir}/#{@test.name.gsub(/\s+/, '_').gsub(':', '_').gsub('::', '_').gsub('/', '_')}.log"
|
105
|
-
@test.log_path = "#{@test.dir}/#{@test.name.demodulize.gsub('/', '_')}.log"
|
106
|
-
# TODO: log path might be specified (to some extent) by the configuration
|
107
|
-
@logger = Logger.new(File.open(@test.log_path, File::WRONLY | File::TRUNC | File::CREAT))
|
108
|
-
@logger.level = @runner.my_config[:log_level] || Logger::DEBUG
|
109
|
-
@current_test = @test
|
110
|
-
|
111
|
-
(1..max_attempts).each do |attempt|
|
112
|
-
|
113
|
-
# Reporting: start_test
|
114
|
-
if attempt == 1
|
115
|
-
@test_reporter.report_start_test(@test.status)
|
116
|
-
end
|
117
|
-
|
118
|
-
@clients.each_value { |c| c.start_test(@test) }
|
119
|
-
@test.before
|
120
|
-
@logger.info "Start: #{@test.name} attempt #{attempt}/#{max_attempts}"
|
121
|
-
|
122
|
-
begin
|
123
|
-
@test.run_test
|
124
|
-
rescue Exceptions::TestForcedPassed, Exceptions::TestForcedFailure, Exceptions::TestSkipped => e
|
125
|
-
@logger.info(e.message)
|
126
|
-
rescue Exception => e
|
127
|
-
@logger.error("#{e.class.name}: #{e.message}")
|
128
|
-
@logger.error(e.backtrace.join("\n"))
|
129
|
-
@clients.each_value { |c| c.handle_test_exception(@test, e) }
|
130
|
-
end
|
131
|
-
|
132
|
-
@test.after
|
133
|
-
@clients.each_value { |c| c.end_test(@test) }
|
134
|
-
|
135
|
-
@logger.info("Result: #{@test.status.results.last.code}")
|
136
|
-
|
137
|
-
# test should have another attempt only in case of an error
|
138
|
-
# pass, skip and fail statuses end attempts
|
139
|
-
if @test.status.results.last.code != Moto::Test::Result::ERROR
|
140
|
-
break
|
141
|
-
end
|
142
|
-
|
143
|
-
# don't go to sleep in the last attempt
|
144
|
-
if attempt < max_attempts
|
145
|
-
sleep sleep_time
|
146
|
-
end
|
147
|
-
|
148
|
-
end # Make another attempt
|
149
|
-
|
150
|
-
# Close and flush stream to file
|
151
|
-
@logger.close
|
152
|
-
|
153
|
-
# Reporting: end_test
|
154
|
-
@test_reporter.report_end_test(@test.status)
|
155
|
-
|
156
|
-
end
|
157
|
-
end
|
158
|
-
@clients.each_value { |c| c.end_run }
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
162
|
-
end
|
data/lib/thread_pool.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Moto
|
2
|
-
class ThreadPool
|
3
|
-
|
4
|
-
def initialize(size=1)
|
5
|
-
@size = size
|
6
|
-
@jobs = Queue.new
|
7
|
-
@pool = Array.new(@size) do |i|
|
8
|
-
Thread.new do
|
9
|
-
Thread.current[:id] = i
|
10
|
-
catch(:exit) do
|
11
|
-
loop do
|
12
|
-
job, args = @jobs.pop
|
13
|
-
job.call(*args)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def schedule(*args, &block)
|
21
|
-
@jobs << [block, args]
|
22
|
-
end
|
23
|
-
|
24
|
-
def shutdown
|
25
|
-
@size.times do
|
26
|
-
schedule { throw :exit }
|
27
|
-
end
|
28
|
-
@pool.each(&:join)
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|