illuminator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/gem/README.md +37 -0
  3. data/gem/bin/illuminatorTestRunner.rb +22 -0
  4. data/gem/lib/illuminator.rb +171 -0
  5. data/gem/lib/illuminator/argument-parsing.rb +299 -0
  6. data/gem/lib/illuminator/automation-builder.rb +39 -0
  7. data/gem/lib/illuminator/automation-runner.rb +589 -0
  8. data/gem/lib/illuminator/build-artifacts.rb +118 -0
  9. data/gem/lib/illuminator/device-installer.rb +45 -0
  10. data/gem/lib/illuminator/host-utils.rb +42 -0
  11. data/gem/lib/illuminator/instruments-runner.rb +301 -0
  12. data/gem/lib/illuminator/javascript-runner.rb +98 -0
  13. data/gem/lib/illuminator/listeners/console-logger.rb +32 -0
  14. data/gem/lib/illuminator/listeners/full-output.rb +13 -0
  15. data/gem/lib/illuminator/listeners/instruments-listener.rb +22 -0
  16. data/gem/lib/illuminator/listeners/intermittent-failure-detector.rb +49 -0
  17. data/gem/lib/illuminator/listeners/pretty-output.rb +26 -0
  18. data/gem/lib/illuminator/listeners/saltinel-agent.rb +66 -0
  19. data/gem/lib/illuminator/listeners/saltinel-listener.rb +26 -0
  20. data/gem/lib/illuminator/listeners/start-detector.rb +52 -0
  21. data/gem/lib/illuminator/listeners/stop-detector.rb +46 -0
  22. data/gem/lib/illuminator/listeners/test-listener.rb +58 -0
  23. data/gem/lib/illuminator/listeners/trace-error-detector.rb +38 -0
  24. data/gem/lib/illuminator/options.rb +96 -0
  25. data/gem/lib/illuminator/resources/IlluminatorGeneratedEnvironment.erb +13 -0
  26. data/gem/lib/illuminator/resources/IlluminatorGeneratedRunnerForInstruments.erb +19 -0
  27. data/gem/lib/illuminator/test-definitions.rb +23 -0
  28. data/gem/lib/illuminator/test-suite.rb +155 -0
  29. data/gem/lib/illuminator/version.rb +3 -0
  30. data/gem/lib/illuminator/xcode-builder.rb +144 -0
  31. data/gem/lib/illuminator/xcode-utils.rb +219 -0
  32. data/gem/resources/BuildConfiguration.xcconfig +10 -0
  33. data/gem/resources/js/AppMap.js +767 -0
  34. data/gem/resources/js/Automator.js +1132 -0
  35. data/gem/resources/js/Base64.js +142 -0
  36. data/gem/resources/js/Bridge.js +102 -0
  37. data/gem/resources/js/Config.js +92 -0
  38. data/gem/resources/js/Extensions.js +2025 -0
  39. data/gem/resources/js/Illuminator.js +228 -0
  40. data/gem/resources/js/Preferences.js +24 -0
  41. data/gem/resources/scripts/UIAutomationBridge.rb +248 -0
  42. data/gem/resources/scripts/common.applescript +25 -0
  43. data/gem/resources/scripts/diff_png.sh +61 -0
  44. data/gem/resources/scripts/kill_all_sim_processes.sh +17 -0
  45. data/gem/resources/scripts/plist_to_json.sh +40 -0
  46. data/gem/resources/scripts/set_hardware_keyboard.applescript +0 -0
  47. metadata +225 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c94f4888bca611f421fcaf152f94e1e5418bed78
