mesa_test 0.0.3

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.
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: []