moto 0.0.31 → 0.0.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/app_generator.rb +2 -2
- data/lib/cli.rb +8 -14
- data/lib/parser.rb +4 -3
- data/lib/reporting/listeners/base.rb +44 -0
- data/lib/reporting/listeners/console.rb +31 -0
- data/lib/reporting/listeners/console_dots.rb +60 -0
- data/lib/reporting/listeners/junit_xml.rb +52 -0
- data/lib/reporting/listeners/webui.rb +81 -0
- data/lib/reporting/run_status.rb +98 -0
- data/lib/reporting/test_reporter.rb +83 -0
- data/lib/runner.rb +10 -21
- data/lib/test/base.rb +168 -0
- data/lib/test/result.rb +28 -0
- data/lib/test/status.rb +106 -0
- data/lib/test_generator.rb +4 -7
- data/lib/thread_context.rb +52 -33
- data/lib/version.rb +1 -1
- metadata +14 -10
- data/lib/assert.rb +0 -32
- data/lib/listeners/base.rb +0 -26
- data/lib/listeners/console.rb +0 -29
- data/lib/listeners/console_dots.rb +0 -63
- data/lib/listeners/junit_xml.rb +0 -37
- data/lib/listeners/webui.rb +0 -71
- data/lib/result.rb +0 -84
- data/lib/test.rb +0 -107
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative 'run_status'
|
2
|
+
|
3
|
+
module Moto
|
4
|
+
module Reporting
|
5
|
+
|
6
|
+
# Manages reporting test and run status' to attached listeners
|
7
|
+
class TestReporter
|
8
|
+
# @param [Array] listeners An array of strings, which represent qualified names of classes (listeners) that will be instantiated.
|
9
|
+
# empty array is passed then :default_listeners will be taken from config
|
10
|
+
# @param [Hash] config to be passed to listeners during creation
|
11
|
+
# @param [String] custom_run_name Optional, to be passed to listeners during creation
|
12
|
+
def initialize(listeners, config, custom_run_name)
|
13
|
+
|
14
|
+
if listeners.empty?
|
15
|
+
config[:moto][:runner][:default_listeners].each do |listener_class_name|
|
16
|
+
listeners << listener_class_name
|
17
|
+
end
|
18
|
+
else
|
19
|
+
listeners.each_with_index do |listener_class_name, index|
|
20
|
+
listeners[index] = ('Moto::Reporting::Listeners::' + listener_class_name.camelize).constantize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@listeners = []
|
25
|
+
@config = config
|
26
|
+
@custom_run_name = custom_run_name
|
27
|
+
listeners.each { |l| add_listener(l) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a listener to the list.
|
31
|
+
# All listeners on the list will have events reported to them.
|
32
|
+
# @param [Moto::Listener::Base] listener class to be added
|
33
|
+
def add_listener(listener)
|
34
|
+
@listeners << listener.new(listener_config(listener), @custom_run_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Hash] hash containing part of the config meant for a specific listener
|
38
|
+
# @param [Moto::Listener::Base] listener class for which config is to be retrieved
|
39
|
+
def listener_config(listener)
|
40
|
+
listener_symbol = listener.name.demodulize.underscore.to_sym
|
41
|
+
@config[:moto][:listeners][listener_symbol]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reports start of the whole run (set of tests) to attached listeners
|
45
|
+
def report_start_run
|
46
|
+
@run_status = Moto::Reporting::RunStatus.new
|
47
|
+
@run_status.initialize_run
|
48
|
+
|
49
|
+
@listeners.each do |l|
|
50
|
+
l.start_run
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Reports end of the whole run (set of tests) to attached listeners
|
55
|
+
def report_end_run
|
56
|
+
@run_status.finalize_run
|
57
|
+
|
58
|
+
@listeners.each do |l|
|
59
|
+
l.end_run(@run_status)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Reports star of a test to all attached listeners
|
64
|
+
# @param [Moto::Test::Status] test_status of test which's start is to be reported on
|
65
|
+
def report_start_test(test_status)
|
66
|
+
@listeners.each do |l|
|
67
|
+
l.start_test(test_status)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reports end of a test to all attached listeners
|
72
|
+
# @param [Moto::Test::Status] test_status of test which's end is to be reported on
|
73
|
+
def report_end_test(test_status)
|
74
|
+
@run_status.add_test_status(test_status)
|
75
|
+
|
76
|
+
@listeners.each do |l|
|
77
|
+
l.end_test(test_status)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/runner.rb
CHANGED
@@ -1,44 +1,33 @@
|
|
1
|
+
require_relative './reporting/test_reporter'
|
2
|
+
|
1
3
|
module Moto
|
2
4
|
class Runner
|
3
5
|
|
4
|
-
attr_reader :result
|
5
|
-
attr_reader :listeners
|
6
6
|
attr_reader :logger
|
7
7
|
attr_reader :environments
|
8
8
|
attr_reader :assert
|
9
9
|
attr_reader :config
|
10
10
|
attr_reader :name
|
11
|
+
attr_reader :test_reporter
|
11
12
|
|
12
|
-
def initialize(tests,
|
13
|
+
def initialize(tests, environments, config, test_reporter)
|
13
14
|
@tests = tests
|
14
15
|
@config = config
|
15
16
|
@thread_pool = ThreadPool.new(my_config[:thread_count] || 1)
|
16
|
-
@
|
17
|
+
@test_reporter = test_reporter
|
17
18
|
|
18
19
|
# TODO: initialize logger from config (yml or just ruby code)
|
19
20
|
# @logger = Logger.new(STDOUT)
|
20
21
|
@logger = Logger.new(File.open("#{MotoApp::DIR}/moto.log", File::WRONLY | File::APPEND | File::CREAT))
|
21
22
|
# @logger.level = Logger::WARN
|
22
23
|
|
23
|
-
@result = Result.new(self)
|
24
|
-
|
25
24
|
# TODO: validate envs, maybe no-env should be supported as well?
|
26
25
|
environments << :__default if environments.empty?
|
27
26
|
@environments = environments
|
28
|
-
|
29
|
-
@listeners = []
|
30
|
-
if listeners.empty?
|
31
|
-
my_config[:default_listeners].each do |l|
|
32
|
-
@listeners << l.new(self)
|
33
|
-
end
|
34
|
-
else
|
35
|
-
listeners.each do |l|
|
36
|
-
@listeners << l.new(self)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
@listeners.unshift(@result)
|
40
27
|
end
|
41
28
|
|
29
|
+
# TODO: Remake
|
30
|
+
# @return [Hash] hash with config
|
42
31
|
def my_config
|
43
32
|
caller_path = caller.first.to_s.split(/:\d/)[0]
|
44
33
|
keys = []
|
@@ -56,17 +45,17 @@ module Moto
|
|
56
45
|
end
|
57
46
|
|
58
47
|
def run
|
59
|
-
@
|
48
|
+
@test_reporter.report_start_run
|
60
49
|
|
61
50
|
@tests.each do |test|
|
62
51
|
@thread_pool.schedule do
|
63
|
-
tc = ThreadContext.new(self, test)
|
52
|
+
tc = ThreadContext.new(self, test, @test_reporter)
|
64
53
|
tc.run
|
65
54
|
end
|
66
55
|
end
|
67
56
|
|
68
57
|
@thread_pool.shutdown
|
69
|
-
@
|
58
|
+
@test_reporter.report_end_run
|
70
59
|
end
|
71
60
|
|
72
61
|
end
|
data/lib/test/base.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require_relative 'status'
|
2
|
+
|
3
|
+
module Moto
|
4
|
+
module Test
|
5
|
+
class Base
|
6
|
+
|
7
|
+
include Moto::ForwardContextMethods
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_writer :context
|
11
|
+
attr_reader :env
|
12
|
+
attr_reader :params
|
13
|
+
attr_accessor :static_path
|
14
|
+
attr_accessor :evaled
|
15
|
+
attr_accessor :status
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.inherited(k)
|
22
|
+
k._path = caller.first.match(/(.+):\d+:in/)[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
self.class._path
|
27
|
+
end
|
28
|
+
|
29
|
+
# Initializes test to be executed with specified params and environment
|
30
|
+
def init(env, params, params_index)
|
31
|
+
@env = env
|
32
|
+
@params = params
|
33
|
+
@name = generate_name(params_index)
|
34
|
+
|
35
|
+
@status = Moto::Test::Status.new
|
36
|
+
@status.name = @name
|
37
|
+
@status.test_class_name = self.class.name
|
38
|
+
@status.env = @env
|
39
|
+
@status.params = @params
|
40
|
+
end
|
41
|
+
|
42
|
+
# Generates name of the test based on its properties:
|
43
|
+
# - number/name of currently executed configuration run
|
44
|
+
# - env
|
45
|
+
def generate_name(params_index)
|
46
|
+
if @env == :__default
|
47
|
+
return "#{self.class.to_s}" if @params.empty?
|
48
|
+
return "#{self.class.to_s}/#{@params['__name']}" if @params.key?('__name')
|
49
|
+
return "#{self.class.to_s}/params_#{params_index}" unless @params.key?('__name')
|
50
|
+
else
|
51
|
+
return "#{self.class.to_s}/#{@env}" if @params.empty?
|
52
|
+
return "#{self.class.to_s}/#{@env}/#{@params['__name']}" if @params.key?('__name')
|
53
|
+
return "#{self.class.to_s}/#{@env}/params_#{params_index}" unless @params.key?('__name')
|
54
|
+
end
|
55
|
+
self.class.to_s
|
56
|
+
end
|
57
|
+
private :generate_name
|
58
|
+
|
59
|
+
# Setter for :log_path
|
60
|
+
def log_path=(param)
|
61
|
+
@log_path = param
|
62
|
+
|
63
|
+
# I hate myself for doing this, but I have no other idea for now how to pass log to Listeners that
|
64
|
+
# make use of it (for example WebUI)
|
65
|
+
@status.log_path = param
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] string with the path to the test's log
|
69
|
+
def log_path
|
70
|
+
@log_path
|
71
|
+
end
|
72
|
+
|
73
|
+
def dir
|
74
|
+
return File.dirname(static_path) unless static_path.nil?
|
75
|
+
File.dirname(self.path)
|
76
|
+
end
|
77
|
+
|
78
|
+
def filename
|
79
|
+
return File.basename(static_path, '.*') unless static_path.nil?
|
80
|
+
File.basename(path, '.*')
|
81
|
+
end
|
82
|
+
|
83
|
+
# Use this to run test
|
84
|
+
# Initializes status, runs test, handles exceptions, finalizes status after run completion
|
85
|
+
def run_test
|
86
|
+
status.initialize_run
|
87
|
+
|
88
|
+
begin
|
89
|
+
run
|
90
|
+
rescue Exception => exception
|
91
|
+
status.log_exception(exception)
|
92
|
+
raise
|
93
|
+
ensure
|
94
|
+
status.finalize_run
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
# Only to be overwritten by final test execution
|
100
|
+
# Use :run_test in order to run test
|
101
|
+
def run
|
102
|
+
# abstract
|
103
|
+
end
|
104
|
+
|
105
|
+
def before
|
106
|
+
# abstract
|
107
|
+
end
|
108
|
+
|
109
|
+
def after
|
110
|
+
# abstract
|
111
|
+
end
|
112
|
+
|
113
|
+
def skip(msg = nil)
|
114
|
+
raise Exceptions::TestSkipped.new(msg.nil? ? 'Test skipped with no reason given.' : "Skip reason: #{msg}")
|
115
|
+
end
|
116
|
+
|
117
|
+
def fail(msg = nil)
|
118
|
+
if msg.nil?
|
119
|
+
msg = 'Test forcibly failed with no reason given.'
|
120
|
+
else
|
121
|
+
msg = "Forced failure, reason: #{msg}"
|
122
|
+
end
|
123
|
+
raise Exceptions::TestForcedFailure.new msg
|
124
|
+
end
|
125
|
+
|
126
|
+
def pass(msg = nil)
|
127
|
+
if msg.nil?
|
128
|
+
msg = 'Test forcibly passed with no reason given.'
|
129
|
+
else
|
130
|
+
msg = "Forced passed, reason: #{msg}"
|
131
|
+
end
|
132
|
+
raise Exceptions::TestForcedPassed.new msg
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Checks for equality of both arguments
|
137
|
+
def assert_equal(a, b)
|
138
|
+
assert(a == b, "Arguments should be equal: #{a} != #{b}.")
|
139
|
+
end
|
140
|
+
|
141
|
+
# Checks if passed value is equal to True
|
142
|
+
def assert_true(value)
|
143
|
+
assert(value, 'Logical condition not met, expecting true, given false.')
|
144
|
+
end
|
145
|
+
|
146
|
+
# Checks if passed value is equal to False
|
147
|
+
def assert_false(value)
|
148
|
+
assert(!value, 'Logical condition not met, expecting false, given true.')
|
149
|
+
end
|
150
|
+
|
151
|
+
# Checks if result of condition equals to True
|
152
|
+
def assert(condition, message)
|
153
|
+
if !condition
|
154
|
+
if evaled
|
155
|
+
# -1 because of added method header in generated class
|
156
|
+
line_number = caller.select { |l| l.match(/\(eval\):\d*:in `run'/) }.first[/\d+/].to_i - 1
|
157
|
+
else
|
158
|
+
line_number = caller.select { |l| l.match(/#{static_path}:\d*:in `run'/) }.first[/\d+/].to_i - 1
|
159
|
+
end
|
160
|
+
|
161
|
+
status.log_failure("ASSERTION FAILED in line #{line_number}: #{message}")
|
162
|
+
logger.error(message)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/test/result.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Moto
|
2
|
+
module Test
|
3
|
+
|
4
|
+
# Value object representing information about results of a single attempt to pass the test
|
5
|
+
class Result
|
6
|
+
|
7
|
+
RUNNING = :running # -1
|
8
|
+
PASSED = :passed # 0
|
9
|
+
FAILURE = :failure # 1
|
10
|
+
ERROR = :error # 2
|
11
|
+
SKIPPED = :skipped # 3
|
12
|
+
|
13
|
+
# Result code of a single test run
|
14
|
+
attr_accessor :code
|
15
|
+
|
16
|
+
# Optional message that might accompany the result of a single test run
|
17
|
+
attr_accessor :message
|
18
|
+
|
19
|
+
# An Array of Strings representing messages that accompany assertion and forced failures
|
20
|
+
attr_accessor :failures
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@failures = []
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/test/status.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require_relative 'result'
|
2
|
+
|
3
|
+
module Moto
|
4
|
+
module Test
|
5
|
+
|
6
|
+
# Representation of single test's run status - it's passes, failures etc.
|
7
|
+
# Pretty much a value object which's purpose is to be passed as a data provider for listeners.
|
8
|
+
# Only methods here are just meant for data preparation. No communication with any external classes.
|
9
|
+
class Status
|
10
|
+
|
11
|
+
# Name of the test
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
# Name of the class representing test
|
15
|
+
attr_accessor :test_class_name
|
16
|
+
|
17
|
+
# Array of [Moto::Test::Result], each item represents a result of a single attempt to pass the test
|
18
|
+
attr_reader :results
|
19
|
+
|
20
|
+
# Environment on which test was run
|
21
|
+
attr_accessor :env
|
22
|
+
|
23
|
+
# Set of params on which test was based
|
24
|
+
attr_accessor :params
|
25
|
+
|
26
|
+
# Time of test's start
|
27
|
+
attr_accessor :time_start
|
28
|
+
|
29
|
+
# Time of test's finish
|
30
|
+
attr_accessor :time_end
|
31
|
+
|
32
|
+
# Test's duration
|
33
|
+
attr_accessor :duration
|
34
|
+
|
35
|
+
# TODO: Burn it with fire...
|
36
|
+
# Path to test's log, for purpose of making test logs accessible via listeners
|
37
|
+
attr_accessor :log_path
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@results = []
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize_run
|
44
|
+
if @time_start.nil?
|
45
|
+
@time_start = Time.now.to_f
|
46
|
+
end
|
47
|
+
|
48
|
+
result = Moto::Test::Result.new
|
49
|
+
result.code = Moto::Test::Result::RUNNING
|
50
|
+
@results.push(result)
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
def finalize_run
|
55
|
+
last_result = @results.last
|
56
|
+
|
57
|
+
if last_result.code == Moto::Test::Result::RUNNING
|
58
|
+
last_result.code = Moto::Test::Result::PASSED
|
59
|
+
end
|
60
|
+
|
61
|
+
@time_end = Time.now.to_f
|
62
|
+
@duration = time_end - time_start
|
63
|
+
end
|
64
|
+
|
65
|
+
# Evaluates result.code and message based on exceptions, dispatched by test during test attempt
|
66
|
+
# @param [Exception] exception thrown during test run
|
67
|
+
def log_exception(exception)
|
68
|
+
current_result = @results.last
|
69
|
+
|
70
|
+
if exception.nil? || exception.is_a?(Moto::Exceptions::TestForcedPassed)
|
71
|
+
current_result.code = Moto::Test::Result::PASSED
|
72
|
+
current_result.message = exception.message
|
73
|
+
elsif exception.is_a?(Moto::Exceptions::TestSkipped)
|
74
|
+
current_result.code = Moto::Test::Result::SKIPPED
|
75
|
+
current_result.message = exception.message
|
76
|
+
elsif exception.is_a?(Moto::Exceptions::TestForcedFailure)
|
77
|
+
log_failure(exception.message)
|
78
|
+
else
|
79
|
+
current_result.code = Moto::Test::Result::ERROR
|
80
|
+
current_result.message = exception.message
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Logs a failure, from assertion or forced, to the list of failures
|
85
|
+
# @param [String] message of a failure to be added to the list
|
86
|
+
def log_failure(message)
|
87
|
+
current_result = @results.last
|
88
|
+
current_result.code = Moto::Test::Result::FAILURE
|
89
|
+
current_result.failures.push(message)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Overwritten definition of to string.
|
94
|
+
# @return [String] string with readable form of @results.last.code
|
95
|
+
def to_s
|
96
|
+
case @results.last.code
|
97
|
+
when Moto::Test::Result::PASSED then return 'PASSED'
|
98
|
+
when Moto::Test::Result::FAILURE then return 'FAILED'
|
99
|
+
when Moto::Test::Result::ERROR then return 'ERROR'
|
100
|
+
when Moto::Test::Result::SKIPPED then return 'SKIPPED'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|