xcoder 0.1.11 → 0.1.12

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 (35) hide show
  1. data/Rakefile +2 -0
  2. data/lib/xcode/builder.rb +32 -19
  3. data/lib/xcode/shell.rb +3 -1
  4. data/lib/xcode/test/parsers/ocunit_parser.rb +94 -0
  5. data/lib/xcode/test/report.rb +121 -0
  6. data/lib/xcode/test/report/suite_result.rb +67 -0
  7. data/lib/xcode/test/report/test_result.rb +49 -0
  8. data/lib/xcode/version.rb +1 -1
  9. data/lib/xcode/workspace.rb +6 -3
  10. data/spec/TestProject/{TestProjectTests/TestProjectTests-Info.plist → ApplicationTests/ApplicationTests-Info.plist} +0 -0
  11. data/spec/TestProject/ApplicationTests/ApplicationTests-Prefix.pch +8 -0
  12. data/spec/TestProject/ApplicationTests/ApplicationTests.h +13 -0
  13. data/spec/TestProject/ApplicationTests/ApplicationTests.m +32 -0
  14. data/spec/TestProject/{TestProjectTests → ApplicationTests}/en.lproj/InfoPlist.strings +0 -0
  15. data/spec/TestProject/{TestProjectTests → LogicTests}/AnotherTest.h +0 -0
  16. data/spec/TestProject/{TestProjectTests → LogicTests}/AnotherTest.m +0 -0
  17. data/spec/TestProject/LogicTests/TestProjectTests-Info.plist +22 -0
  18. data/spec/TestProject/{TestProjectTests → LogicTests}/TestProjectTests.h +0 -0
  19. data/spec/TestProject/{TestProjectTests → LogicTests}/TestProjectTests.m +0 -0
  20. data/spec/TestProject/LogicTests/en.lproj/InfoPlist.strings +2 -0
  21. data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1329 -556
  22. data/spec/TestProject/TestProject.xcodeproj/xcshareddata/xcschemes/TestProject.xcscheme +13 -2
  23. data/spec/TestWorkspace2.xcworkspace/contents.xcworkspacedata +7 -0
  24. data/spec/TestWorkspace2.xcworkspace/xcuserdata/ray.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  25. data/spec/build_phase_spec.rb +2 -2
  26. data/spec/builder_spec.rb +51 -10
  27. data/spec/integration/builder_spec.rb +60 -4
  28. data/spec/ocunit_parser_spec.rb +164 -0
  29. data/spec/project_spec.rb +1 -1
  30. data/spec/workspace_spec.rb +8 -3
  31. metadata +44 -29
  32. data/lib/xcode/test/ocunit_report_parser.rb +0 -174
  33. data/lib/xcode/test/suite_result.rb +0 -39
  34. data/lib/xcode/test/test_result.rb +0 -43
  35. 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
@@ -1,6 +1,6 @@
1
1
  require 'xcode/shell'
2
2
  require 'xcode/provisioning_profile'
3
- require 'xcode/test/ocunit_report_parser.rb'
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=@sdk)
30
- cmd = build_command(@sdk)
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
- def test
38
- cmd = build_command('iphonesimulator')
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
- parser = Xcode::Test::OCUnitReportParser.new
43
- yield(parser) if block_given?
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
- self
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(sdk=@sdk)
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=\"#{@build_path}\""
219
- cmd << "SYMROOT=\"#{@build_path}\""
231
+ cmd << "OBJROOT=\"#{@objroot}\""
232
+ cmd << "SYMROOT=\"#{@symroot}\""
220
233
  cmd << "PROVISIONING_PROFILE=#{profile.uuid}" unless profile.nil?
221
234
  cmd
222
235
  end
@@ -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