moto 0.9.11 → 0.9.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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