moto 0.0.31 → 0.0.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, listeners, environments, config, name)
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
- @name = name
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
- @listeners.each { |l| l.start_run }
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
- @listeners.each { |l| l.end_run }
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
@@ -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
@@ -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