moto 0.9.11 → 0.9.17

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.
@@ -0,0 +1,81 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require_relative '../../config'
4
+
5
+ module Moto
6
+ module Modes
7
+ module Run
8
+ class ThreadContext
9
+
10
+ attr_reader :test
11
+
12
+ def initialize(test, test_reporter)
13
+ @test = test
14
+ @test_reporter = test_reporter
15
+
16
+ log_directory = File.dirname(@test.log_path)
17
+ if !File.directory?(log_directory)
18
+ FileUtils.mkdir_p(log_directory)
19
+ end
20
+
21
+ file = File.open(@test.log_path, File::WRONLY | File::TRUNC | File::CREAT)
22
+ file.chmod(0o666)
23
+ Thread.current['logger'] = Logger.new(file)
24
+ Thread.current['logger'].level = config[:test_log_level] || Logger::DEBUG
25
+ end
26
+
27
+ def run
28
+ max_attempts = config[:test_attempt_max] || 1
29
+ sleep_time = config[:test_attempt_sleep] || 0
30
+
31
+ # Reporting: start_test
32
+ @test_reporter.report_start_test(@test.status, @test.metadata)
33
+
34
+ (1..max_attempts).each do |attempt|
35
+
36
+ @test.before
37
+ Thread.current['logger'].info("Start: #{@test.name} attempt #{attempt}/#{max_attempts}")
38
+
39
+ begin
40
+ @test.run_test
41
+ rescue Exceptions::TestForcedPassed, Exceptions::TestForcedFailure, Exceptions::TestSkipped => e
42
+ Thread.current['logger'].info(e.message)
43
+ rescue Exception => e
44
+ Thread.current['logger'].error("#{e.class.name}: #{e.message}")
45
+ Thread.current['logger'].error(e.backtrace.join("\n"))
46
+ end
47
+
48
+ @test.after
49
+
50
+ Thread.current['logger'].info("Result: #{@test.status.results.last.code}")
51
+
52
+ # test should have another attempt in case of an error / failure / none at all
53
+ unless (@test.status.results.last.code == Moto::Test::Result::ERROR && config[:test_reattempt_on_error]) ||
54
+ (@test.status.results.last.code == Moto::Test::Result::FAILURE && config[:test_reattempt_on_fail])
55
+ break
56
+ end
57
+
58
+ # don't go to sleep in the last attempt
59
+ if attempt < max_attempts
60
+ sleep sleep_time
61
+ end
62
+
63
+ end # Make another attempt
64
+
65
+ # Close and flush stream to file
66
+ Thread.current['logger'].close
67
+
68
+ # Reporting: end_test
69
+ @test_reporter.report_end_test(@test.status)
70
+ end
71
+
72
+ # @return [Hash] Hash with config for ThreadContext
73
+ def config
74
+ Moto::Lib::Config.moto[:test_runner]
75
+ end
76
+ private :config
77
+
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,87 @@
1
+ require_relative '../../reporting/test_reporter'
2
+ require_relative '../../exceptions/test_skipped'
3
+ require_relative '../../exceptions/test_forced_failure'
4
+ require_relative '../../exceptions/test_forced_passed'
5
+ require_relative '../../test/generator'
6
+
7
+ module Moto
8
+ module Modes
9
+ module Validate
10
+ class TestValidator
11
+
12
+ # @param [Array] tests_metadata Collection of [Moto::Test::Metadata] objects describing Tests
13
+ # @param [Hash] validation_options User input in form of a Hash - specifies options of validation
14
+ # @param [Moto::Reporting::TestReporter] test_reporter Reporter of test/run statuses that communicates with external status listeners
15
+ def initialize(tests_metadata, validation_options, test_reporter)
16
+
17
+ @tests_metadata = tests_metadata
18
+ @validation_options = validation_options
19
+ @test_reporter = test_reporter
20
+ end
21
+
22
+ def run
23
+ @test_reporter.report_start_run
24
+
25
+ test_generator = Moto::Test::Generator.new
26
+
27
+ @tests_metadata.each do |metadata|
28
+ tests = test_generator.get_test_with_variants(metadata, 1)
29
+ tests.each do |test|
30
+ @test_reporter.report_start_test(test.status, test.metadata)
31
+ test.status.initialize_run
32
+
33
+ # Validate if tags are not empty
34
+ if @validation_options[:has_tags] && metadata.tags.empty?
35
+ test.status.log_exception(Exceptions::TestForcedFailure.new('No tags.'))
36
+ end
37
+
38
+ # Validate if test description was provided
39
+ if @validation_options[:has_description] && metadata.description.empty?
40
+ test.status.log_exception(Exceptions::TestForcedFailure.new('No description.'))
41
+ end
42
+
43
+ # Validate if tags contain entries only from the whitelist
44
+ if @validation_options.key?(:tag_whitelist)
45
+ metadata.tags.each do |tag|
46
+ if !@validation_options[:tag_whitelist].include?(tag)
47
+ test.status.log_exception(Exceptions::TestForcedFailure.new("Tags contain non-whitelisted entry: #{tag}"))
48
+ break
49
+ end
50
+ end
51
+ end
52
+
53
+ # Validate if provided regex is found within tags
54
+ if @validation_options.key?(:tags_regex_positive)
55
+ tags_string = metadata.tags.join(',')
56
+ regexp = Regexp.new(@validation_options[:tags_regex_positive])
57
+ result = regexp.match(tags_string)
58
+ if result.nil?
59
+ test.status.log_exception(Exceptions::TestForcedFailure.new("Positive match should have been found in: #{metadata.tags.join(',')}"))
60
+ end
61
+ end
62
+
63
+ # Validate if provided regex is NOT found within tags
64
+ if @validation_options.key?(:tags_regex_negative)
65
+ tags_string = metadata.tags.join(',')
66
+ regexp = Regexp.new(@validation_options[:tags_regex_negative])
67
+ result = regexp.match(tags_string)
68
+ if !result.nil?
69
+ test.status.log_exception(Exceptions::TestForcedFailure.new("Negative match shouldn't have been found in: #{metadata.tags.join(',')}"))
70
+ end
71
+ end
72
+
73
+ test.status.finalize_run
74
+ @test_reporter.report_end_test(test.status)
75
+ end
76
+ end
77
+
78
+ @test_reporter.report_end_run
79
+
80
+ # Exit application with code that represents status of test run
81
+ Kernel.exit(@test_reporter.run_status.bitmap)
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,282 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ Bundler.require
4
+ rescue
5
+ nil
6
+ end
7
+
8
+
9
+ require 'optparse'
10
+
11
+ require_relative 'config'
12
+ require_relative 'modes/mode_selector'
13
+
14
+ module Moto
15
+ class ParameterParser
16
+
17
+ def self.parse_user_input(argv)
18
+ begin
19
+
20
+ mode_selector = Moto::Modes::ModeSelector.new
21
+
22
+ if argv[0] == '--version'
23
+ mode_selector.version
24
+ elsif argv[0] == 'run' && argv.length > 1
25
+ mode_selector.run(run_parse(argv))
26
+ elsif argv[0] == 'generate' && argv.length > 1
27
+ mode_selector.generate(generate_parse(argv))
28
+ elsif argv[0] == 'validate' && argv.length > 1
29
+ mode_selector.validate(validate_parse(argv))
30
+ else
31
+ show_help
32
+ end
33
+
34
+ rescue SystemExit => e
35
+ Kernel.exit(e.status)
36
+ rescue Exception => e
37
+ puts e.message + "\n\n"
38
+ puts e.backtrace.join("\n")
39
+ end
40
+ end
41
+
42
+ def self.run_parse(argv)
43
+ # Default options
44
+ options = {}
45
+ options[:listeners] = []
46
+ options[:run_name] = nil
47
+ options[:suite_name] = nil
48
+ options[:assignee] = nil
49
+ options[:stop_on] = {error: false, fail: false, skip: false}
50
+
51
+ # Parse arguments
52
+ OptionParser.new do |opts|
53
+ opts.on('-t', '--tests Tests', Array) {|v| options[:tests] = v}
54
+ opts.on('-g', '--tags Tags', Array) {|v| options[:tags] = v}
55
+ opts.on('-f', '--filters Filters', Array) {|v| options[:filters] = v}
56
+ opts.on('-l', '--listeners Listeners', Array) {|v| options[:listeners] = v}
57
+ opts.on('-e', '--environment Environment') {|v| options[:environment] = v}
58
+ opts.on('-r', '--runname RunName') {|v| options[:run_name] = v}
59
+ opts.on('-s', '--suitename SuiteName') {|v| options[:suite_name] = v}
60
+ opts.on('-a', '--assignee Assignee') {|v| options[:assignee] = v}
61
+ opts.on('-c', '--config Config') {|v| options[:config_name] = v}
62
+ opts.on('--threads ThreadCount', Integer) {|v| options[:threads] = v}
63
+ opts.on('--attempts AttemptCount', Integer) {|v| options[:attempts] = v}
64
+ opts.on('--stop-on-error') {options[:stop_on][:error] = true}
65
+ opts.on('--stop-on-fail') {options[:stop_on][:fail] = true}
66
+ opts.on('--stop-on-skip') {options[:stop_on][:skip] = true}
67
+ opts.on('--dry-run') {options[:dry_run] = true}
68
+ end.parse!
69
+
70
+ if options[:tests]
71
+ options[:tests].each do |path|
72
+ path.sub!(%r{\/$}, '') # remove trailing "/"
73
+ end
74
+ end
75
+
76
+ if options[:run_name].nil?
77
+ options[:run_name] = evaluate_name(options[:tests], options[:tags], options[:filters])
78
+ end
79
+
80
+ if options[:environment]
81
+ Moto::Lib::Config.environment = options[:environment]
82
+ else
83
+ puts 'ERROR: Environment is mandatory.'
84
+ Kernel.exit(-1)
85
+ end
86
+
87
+ Moto::Lib::Config.load_configuration(options[:config_name] ? options[:config_name] : 'moto')
88
+
89
+ Moto::Lib::Config.moto[:test_runner][:thread_count] = options[:threads] if options[:threads]
90
+ Moto::Lib::Config.moto[:test_runner][:test_attempt_max] = options[:attempts] if options[:attempts]
91
+ Moto::Lib::Config.moto[:test_runner][:dry_run] = options[:dry_run] if options[:dry_run]
92
+
93
+ return options
94
+ end
95
+
96
+ def self.validate_parse(argv)
97
+ # Default options
98
+ options = {}
99
+ options[:listeners] = []
100
+ options[:run_name] = nil
101
+ options[:suite_name] = nil
102
+ options[:assignee] = nil
103
+
104
+ # Parse arguments
105
+ OptionParser.new do |opts|
106
+ opts.on('-t', '--tests Tests', Array) {|v| options[:tests] = v}
107
+ opts.on('-g', '--tags Tags', Array) {|v| options[:tags] = v}
108
+ opts.on('-f', '--filters Filters', Array) {|v| options[:filters] = v}
109
+ opts.on('-l', '--listeners Listeners', Array) {|v| options[:listeners] = v}
110
+ opts.on('-r', '--runname RunName') {|v| options[:run_name] = v}
111
+ opts.on('-s', '--suitename SuiteName') {|v| options[:suite_name] = v}
112
+ opts.on('-a', '--assignee Assignee') {|v| options[:assignee] = v}
113
+ opts.on('-c', '--config Config') {|v| options[:config_name] = v}
114
+ opts.on('-p', '--tagregexpos RegexPositive') {|v| options[:validator_regex_positive] = v}
115
+ opts.on('-n', '--tagregexneg RegexNegative') {|v| options[:validator_regex_negative] = v}
116
+ opts.on('-h', '--hastags') {|v| options[:validate_has_tags] = v}
117
+ opts.on('-d', '--hasdescription') {|v| options[:validate_has_description] = v}
118
+ opts.on('-w', '--tagwhitelist TagWhitelist', Array) {|v| options[:tag_whitelist] = v}
119
+ end.parse!
120
+
121
+ if options[:tests]
122
+ options[:tests].each do |path|
123
+ path.sub!(%r{\/$}, '') # remove trailing "/"
124
+ end
125
+ end
126
+
127
+ if options[:run_name].nil?
128
+ options[:run_name] = evaluate_name(options[:tests], options[:tags], options[:filters])
129
+ end
130
+
131
+ Moto::Lib::Config.load_configuration(options[:config_name] ? options[:config_name] : 'moto')
132
+
133
+ return options
134
+ end
135
+
136
+ # Generate default name based on input parameters
137
+ def self.evaluate_name(tests, tags, filters)
138
+ name = ''
139
+
140
+ if tests
141
+ name << "Tests: #{tests.join(',')} "
142
+ end
143
+
144
+ if tags
145
+ name << "Tags: #{tags.join(',')} "
146
+ end
147
+
148
+ if filters
149
+ name << "Filters: #{filters.join(',')} "
150
+ end
151
+
152
+ return name
153
+ end
154
+
155
+ # Parses attributes passed to the application when run by 'moto generate'
156
+ def self.generate_parse(argv)
157
+ options = {}
158
+
159
+ OptionParser.new do |opts|
160
+ opts.on('-t', '--test Test') {|v| options[:dir] = v}
161
+ opts.on('-a', '--appname AppName') {|v| options[:app_name] = v}
162
+ opts.on('-b', '--baseclass BaseClass') {|v| options[:base_class] = v}
163
+ opts.on('-f', '--force') {options[:force] = true}
164
+ end.parse!
165
+
166
+ options[:dir] = options[:dir].underscore
167
+
168
+ if options[:app_name].nil?
169
+ options[:app_name] = 'MotoApp'
170
+ end
171
+
172
+ return options
173
+ end
174
+
175
+ def self.show_help
176
+ puts "" "
177
+ Moto (#{Moto::VERSION}) CLI Help:
178
+ moto --version Display current version
179
+
180
+
181
+
182
+ Exemplary usage:
183
+ moto run PARAMTERES
184
+ moto generate PARAMETERS
185
+ moto validate PARAMETERS
186
+
187
+
188
+
189
+ =========
190
+ MOTO RUN:
191
+ =========
192
+ -t, --tests Path of tests to be executed. Root: moto-phq/tests/<TESTS PATH>
193
+ -g, --tags Tags of tests to be executed.
194
+ Use # MOTO_TAGS: TAGNAME in test to assign tag.
195
+ -f, --filters Tags that filter tests passed via -t parameter.
196
+ Only tests in appropriate directory, having all of the specified tags will be executed.
197
+ Use # MOTO_TAGS: TAGNAME1 in test to assign tag.
198
+ Use ~ to filter tests that do not contain specific tag, e.g. ~tag
199
+
200
+
201
+ -e, --environment Mandatory environment. Environment constants and tests parametrized in certain way depend on this.
202
+ -c, --config Name of the config, without extension, to be loaded from MotoApp/config/CONFIG_NAME.rb
203
+ Default: moto (which loads: MotoApp/config/moto.rb)
204
+
205
+
206
+ -l, --listeners Reporters to be used.
207
+ Defaults are Moto::Reporting::Listeners::ConsoleDots, Moto::Reporting::Listeners::JunitXml
208
+ One reporter that is always used: Moto::Reporting::Listeners::KernelCode
209
+ -s, --suitename Name of the test suite to which should aggregate the results of current test run.
210
+ Required when specifying MotoWebUI as one of the listeners.
211
+ -r, --runname Name of the test run to which everything will be reported when using MotoWebUI.
212
+ Default: Value of -g or -t depending on which one was specified.
213
+ -a, --assignee ID of a person responsible for current test run.
214
+ Can have a default value set in config/webui section.
215
+ --threads Thread count. Run tests in parallel.
216
+ --attempts Attempt count. Max number of test execution times if failed.
217
+
218
+ --stop-on-error Moto will stop test execution when an error is encountered in test results
219
+ --stop-on-fail Moto will stop test execution when a failure is encountered in test results
220
+ --stop-on-skip Moto will stop test execution when a skip is encountered in test results
221
+ --dry-run Moto will list all test cases which would be run with provided arguments
222
+
223
+
224
+
225
+ ==============
226
+ MOTO VALIDATE:
227
+ ==============
228
+ -t, --tests Path of tests to be validate. Root: moto-phq/tests/<TESTS PATH>
229
+ -g, --tags Tags of tests to be validated.
230
+ Use # MOTO_TAGS: TAGNAME in test to assign tag.
231
+ -f, --filters Tags that filter tests passed via -t parameter.
232
+ Only tests in appropriate directory, having all of the specified tags will be executed.
233
+ Use # MOTO_TAGS: TAGNAME1 in test to assign tag.
234
+ Use ~ to filter tests that do not contain specific tag, e.g. ~tag
235
+
236
+
237
+ -c, --config Name of the config, without extension, to be loaded from MotoApp/config/CONFIG_NAME.rb
238
+ Default: moto (which loads: MotoApp/config/moto.rb)
239
+
240
+
241
+ -l, --listeners Reporters to be used.
242
+ Defaults are Moto::Reporting::Listeners::ConsoleDots, Moto::Reporting::Listeners::JunitXml
243
+ One reporter that is always used: Moto::Reporting::Listeners::KernelCode
244
+ -s, --suitename Name of the test suite to which should aggregate the results of current test run.
245
+ Required when specifying MotoWebUI as one of the listeners.
246
+ -r, --runname Name of the test run to which everything will be reported when using MotoWebUI.
247
+ Default: Value of -g or -t depending on which one was specified.
248
+ -a, --assignee ID of a person responsible for current test run.
249
+ Can have a default value set in config/webui section.
250
+
251
+ -p, --tagregexpos Regex which will be matched against tags joined on ','.
252
+ Validation will pass if there is a match.
253
+ -n, --tagregexneg Regex which will be matched against tags joined on ','.
254
+ Validation will pass if there is no match.
255
+ -h, --hastags Validates if tests have #MOTO_TAGS with any tags.
256
+ -d, --hasdescription Validates if tests have #DESC with any text.
257
+ -w, --tagwhitelist Only tags from the whitelist will be allowed.
258
+ Provide in format: tag1,tag2,tag3 etc.
259
+
260
+
261
+
262
+ ==============
263
+ MOTO GENERATE:
264
+ ==============
265
+ -t, --test Path and name of the test to be created.
266
+ Examples:
267
+ -ttest_name will create MotoApp/tests/test_name/test_name.rb
268
+ -tdir/test_name will create MotoApp/tests/dir/test_name/test_name.rb
269
+ -a, --appname Name of the application. Will be also used as topmost module in test file.
270
+ Default: MotoApp
271
+ -b, --baseclass File, without extension, with base class from which test will derive. Assumes one class per file.
272
+ Examples:
273
+ -btest_base will use the file in MotoApp/lib/test/test_base.rb
274
+ -bsubdir/test_base will use the file in MotoApp/lib/test/subdir/test_base.rb
275
+ By default class will derive from Moto::Test
276
+ -f, --force Forces generator to overwrite previously existing class file in specified location.
277
+ You have been warned.
278
+ " ""
279
+ end
280
+
281
+ end
282
+ end