illuminator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/gem/README.md +37 -0
- data/gem/bin/illuminatorTestRunner.rb +22 -0
- data/gem/lib/illuminator.rb +171 -0
- data/gem/lib/illuminator/argument-parsing.rb +299 -0
- data/gem/lib/illuminator/automation-builder.rb +39 -0
- data/gem/lib/illuminator/automation-runner.rb +589 -0
- data/gem/lib/illuminator/build-artifacts.rb +118 -0
- data/gem/lib/illuminator/device-installer.rb +45 -0
- data/gem/lib/illuminator/host-utils.rb +42 -0
- data/gem/lib/illuminator/instruments-runner.rb +301 -0
- data/gem/lib/illuminator/javascript-runner.rb +98 -0
- data/gem/lib/illuminator/listeners/console-logger.rb +32 -0
- data/gem/lib/illuminator/listeners/full-output.rb +13 -0
- data/gem/lib/illuminator/listeners/instruments-listener.rb +22 -0
- data/gem/lib/illuminator/listeners/intermittent-failure-detector.rb +49 -0
- data/gem/lib/illuminator/listeners/pretty-output.rb +26 -0
- data/gem/lib/illuminator/listeners/saltinel-agent.rb +66 -0
- data/gem/lib/illuminator/listeners/saltinel-listener.rb +26 -0
- data/gem/lib/illuminator/listeners/start-detector.rb +52 -0
- data/gem/lib/illuminator/listeners/stop-detector.rb +46 -0
- data/gem/lib/illuminator/listeners/test-listener.rb +58 -0
- data/gem/lib/illuminator/listeners/trace-error-detector.rb +38 -0
- data/gem/lib/illuminator/options.rb +96 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedEnvironment.erb +13 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedRunnerForInstruments.erb +19 -0
- data/gem/lib/illuminator/test-definitions.rb +23 -0
- data/gem/lib/illuminator/test-suite.rb +155 -0
- data/gem/lib/illuminator/version.rb +3 -0
- data/gem/lib/illuminator/xcode-builder.rb +144 -0
- data/gem/lib/illuminator/xcode-utils.rb +219 -0
- data/gem/resources/BuildConfiguration.xcconfig +10 -0
- data/gem/resources/js/AppMap.js +767 -0
- data/gem/resources/js/Automator.js +1132 -0
- data/gem/resources/js/Base64.js +142 -0
- data/gem/resources/js/Bridge.js +102 -0
- data/gem/resources/js/Config.js +92 -0
- data/gem/resources/js/Extensions.js +2025 -0
- data/gem/resources/js/Illuminator.js +228 -0
- data/gem/resources/js/Preferences.js +24 -0
- data/gem/resources/scripts/UIAutomationBridge.rb +248 -0
- data/gem/resources/scripts/common.applescript +25 -0
- data/gem/resources/scripts/diff_png.sh +61 -0
- data/gem/resources/scripts/kill_all_sim_processes.sh +17 -0
- data/gem/resources/scripts/plist_to_json.sh +40 -0
- data/gem/resources/scripts/set_hardware_keyboard.applescript +0 -0
- metadata +225 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
require_relative './build-artifacts'
|
8
|
+
require_relative './host-utils'
|
9
|
+
require_relative './xcode-utils'
|
10
|
+
|
11
|
+
# Class to handle all configuration relating to the javascript environment
|
12
|
+
# "runner" is a bit of a misnomer (this runs as part of instruments) but without this code, nothing runs
|
13
|
+
class JavascriptRunner
|
14
|
+
|
15
|
+
attr_reader :saltinel # the salted sentinel
|
16
|
+
attr_accessor :target_device_id
|
17
|
+
attr_accessor :is_hardware
|
18
|
+
attr_accessor :entry_point
|
19
|
+
attr_accessor :test_path
|
20
|
+
attr_accessor :implementation
|
21
|
+
attr_accessor :sim_device
|
22
|
+
attr_accessor :sim_version
|
23
|
+
attr_accessor :random_seed
|
24
|
+
attr_accessor :tags_any
|
25
|
+
attr_accessor :tags_all
|
26
|
+
attr_accessor :tags_none
|
27
|
+
attr_accessor :scenario_list
|
28
|
+
attr_accessor :scenario_number_offset # for consistent numbering after restarts
|
29
|
+
attr_accessor :app_specific_config
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@tags_any = Array.new(0)
|
33
|
+
@tags_all = Array.new(0)
|
34
|
+
@tags_none = Array.new(0)
|
35
|
+
@scenario_list = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def assemble_config
|
40
|
+
@full_config = {}
|
41
|
+
|
42
|
+
# a mapping of the full config key name to the local value that corresponds to it
|
43
|
+
key_defs = {
|
44
|
+
'saltinel' => @saltinel,
|
45
|
+
'entryPoint' => @entry_point,
|
46
|
+
'implementation' => @implementation,
|
47
|
+
'automatorDesiredSimDevice' => @sim_device,
|
48
|
+
'automatorDesiredSimVersion' => @sim_version,
|
49
|
+
'targetDeviceID' => @target_device_id,
|
50
|
+
'isHardware' => @is_hardware,
|
51
|
+
'xcodePath' => Illuminator::XcodeUtils.instance.get_xcode_path,
|
52
|
+
'automatorSequenceRandomSeed' => @random_seed,
|
53
|
+
'automatorTagsAny' => @tags_any,
|
54
|
+
'automatorTagsAll' => @tags_all,
|
55
|
+
'automatorTagsNone' => @tags_none,
|
56
|
+
'automatorScenarioNames' => @scenario_list,
|
57
|
+
'automatorScenarioOffset' => @scenario_number_offset,
|
58
|
+
'customConfig' => (@app_specific_config.is_a? Hash) ? @app_specific_config : @app_specific_config.to_h,
|
59
|
+
}
|
60
|
+
|
61
|
+
key_defs.each do |key, value|
|
62
|
+
@full_config[key] = value unless value.nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def write_configuration()
|
69
|
+
# instance variables required for render_template
|
70
|
+
@saltinel = Digest::SHA1.hexdigest (Time.now.to_i.to_s + Socket.gethostname)
|
71
|
+
@illuminator_root = Illuminator::HostUtils.realpath(File.join(File.dirname(__FILE__), "../../resources/js/"))
|
72
|
+
@illuminator_scripts = Illuminator::HostUtils.realpath(File.join(File.dirname(__FILE__), "../../resources/scripts/"))
|
73
|
+
@artifacts_root = Illuminator::BuildArtifacts.instance.root
|
74
|
+
@illuminator_instruments_root = Illuminator::BuildArtifacts.instance.instruments
|
75
|
+
@environment_file = Illuminator::BuildArtifacts.instance.illuminator_js_environment
|
76
|
+
|
77
|
+
# prepare @full_config
|
78
|
+
assemble_config
|
79
|
+
|
80
|
+
render_template('/resources/IlluminatorGeneratedRunnerForInstruments.erb',
|
81
|
+
Illuminator::BuildArtifacts.instance.illuminator_js_runner)
|
82
|
+
render_template('/resources/IlluminatorGeneratedEnvironment.erb',
|
83
|
+
Illuminator::BuildArtifacts.instance.illuminator_js_environment)
|
84
|
+
|
85
|
+
Illuminator::HostUtils.save_json(@full_config, Illuminator::BuildArtifacts.instance.illuminator_config_file)
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def render_template source_file, destination_file
|
90
|
+
contents = File.open(File.dirname(__FILE__) + source_file, 'r') { |f| f.read }
|
91
|
+
|
92
|
+
renderer = ERB.new(contents)
|
93
|
+
new_file = File.open(destination_file, 'w')
|
94
|
+
new_file.write(renderer.result(binding))
|
95
|
+
new_file.close
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
require_relative '../build-artifacts'
|
4
|
+
require_relative './instruments-listener'
|
5
|
+
|
6
|
+
class ConsoleLogger < InstrumentsListener
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@run_number = 0
|
10
|
+
@logger = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare_logger
|
14
|
+
return unless @logger.nil?
|
15
|
+
@run_number += 1
|
16
|
+
filename = File.join(Illuminator::BuildArtifacts.instance.console, "instruments#{@run_number.to_s.rjust(3, "0")}.log")
|
17
|
+
FileUtils.rmtree filename
|
18
|
+
@logger = Logger.new(filename)
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive (message)
|
22
|
+
prepare_logger
|
23
|
+
@logger << message.full_line
|
24
|
+
@logger << "\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_automation_finished
|
28
|
+
@logger.close unless @logger.nil?
|
29
|
+
@logger = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
class InstrumentsListener
|
3
|
+
|
4
|
+
def receive (message)
|
5
|
+
puts message.full_line
|
6
|
+
puts " date: #{message.date}"
|
7
|
+
puts " time: #{message.time}"
|
8
|
+
puts " tz: #{message.tz}"
|
9
|
+
puts " status: #{message.status}"
|
10
|
+
puts " message: #{message.message}"
|
11
|
+
puts " --- If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
12
|
+
puts
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_automation_finished
|
16
|
+
puts " ==="
|
17
|
+
puts " === If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
18
|
+
puts " ==="
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
require_relative './saltinel-listener'
|
3
|
+
|
4
|
+
module IntermittentFailureDetectorEventSink
|
5
|
+
|
6
|
+
def intermittent_failure_detector_triggered message
|
7
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# IntermittentFailureDetector monitors the logs for things that indicate a transient failure to start instruments
|
13
|
+
# - UIATargetHasGoneAWOLException
|
14
|
+
# - etc
|
15
|
+
class IntermittentFailureDetector < SaltinelListener
|
16
|
+
|
17
|
+
attr_accessor :event_sink
|
18
|
+
|
19
|
+
def on_init
|
20
|
+
@already_started = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def trigger message
|
24
|
+
@event_sink.intermittent_failure_detector_triggered message
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive message
|
28
|
+
super # run the SaltinelListener processor
|
29
|
+
|
30
|
+
# error cases that should trigger a restart
|
31
|
+
p = /Automation Instrument ran into an exception while trying to run the script. UIATargetHasGoneAWOLException/
|
32
|
+
if p =~ message.full_line
|
33
|
+
trigger "UIATargetHasGoneAWOLException"
|
34
|
+
end
|
35
|
+
|
36
|
+
p = /Fail: Could not start script, target application is not frontmost./
|
37
|
+
if p =~ message.full_line
|
38
|
+
trigger "Could not start script, target application is not frontmost"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_saltinel inner_message
|
43
|
+
# no cases yet
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_automation_finished
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require_relative './instruments-listener'
|
3
|
+
|
4
|
+
class PrettyOutput < InstrumentsListener
|
5
|
+
|
6
|
+
def receive (message)
|
7
|
+
case message.status
|
8
|
+
when :start
|
9
|
+
puts "Start: #{message.message}".green
|
10
|
+
when :pass
|
11
|
+
puts "Pass: #{message.message}".green
|
12
|
+
puts
|
13
|
+
when :fail, :error, :issue
|
14
|
+
puts "Fail: #{message.message}".red
|
15
|
+
when :warning
|
16
|
+
puts "Warn: #{message.message}".yellow
|
17
|
+
when :default
|
18
|
+
puts " #{message.message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_automation_finished
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
require_relative './saltinel-listener'
|
3
|
+
|
4
|
+
module SaltinelAgentEventSink
|
5
|
+
|
6
|
+
def saltinel_agent_got_scenario_list jsonPath
|
7
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
8
|
+
end
|
9
|
+
|
10
|
+
def saltinel_agent_got_scenario_definitions jsonPath
|
11
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
12
|
+
end
|
13
|
+
|
14
|
+
def saltinel_agent_got_stacktrace_hint
|
15
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
16
|
+
end
|
17
|
+
|
18
|
+
def saltinel_agent_got_restart_request
|
19
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# Saltinel Agent handles all known saltinel messages and runs callbacks
|
25
|
+
class SaltinelAgent < SaltinelListener
|
26
|
+
|
27
|
+
attr_accessor :event_sink
|
28
|
+
|
29
|
+
def on_init
|
30
|
+
@recognizers = {
|
31
|
+
"recognize_test_list" => /Saved intended test list to: (.*)/,
|
32
|
+
"recognize_test_defs" => /Saved scenario definitions to: (.*)/,
|
33
|
+
"recognize_stacktrace" => /Stack trace follows:/,
|
34
|
+
"recognize_restart_request" => /Request instruments restart/,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def recognize_test_list regexResult
|
39
|
+
# assume developer has set event_sink already
|
40
|
+
@event_sink.saltinel_agent_got_scenario_list(regexResult.to_a[1])
|
41
|
+
end
|
42
|
+
|
43
|
+
def recognize_test_defs regexResult
|
44
|
+
# assume developer has set event_sink already
|
45
|
+
@event_sink.saltinel_agent_got_scenario_definitions(regexResult.to_a[1])
|
46
|
+
end
|
47
|
+
|
48
|
+
def recognize_stacktrace _
|
49
|
+
@event_sink.saltinel_agent_got_stacktrace_hint
|
50
|
+
end
|
51
|
+
|
52
|
+
def recognize_restart_request _
|
53
|
+
@event_sink.saltinel_agent_got_restart_request
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_saltinel inner_message
|
57
|
+
@recognizers.each do |fn, regex|
|
58
|
+
result = regex.match(inner_message)
|
59
|
+
send(fn, result) unless result.nil?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_automation_finished
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require_relative './instruments-listener'
|
3
|
+
|
4
|
+
# A listener class that just looks for saltinel messages and parses them,
|
5
|
+
# sending them off to a subclassable handler
|
6
|
+
class SaltinelListener < InstrumentsListener
|
7
|
+
|
8
|
+
def initialize saltinel
|
9
|
+
@saltinel = saltinel
|
10
|
+
on_init
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_init
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive (message)
|
17
|
+
unsalted = /^#{@saltinel} (.*) #{@saltinel}/.match(message.message)
|
18
|
+
on_saltinel(unsalted.to_a[1]) unless unsalted.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_saltinel inner_message
|
22
|
+
puts "CAUGHT SALTINEL MESSAGE in #{self.class.name}.on_saltinel".red
|
23
|
+
puts inner_message.green
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require_relative './saltinel-listener'
|
3
|
+
|
4
|
+
module StartDetectorEventSink
|
5
|
+
|
6
|
+
def start_detector_triggered
|
7
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# StartDetector monitors the logs for things that indicate a successful startup of instruments
|
13
|
+
# - the saltinel for the intended test list
|
14
|
+
# - a javascript error
|
15
|
+
# - etc
|
16
|
+
class StartDetector < SaltinelListener
|
17
|
+
|
18
|
+
attr_accessor :event_sink
|
19
|
+
|
20
|
+
def on_init
|
21
|
+
@already_started = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def trigger
|
25
|
+
# assume developer has set event_sink already
|
26
|
+
@event_sink.start_detector_triggered unless @already_started
|
27
|
+
@already_started = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive message
|
31
|
+
super # run the SaltinelListener processor
|
32
|
+
|
33
|
+
# error cases that indicate successful start but involve errors that won't be fixed by a restart
|
34
|
+
trigger if :error == message.status and /Script threw an uncaught JavaScript error:/ =~ message.message
|
35
|
+
|
36
|
+
# Instruments usage error generally means we can't recover... unless it's a device booting issue
|
37
|
+
if /^Instruments Usage Error :/ =~ message.full_line
|
38
|
+
unless /^Instruments Usage Error : Timed out waiting for device to boot:/ =~ message.full_line
|
39
|
+
trigger
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_saltinel inner_message
|
45
|
+
trigger if /Saved intended test list to/ =~ inner_message
|
46
|
+
trigger if /Successful launch/ =~ inner_message
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_automation_finished
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
require_relative 'instruments-listener'
|
3
|
+
|
4
|
+
module StopDetectorEventSink
|
5
|
+
|
6
|
+
# fatal means that restarting instruments won't fix it
|
7
|
+
def stop_detector_triggered(fatal, message)
|
8
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
# StopDetector monitors the logs for things that indicate that instruments should be stopped (user errors)
|
14
|
+
# - a message that automation has stopped
|
15
|
+
# - a message that the user needs to enter credentials
|
16
|
+
# - etc
|
17
|
+
class StopDetector < InstrumentsListener
|
18
|
+
|
19
|
+
attr_accessor :event_sink
|
20
|
+
|
21
|
+
def trigger(fatal, message)
|
22
|
+
# assume developer has set event_sink already
|
23
|
+
@event_sink.stop_detector_triggered(fatal, message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive message
|
27
|
+
# error cases that indicate successful stop but involve errors that won't be fixed by a restart
|
28
|
+
|
29
|
+
# like if instruments says it stopped
|
30
|
+
trigger(false, message.message) if :stopped == message.status
|
31
|
+
|
32
|
+
# or if instruments prompts for username/password
|
33
|
+
p1 = "instruments: Instruments wants permission to analyze other processes."
|
34
|
+
p2 = "Please enter an administrator username and password to allow this."
|
35
|
+
|
36
|
+
p = /#{p1} #{p2}/
|
37
|
+
if p =~ message.full_line
|
38
|
+
trigger(true, "Instruments needs permission to analyze other processes. Please run instruments manually to permit this.")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_automation_finished
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require_relative './instruments-listener'
|
4
|
+
|
5
|
+
module TestListenerEventSink
|
6
|
+
|
7
|
+
def test_listener_got_test_start name
|
8
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_listener_got_test_pass name
|
12
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_listener_got_test_fail message
|
16
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_listener_got_test_error message
|
20
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def test_listener_got_line(status, message)
|
25
|
+
puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
class TestListener < InstrumentsListener
|
32
|
+
|
33
|
+
attr_accessor :event_sink
|
34
|
+
|
35
|
+
def receive(message)
|
36
|
+
# assume developer has set event_sink already
|
37
|
+
|
38
|
+
# signal test starts before general logging
|
39
|
+
@event_sink.test_listener_got_test_start message.message if message.status == :start
|
40
|
+
|
41
|
+
# log all lines
|
42
|
+
if message.status == :unknown
|
43
|
+
@event_sink.test_listener_got_line nil, message.full_line
|
44
|
+
else
|
45
|
+
@event_sink.test_listener_got_line message.status, message.message
|
46
|
+
end
|
47
|
+
|
48
|
+
# signal test ends after logs
|
49
|
+
@event_sink.test_listener_got_test_pass message.message if message.status == :pass
|
50
|
+
@event_sink.test_listener_got_test_fail message.message if message.status == :fail
|
51
|
+
@event_sink.test_listener_got_test_error message.message if message.status == :error
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_automation_finished
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|