illuminator 0.1.0

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 (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