mesa_test 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mesa_test +221 -0
  3. data/lib/mesa_test.rb +996 -0
  4. metadata +89 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a51f1b2345eafea6517e911afc3aabaaacd45356
4
+ data.tar.gz: b3e90e330c892fe05a0fc47f8d0c338f35f8085e
5
+ SHA512:
6
+ metadata.gz: 2d775bcc97dd0d959dea7db95cb454163a402b94fb9f73cd84509ed9c596303a9e456ddfccc6ece2f9e8a352f6309cdecb57ab0870c78ef0457b6812afc506d2
7
+ data.tar.gz: cab50479ae7d1cefd42359a4de2e0bfc845d003d7f0533677710f57263e59778fa648b34e54df849389bef1d60e2ed679c1b00a9ee12391940cec3e598a602a6
data/bin/mesa_test ADDED
@@ -0,0 +1,221 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'mesa_test'
4
+ require 'thor'
5
+
6
+ class MesaTest < Thor
7
+
8
+ desc "setup [CONFIG_FILE]", "Setup MesaTestHub config file."
9
+ long_desc <<-LONGDESC
10
+ If optional CONFIG_FILE is provided, search for that file and load it
11
+ to provide default values for configuration. If not provided, the default
12
+ file, ~/.mesa_test.yml, is used. The resulting config file is saved to
13
+ the same location it was read from.
14
+ LONGDESC
15
+ def setup(config_file=File.join(ENV['HOME'], '.mesa_test.yml'))
16
+ MesaTestSubmitter.new_from_config(config_file: config_file)
17
+ end
18
+
19
+
20
+
21
+ desc "test_one MESA_DIR TEST_CASE", "run, check, and submit one test case"
22
+ long_desc <<-LONGDESC
23
+ Run and check TEST_CASE, which resides in MESA_DIR/star/test_suite. Then
24
+ report results to MesaTestHub.
25
+
26
+ With --force option, skip confirmation of computer details, assuming values
27
+ in ~/.mesa_test.yml are correct.
28
+
29
+ With --log option, save yml file of test results in test directory, on
30
+ by default. Shut off with --no-log.
31
+
32
+ With --submit option, upload results to MESATestHub. By default, this is on.
33
+ To run without submission, use --no-submit.
34
+ LONGDESC
35
+ option :force, type: :boolean
36
+ option :log, type: :boolean, default: true
37
+ option :submit, type: :boolean, default: true
38
+ def test_one(mesa_dir, test_case_name)
39
+
40
+ if options[:submit]
41
+ s = MesaTestSubmitter.new_from_config
42
+ unless options[:force]
43
+ unless s.confirm_computer_data
44
+ s.setup
45
+ end
46
+ end
47
+
48
+ check_user_and_computer s
49
+ end
50
+
51
+ # set up and check mesa directory (doesn't really check to see if it's
52
+ # installed, just to see if it has a version number and a test suite
53
+ # directory)
54
+ m = Mesa.new mesa_dir: mesa_dir
55
+ raise MesaDirError, "Invalid MESA_DIR: #{mesa_dir}. Please download and " +
56
+ "install a valid MESA version or provide the path to one." unless
57
+ m.installed?
58
+ m.load_test_source_data
59
+
60
+ # make sure the test case is valid
61
+ t = m.find_test_case test_case_name: test_case_name
62
+ if t.nil?
63
+ msg = "No such test case, #{test_case_name} found in any of "
64
+ msg << MesaTestCase.modules.map do |mod|
65
+ File.join(m.test_suite_dir(mod: mod), 'do1_test_source')
66
+ end.join(' or ')
67
+ msg << '.'
68
+ raise TestCaseDirError, msg
69
+ end
70
+
71
+ # clean and run test
72
+ t.clean
73
+ t.do_one
74
+
75
+ # log results
76
+ t.log_results if options[:log]
77
+
78
+ # submit results
79
+ if options[:submit]
80
+ print 'Submitting results to ' + s.base_uri + '... '
81
+ s.submit(t)
82
+ puts "Done."
83
+ puts ""
84
+ end
85
+ end
86
+
87
+
88
+ desc "test_all MESA_DIR", "run, check, and submit all test cases"
89
+ long_desc <<-LONGDESC
90
+ Run and check all test cases residing in MESA_DIR/star/test_suite. Then
91
+ report results to MesaTestHub. Specifically, runs and checks all tests
92
+ detailed in MESA_DIR/star/test_suite/do1_test_source.
93
+
94
+ With --force option, skip confirmation of computer details, assuming values
95
+ in ~/.mesa_test.yml are correct. Only relevant if --submit option is on
96
+ (by default it is).
97
+
98
+ With --log option, save yml file of test results in test directory and a
99
+ summary in the test suite directory. On by default. Shut off with --no-log.
100
+
101
+ With --submit option, upload results to MESATestHub. By default, this is on.
102
+ To run without submission, use --no-submit.
103
+ LONGDESC
104
+ option :force, type: :boolean
105
+ option :log, type: :boolean, default: true
106
+ option :submit, type: :boolean, default: true
107
+ def test_all(mesa_dir)
108
+
109
+ if options[:submit]
110
+ s = MesaTestSubmitter.new_from_config
111
+ unless options[:force]
112
+ unless s.confirm_computer_data
113
+ s.setup
114
+ end
115
+ end
116
+ check_user_and_computer s
117
+ end
118
+
119
+ # set up and check mesa directory (doesn't really check to see if it's
120
+ # installed, just to see if it has a version number and a test suite
121
+ # directory)
122
+ m = Mesa.new mesa_dir: mesa_dir
123
+ raise MesaDirError, "Invalid MESA_DIR: #{mesa_dir}. Please download and " +
124
+ "install a valid MESA version or provide the path to one." unless
125
+ m.installed?
126
+ m.load_test_source_data
127
+
128
+ # run all tests
129
+ m.each_test_run_and_diff(log_results: options[:log])
130
+
131
+ # submit all tests
132
+ s.submit_all(m) if options[:submit]
133
+ end
134
+
135
+
136
+ desc "install VERSION_NUMBER MESA_DIR", 'download and install mesa release '+
137
+ 'VERSION_NUMBER to directory MESA_DIR'
138
+ long_desc <<-LONGDESC
139
+ Calls to svn to install mesa release VERSION_NUMBER into the directory
140
+ MESA_DIR. Basically just an svn checkout followed by going into the directory
141
+ and running ./clean and ./install. SDK or compilers must be set up prior.
142
+ Does not affect the user's MESA_DIR or other environment variables.
143
+ LONGDESC
144
+ def install(version, mesa_dir)
145
+ m = Mesa.download(version_number:version, new_mesa_dir: mesa_dir)
146
+ m.clean
147
+ m.install
148
+ end
149
+
150
+
151
+ desc "install_and_run_all VERSION_NUMBER MESA_DIR", 'download and install ' +
152
+ 'mesa release VERSION_NUMBER to directory MESA_DIR and run/submit all tests'
153
+ long_desc <<-LONGDESC
154
+ Calls to svn to install mesa release VERSION_NUMBER into the directory
155
+ MESA_DIR. Basically just an svn checkout followed by going into the directory
156
+ and running ./clean and ./install. SDK or compilers must be set up prior.
157
+ Once installation is complete, run the test suite, and report results to
158
+ MesaTestHub. Does not affect the user's MESA_DIR or other environmnet
159
+ variables. This is bascially a shortcut for running
160
+
161
+ `mesa_test install SOME_VERSION SOME_DIR`
162
+
163
+ followed by
164
+
165
+ `mesa_test test_all SOME_DIR`
166
+
167
+ Use flag --destroy to self destruct MESA_DIR after successful test
168
+ submission. Essentially does rm -rf MESA_DIR after the test suite. Useful
169
+ for automated testing without piling up disk space.
170
+
171
+ Use flag --force to skip confirmation of computer details if they can be
172
+ read from ~/.mesa_test.yml.
173
+ LONGDESC
174
+ option :destroy, type: :boolean
175
+ option :force, type: :boolean
176
+ def install_and_test_all(version, mesa_dir)
177
+ s = MesaTestSubmitter.new_from_config
178
+ unless options[:force]
179
+ unless s.confirm_computer_data
180
+ s.setup
181
+ end
182
+ end
183
+
184
+ m = Mesa.download(version_number:version, new_mesa_dir: mesa_dir)
185
+ m.clean
186
+ m.install
187
+
188
+ raise MesaDirError, "Invalid MESA_DIR: #{mesa_dir}. Please download and " +
189
+ "install a valid MESA version or provide the path to one." unless
190
+ m.installed?
191
+ m.load_test_source_data
192
+
193
+ # run all tests
194
+ m.each_test_run_and_diff
195
+
196
+ # submit all tests
197
+ successfully_submitted = s.submit_all(m)
198
+
199
+ # if requested, and if submission successful, destroy the directory
200
+ if successfully_submitted and options[:destroy]
201
+ m.destroy
202
+ end
203
+ end
204
+
205
+ private
206
+ def check_user_and_computer(submitter)
207
+ computer_check = submitter.confirm_computer
208
+ if computer_check['valid']
209
+ puts computer_check['message']
210
+ else
211
+ $stderr.puts computer_check['message']
212
+ exit 1
213
+ end
214
+ end
215
+
216
+
217
+ end
218
+
219
+
220
+ # actually start the CLI
221
+ MesaTest.start(ARGV)
data/lib/mesa_test.rb ADDED
@@ -0,0 +1,996 @@
1
+ require 'fileutils'
2
+ require 'socket'
3
+ require 'os'
4
+ require 'yaml'
5
+ require 'uri'
6
+ require 'net/http'
7
+ require 'net/https'
8
+ require 'thor'
9
+ require 'json'
10
+
11
+ class MesaDirError < StandardError; end
12
+ class TestCaseDirError < StandardError; end
13
+ class InvalidDataType < StandardError; end
14
+
15
+ class MesaTestSubmitter
16
+
17
+ # set up config file for computer
18
+ def setup
19
+ update do |s|
20
+ puts "This wizard will guide you through setting up a computer profile
21
+ and default data for test case submissions to MESATestHub. You
22
+ will be able to confirm entries at the end. Default/current values are always
23
+ shown in parentheses at the end of a prompt. Pressing enter will accept the
24
+ default values.
25
+
26
+ To submit to MESATestHub, a valid computer name, email address, and password
27
+ are all required. All other data are useful, but optional. Any data
28
+ transferred to MESATestHub will be encrypted via HTTPS, but be warned that your
29
+ e-mail and password will be stored in plain text.
30
+ "
31
+ # Get computer name
32
+ response = shell.ask("What is the name of this computer (required)? "+
33
+ "(#{s.computer_name}):", color = :blue)
34
+ s.computer_name = response unless response.empty?
35
+
36
+ # Get user name
37
+ response = shell.ask "What is the name of the operator of this " +
38
+ "computer? (#{s.user_name}):", color = :blue
39
+ s.user_name = response unless response.empty?
40
+
41
+ # Get user e-mail
42
+ response = shell.ask "What is the email you can be reached " +
43
+ "at (required)? (#{s.email}):", color = :blue
44
+ s.email = response unless response.empty?
45
+
46
+ # Get user password
47
+ response = shell.ask "What is the password associated with the email " +
48
+ "#{s.email} (required)? (#{s.password})", color = :blue
49
+ s.password = response unless response.empty?
50
+
51
+ # Get platform information
52
+ response = shell.ask "What is the platform of this computer (eg. " +
53
+ "macOS, Ubuntu)? (#{s.platform}):", color = :blue
54
+ s.platform = response unless response.empty?
55
+ response = shell.ask "What is the version of the platform (eg. 10.13, "+
56
+ "16.04)? (#{s.platform_version}):", color = :blue
57
+ s.platform_version = response unless response.empty?
58
+
59
+ # Get processor information
60
+ response = shell.ask "What type of processor does this computer have " +
61
+ "(eg. 3.1 GHz Intel i7)? (#{s.processor}):", color = :blue
62
+ s.processor = response unless response.empty?
63
+
64
+ # Get ram information
65
+ response = shell.ask "How much RAM (in integer GB) does this computer " +
66
+ "have (eg. 8)? (#{s.ram_gb}) ", color = :blue
67
+ s.ram_gb = response.to_i unless response.empty?
68
+
69
+ # Get compiler information
70
+ response = shell.ask "Which compiler are you using? (#{s.compiler}):",
71
+ color = :blue, limited_to: ['', 'SDK', 'gfortran', 'ifort']
72
+ s.compiler = response unless response.empty?
73
+
74
+ # Get compiler version
75
+ response = shell.ask "What version of the compiler (eg. 20170921 or " +
76
+ "7.2.0)? (#{s.compiler_version}): ", color = :blue
77
+ s.compiler_version = response unless response.empty?
78
+
79
+ # Confirm save location
80
+ response = shell.ask "This will be saved in #{s.config_file}. Press " +
81
+ "enter to accept or enter a new location:", color = :blue, path: true
82
+ s.config_file = response unless response.empty?
83
+ end
84
+
85
+ # Confirm data. If not confirmed, restart whole wizard.
86
+ if confirm_computer_data
87
+ save_computer_data
88
+ else
89
+ puts "Restarting wizard.\n"
90
+ setup
91
+ end
92
+ end
93
+
94
+ def self.new_from_config(
95
+ config_file: File.join(ENV['HOME'], '.mesa_test.yml'), force_setup: false,
96
+ base_uri: 'https://mesa-test-hub.herokuapp.com')
97
+ new_submitter = self.new(config_file: config_file, base_uri: base_uri)
98
+ if force_setup
99
+ new_submitter.setup
100
+ elsif not File.exist? config_file
101
+ puts "No such config file #{config_file}. Starting setup wizard."
102
+ new_submitter.setup
103
+ end
104
+ new_submitter.load_computer_data
105
+ return new_submitter
106
+ end
107
+
108
+ attr_accessor :computer_name, :user_name, :email, :password, :platform,
109
+ :platform_version, :processor, :ram_gb, :compiler, :compiler_version,
110
+ :config_file, :base_uri
111
+
112
+ attr_reader :shell
113
+
114
+ # many defaults are set in body
115
+ def initialize(computer_name: nil, user_name: nil, email: nil, platform: nil,
116
+ platform_version: nil, processor: nil, ram_gb: nil, compiler: nil,
117
+ compiler_version: nil, config_file: nil, base_uri: nil)
118
+ @computer_name = computer_name || Socket.gethostname.scan(/^[^\.]+\.?/)[0]
119
+ @computer_name.chomp!('.') if @computer_name
120
+ @user_name = user_name || (ENV['USER'] || ENV['USERNAME'])
121
+ @email = email || ''
122
+ @password = password || ''
123
+ @platform = platform
124
+ if @platform.nil?
125
+ @platform = if OS.osx?
126
+ 'macOS'
127
+ elsif OS.linux?
128
+ 'Linux'
129
+ else
130
+ ''
131
+ end
132
+ end
133
+ @platform_version = platform_version || ''
134
+ @processor = processor || ''
135
+ @ram_gb = ram_gb || 0
136
+ @compiler = compiler || 'SDK'
137
+ @compiler_version = compiler_version || ''
138
+ @config_file = config_file || File.join(ENV['HOME'], '.mesa_test.yml')
139
+ @base_uri = base_uri
140
+
141
+ # set up thor-proof way to get responses from user. Thor hijacks the
142
+ # gets command, so we have to use its built-in "ask" method, which is
143
+ # actually more useful
144
+ @shell = Thor::Shell::Color.new
145
+ yield self if block_given?
146
+ end
147
+
148
+ # ease setup of a blank/default submitter
149
+ def update
150
+ yield self if block_given?
151
+ end
152
+
153
+ def confirm_computer_data
154
+ puts "Ready to submit the following data:"
155
+ puts "-------------------------------------------------------"
156
+ puts "Computer Name #{computer_name}"
157
+ puts "User Name #{user_name}"
158
+ puts "User email #{email}"
159
+ puts "Password ***********"
160
+ puts "Platform #{platform} #{platform_version}"
161
+ puts "Processor #{processor}"
162
+ puts "RAM #{ram_gb} GB"
163
+ puts "Compiler #{compiler} #{compiler_version}"
164
+ puts "Config location #{config_file}"
165
+ puts "-------------------------------------------------------"
166
+ puts ""
167
+ shell = Thor.new
168
+ response = shell.ask "Is this correct? (y/Y = Yes, anything else = No):"
169
+ if response.strip.downcase == 'y'
170
+ return true
171
+ else
172
+ return false
173
+ end
174
+ end
175
+
176
+ # For one "computer" on the web server, and for [subjective] consistency
177
+ # reasons, the platform, processor, and RAM cannot be changed! If you
178
+ # change platforms (i.e. switch from mac to linux, or change between linux
179
+ # flavors), you should create a new computer account. Similarly, create new
180
+ # computer accounts if you change your RAM or processor. You do not need
181
+ # to change computers if you upgrade your platform (macOS 10.12 -> 10.13) or
182
+ # if you try different compilers
183
+ #
184
+ # Note this is NOT checked! The server really only uses the test-by-test
185
+ # quantities (platform version, compiler, complier version) and the
186
+ # computer name. Once the computer is found (by the name) all the other
187
+ # data is assumed to be fixed. The others... probably shouldn't be here,
188
+ # but remain so you can confirm that the computer on the web server is the
189
+ # same one you think you are working with locally.
190
+ def save_computer_data
191
+ data_hash = {
192
+ computer_name: computer_name,
193
+ user_name: user_name,
194
+ email: email,
195
+ password: password,
196
+ platform: platform,
197
+ processor: processor,
198
+ ram_gb: ram_gb,
199
+ platform_version: platform_version,
200
+ compiler: compiler,
201
+ compiler_version: compiler_version
202
+ }
203
+ File.open(config_file, 'w') { |f| f.write(YAML.dump(data_hash))}
204
+ end
205
+
206
+ def load_computer_data
207
+ data_hash = YAML::load(File.read(config_file))
208
+ @computer_name = data_hash[:computer_name]
209
+ @user_name = data_hash[:user_name]
210
+ @email = data_hash[:email]
211
+ @password = data_hash[:password]
212
+ @platform = data_hash[:platform]
213
+ @processor = data_hash[:processor]
214
+ @ram_gb = data_hash[:ram_gb]
215
+ @platform_version = data_hash[:platform_version]
216
+ @compiler = data_hash[:compiler]
217
+ @compiler_version = data_hash[:compiler_version]
218
+ end
219
+
220
+ # create and return hash of parameters for a TestInstance submission
221
+ def submit_params(test_case)
222
+ res = {
223
+ test_case: test_case.test_name,
224
+ computer: computer_name,
225
+ email: email,
226
+ password: password,
227
+ runtime_seconds: test_case.runtime_seconds,
228
+ mesa_version: test_case.mesa_version,
229
+ passed: test_case.passed?,
230
+ compiler: compiler,
231
+ compiler_version: compiler_version,
232
+ omp_num_threads: test_case.test_omp_num_threads,
233
+ }
234
+
235
+ # enter in test-specific data
236
+ test_case.data_names.each do |data_name|
237
+ unless test_case.data[data_name].nil?
238
+ res[data_name] = test_case.data[data_name]
239
+ end
240
+ end
241
+ res
242
+ end
243
+
244
+ def confirm_computer
245
+ uri = URI.parse(base_uri + '/check_computer.json')
246
+ https = Net::HTTP.new(uri.hostname, uri.port)
247
+ https.use_ssl = true if base_uri.include? 'https'
248
+
249
+ request = Net::HTTP::Post.new(uri,
250
+ initheader = { 'Content-Type' => 'application/json' })
251
+ request.body = {
252
+ email: email,
253
+ password: password,
254
+ computer_name: computer_name
255
+ }.to_json
256
+
257
+ JSON.load(https.request(request).body).to_hash
258
+ end
259
+
260
+ # attempt to post to MesaTestHub with test_case parameters
261
+ # returns true if the id is in the returned JSON (indicating success)
262
+ # otherwise returns false (maybe failed in authorization or in finding
263
+ # computer or test case) No error thrown for failure, though.
264
+ def submit(test_case, verbose=false)
265
+ uri = URI.parse(base_uri + '/test_instances/submit.json')
266
+ https = Net::HTTP.new(uri.hostname, uri.port)
267
+ https.use_ssl = true if base_uri.include? 'https'
268
+
269
+ request = Net::HTTP::Post.new(uri,
270
+ initheader = {'Content-Type' => 'application/json'})
271
+ request.body = submit_params(test_case).to_json
272
+
273
+ response = https.request request
274
+ puts JSON.load(response.body).to_hash if verbose
275
+ not response.kind_of? Net::HTTPUnprocessableEntity
276
+ end
277
+
278
+ def submit_all(mesa)
279
+ uri=URI.parse(base_uri + '/test_instances/submit.json')
280
+ submitted_cases = []
281
+ unsubmitted_cases = []
282
+ mesa.test_names.each do |mod, test_names|
283
+ test_names.each do |test_name|
284
+ # get at test case
285
+ test_case = mesa.test_cases[mod][test_name]
286
+ # try to submit and note if it does or doesn't successfully submit
287
+ submitted = false
288
+ unless test_case.outcome == :not_tested
289
+ submitted = submit(test_case)
290
+ end
291
+
292
+ if submitted
293
+ submitted_cases << test_name
294
+ else
295
+ unsubmitted_cases << test_name
296
+ end
297
+ end
298
+ end
299
+ puts ''
300
+ if submitted_cases.length > 0
301
+ shell.say "Submitted the following cases:", color = :green
302
+ puts submitted_cases.join("\n")
303
+ else
304
+ shell.say "Did not successfully submit any cases.", color = :red
305
+ end
306
+ if unsubmitted_cases.length > 0
307
+ puts "\n\n\n"
308
+ shell.say "Failed to submit the following cases:", color = :red
309
+ puts unsubmitted_cases.join("\n")
310
+ end
311
+ # return true if all cases were submitted
312
+ submitted_cases.length == mesa.test_names.length
313
+ end
314
+
315
+ end
316
+
317
+
318
+ class Mesa
319
+ attr_reader :mesa_dir, :test_data, :test_names, :test_cases, :shell
320
+
321
+ def self.download(version_number: nil, new_mesa_dir: nil)
322
+ new_mesa_dir ||= File.join(ENV['HOME'], 'mesa-test-r' + version_number.to_s)
323
+ success = system("svn co -r #{version_number} "+
324
+ "svn://svn.code.sf.net/p/mesa/code/trunk #{new_mesa_dir}")
325
+ unless success
326
+ raise MesaDirError, "Encountered a problem in downlaod mesa " +
327
+ "revision #{version_number}."
328
+ end
329
+ Mesa.new(mesa_dir: new_mesa_dir)
330
+ end
331
+
332
+ def initialize(mesa_dir: ENV['MESA_DIR'])
333
+ @mesa_dir = mesa_dir
334
+
335
+ # these get populated by calling #load_test_data
336
+ @test_data = {}
337
+ @test_names = {}
338
+ @test_cases = {}
339
+
340
+ # way to output colored text
341
+ @shell = Thor::Shell::Color.new
342
+ end
343
+
344
+ # read version number from $MESA_DIR/data/version_number
345
+ def version_number
346
+ contents = ''
347
+ File.open(File.join(mesa_dir, 'data', 'version_number'), 'r') do |f|
348
+ contents = f.read
349
+ end
350
+ contents.strip.to_i
351
+ end
352
+
353
+ def clean
354
+ visit_and_check mesa_dir, MesaDirError, "Encountered a problem in " +
355
+ "running `clean` in #{mesa_dir}." do
356
+ puts './clean'
357
+ system('./clean')
358
+ end
359
+ self
360
+ end
361
+
362
+ def install
363
+ visit_and_check mesa_dir, MesaDirError, "Encountered a problem in " +
364
+ "running `install` in #{mesa_dir}." do
365
+ puts './install'
366
+ system('./install')
367
+ end
368
+ self
369
+ end
370
+
371
+ def destroy
372
+ FileUtils.rm_rf mesa_dir
373
+ end
374
+
375
+
376
+ ## TEST SUITE METHODS
377
+
378
+ def check_mod(mod)
379
+ unless MesaTestCase.modules.include? mod
380
+ raise TestCaseDirError, "Invalid module: #{mod}. Must be one of: " +
381
+ MesaTestCase.modules.join(', ')
382
+ end
383
+ end
384
+
385
+ def test_suite_dir(mod: nil)
386
+ check_mod mod
387
+ File.join(mesa_dir, mod.to_s, 'test_suite')
388
+ end
389
+
390
+ # load data from the `do1_test_source` file that gets used in a lot of
391
+ # testing
392
+ def load_test_source_data(mod: :all)
393
+ # allow for brainless loading of all module data
394
+ if mod == :all
395
+ MesaTestCase.modules.each do |this_mod|
396
+ load_test_source_data(mod: this_mod)
397
+ end
398
+ else
399
+ check_mod mod
400
+ # load data from the source file
401
+ source_lines = IO.readlines(File.join(test_suite_dir(mod: mod),
402
+ 'do1_test_source'))
403
+
404
+ # initialize data hash to empty hash and name array to empty array
405
+ @test_data[mod] = {}
406
+ @test_names[mod] = []
407
+ @test_cases[mod] = {}
408
+
409
+ # read through each line and find four data, name, success string, final
410
+ # model name, and photo. Either of model name and photo can be "skip"
411
+ source_lines.each do |line|
412
+ no_skip = /^do_one (.+) "([^"]*)" "([^"]+)" (x?\d+)/
413
+ one_skip = /^do_one (.+) "([^"]*)" "([^"]+)" skip/
414
+ two_skip = /^do_one (.+) "([^"]*)" skip skip/
415
+ found_test = false
416
+ if line =~ no_skip
417
+ found_test = true
418
+ @test_data[mod][$1] = {success_string: $2, final_model: $3,
419
+ photo: $4}
420
+ elsif line =~ one_skip
421
+ found_test = true
422
+ @test_data[mod][$1] = {success_string: $2, final_model: $3,
423
+ photo: nil}
424
+ elsif line =~ two_skip
425
+ found_test = true
426
+ @test_data[mod][$1] = {success_string: $2, final_model: nil,
427
+ photo: nil}
428
+ end
429
+
430
+ if found_test
431
+ @test_names[mod] << $1 unless @test_names.include? $1
432
+ end
433
+ end
434
+
435
+ # make MesaTestCase objects accessible by name
436
+ @test_names[mod].each do |test_name|
437
+ data = @test_data[mod][test_name]
438
+ @test_cases[mod][test_name] = MesaTestCase.new(test: test_name,
439
+ mesa: self, success_string: data[:success_string], mod: mod,
440
+ final_model: data[:final_model], photo: data[:photo])
441
+ end
442
+ end
443
+ end
444
+
445
+ def find_test_case(test_case_name: nil, mod: :all)
446
+ if mod == :all
447
+ # look through all loaded modules for desired test case name, return
448
+ # FIRST found (assuming no name duplication across modules)
449
+ @test_names.each do |mod, names|
450
+ if names.include? test_case_name
451
+ return @test_cases[mod][test_case_name]
452
+ end
453
+ end
454
+ # didn't find any matches, return nil
455
+ return nil
456
+ else
457
+ # module specified; check it and return the proper test case (may be nil
458
+ # if the test case doesn't exist)
459
+ check_mod mod
460
+ @test_cases[mod][test_case_name]
461
+ end
462
+ end
463
+
464
+ # based off of `$MESA_DIR/star/test_suite/each_test_run_and_diff` from
465
+ # revision 10000
466
+ def each_test_clean(mod: :all)
467
+ if mod == :all
468
+ MesaTestCase.modules.each { |this_mod| each_test_clean mod: this_mod }
469
+ else
470
+ check_mod mod
471
+ test_names[mod].each do |test_name|
472
+ test_cases[mod][test_name].clean
473
+ end
474
+ end
475
+ end
476
+
477
+ def each_test_run_and_diff(mod: :all, log_results: false)
478
+ each_test_clean(mod: mod)
479
+
480
+ if mod == :all
481
+ MesaTestCase.modules.each do
482
+ |this_mod| each_test_run_and_diff(mod: this_mod,
483
+ log_results: log_results)
484
+ end
485
+ else
486
+ test_names[mod].each do |test_name|
487
+ test_cases[mod][test_name].do_one
488
+ test_cases[mod][test_name].log_results if log_results
489
+ end
490
+ log_summary if log_results
491
+ end
492
+ end
493
+
494
+ # note that this only changes MESA_DIR for subprocesses launched from ruby
495
+ # the old value of MESA_DIR will persist after the ruby process ends
496
+ def set_mesa_dir
497
+ ENV['MESA_DIR'] = mesa_dir
498
+ end
499
+
500
+ def installed?
501
+ check_mesa_dir
502
+ end
503
+
504
+ private
505
+
506
+ # verify that mesa_dir is valid by checking for version number and test_suite
507
+ # directory
508
+ def check_mesa_dir
509
+ res = File.exist?(File.join(mesa_dir, 'data', 'version_number'))
510
+ MesaTestCase.modules.each do |mod|
511
+ res = res and File.directory? test_suite_dir(mod: mod)
512
+ end
513
+ res
514
+ end
515
+
516
+ def log_summary(mod: :all)
517
+ if mod == :all
518
+ MesaTestCase.modules.each do |this_mod|
519
+ log_summary(mod: this_mod)
520
+ end
521
+ else
522
+ check_mod mod
523
+ res = []
524
+ test_names[mod].each do |test_name|
525
+ test_case = test_cases[mod][test_name]
526
+ res << {
527
+ test_name: test_case.test_name,
528
+ outcome: test_case.outcome,
529
+ failure_type: test_case.failure_type,
530
+ success_type: test_case.success_type,
531
+ runtime_seconds: test_case.runtime_seconds,
532
+ omp_num_threads: test_case.test_omp_num_threads,
533
+ mesa_version: test_case.mesa_version
534
+ }
535
+ end
536
+ summary_file = File.join(test_suite_dir(mod: mod), 'test_summary.yml')
537
+ File.open(summary_file, 'w') do |f|
538
+ f.write(YAML::dump(res))
539
+ end
540
+ end
541
+ end
542
+ end
543
+
544
+
545
+ class MesaTestCase
546
+ attr_reader :test_name, :mesa_dir, :mesa, :success_string, :final_model,
547
+ :failure_msg, :success_msg, :photo, :runtime_seconds,
548
+ :test_omp_num_threads, :mesa_version, :shell
549
+ attr_accessor :data_names, :data_types, :failure_type, :success_type,
550
+ :outcome
551
+
552
+ def self.modules
553
+ [:star, :binary]
554
+ end
555
+
556
+ def initialize(test: nil, mesa: nil, success_string: '',
557
+ final_model: 'final.mod', photo: nil, mod: nil)
558
+ @test_name = test
559
+ @mesa_dir = mesa.mesa_dir
560
+ @mesa = mesa
561
+ @mesa_version = mesa.version_number
562
+ @success_string = success_string
563
+ @final_model = final_model
564
+ @photo = photo
565
+ @failure_type = nil
566
+ @success_type = nil
567
+ @outcome = :not_tested
568
+ @runtime_seconds = 0
569
+ @test_omp_num_threads = 1
570
+ unless MesaTestCase.modules.include? mod
571
+ raise TestCaseDirError, "Invalid module: #{mod}. Must be one of: " +
572
+ MesaTestCase.modules.join(', ')
573
+ end
574
+ @mod = mod
575
+ @failure_msg = {
576
+ run_test_string: "#{test_name} failed: does not match test string",
577
+ run_checksum: "#{test_name} run failed: checksum for #{final_model} " +
578
+ "does not match after ./rn",
579
+ run_diff: "#{test_name} run failed: diff #{final_model} " +
580
+ "final_check.mod after ./rn",
581
+ photo_file: "#{test_name} restart failed: #{photo} does not exist",
582
+ photo_checksum: "#{test_name} restart failed: checksum for " +
583
+ "#{final_model} does not match after ./re",
584
+ photo_diff: "#{test_name} restart failed: diff #{final_model} " +
585
+ "final_check.mod after ./re"
586
+ }
587
+ @success_msg = {
588
+ run_test_string: "#{test_name} run: found test string: " +
589
+ "'#{success_string}'",
590
+ run_checksum: "#{test_name} run: checksum for #{final_model} matches " +
591
+ "after ./rn",
592
+ photo_checksum: "#{test_name} restart: checksum for #{final_model} " +
593
+ "matches after ./re #{photo}"
594
+ }
595
+
596
+
597
+ # validate stuff
598
+ check_mesa_dir
599
+ check_test_case
600
+
601
+ @data = {}
602
+ @data_names = []
603
+
604
+ # way to output colored text to shell
605
+ @shell = Thor::Shell::Color.new
606
+ end
607
+
608
+ def passed?
609
+ if @outcome == :pass
610
+ true
611
+ elsif @outcome == :fail
612
+ false
613
+ else
614
+ raise TestCaseDirError, "Cannot determine pass/fail status of " +
615
+ "#{test_name} yet."
616
+ end
617
+ end
618
+
619
+ def test_suite_dir
620
+ mesa.test_suite_dir(mod: @mod)
621
+ end
622
+
623
+ def test_case_dir
624
+ File.join(test_suite_dir, test_name)
625
+ end
626
+
627
+ def add_datum(datum_name, datum_type)
628
+ raise InvalidDataType, "Invalid data type: #{datum_type}. Must be one of "+
629
+ data_types.join(', ') + '.' unless data_types.include? datum_type.to_sym
630
+ @data[datum_name] = datum_type
631
+ @data_names << datum_name
632
+ end
633
+
634
+ def omp_num_threads
635
+ return ENV['OMP_NUM_THREADS'].to_i || 1
636
+ end
637
+
638
+ # based on $MESA_DIR/star/test_suite/each_test_clean, revision 10000
639
+ def clean
640
+ shell.say("cleaning #{test_name}", color = :blue)
641
+ puts ''
642
+ check_mesa_dir
643
+ check_test_case
644
+ in_dir do
645
+ puts "./clean"
646
+ unless system('./clean')
647
+ raise TestCaseDirError, "Encountered an error while running ./clean " +
648
+ "in #{Dir.getwd}."
649
+ end
650
+ shell.say "Removing all files from LOGS, LOGS1, LOGS2, photos, " +
651
+ "photos1, and photos2", color = :blue
652
+ FileUtils.rm_f Dir.glob('LOGS/*')
653
+ FileUtils.rm_f Dir.glob('LOGS1/*')
654
+ FileUtils.rm_f Dir.glob('LOGS2/*')
655
+ FileUtils.rm_f Dir.glob('photos/*')
656
+ FileUtils.rm_f Dir.glob('photos1/*')
657
+ FileUtils.rm_f Dir.glob('photos2/*')
658
+
659
+ shell.say "Removing files binary_history.data, out.txt, and " +
660
+ "test_results.yml", color = :blue
661
+ FileUtils.rm_f 'binary_history.data'
662
+ FileUtils.rm_f 'out.txt'
663
+ if File.directory? File.join('star_history','history_out')
664
+ shell.say "Removing all files of the form history_out* from star_history",
665
+ color = :blue
666
+ FileUtils.rm_f Dir.glob(File.join('star_history', 'history_out', '*'))
667
+ end
668
+ if File.directory? File.join('star_profile', 'profiles_out')
669
+ shell.say "Removing all files of the form profiles_out* from " +
670
+ "star_profile", color = :blue
671
+ FileUtils.rm_f Dir.glob(File.join('star_profile', 'profiles_out', '*'))
672
+ end
673
+ shell.say "Removing .running", color = :blue
674
+ FileUtils.rm_f '.running'
675
+ end
676
+ end
677
+
678
+ # based on $MESA_DIR/star/test_suite/each_test_run_and_diff, revision 10000
679
+ def do_one
680
+ @test_omp_num_threads = omp_num_threads
681
+ in_dir do
682
+ FileUtils.touch '.running'
683
+ shell.say("building and running #{test_name}", color = :blue)
684
+ puts ''
685
+ build_and_run
686
+ FileUtils.rm '.running'
687
+ puts ''
688
+ end
689
+ end
690
+
691
+ def log_results
692
+ # gets all parameters that would be submitted as well as computer
693
+ # information and dumps to a yml file in the test case directory
694
+ save_file = File.join(test_case_dir, 'test_results.yml')
695
+ shell.say "Logging test results to #{save_file}...", color = :blue
696
+ res = {
697
+ test_case: test_name,
698
+ runtime_seconds: runtime_seconds,
699
+ mesa_version: mesa_version,
700
+ outcome: outcome,
701
+ omp_num_threads: test_omp_num_threads,
702
+ success_type: success_type,
703
+ failure_type: failure_type
704
+ }
705
+ File.open(save_file, 'w') { |f| f.write(YAML::dump(res)) }
706
+ shell.say "Successfully saved results to file #{save_file}.",
707
+ color = :green
708
+ puts ''
709
+ end
710
+
711
+ def load_results
712
+ # loads all parameters from a previous test run, likely for submission
713
+ # purposes
714
+ load_file = File.join(test_case_dir, 'test_results.yml')
715
+ shell.say "Loading data from #{load_file}...", color = :blue
716
+ data = YAML::load(File.read(load_file))
717
+ @runtime_seconds = data[:runtime_seconds]
718
+ @mesa_version = data[:mesa_version]
719
+ @outcome = data[:outcome].to_sym
720
+ @test_omp_num_threads = data[:omp_num_threads]
721
+ @success_type = data[:success_type]
722
+ @failure_type = data[:failure_type]
723
+ shell.say "Done loading data from #{load_file}.", color = :green
724
+ puts ''
725
+ end
726
+
727
+ private
728
+
729
+ def data_types
730
+ return [:float, :integer, :string, :boolean]
731
+ end
732
+
733
+ # cd into the test case directory, do something in a block, then cd back
734
+ # to original directory
735
+ def in_dir(&block)
736
+ visit_dir(test_case_dir, &block)
737
+ end
738
+
739
+ # make sure that we can get to the test case directory. Throw an exception
740
+ # if we cannot
741
+ def check_test_case
742
+ unless File.directory? test_case_dir
743
+ raise TestCaseDirError, "No such test case: #{test_case_dir}."
744
+ end
745
+ end
746
+
747
+ # verify that mesa_dir is valid by checking for version number and test_suite
748
+ # directory
749
+ def check_mesa_dir
750
+ is_valid = File.exist?(File.join(mesa_dir, 'data', 'version_number')) and
751
+ File.directory?(test_suite_dir)
752
+ raise MesaDirError, "Invalid MESA dir: #{mesa_dir}" unless is_valid
753
+ end
754
+
755
+ # append message to log file
756
+ def log_message(msg, color = nil, log_file = 'out.txt')
757
+ if color.nil?
758
+ shell.say msg
759
+ else
760
+ shell.say msg, color = color
761
+ end
762
+ File.open('out.txt', 'a') { |f| f.puts(msg) }
763
+ end
764
+
765
+ # write failure message to log file
766
+ def write_failure_message
767
+ msg = "******************** #{failure_msg[@failure_type]} " +
768
+ "********************"
769
+ log_message(msg, color = :red)
770
+ end
771
+
772
+ # write success message to log file
773
+ def write_success_msg(success_type)
774
+ msg = 'PASS ' + success_msg[success_type]
775
+ log_message(msg, color = :green)
776
+ end
777
+
778
+ # used as return value for run or photo test. Logs failure to text file, and
779
+ # sets internal status to failing
780
+ def fail_test(failure_type)
781
+ @failure_type = failure_type
782
+ @outcome = :fail
783
+ write_failure_message
784
+ return false
785
+ end
786
+
787
+ # used as return value for run or photo test. Logs data to text file, and
788
+ # sets internal status to passing
789
+ def succeed(success_type)
790
+ @success_type = success_type
791
+ @outcome = :pass
792
+ write_success_msg(success_type)
793
+ return true
794
+ end
795
+
796
+ def check_run
797
+ # assumes we are in the directory already, called from something else
798
+ run_start = Time.now
799
+
800
+ # do the run
801
+ puts './rn >> out.txt 2> err.txt'
802
+ system('./rn >> out.txt 2> err.txt')
803
+
804
+ # report runtime and clean up
805
+ run_finish = Time.now
806
+ @runtime_seconds = (run_finish - run_start).to_i
807
+ shell.say "Finished with ./rn; runtime = #{@runtime_seconds} seconds.",
808
+ color = :blue
809
+ append_and_rm_err
810
+
811
+
812
+ # look for success text
813
+ success = true
814
+ File.open('out.txt', 'r') do |f|
815
+ success = !f.read.downcase.scan(success_string.downcase).empty?
816
+ end
817
+ # bail if there was no test string found
818
+ unless success
819
+ return fail_test(:run_test_string)
820
+ end
821
+
822
+ # additional checks for final model, if it is specified
823
+ if final_model
824
+ # update checks after new run (Bill W. doesn't know what this does)
825
+ # (is this supposed to mark things as passed? The original function
826
+ # just has a standard "return" statement, which I interpret as passing)
827
+ if ENV.include? 'UPDATE_CHECKS'
828
+ system("md5sum \"#{final_model}\" > checks.md5")
829
+ puts "md5sum \"#{final_model}\" > checks.md5"
830
+ FileUtils.cp final_model 'final_check.mod'
831
+ return true
832
+ end
833
+
834
+ # display runtime message
835
+ puts IO.readlines('out.txt').select { |line| line.scan(/runtime/i) }[-1]
836
+
837
+ # check that final model matches
838
+ puts './ck >& final_check_diff.txt'
839
+ if not system('./ck >& final_check_diff.txt')
840
+ return fail_test(:run_checksum)
841
+ elsif File.exist? 'final_check_diff.txt' and
842
+ not File.read('final_check_diff.txt').empty?
843
+ return fail_test(:run_diff)
844
+ elsif File.exist? final_model
845
+ return succeed(:run_checksum)
846
+ end
847
+
848
+ # no final model to check, and we already found the test string, so pass
849
+ else
850
+ return succeed(:run_test_string)
851
+ end
852
+
853
+ end
854
+
855
+ # prepare for and do restart, check results, and return pass/fail status
856
+ def check_restart
857
+ # abort if there is not photo specified
858
+ return unless photo
859
+
860
+ # check that photo file actually exists
861
+ unless File.exist?(File.join('photos', photo)) or
862
+ File.exist?(File.join('photos1', photo))
863
+ return fail_test(:photo_file)
864
+ end
865
+
866
+ # remove final model since it will be remade by restart
867
+ FileUtils.rm_f final_model
868
+
869
+ # do restart and consolidate output
870
+ puts "./re #{photo} >> out.txt 2> err.txt"
871
+ system("./re #{photo} >> out.txt 2> err.txt")
872
+ append_and_rm_err
873
+
874
+ # check that final model matches
875
+ puts "./ck >& final_check_diff.txt"
876
+ if not system("./ck >& final_check_diff.txt")
877
+ return fail_test(:photo_checksum)
878
+ elsif File.exist?('final_check_diff.txt') and not
879
+ File.read('final_check_diff.txt').empty?
880
+ return fail_test(:photo_diff)
881
+ else
882
+ return succeed(:photo_checksum)
883
+ end
884
+ end
885
+
886
+ def build_and_run
887
+ # assumes we are in the test case directory. Should only be called
888
+ # in the context of an `in_dir` block.
889
+
890
+
891
+ # first clean and make... worried about shell compatibility since we
892
+ # aren't forcing bash. Hopefully '>' for redirecting output is pretty
893
+ # universal
894
+ puts './clean'
895
+ unless system('./clean')
896
+ raise TestCaseDirError, "Encountered an error when running `clean` in " +
897
+ "#{Dir.getwd} for test case #{test_name}."
898
+ end
899
+
900
+ puts './mk > mk.txt'
901
+ unless system('./mk > mk.txt')
902
+ raise TestCaseDirError, "Encountered an error when running `mk` in " +
903
+ "#{Dir.getwd} for test case #{test_name}."
904
+ end
905
+ FileUtils.rm 'mk.txt'
906
+
907
+ # remove final model if it already exists
908
+ unless final_model.nil?
909
+ FileUtils.rm final_model if File.exist? final_model
910
+ end
911
+
912
+ if check_run
913
+ # only check restart/photo if we get through run successfully
914
+ check_restart
915
+ end
916
+ end
917
+
918
+ # append contents of err.txt to end of out.txt, then delete err.txt
919
+ def append_and_rm_err(outfile='out.txt', errfile='err.txt')
920
+ File.open(outfile, 'a') do |f_out|
921
+ err_contents = File.read(errfile)
922
+ unless err_contents.strip.empty?
923
+ shell.say "\nERRORS", color = :red
924
+ puts err_contents
925
+ shell.say "END OF ERRORS (appended to #{outfile})", color = :red
926
+ puts ''
927
+ f_out.write(err_contents)
928
+ end
929
+ end
930
+ FileUtils.rm errfile
931
+ end
932
+
933
+ end
934
+
935
+
936
+ ################################
937
+ # GENERAL METHODS #
938
+ ################################
939
+
940
+
941
+ # cd into a new directory, execute a block whose return value is either
942
+ # true or false. Either way, cd back to original directory. Raise an
943
+ # exception if the block failed (returned false or nil)
944
+ def visit_and_check(new_dir, exception, message)
945
+ cwd = Dir.getwd
946
+ shell.say "Leaving #{cwd}", color = :blue
947
+ puts ""
948
+ shell.say "Entering #{new_dir}.", color = :blue
949
+ Dir.chdir(new_dir)
950
+ success = yield if block_given?
951
+ shell.say "Leaving #{new_dir}", color = :blue
952
+ puts ""
953
+ shell.say "Entering #{cwd}.", color = :blue
954
+ Dir.chdir(cwd)
955
+ unless success
956
+ raise exception, message
957
+ end
958
+ end
959
+
960
+ # cd into a new directory, execute a block, then cd back into original
961
+ # directory
962
+ def visit_dir(new_dir)
963
+ cwd = Dir.getwd
964
+ shell.say "Leaving #{cwd}", color = :blue
965
+ puts ""
966
+ shell.say "Entering #{new_dir}.", color = :blue
967
+ Dir.chdir(new_dir)
968
+ yield if block_given?
969
+ shell.say "Leaving #{new_dir}", color = :blue
970
+ puts ""
971
+ shell.say "Entering #{cwd}.", color = :blue
972
+ Dir.chdir(cwd)
973
+ end
974
+
975
+ # create seed data for test cases for MesaTestHub of a given mesa version
976
+ def generate_seeds_rb(mesa_dir, outfile)
977
+ m = Mesa.new(mesa_dir: mesa_dir)
978
+ m.load_test_source_data
979
+ File.open(outfile, 'w') do |f|
980
+ f.puts 'test_cases = TestCase.create!('
981
+ f.puts ' ['
982
+ m.test_names.each do |test_case_name|
983
+ f.puts " {"
984
+ f.puts " name: '#{test_case_name}',"
985
+ f.puts " version_added: #{m.version_number},"
986
+ # no comma on last one
987
+ if test_case_name == m.test_names[-1]
988
+ f.puts " }"
989
+ else
990
+ f.puts " },"
991
+ end
992
+ end
993
+ f.puts ' ]'
994
+ f.puts ')'
995
+ end
996
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mesa_test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - William Wolf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: os
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ description: mesa_test is a command-line interface for running the test suites in
56
+ MESA and submitting them to the companion website MESATestHub.
57
+ email: wmwolf@asu.edu
58
+ executables:
59
+ - mesa_test
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/mesa_test
64
+ - lib/mesa_test.rb
65
+ homepage: https://github.com/wmwolf/mesa_test
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.0.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.6.14
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Command line tool for running and reporting the MESA test suites.
89
+ test_files: []