xcoder 0.1.11 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -0
- data/lib/xcode/builder.rb +32 -19
- data/lib/xcode/shell.rb +3 -1
- data/lib/xcode/test/parsers/ocunit_parser.rb +94 -0
- data/lib/xcode/test/report.rb +121 -0
- data/lib/xcode/test/report/suite_result.rb +67 -0
- data/lib/xcode/test/report/test_result.rb +49 -0
- data/lib/xcode/version.rb +1 -1
- data/lib/xcode/workspace.rb +6 -3
- data/spec/TestProject/{TestProjectTests/TestProjectTests-Info.plist → ApplicationTests/ApplicationTests-Info.plist} +0 -0
- data/spec/TestProject/ApplicationTests/ApplicationTests-Prefix.pch +8 -0
- data/spec/TestProject/ApplicationTests/ApplicationTests.h +13 -0
- data/spec/TestProject/ApplicationTests/ApplicationTests.m +32 -0
- data/spec/TestProject/{TestProjectTests → ApplicationTests}/en.lproj/InfoPlist.strings +0 -0
- data/spec/TestProject/{TestProjectTests → LogicTests}/AnotherTest.h +0 -0
- data/spec/TestProject/{TestProjectTests → LogicTests}/AnotherTest.m +0 -0
- data/spec/TestProject/LogicTests/TestProjectTests-Info.plist +22 -0
- data/spec/TestProject/{TestProjectTests → LogicTests}/TestProjectTests.h +0 -0
- data/spec/TestProject/{TestProjectTests → LogicTests}/TestProjectTests.m +0 -0
- data/spec/TestProject/LogicTests/en.lproj/InfoPlist.strings +2 -0
- data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1329 -556
- data/spec/TestProject/TestProject.xcodeproj/xcshareddata/xcschemes/TestProject.xcscheme +13 -2
- data/spec/TestWorkspace2.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/TestWorkspace2.xcworkspace/xcuserdata/ray.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/spec/build_phase_spec.rb +2 -2
- data/spec/builder_spec.rb +51 -10
- data/spec/integration/builder_spec.rb +60 -4
- data/spec/ocunit_parser_spec.rb +164 -0
- data/spec/project_spec.rb +1 -1
- data/spec/workspace_spec.rb +8 -3
- metadata +44 -29
- data/lib/xcode/test/ocunit_report_parser.rb +0 -174
- data/lib/xcode/test/suite_result.rb +0 -39
- data/lib/xcode/test/test_result.rb +0 -43
- data/spec/test_report_spec.rb +0 -147
data/Rakefile
CHANGED
@@ -4,10 +4,12 @@ require "yard/rake/yardoc_task"
|
|
4
4
|
|
5
5
|
task :default => [:specs, :build]
|
6
6
|
|
7
|
+
desc "Run specs"
|
7
8
|
task :specs do
|
8
9
|
system "rspec --color --format d --tag ~integration"
|
9
10
|
end
|
10
11
|
|
12
|
+
desc "Run integration tests"
|
11
13
|
task :integration do
|
12
14
|
system "rspec --color --format d --tag integration"
|
13
15
|
end
|
data/lib/xcode/builder.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'xcode/shell'
|
2
2
|
require 'xcode/provisioning_profile'
|
3
|
-
require 'xcode/test/
|
3
|
+
require 'xcode/test/parsers/ocunit_parser.rb'
|
4
4
|
require 'xcode/testflight'
|
5
5
|
|
6
6
|
module Xcode
|
@@ -10,7 +10,7 @@ module Xcode
|
|
10
10
|
# project build tasks.
|
11
11
|
#
|
12
12
|
class Builder
|
13
|
-
attr_accessor :profile, :identity, :build_path, :keychain, :sdk
|
13
|
+
attr_accessor :profile, :identity, :build_path, :keychain, :sdk, :objroot, :symroot
|
14
14
|
|
15
15
|
def initialize(config)
|
16
16
|
if config.is_a? Xcode::Scheme
|
@@ -23,37 +23,49 @@ module Xcode
|
|
23
23
|
@sdk = @target.project.sdk
|
24
24
|
@config = config
|
25
25
|
@build_path = "#{File.dirname(@target.project.path)}/build/"
|
26
|
+
@objroot = @build_path
|
27
|
+
@symroot = @build_path
|
26
28
|
end
|
27
29
|
|
28
30
|
|
29
|
-
def build(sdk
|
30
|
-
cmd = build_command(
|
31
|
+
def build(options = {:sdk => @sdk})
|
32
|
+
cmd = build_command(options)
|
31
33
|
with_keychain do
|
32
34
|
Xcode::Shell.execute(cmd)
|
33
35
|
end
|
34
36
|
self
|
35
37
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
|
39
|
+
#
|
40
|
+
# Invoke the configuration's test target and parse the resulting output
|
41
|
+
#
|
42
|
+
# If a block is provided, the report is yielded for configuration before the test is run
|
43
|
+
#
|
44
|
+
def test(options = {:sdk => 'iphonesimulator'}) #, :parser => :OCUnit })
|
45
|
+
cmd = build_command(options)
|
39
46
|
cmd << "TEST_AFTER_BUILD=YES"
|
40
|
-
cmd << "TEST_HOST=''"
|
41
47
|
|
42
|
-
|
43
|
-
|
48
|
+
report = Xcode::Test::Report.new
|
49
|
+
if block_given?
|
50
|
+
yield(report)
|
51
|
+
else
|
52
|
+
report.add_formatter :stdout
|
53
|
+
report.add_formatter :junit, 'test-reports'
|
54
|
+
end
|
55
|
+
|
56
|
+
parser = Xcode::Test::Parsers::OCUnitParser.new report
|
44
57
|
|
45
58
|
begin
|
46
59
|
Xcode::Shell.execute(cmd, false) do |line|
|
47
60
|
parser << line
|
48
61
|
end
|
49
|
-
rescue => e
|
62
|
+
rescue Xcode::Shell::ExecutionError => e
|
63
|
+
puts "Test platform exited: #{e.message}"
|
64
|
+
ensure
|
50
65
|
parser.flush
|
51
|
-
# Let the failure bubble up unless parser has got an error from the output
|
52
|
-
raise e unless parser.failed?
|
53
66
|
end
|
54
|
-
exit 1 if parser.failed?
|
55
67
|
|
56
|
-
|
68
|
+
report
|
57
69
|
end
|
58
70
|
|
59
71
|
def testflight(api_token, team_token)
|
@@ -202,11 +214,12 @@ module Xcode
|
|
202
214
|
p
|
203
215
|
end
|
204
216
|
|
205
|
-
def build_command(
|
217
|
+
def build_command(options = {})
|
218
|
+
options = {:sdk => @sdk}.merge options
|
206
219
|
profile = install_profile
|
207
220
|
cmd = []
|
208
221
|
cmd << "xcodebuild"
|
209
|
-
cmd << "-sdk #{sdk}" unless sdk.nil?
|
222
|
+
cmd << "-sdk #{options[:sdk]}" unless options[:sdk].nil?
|
210
223
|
cmd << "-project \"#{@target.project.path}\""
|
211
224
|
|
212
225
|
cmd << "-scheme \"#{@scheme.name}\"" unless @scheme.nil?
|
@@ -215,8 +228,8 @@ module Xcode
|
|
215
228
|
|
216
229
|
cmd << "OTHER_CODE_SIGN_FLAGS='--keychain #{@keychain.path}'" unless @keychain.nil?
|
217
230
|
cmd << "CODE_SIGN_IDENTITY=\"#{@identity}\"" unless @identity.nil?
|
218
|
-
cmd << "OBJROOT=\"#{@
|
219
|
-
cmd << "SYMROOT=\"#{@
|
231
|
+
cmd << "OBJROOT=\"#{@objroot}\""
|
232
|
+
cmd << "SYMROOT=\"#{@symroot}\""
|
220
233
|
cmd << "PROVISIONING_PROFILE=#{profile.uuid}" unless profile.nil?
|
221
234
|
cmd
|
222
235
|
end
|
data/lib/xcode/shell.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Xcode
|
2
2
|
module Shell
|
3
3
|
|
4
|
+
class ExecutionError < StandardError; end
|
5
|
+
|
4
6
|
def self.execute(bits, show_output=true)
|
5
7
|
out = []
|
6
8
|
cmd = bits.is_a?(Array) ? bits.join(' ') : bits
|
@@ -14,7 +16,7 @@ module Xcode
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
#Process.wait
|
17
|
-
raise "Error (#{$?.exitstatus}) executing '#{cmd}'\n\n #{out.join(" ")}" if $?.exitstatus>0
|
19
|
+
raise ExecutionError.new("Error (#{$?.exitstatus}) executing '#{cmd}'\n\n #{out.join(" ")}") if $?.exitstatus>0
|
18
20
|
#puts "RETURN: #{out.inspect}"
|
19
21
|
out
|
20
22
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'xcode/test/report'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Test
|
6
|
+
module Parsers
|
7
|
+
|
8
|
+
class OCUnitParser
|
9
|
+
attr_accessor :report, :builder
|
10
|
+
|
11
|
+
def initialize(report = Xcode::Test::Report.new)
|
12
|
+
@report = report
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def flush
|
17
|
+
@report.finish
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(piped_row)
|
21
|
+
case piped_row.force_encoding("UTF-8")
|
22
|
+
|
23
|
+
when /Test Suite '(\S+)'.*started at\s+(.*)/
|
24
|
+
name = $1
|
25
|
+
time = Time.parse($2)
|
26
|
+
if name=~/\//
|
27
|
+
@report.start
|
28
|
+
else
|
29
|
+
@report.add_suite name, time
|
30
|
+
end
|
31
|
+
|
32
|
+
when /Test Suite '(\S+)'.*finished at\s+(.*)./
|
33
|
+
time = Time.parse($2)
|
34
|
+
name = $1
|
35
|
+
if name=~/\//
|
36
|
+
@report.finish
|
37
|
+
else
|
38
|
+
@report.in_current_suite do |suite|
|
39
|
+
suite.finish(time)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
when /Test Case '-\[\S+\s+(\S+)\]' started./
|
44
|
+
name = $1
|
45
|
+
@report.in_current_suite do |suite|
|
46
|
+
suite.add_test_case name
|
47
|
+
end
|
48
|
+
|
49
|
+
when /Test Case '-\[\S+\s+(\S+)\]' passed \((.*) seconds\)/
|
50
|
+
duration = $2.to_f
|
51
|
+
@report.in_current_test do |test|
|
52
|
+
test.passed(duration)
|
53
|
+
end
|
54
|
+
|
55
|
+
when /(.*): error: -\[(\S+) (\S+)\] : (.*)/
|
56
|
+
message = $4
|
57
|
+
location = $1
|
58
|
+
@report.in_current_test do |test|
|
59
|
+
test.add_error(message, location)
|
60
|
+
end
|
61
|
+
|
62
|
+
when /Test Case '-\[\S+ (\S+)\]' failed \((\S+) seconds\)/
|
63
|
+
duration = $2.to_f
|
64
|
+
@report.in_current_test do |test|
|
65
|
+
test.failed(duration)
|
66
|
+
end
|
67
|
+
|
68
|
+
# when /failed with exit code (\d+)/,
|
69
|
+
when /BUILD FAILED/
|
70
|
+
@report.finish
|
71
|
+
|
72
|
+
when /Segmentation fault/
|
73
|
+
@report.abort
|
74
|
+
|
75
|
+
when /Run test case (\w+)/
|
76
|
+
# ignore
|
77
|
+
when /Run test suite (\w+)/
|
78
|
+
# ignore
|
79
|
+
when /Executed (\d+) test, with (\d+) failures \((\d+) unexpected\) in (\S+) \((\S+)\) seconds/
|
80
|
+
# ignore
|
81
|
+
when /the iPhoneSimulator platform does not currently support application-hosted tests/
|
82
|
+
raise "Application tests are not currently supported by the iphone simulator. If these are logic tests, try unsetting TEST_HOST in your project config"
|
83
|
+
else
|
84
|
+
@report.in_current_test do |test|
|
85
|
+
test << piped_row
|
86
|
+
end
|
87
|
+
end # case
|
88
|
+
|
89
|
+
end # <<
|
90
|
+
|
91
|
+
end # OCUnitParser
|
92
|
+
end # Parsers
|
93
|
+
end # Test
|
94
|
+
end # Xcode
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'xcode/test/report/suite_result'
|
2
|
+
require 'xcode/test/report/test_result'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Test
|
6
|
+
|
7
|
+
module Formatters
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
# The report is the abstract representation of a collection of suites of tests. Formatters can be attached to write output
|
12
|
+
# in real time
|
13
|
+
class Report
|
14
|
+
attr_reader :suites, :observers
|
15
|
+
attr_accessor :start_time, :end_time, :exit_code, :unexpected
|
16
|
+
|
17
|
+
|
18
|
+
class InvalidStateException < StandardError; end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@debug = false
|
22
|
+
@exit_code = 0
|
23
|
+
@suites = []
|
24
|
+
@formatters = []
|
25
|
+
@start_time = nil
|
26
|
+
@end_time = nil
|
27
|
+
@unexpected = false
|
28
|
+
@observers = []
|
29
|
+
|
30
|
+
yield self if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_formatter(format, *args)
|
34
|
+
require "xcode/test/formatters/#{format.to_s}_formatter"
|
35
|
+
formatter = Xcode::Test::Formatters.const_get("#{format.to_s.capitalize}Formatter").new(*args)
|
36
|
+
@observers << formatter
|
37
|
+
end
|
38
|
+
|
39
|
+
def unexpected?
|
40
|
+
@unexpected
|
41
|
+
end
|
42
|
+
|
43
|
+
def succeed?
|
44
|
+
!self.failed?
|
45
|
+
end
|
46
|
+
|
47
|
+
def failed?
|
48
|
+
return true if unexpected?
|
49
|
+
|
50
|
+
@suites.each do |suite|
|
51
|
+
suite.tests.each do |test|
|
52
|
+
return true if test.failed?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def start
|
60
|
+
@start_time = Time.now
|
61
|
+
notify_observers :before, self
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_suite(name, time=Time.now)
|
65
|
+
suite = Xcode::Test::Report::SuiteResult.new(self, name, time)
|
66
|
+
@suites << suite
|
67
|
+
end
|
68
|
+
|
69
|
+
def finished?
|
70
|
+
!@end_time.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def duration
|
74
|
+
return 0 if @start_time.nil?
|
75
|
+
return Time.now - @start_time if @end_time.nil?
|
76
|
+
@end_time - @start_time
|
77
|
+
end
|
78
|
+
|
79
|
+
def in_current_suite
|
80
|
+
# raise InvalidStateException.new("There is no active suite")
|
81
|
+
return if @suites.size==0 or !@suites.last.end_time.nil?
|
82
|
+
yield @suites.last
|
83
|
+
end
|
84
|
+
|
85
|
+
def in_current_test
|
86
|
+
in_current_suite do |suite|
|
87
|
+
# raise InvalidStateException.new("There is no active test case")
|
88
|
+
return if suite.tests.size==0
|
89
|
+
yield suite.tests.last
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def finish
|
94
|
+
return if finished?
|
95
|
+
|
96
|
+
# if there is a current suite which isnt finished - finish it
|
97
|
+
in_current_suite do |suite|
|
98
|
+
unless suite.finished?
|
99
|
+
@unexpected = true
|
100
|
+
suite.finish
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
@end_time = Time.now
|
105
|
+
notify_observers :after, self
|
106
|
+
end
|
107
|
+
|
108
|
+
def abort
|
109
|
+
@report.unexpected=true
|
110
|
+
finish
|
111
|
+
end
|
112
|
+
|
113
|
+
def notify_observers(event, obj=nil)
|
114
|
+
@observers.each do |f|
|
115
|
+
f.send event, obj if f.respond_to? event
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end # Report
|
120
|
+
end # Test
|
121
|
+
end # Xcode
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Xcode
|
2
|
+
module Test
|
3
|
+
class Report
|
4
|
+
class SuiteResult
|
5
|
+
attr_accessor :tests, :name, :start_time, :end_time, :report
|
6
|
+
|
7
|
+
def initialize(report, name, start_time)
|
8
|
+
@report = report
|
9
|
+
@name = name
|
10
|
+
@start_time = start_time
|
11
|
+
@tests = []
|
12
|
+
|
13
|
+
@report.notify_observers :before_suite, self
|
14
|
+
end
|
15
|
+
|
16
|
+
def finish(time=Time.now)
|
17
|
+
raise "Time is nil" if time.nil?
|
18
|
+
|
19
|
+
# Fail any lingering test
|
20
|
+
finish_current_test
|
21
|
+
|
22
|
+
@end_time = time
|
23
|
+
@report.notify_observers :after_suite, self
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_test_case(name)
|
27
|
+
finish_current_test
|
28
|
+
|
29
|
+
test = Xcode::Test::Report::TestResult.new self, name
|
30
|
+
@tests << test
|
31
|
+
yield(test) if block_given?
|
32
|
+
test
|
33
|
+
end
|
34
|
+
|
35
|
+
def finished?
|
36
|
+
!@end_time.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def total_errors
|
40
|
+
errors = 0
|
41
|
+
@tests.each do |t|
|
42
|
+
errors = errors + t.errors.count if t.failed?
|
43
|
+
end
|
44
|
+
errors
|
45
|
+
end
|
46
|
+
|
47
|
+
def total_passed_tests
|
48
|
+
@tests.select {|t| t.passed? }.count
|
49
|
+
end
|
50
|
+
|
51
|
+
def total_failed_tests
|
52
|
+
@tests.select {|t| t.failed? }.count
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def finish_current_test
|
58
|
+
# Fail any lingering test
|
59
|
+
unless @tests.size==0 or @tests.last.passed?
|
60
|
+
@tests.last.failed(0)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Xcode
|
2
|
+
module Test
|
3
|
+
class Report
|
4
|
+
class TestResult
|
5
|
+
attr_reader :name, :time, :errors, :suite, :data
|
6
|
+
|
7
|
+
def initialize(suite, name)
|
8
|
+
@name = name
|
9
|
+
@data = []
|
10
|
+
@suite = suite
|
11
|
+
@errors = []
|
12
|
+
|
13
|
+
@suite.report.notify_observers :before_test, self
|
14
|
+
end
|
15
|
+
|
16
|
+
def passed?
|
17
|
+
@passed
|
18
|
+
end
|
19
|
+
|
20
|
+
def failed?
|
21
|
+
!@passed
|
22
|
+
end
|
23
|
+
|
24
|
+
def passed(time)
|
25
|
+
@passed = true
|
26
|
+
@time = time
|
27
|
+
@suite.report.notify_observers :after_test, self
|
28
|
+
end
|
29
|
+
|
30
|
+
def failed(time)
|
31
|
+
@passed = false
|
32
|
+
@time = time
|
33
|
+
@suite.report.notify_observers :after_test, self
|
34
|
+
end
|
35
|
+
|
36
|
+
def << (line)
|
37
|
+
# puts "[#{@suite.name} #{@name}] << #{line}"
|
38
|
+
return if @data.count==0 and line.strip.empty?
|
39
|
+
@data << line
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_error(error_message,error_location)
|
43
|
+
@errors << {:message => error_message, :location => error_location, :data => @data}
|
44
|
+
@data = []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|