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,38 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module TraceErrorDetectorEventSink
4
+
5
+ # fatal means that restarting instruments won't fix it
6
+ def trace_error_detector_triggered(fatal, message)
7
+ puts " +++ If you're seeing this, #{self.class.name}.#{__method__} was not overridden"
8
+ end
9
+
10
+ end
11
+
12
+ # TraceErrorDetector monitors the logs for things that indicate a transient failure to start instruments
13
+ # - "unable to install app"
14
+ # - etc
15
+ class TraceErrorDetector < InstrumentsListener
16
+
17
+ attr_accessor :event_sink
18
+
19
+ def trigger(fatal, message)
20
+ @event_sink.trace_error_detector_triggered(fatal, message)
21
+ end
22
+
23
+ def receive message
24
+ itr = "Instruments Trace Error : Target failed to run:"
25
+ if message.full_line =~ /#{itr} Unable to install app with path:/
26
+ trigger(false, "Failed to install app because #{message.full_line.split(': ')[-1]}")
27
+ elsif message.full_line =~ /#{itr} The operation couldn’t be completed./
28
+ trigger(false, "An operation couldn't be completed because #{message.full_line.split(': ')[-1]}")
29
+ elsif message.full_line =~ /Instruments Trace Error/i
30
+ trigger(false, message.full_line.split(' : ')[1..-1].join)
31
+ end
32
+
33
+ end
34
+
35
+ def on_automation_finished
36
+ end
37
+
38
+ end
@@ -0,0 +1,96 @@
1
+
2
+ require 'ostruct'
3
+
4
+ class RecursiveOpenStruct < OpenStruct
5
+
6
+ def initialize(hash=nil)
7
+ # preprocess hash objects into openstruct objects
8
+ unless hash.nil?
9
+ hash.each do |k, v|
10
+ hash[k] = RecursiveOpenStruct.new(v) if v.is_a? Hash
11
+ end
12
+ end
13
+
14
+ super
15
+ end
16
+
17
+ # recursively translate the openstruct hierarchy to a hash hierarchy
18
+ def to_h
19
+ ret = super
20
+
21
+ ret.each do |k, v|
22
+ ret[k] = v.to_h if v.is_a? RecursiveOpenStruct
23
+ end
24
+
25
+ return ret
26
+ end
27
+ end
28
+
29
+ module Illuminator
30
+ class Options < RecursiveOpenStruct
31
+
32
+ def initialize(hash=nil)
33
+ super
34
+ return unless hash.nil?
35
+
36
+ # stub out all the branches
37
+ self.xcode = RecursiveOpenStruct.new
38
+ self.instruments = RecursiveOpenStruct.new
39
+ self.simulator = RecursiveOpenStruct.new
40
+ self.javascript = RecursiveOpenStruct.new
41
+ self.illuminator = RecursiveOpenStruct.new
42
+ self.app_specific = nil # all unknown options will go here
43
+ self.build_artifacts_dir = nil
44
+
45
+ self.illuminator.clean = RecursiveOpenStruct.new
46
+ self.illuminator.task = RecursiveOpenStruct.new
47
+ self.illuminator.test = RecursiveOpenStruct.new
48
+
49
+ self.illuminator.test.tags = RecursiveOpenStruct.new
50
+ self.illuminator.test.retest = RecursiveOpenStruct.new
51
+
52
+ # name all the keys (just for visibiilty)
53
+ self.xcode.project_dir = nil
54
+ self.xcode.project = nil
55
+ self.xcode.app_name = nil
56
+ self.xcode.sdk = nil
57
+ self.xcode.workspace = nil
58
+ self.xcode.scheme = nil
59
+ self.xcode.environment_vars = nil
60
+
61
+ self.illuminator.entry_point = nil
62
+ self.illuminator.test.random_seed = nil
63
+ self.illuminator.test.tags.any = nil
64
+ self.illuminator.test.tags.all = nil
65
+ self.illuminator.test.tags.none = nil
66
+ self.illuminator.test.names = nil
67
+ self.illuminator.test.retest.attempts = nil
68
+ self.illuminator.test.retest.solo = nil
69
+ self.illuminator.clean.xcode = nil
70
+ self.illuminator.clean.derived = nil
71
+ self.illuminator.clean.artifacts = nil
72
+ self.illuminator.clean.no_delay = nil
73
+ self.illuminator.task.build = nil
74
+ self.illuminator.task.automate = nil
75
+ self.illuminator.task.set_sim = nil
76
+ self.illuminator.task.coverage = nil
77
+ self.illuminator.hardware_id = nil
78
+
79
+ self.simulator.device = nil
80
+ self.simulator.version = nil
81
+ self.simulator.language = nil
82
+ self.simulator.kill_after = nil
83
+
84
+ self.instruments.do_verbose = nil
85
+ self.instruments.timeout = nil
86
+ self.instruments.max_silence = nil
87
+ self.instruments.attempts = nil
88
+ self.instruments.app_location = nil # normally, this is where we build to
89
+
90
+ self.javascript.test_path = nil
91
+ self.javascript.implementation = nil
92
+ self.javascript.app_specific_config = nil
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,13 @@
1
+ /**
2
+ * This file is generated automatically.
3
+ * It provides global path information that would be problematic to introduce through config values
4
+ * (especially given that the config comes from a file)
5
+ */
6
+
7
+ // Based on the way UIAutomation processes imports, this will be the first line of code to actually execute
8
+ UIALogger.logDebug("Illuminator has launched.");
9
+
10
+ var IlluminatorRootDirectory = "<%= @illuminator_root %>";
11
+ var IlluminatorScriptsDirectory = "<%= @illuminator_scripts %>";
12
+ var IlluminatorBuildArtifactsDirectory = "<%= @artifacts_root %>";
13
+ var IlluminatorInstrumentsOutputDirectory = "<%= @illuminator_instruments_root %>";
@@ -0,0 +1,19 @@
1
+ #import "<%= @environment_file %>";
2
+ #import "<%= @illuminator_root %>/Preferences.js";
3
+ #import "<%= @illuminator_root %>/Extensions.js";
4
+ #import "<%= @illuminator_root %>/Base64.js";
5
+ #import "<%= @illuminator_root %>/Config.js";
6
+ #import "<%= @illuminator_root %>/AppMap.js";
7
+ #import "<%= @illuminator_root %>/Automator.js";
8
+ #import "<%= @illuminator_root %>/Bridge.js";
9
+ #import "<%= @illuminator_root %>/Illuminator.js";
10
+ #import "<%= @test_path %>";
11
+
12
+ /**
13
+ * IlluminatorGeneratedRunnerForInstruments.js is generated from
14
+ * IlluminatorGeneratedRunnerForInstruments.erb; it should not be hand-edited.
15
+ *
16
+ * This file is used to import your test definitions into the automation framework.
17
+ */
18
+
19
+ IlluminatorIlluminate();
@@ -0,0 +1,23 @@
1
+ require 'json'
2
+
3
+ # A class to hold the defintions of all automator tests, as defined in the (generated) automatorScenarios.json
4
+ class TestDefinitions
5
+
6
+ def initialize automator_settings_json_path
7
+ raw_defs = JSON.parse( IO.read(automator_settings_json_path) )
8
+ @in_order = raw_defs["scenarios"].dup
9
+
10
+ # save test defs for use later (as lookups)
11
+ @by_name = {}
12
+ @in_order.each { |scen| @by_name[scen["title"]] = scen }
13
+ end
14
+
15
+ def by_name name
16
+ @by_name[name].dup
17
+ end
18
+
19
+ def by_index idx
20
+ @in_order[idx].dup
21
+ end
22
+
23
+ end
@@ -0,0 +1,155 @@
1
+ require 'date'
2
+
3
+ class TestSuite
4
+
5
+ attr_reader :test_cases
6
+ attr_reader :implementation
7
+
8
+ def initialize(implementation)
9
+ @implementation = implementation
10
+ @test_cases = []
11
+ @case_lookup = {}
12
+ end
13
+
14
+ def add_test_case(class_name, name)
15
+ test = TestCase.new(@implementation, class_name, name)
16
+ @test_cases << test
17
+ @case_lookup[name] = test
18
+ end
19
+
20
+ def [](test_case_name)
21
+ @case_lookup[test_case_name]
22
+ end
23
+
24
+ # TODO: fix naming, some of these return test cases and some return arrays of names
25
+
26
+ def unstarted_tests
27
+ @test_cases.reject { |t| t.ran? }.map { |t| t.name }
28
+ end
29
+
30
+ def finished_tests
31
+ @test_cases.select { |t| t.ran? } .map { |t| t.name }
32
+ end
33
+
34
+ def all_tests
35
+ @test_cases.dup
36
+ end
37
+
38
+ def passed_tests
39
+ @test_cases.select { |t| t.passed? }
40
+ end
41
+
42
+ def unpassed_tests
43
+ @test_cases.reject { |t| t.passed? }
44
+ end
45
+
46
+ def to_xml
47
+ output = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' << "\n"
48
+
49
+ output << "<testsuite>\n"
50
+ @test_cases.each { |test| output << test.to_xml }
51
+ output << "</testsuite>" << "\n"
52
+ output
53
+ end
54
+ end
55
+
56
+ class TestCase
57
+ attr_reader :name
58
+ attr_reader :class_name
59
+ attr_reader :implementation
60
+
61
+ attr_accessor :stacktrace
62
+
63
+ def initialize(implementation, class_name, name)
64
+ @implementation = implementation
65
+ @class_name = class_name
66
+ @name = name
67
+ reset!
68
+ end
69
+
70
+ def <<(stdoutLine)
71
+ @stdout << stdoutLine
72
+ end
73
+
74
+ def reset!
75
+ @stdout = []
76
+ @stacktrace = ""
77
+ @fail_message = ""
78
+ @fail_tag = nil
79
+ @time_start = nil
80
+ @time_finish = nil
81
+ end
82
+
83
+ def start!
84
+ @time_start = Time.now
85
+ end
86
+
87
+ def pass!
88
+ @time_finish = Time.now
89
+ end
90
+
91
+ def fail message
92
+ @time_finish = Time.now
93
+ @fail_tag = "failure"
94
+ @fail_message = message
95
+ end
96
+
97
+ def error message
98
+ @time_finish = Time.now
99
+ @fail_tag = "error"
100
+ @fail_message = message
101
+ end
102
+
103
+ def ran?
104
+ not (@time_start.nil? or @time_finish.nil?)
105
+ end
106
+
107
+ def passed?
108
+ @fail_tag.nil?
109
+ end
110
+
111
+ # this is NOT the opposite of passed! this does not count errored tests
112
+ def failed?
113
+ @fail_tag == "failure"
114
+ end
115
+
116
+ def errored?
117
+ @fail_tag == "error"
118
+ end
119
+
120
+ def time
121
+ return 0 if @time_finish.nil?
122
+ @time_finish - @time_start
123
+ end
124
+
125
+ def to_xml
126
+ attrs = {
127
+ "name" => @name,
128
+ "classname" => "#{@implementation}.#{@class_name}",
129
+ "time" => time,
130
+ }
131
+
132
+ output = " <testcase"
133
+ attrs.each { |key, value| output << " #{key}=#{value.to_s.encode(:xml => :attr)}" }
134
+ output << ">\n"
135
+
136
+ if not ran?
137
+ output << " <skipped />\n"
138
+ elsif (not @fail_tag.nil?)
139
+ fattrs = {
140
+ "message" => @fail_message,
141
+ }
142
+
143
+ output << " <#{@fail_tag}"
144
+ fattrs.each { |key, value| output << " #{key}=#{value.to_s.encode(:xml => :attr)}" }
145
+ output << ">#{@stacktrace.to_s.encode(:xml => :text)}" << "\n"
146
+ output << " </#{@fail_tag}>" << "\n"
147
+ end
148
+
149
+ output << " <system-out>#{@stdout.map { |m| m.encode(:xml => :text) }.join("\n")}" << "\n"
150
+ output << " </system-out>" << "\n"
151
+
152
+ output << " </testcase>" << "\n"
153
+
154
+ end
155
+ end
@@ -0,0 +1,3 @@
1
+ module Illuminator
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,144 @@
1
+ require 'colorize'
2
+
3
+ require_relative './build-artifacts'
4
+ require_relative './host-utils'
5
+
6
+ module Illuminator
7
+ class XcodeBuilder
8
+ attr_accessor :project
9
+ attr_accessor :configuration
10
+ attr_accessor :sdk
11
+ attr_accessor :arch
12
+ attr_accessor :scheme
13
+ attr_accessor :project_dir
14
+ attr_accessor :workspace
15
+ attr_accessor :destination
16
+ attr_accessor :xcconfig
17
+ attr_accessor :do_clean
18
+ attr_accessor :do_test
19
+ attr_accessor :do_build
20
+ attr_accessor :do_archive
21
+ attr_accessor :derived_data_is_artifact
22
+
23
+ attr_reader :exit_code
24
+
25
+ def initialize
26
+ @parameters = Hash.new
27
+ @environment_vars = Hash.new
28
+ @project_dir = nil
29
+ @do_clean = FALSE
30
+ @do_test = FALSE
31
+ @do_build = TRUE
32
+ @do_archive = FALSE
33
+ @exit_code = nil
34
+
35
+ @derived_data_is_artifact = FALSE
36
+
37
+ result_path = Illuminator::BuildArtifacts.instance.xcode
38
+ add_environment_variable('CONFIGURATION_BUILD_DIR', "'#{result_path}'")
39
+ add_environment_variable('CONFIGURATION_TEMP_DIR', "'#{result_path}'")
40
+ end
41
+
42
+ def set_build_artifacts_root root_dir
43
+ Illuminator::BuildArtifacts.instance.set_root(root_dir)
44
+ end
45
+
46
+ def add_parameter(parameter_name = '',parameter_value = '')
47
+ @parameters[parameter_name] = parameter_value
48
+ end
49
+
50
+ def add_environment_variable(parameter_name = '',parameter_value = '')
51
+ @environment_vars[parameter_name] = parameter_value
52
+ end
53
+
54
+ def _assemble_config
55
+ # put standard parameters into parameters
56
+ key_defs = {
57
+ 'project' => @project,
58
+ 'configuration' => @configuration,
59
+ 'sdk' => @sdk,
60
+ 'arch' => @arch,
61
+ 'scheme' => @scheme,
62
+ 'destination' => @destination,
63
+ 'workspace' => @workspace,
64
+ 'xcconfig' => @xcconfig,
65
+ }
66
+
67
+ # since derived data can take quite a lot of disk space, don't automatically store it
68
+ # in build-specific directory
69
+ if @derived_data_is_artifact
70
+ key_defs['derivedDataPath'] = Illuminator::BuildArtifacts.instance.derived_data
71
+ end
72
+
73
+ key_defs.each do |key, value|
74
+ add_parameter(key, value) unless value.nil?
75
+ end
76
+ end
77
+
78
+
79
+ def _build_command
80
+ use_pipefail = false # debug option
81
+ _assemble_config
82
+
83
+ parameters = ''
84
+ environment_vars = ''
85
+ tasks = ''
86
+
87
+ @parameters.each { |name, value| parameters << " -#{name} \"#{value}\"" }
88
+ @environment_vars.each { |name, value| environment_vars << " #{name}=#{value}" }
89
+
90
+ tasks << ' clean' if @do_clean
91
+ tasks << ' build' if @do_build
92
+ tasks << ' archive' if @do_archive
93
+ tasks << ' test' if @do_test
94
+
95
+ command = ''
96
+ command << 'set -o pipefail && ' if use_pipefail
97
+ command << 'xcodebuild'
98
+ command << parameters << environment_vars << tasks
99
+ command << " | tee '#{logfile_path}'"
100
+ unless Illuminator::HostUtils.which("xcpretty").nil? # use xcpretty if available
101
+ command << " | xcpretty -c -r junit -o \"#{BuildArtifacts.instance.xcpretty_report_file}\""
102
+ end
103
+ command << ' && exit ${PIPESTATUS[0]}' unless use_pipefail
104
+
105
+ command
106
+ end
107
+
108
+
109
+ def logfile_path
110
+ log_file = File.join(Illuminator::BuildArtifacts.instance.console, 'xcodebuild.log')
111
+ end
112
+
113
+
114
+ def _execute_build_command command
115
+ puts command.green
116
+ process = IO.popen(command) do |io|
117
+ io.each {|line| puts line}
118
+ io.close
119
+ end
120
+
121
+ ec = $?
122
+ @exit_code = ec.exitstatus
123
+ return @exit_code == 0
124
+ end
125
+
126
+
127
+ def build
128
+ command = _build_command
129
+
130
+ # switch to a directory (if desired) and build
131
+ directory = Dir.pwd
132
+ retval = nil
133
+ begin
134
+ Dir.chdir(@project_dir) unless @project_dir.nil?
135
+ retval = _execute_build_command command
136
+ ensure
137
+ Dir.chdir(directory) unless @project_dir.nil?
138
+ end
139
+
140
+ retval
141
+ end
142
+
143
+ end
144
+ end