4
+ data.tar.gz: f967a48a1432cf3419c7e7d62e269a0769159d33
5
+ SHA512:
6
+ metadata.gz: 6ebf64b498f0f7fee2b399c824a7d307bcb4c1bf58536685bdd1232cbb98c7a425753a5b42ffa4d58729a69b3f242da2e22f40a3961185f798ea0e757701a119
7
+ data.tar.gz: 1b00dd71f903e20cc000dad3f78effe97879544bf61596b7a0925d9b49e0ca7522de20f882b5926361d545c32c2463dc756f0bf0f5c289198fd82a9fc13d4934
@@ -0,0 +1,37 @@
1
+ # Illuminator gem
2
+
3
+ This gem contains ruby modules necessary to run tests using the [Illuminator framework](http://github.com/paypal/Illuminator).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'illuminator'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install illuminator
20
+
21
+ ## Usage
22
+
23
+ See the [main Illuminator documentation](https://github.com/paypal/Illuminator).
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/paypal/illuminator/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'illuminator'
4
+ require 'pathname'
5
+
6
+ options = {}
7
+ parser_factory = Illuminator::ParserFactory.new
8
+
9
+ parser_factory.prepare({}, # no defaults are being set
10
+ {}, # no extra parse flags are being defined
11
+ {}) # no argument processing overrides are being provided
12
+
13
+ # Each command line option has a single-character code, so we lay out the order of the options (# for separator) here
14
+ #parser = parser_factory.build_parser(options, 'APDasdq#xtoni#rvmw#bzl#Bfek#')
15
+ parser = parser_factory.build_parser(options, 'AyfEBxrc#aDPWqs#dbzlek#vmp#xtoniw#')
16
+
17
+ # read the options into an Illuminator::Options structure
18
+ option_struct = parser.parse ARGV
19
+
20
+ # use the defined options
21
+ success = Illuminator::run_with_options option_struct
22
+ exit 1 unless success
@@ -0,0 +1,171 @@
1
+ require "illuminator/version"
2
+
3
+ require "illuminator/automation-builder"
4
+ require "illuminator/automation-runner"
5
+ require "illuminator/argument-parsing"
6
+ require "illuminator/device-installer"
7
+ require "illuminator/build-artifacts"
8
+ require "illuminator/host-utils"
9
+ require "illuminator/xcode-utils"
10
+ require "illuminator/options"
11
+
12
+
13
+ module Illuminator
14
+
15
+ class Framework
16
+
17
+ def self.will_clean options
18
+ return true if options.illuminator.clean.derived
19
+ return true if options.illuminator.clean.artifacts
20
+ return true if options.illuminator.clean.xcode
21
+ return false
22
+ end
23
+
24
+ def self.clean_countdown
25
+ countdown = "3....2....1...."
26
+ print "Cleaning in ".yellow
27
+ countdown.split("").each do |c|
28
+ print c.yellow
29
+ sleep(0.2)
30
+ end
31
+ print "\n"
32
+ end
33
+
34
+ def self.validate_options(options)
35
+ noproblems = true
36
+
37
+ # so we can call BS on the user
38
+ bs = lambda do |message|
39
+ puts message.red
40
+ noproblems = false
41
+ end
42
+
43
+ # some things to check
44
+ things = {
45
+ "Build artifacts directory" => options.build_artifacts_dir,
46
+ }
47
+
48
+ # now check them
49
+ things.each { |k, v| bs.call "#{k} was not specified" if v.nil? }
50
+
51
+ # fail quickly if simulator device and/or version are wrong
52
+ if options.illuminator.hardware_id.nil?
53
+ device = options.simulator.device
54
+ version = options.simulator.version
55
+ devices = XcodeUtils.instance.get_simulator_device_types()
56
+ versions = XcodeUtils.instance.get_simulator_runtimes()
57
+
58
+
59
+ unless devices.include? device
60
+ bs.call "Specified simulator device '#{device}' does not appear to be installed - options are #{devices}"
61
+ end
62
+
63
+ unless versions.include? version
64
+ bs.call "Specified simulator iOS version '#{version}' does not appear to be installed - options are #{versions}"
65
+ end
66
+
67
+ end
68
+
69
+ # check paths
70
+ if options.illuminator.task.automate
71
+ bs.call "Implementation was not specified" if options.javascript.implementation.nil?
72
+
73
+ if options.javascript.test_path.nil?
74
+ bs.call "Javascript test definitions file was not specified"
75
+ else
76
+ unless File.exists? options.javascript.test_path
77
+ bs.call "Could not find specified javascript test definitions file at '#{options.javascript.test_path}'"
78
+ end
79
+ end
80
+ end
81
+
82
+ return noproblems
83
+ end
84
+
85
+ end
86
+
87
+
88
+
89
+ def self.run_with_options(originalOptions)
90
+
91
+ options = Options.new(originalOptions.to_h) # immediately create a copy of the options, because we may mangle them
92
+
93
+ # validate some inputs
94
+ return false unless Framework.validate_options(options)
95
+
96
+ Illuminator::BuildArtifacts.instance.set_root options.build_artifacts_dir
97
+
98
+ hardware_id = options.illuminator.hardware_id
99
+ app_name = options.xcode.app_name
100
+
101
+ # do any initial cleaning
102
+ clean_dirs = {
103
+ HostUtils.realpath("~/Library/Developer/Xcode/DerivedData") => options.illuminator.clean.derived,
104
+ Illuminator::BuildArtifacts.instance.root(true) => options.illuminator.clean.artifacts,
105
+ }
106
+ Framework.clean_countdown if Framework.will_clean(options) and (not options.illuminator.clean.no_delay)
107
+ clean_dirs.each do |d, do_clean|
108
+ dir = HostUtils.realpath d
109
+ if do_clean
110
+ puts "Illuminator cleanup: removing #{dir}"
111
+ FileUtils.rmtree dir
112
+ end
113
+ end
114
+
115
+ # Initialize builder and build
116
+ if (not options.instruments.app_location.nil?)
117
+ puts "Skipping build because app_location was provided".yellow if options.illuminator.task.build
118
+ options.instruments.app_location = HostUtils::realpath(options.instruments.app_location)
119
+ elsif (not options.illuminator.task.build)
120
+ options.instruments.app_location = Illuminator::BuildArtifacts.instance.app_location(app_name) # assume app is here
121
+ else
122
+ builder = AutomationBuilder.new
123
+ builder.project_dir = options.xcode.project_dir
124
+ builder.project = options.xcode.project
125
+ builder.scheme = options.xcode.scheme
126
+ builder.workspace = options.xcode.workspace
127
+ builder.do_clean = options.illuminator.clean.xcode
128
+ unless options.xcode.environment_vars.nil?
129
+ options.xcode.environment_vars.each { |name, value| builder.add_environment_variable(name, value) }
130
+ end
131
+
132
+ # if app name is not specified, make sure that we will only have one to run
133
+ XcodeUtils.remove_existing_apps(Illuminator::BuildArtifacts.instance.xcode) if app_name.nil?
134
+ if builder.build_for_automation(options.xcode.sdk, hardware_id)
135
+ puts 'Build succeded'.green
136
+ options.instruments.app_location = Illuminator::BuildArtifacts.instance.app_location(app_name)
137
+ else
138
+ puts 'Build failed, check logs for results'.red
139
+ exit builder.exit_code
140
+ end
141
+ end
142
+
143
+ return true unless options.illuminator.task.automate
144
+
145
+ # Install on real device
146
+ unless hardware_id.nil?
147
+ DeviceInstaller.instance.install_on_device(options.instruments.app_location, hardware_id)
148
+ end
149
+
150
+ # Initialize automation
151
+ runner = AutomationRunner.new
152
+ runner.app_name = app_name
153
+ runner.cleanup
154
+ return runner.run_with_options(options)
155
+
156
+ end
157
+
158
+ # override_options is a lambda function that acts on the options object
159
+ def self.rerun(config_path, override_options = nil)
160
+
161
+ # load config from supplied path
162
+ json_config = IO.read(config_path)
163
+
164
+ # process any overrides
165
+ options = override_options.(Illuminator::Options.new(JSON.parse(json_config))) unless override_options.nil?
166
+
167
+ return run_with_options options
168
+ end
169
+
170
+
171
+ end
@@ -0,0 +1,299 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ require_relative './options'
5
+ require_relative './host-utils'
6
+
7
+ module Illuminator
8
+
9
+ class Parser < OptionParser
10
+ attr_reader :positional_args
11
+
12
+ def initialize options
13
+ super
14
+ @_options = options
15
+ end
16
+
17
+ def check_retest_args
18
+ known_retests = ["solo"]
19
+
20
+ @_options["retest"] = [] if @_options["retest"].nil?
21
+
22
+ @_options["retest"].each do |r|
23
+ if known_retests.include? r
24
+ # ok
25
+ elsif /^\d+x$/.match(r)
26
+ # ok (1x, 2x, 3x...)
27
+ else
28
+ puts "Got unknown --retest specifier '#{r}'".yellow
29
+ end
30
+ end
31
+ end
32
+
33
+ def get_max_retests
34
+ ret = 0
35
+ @_options["retest"].each do |r|
36
+ matches = /^(\d+)x$/.match(r)
37
+ unless matches.nil?
38
+ ret = [ret, matches[1].to_i].max
39
+ end
40
+ end
41
+ ret
42
+ end
43
+
44
+ def check_clean_args
45
+ known_cleans = ["xcode", "buildArtifacts", "derivedData", "noDelay"]
46
+
47
+ @_options["clean"] = [] if @_options["clean"].nil?
48
+
49
+ @_options["clean"].each do |c|
50
+ unless known_cleans.include? c
51
+ puts "Got unknown --clean specifier '#{c}'".yellow
52
+ end
53
+ end
54
+ end
55
+
56
+ # copy internal options storage into a options object
57
+ def copy_parsed_options_into(illuminatorOptions)
58
+ check_clean_args
59
+ check_retest_args
60
+
61
+ # load up known illuminatorOptions
62
+ # we only load non-nil options, just in case there was already something in the illuminatorOptions obj
63
+ illuminatorOptions.build_artifacts_dir = @_options["buildArtifacts"] unless @_options["buildArtifacts"].nil?
64
+
65
+ illuminatorOptions.xcode.app_name = @_options["app_name"] unless @_options["app_name"].nil?
66
+ illuminatorOptions.xcode.sdk = @_options["sdk"] unless @_options["sdk"].nil?
67
+ illuminatorOptions.xcode.scheme = @_options["scheme"] unless @_options["scheme"].nil?
68
+ illuminatorOptions.xcode.workspace = @_options["xcodeWorkspace"] unless @_options["xcodeWorkspace"].nil?
69
+
70
+ illuminatorOptions.illuminator.entry_point = @_options["entry_point"] unless @_options["entry_point"].nil?
71
+ illuminatorOptions.illuminator.test.random_seed = @_options["random_seed"].to_i unless @_options["random_seed"].nil?
72
+ illuminatorOptions.illuminator.test.tags.any = @_options["tags_any"] unless @_options["tags_any"].nil?
73
+ illuminatorOptions.illuminator.test.tags.all = @_options["tags_all"] unless @_options["tags_all"].nil?
74
+ illuminatorOptions.illuminator.test.tags.none = @_options["tags_none"] unless @_options["tags_none"].nil?
75
+
76
+ illuminatorOptions.illuminator.test.retest.attempts = get_max_retests
77
+ illuminatorOptions.illuminator.test.retest.solo = @_options["retest"].include? "solo"
78
+
79
+ illuminatorOptions.illuminator.clean.xcode = @_options["clean"].include? "xcode"
80
+ illuminatorOptions.illuminator.clean.derived = @_options["clean"].include? "derivedData"
81
+ illuminatorOptions.illuminator.clean.artifacts = @_options["clean"].include? "buildArtifacts"
82
+ illuminatorOptions.illuminator.clean.noDelay = @_options["clean"].include? "noDelay"
83
+
84
+ illuminatorOptions.illuminator.task.build = (not @_options["skipBuild"]) unless @_options["skipBuild"].nil?
85
+ illuminatorOptions.illuminator.task.automate = (not @_options["skipAutomate"]) unless @_options["skipAutomate"].nil?
86
+ illuminatorOptions.illuminator.task.set_sim = (not @_options["skipSetSim"]) unless @_options["skipSetSim"].nil?
87
+ illuminatorOptions.illuminator.task.coverage = @_options["coverage"] unless @_options["coverage"].nil?
88
+ illuminatorOptions.illuminator.hardware_id = @_options["hardware_id"] unless @_options["hardware_id"].nil?
89
+
90
+ illuminatorOptions.simulator.device = @_options["sim_device"] unless @_options["sim_device"].nil?
91
+ illuminatorOptions.simulator.version = @_options["sim_version"] unless @_options["sim_version"].nil?
92
+ illuminatorOptions.simulator.language = @_options["sim_language"] unless @_options["sim_language"].nil?
93
+ illuminatorOptions.simulator.kill_after = (not @_options["skipKillAfter"]) unless @_options["skipKillAfter"].nil?
94
+
95
+ illuminatorOptions.instruments.app_location = @_options["app_location"] unless @_options["app_location"].nil?
96
+ illuminatorOptions.instruments.do_verbose = @_options["verbose"] unless @_options["verbose"].nil?
97
+ illuminatorOptions.instruments.timeout = @_options["timeout"].to_i unless @_options["timeout"].nil?
98
+
99
+ illuminatorOptions.javascript.test_path = @_options["test_path"] unless @_options["test_path"].nil?
100
+ illuminatorOptions.javascript.implementation = @_options["implementation"] unless @_options["implementation"].nil?
101
+
102
+ known_keys = Illuminator::ParserFactory.new.letter_map.values # get option keynames from a plain vanilla factory
103
+
104
+ # load up unknown illuminatorOptions
105
+ illuminatorOptions.app_specific = @_options.select { |keyname, _| not (known_keys.include? keyname) }
106
+
107
+ return illuminatorOptions
108
+ end
109
+
110
+ def parse args
111
+ @positional_args = super(args)
112
+ return copy_parsed_options_into(Illuminator::Options.new)
113
+ end
114
+
115
+ end
116
+
117
+
118
+
119
+ class ParserFactory
120
+
121
+ attr_reader :letter_map
122
+
123
+ # the currency of this parser factory is the "short" single-letter argument switch
124
+ def initialize()
125
+ @options = nil
126
+ @switches = {}
127
+
128
+ # build the list of how each parameter will be saved in the output
129
+ @letter_map = {
130
+ 'A' => 'buildArtifacts',
131
+ 'x' => 'entry_point',
132
+ 'p' => 'test_path',
133
+ 'a' => 'app_name',
134
+ 'D' => 'xcodeProjectDir',
135
+ 'P' => 'xcodeProject',
136
+ 'W' => 'xcodeWorkspace',
137
+ 't' => 'tags_any',
138
+ 'o' => 'tags_all',
139
+ 'n' => 'tags_none',
140
+ 'q' => 'sdk',
141
+ 's' => 'scheme',
142
+ 'd' => 'hardware_id',
143
+ 'i' => 'implementation',
144
+ 'E' => 'app_location',
145
+ 'b' => 'sim_device',
146
+ 'z' => 'sim_version',
147
+ 'l' => 'sim_language',
148
+ 'f' => 'skipBuild',
149
+ 'B' => 'skipAutomate',
150
+ 'e' => 'skipSetSim',
151
+ 'k' => 'skipKillAfter',
152
+ 'c' => 'coverage',
153
+ 'r' => 'retest',
154
+ 'v' => 'verbose',
155
+ 'm' => 'timeout',
156
+ 'w' => 'random_seed',
157
+ 'y' => 'clean',
158
+ }
159
+
160
+ @letter_processing = {
161
+ 'p' => lambda {|p| Illuminator::HostUtils.realpath(p) }, # get real path to tests file
162
+ 'E' => lambda {|p| Illuminator::HostUtils.realpath(p) }, # get real path to app
163
+ 'y' => lambda {|p| p.split(',')}, # split comma-separated string into array
164
+ 'r' => lambda {|p| p.split(',')}, # split comma-separated string into array
165
+ 't' => lambda {|p| p.split(',')}, # split comma-separated string into array
166
+ 'o' => lambda {|p| p.split(',')}, # split comma-separated string into array
167
+ 'n' => lambda {|p| p.split(',')}, # split comma-separated string into array
168
+ }
169
+
170
+ @default_values = {
171
+ # 'D' => Dir.pwd, # Since this effectively happens in xcode-builder, DON'T do it here too
172
+ 'A' => File.join(Dir.pwd, "buildArtifacts"),
173
+ 'b' => 'iPhone 5',
174
+ 'z' => '8.2',
175
+ 'q' => 'iphonesimulator',
176
+ 'l' => 'en',
177
+ 'x' => 'runTestsByTag',
178
+ 'm' => 30,
179
+ 'f' => false,
180
+ 'B' => false,
181
+ 'e' => false,
182
+ 'k' => false,
183
+ 'c' => false,
184
+ }
185
+ end
186
+
187
+ # you must custom prepare before you can add custom switches... otherwise things get all stupid
188
+ def prepare(default_values = nil, letter_map_updates = nil, letter_processing_updates = nil)
189
+ @letter_map = @letter_map.merge(letter_map_updates) unless letter_map_updates.nil?
190
+ @letter_processing = @letter_processing.merge(letter_processing_updates) unless letter_processing_updates.nil?
191
+ @default_values = @default_values.merge default_values unless default_values.nil?
192
+
193
+ add_switch('A', ['-A', '--buildArtifacts PATH', 'The directory in which to store build artifacts'])
194
+ add_switch('x', ['-x', '--entryPoint LABEL', 'The execution entry point {runTestsByTag, runTestsByName, describe}'])
195
+ add_switch('p', ['-p', '--test_path PATH', 'Path to js file with all tests imported'])
196
+ add_switch('a', ['-a', '--app_name APPNAME', "Name of the app to build / run"])
197
+ add_switch('D', ['-D', '--xcodeProjectDirectory PATH', "Directory containing the Xcode project to build"])
198
+ add_switch('P', ['-P', '--xcodeProject PROJECTNAME', "Project to build -- required if there are 2 in the same directory"])
199
+ add_switch('W', ['-W', '--xcodeWorkspace WORKSPACENAME', "Workspace to build"])
200
+ add_switch('t', ['-t', '--tags-any TAGSANY', 'Run tests with any of the given tags'])
201
+ add_switch('o', ['-o', '--tags-all TAGSALL', 'Run tests with all of the given tags'])
202
+ add_switch('n', ['-n', '--tags-none TAGSNONE', 'Run tests with none of the given tags'])
203
+ add_switch('q', ['-q', '--sdk SDK', 'SDK to build against'])
204
+ add_switch('s', ['-s', '--scheme SCHEME', 'Build and run specific tests on given workspace scheme'])
205
+ add_switch('d', ['-d', '--hardware_id ID', 'hardware id of device to run on instead of simulator'])
206
+ add_switch('i', ['-i', '--implementation IMPL', 'Device tests implementation'])
207
+ add_switch('E', ['-E', '--app_location LOCATION', 'Location of app executable, if pre-built'])
208
+ add_switch('b', ['-b', '--simDevice DEVICE', 'Run on given simulated device'])
209
+ add_switch('z', ['-z', '--simVersion VERSION', 'Run on given simulated iOS version'])
210
+ add_switch('l', ['-l', '--simLanguage LANGUAGE', 'Run on given simulated iOS language'])
211
+ add_switch('f', ['-f', '--skip-build', 'Just automate; assume already built'])
212
+ add_switch('B', ['-B', '--skip-automate', "Don't automate; build only"])
213
+ add_switch('e', ['-e', '--skip-set-sim', 'Assume that simulator has already been chosen and properly reset'])
214
+ add_switch('k', ['-k', '--skip-kill-after', 'Leave the simulator open after the run'])
215
+ add_switch('y', ['-y', '--clean PLACES', 'Comma-separated list of places to clean {xcode, buildArtifacts, derivedData}'])
216
+ add_switch('c', ['-c', '--coverage', 'Generate coverage files'])
217
+ add_switch('r', ['-r', '--retest OPTIONS', 'Immediately retest failed tests with comma-separated options {1x, solo}'])
218
+ add_switch('v', ['-v', '--verbose', 'Show verbose output from instruments'])
219
+ add_switch('m', ['-m', '--timeout TIMEOUT', 'Seconds to wait for instruments tool to start tests'])
220
+ add_switch('w', ['-w', '--random-seed SEED', 'Randomize test order based on given integer seed'])
221
+ end
222
+
223
+ # add a parse switch for the given letter key, using the given options.
224
+ # the parse action is defined by the existence of letter_processing for the letter key,
225
+ # which by default is simple assignment
226
+ def add_switch(letter, opts)
227
+ dest = get_letter_destination(letter)
228
+
229
+ # alter opts to include the default values
230
+ altered = false
231
+ if @default_values[letter].nil?
232
+ opts_with_default = opts
233
+ else
234
+ opts_with_default = opts.map do |item|
235
+ if (!altered and item.chars.first != '-')
236
+ item += " :: Defaults to \"#{@default_values[letter]}\""
237
+ altered = true
238
+ end
239
+ item
240
+ end
241
+ end
242
+
243
+ @switches[letter] = OpenStruct.new(:opts => opts_with_default,
244
+ :block => lambda do |newval|
245
+ # assign the parsed value to the output, processing it if necessary
246
+ if @letter_processing[letter]
247
+ @options[dest] = @letter_processing[letter].call(newval)
248
+ else
249
+ @options[dest] = newval
250
+ end
251
+ end)
252
+ end
253
+
254
+
255
+ # letter destination defaults to the letter itself, but can be overwritten by letter_map
256
+ def get_letter_destination(letter)
257
+ return @letter_map[letter]? @letter_map[letter] : letter
258
+ end
259
+
260
+
261
+ # factory function
262
+ def build_parser(options, letters = nil)
263
+ @options = options
264
+
265
+ if letters.nil?
266
+ letters = switches.keys.join('')
267
+ end
268
+
269
+ # helpful error message for bad chars
270
+ bad_chars = letters.chars.to_a.select{|c| c != "#" and @switches[c].nil?}
271
+ unless bad_chars.empty?
272
+ raise ArgumentError, "build_parser got letters (#{letters}) containing unknown option characters: #{bad_chars.to_s}"
273
+ end
274
+
275
+ retval = Illuminator::Parser.new options
276
+
277
+ # build a parser as specified by the user
278
+ letters.each_char do |c|
279
+ options[get_letter_destination(c)] = @default_values[c] unless @default_values[c].nil?
280
+
281
+ if c == '#'
282
+ retval.separator(' ---------------------------------------------------------------------------------')
283
+ else
284
+ retval.on(*(@switches[c].send(:opts))) {|foo| @switches[c].send(:block).call(foo)}
285
+ end
286
+ end
287
+
288
+ # help message is hard coded!
289
+ retval.on_tail('-h', '--help', 'Show this help message') {|foo| puts retval.help(); exit }
290
+
291
+ retval.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]"
292
+
293
+ #puts retval
294
+ return retval
295
+ end
296
+
297
+ end
298
+
299
+ end