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