moto 0.0.33 → 0.0.40
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/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
|