Skip to content

Commit

Permalink
Add support for OCUnit test reports and update the builder.test method
Browse files Browse the repository at this point in the history
  • Loading branch information
rayh committed Feb 10, 2012
1 parent 70758aa commit e79795a
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 31 deletions.
3 changes: 2 additions & 1 deletion Gemfile
@@ -1,4 +1,5 @@
source "http://rubygems.org"

gemspec
gem 'rspec'
gem 'rspec'
gem 'builder'
Empty file modified bin/xcoder-build 100644 → 100755
Empty file.
69 changes: 40 additions & 29 deletions lib/xcode/builder.rb
@@ -1,16 +1,18 @@
require 'xcode/shell'
require 'xcode/provisioning_profile'
require 'xcode/test/report_parser.rb'

module Xcode
class Builder
attr_accessor :profile, :identity, :build_path, :keychain
attr_accessor :profile, :identity, :build_path, :keychain, :sdk

def initialize(config)
if config.is_a? Xcode::Scheme
@scheme = config
config = config.launch
end
@target = config.target
@sdk = @target.project.sdk
@config = config
@build_path = "#{File.dirname(@target.project.path)}/build/"
end
Expand All @@ -30,40 +32,28 @@ def install_profile
p
end

def build
profile = install_profile

cmd = []
cmd << "xcodebuild"
cmd << "-sdk #{@target.project.sdk}" unless @target.project.sdk.nil?
cmd << "-project \"#{@target.project.path}\""

cmd << "-scheme #{@scheme.name}" unless @scheme.nil?
cmd << "-target \"#{@target.name}\"" if @scheme.nil?
cmd << "-configuration \"#{@config.name}\"" if @scheme.nil?

cmd << "OTHER_CODE_SIGN_FLAGS=\"--keychain #{@keychain.path}\"" unless @keychain.nil?
cmd << "CODE_SIGN_IDENTITY=\"#{@identity}\"" unless @identity.nil?
cmd << "OBJROOT=\"#{@build_path}\""
cmd << "SYMROOT=\"#{@build_path}\""
cmd << "PROVISIONING_PROFILE=#{profile.uuid}" unless profile.nil?
yield(cmd) if block_given?

def build(sdk=@sdk)
profile = install_profile
cmd = build_command(@sdk)
Xcode::Shell.execute(cmd)
end

def test
build do |cmd|
cmd.select! do |line|
!line=~/\^-sdk/
end
cmd << "TEST_AFTER_BUILD=YES"
cmd << "TEST_HOST=''"
cmd << "-sdk iphonesimulator5.0" # FIXME: hardcoded version, should be smarter
cmd = build_command('iphonesimulator')
cmd << "TEST_AFTER_BUILD=YES"
cmd << "TEST_HOST=''"

parser = Xcode::Test::ReportParser.new
Xcode::Shell.execute(cmd, false) do |line|
puts line
parser << line
end

Xcode::Shell.execute(cmd)
yield(parser) if block_given?

exit parser.exit_code if parser.exit_code!=0

parser
end

def clean
Expand Down Expand Up @@ -158,5 +148,26 @@ def dsym_zip_path
"#{product_version_basename}.dSYM.zip"
end


private

def build_command(sdk=@sdk)
cmd = []
cmd << "xcodebuild"
cmd << "-sdk #{sdk}" unless sdk.nil?
cmd << "-project \"#{@target.project.path}\""

cmd << "-scheme #{@scheme.name}" unless @scheme.nil?
cmd << "-target \"#{@target.name}\"" if @scheme.nil?
cmd << "-configuration \"#{@config.name}\"" if @scheme.nil?

cmd << "OTHER_CODE_SIGN_FLAGS=\"--keychain #{@keychain.path}\"" unless @keychain.nil?
cmd << "CODE_SIGN_IDENTITY=\"#{@identity}\"" unless @identity.nil?
cmd << "OBJROOT=\"#{@build_path}\""
cmd << "SYMROOT=\"#{@build_path}\""
cmd << "PROVISIONING_PROFILE=#{profile.uuid}" unless profile.nil?
cmd
end

end
end
1 change: 1 addition & 0 deletions lib/xcode/shell.rb
Expand Up @@ -8,6 +8,7 @@ def self.execute(bits, show_output=true)
IO.popen (cmd) do |f|
f.each do |line|
puts line if show_output
yield(line) if block_given?
out << line
end
end
Expand Down
172 changes: 172 additions & 0 deletions lib/xcode/test/report_parser.rb
@@ -0,0 +1,172 @@
require 'time'
require 'FileUtils'
require 'socket'
require 'builder'

module Xcode
module Test
module Formatters
class JunitFormatter
def initialize(dir)
@dir = dir
end

def string_to_xml(s)
s.gsub(/&/, '&amp;').gsub(/'/, '&quot;').gsub(/</, '&lt;')
end

def write(report)
if report.end_time.nil?
raise "Report #{report} #{report.name} has a nil end time!?"
end
xml = ::Builder::XmlMarkup.new( :indent => 2 )
xml.instruct! :xml, :encoding => "UTF-8"
xml.testsuite(:errors => report.total_error_tests,
:failures => report.total_failed_tests,
:hostname => Socket.gethostname,
:name => report.name,
:tests => report.tests.count,
:time => (report.end_time - report.start_time),
:timestamp => report.end_time
) do |p|

report.tests.each do |t|
p.testcase(:classname => report.name,
:name => t.name,
:time => t.time
) do |e|

if t.error?
e.failure t.error_location, :message => t.error_message, :type => 'Failure'
end
end
end
end

File.open("#{@dir}/TEST-#{report.name}.xml", 'w') do |current_file|
current_file.write xml.target!
end

end
end
end

class SuiteReport
attr_accessor :tests, :name, :start_time, :end_time

def initialize(name, start_time)
@name = name
@start_time = start_time
@tests = []
end

def finish(time)
raise "Time is nil" if time.nil?
@end_time = time
end

def total_error_tests
@tests.select {|t| t.error? }.count
end

def total_passed_tests
@tests.select {|t| t.passed? }.count
end

def total_failed_tests
@tests.select {|t| t.failed? }.count
end

end

class CaseReport
attr_reader :name, :time, :error_message, :error_location

def initialize(name)
@name = name
end

def passed?
@passed
end

def failed?
error? or !@passed
end

def error?
!@error_message.nil?
end

def passed(time)
@passed = true
@time = time
end

def failed(time)
@passed = false
@time = time
end

def error(error_message,error_location)
@error_message = error_message
@error_location = error_location
end
end

class ReportParser

attr_reader :exit_code, :reports

def initialize
@exit_code = 0
@reports = []
end

def write(dir, format=:junit)
dir = File.expand_path(dir)
FileUtils.mkdir_p(dir)

formatter = Formatters.const_get("#{format.capitalize}Formatter").new(dir)
@reports.each do |r|
formatter.write(r)
end
end

def <<(piped_row)
case piped_row

when /Test Suite '(\S+)'.*started at\s+(.*)/
name = $1
time = Time.parse($2)
@reports << SuiteReport.new(name, time) unless name=~/\// # ignore if its a file path

when /Test Suite '(\S+)'.*finished at\s+(.*)./
@reports.last.finish(Time.parse($2))

when /Test Case '-\[\S+\s+(\S+)\]' started./
test = CaseReport.new($1)
@reports.last.tests << test

when /Test Case '-\[\S+\s+(\S+)\]' passed \((.*) seconds\)/
@reports.last.tests.last.passed($2.to_f)

when /(.*): error: -\[(\S+) (\S+)\] : (.*)/
@reports.last.tests.last.error(error_message,error_location)
@exit_code = 1 # should terminate

when /Test Case '-\[\S+ (\S+)\]' failed \((\S+) seconds\)/
@reports.last.tests.last.failed($2.to_f)
@exit_code = 1 # should terminate

when /failed with exit code (\d+)/
@exit_code = $1.to_i

when
/BUILD FAILED/
@exit_code = -1;
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/xcode/version.rb
@@ -1,3 +1,3 @@
module Xcode
VERSION = "0.0.17"
VERSION = "0.0.18"
end
85 changes: 85 additions & 0 deletions spec/test_report_spec.rb
@@ -0,0 +1,85 @@
require 'rspec'
require 'xcoder'

describe Xcode::Test::ReportParser do

def example_report
t = Xcode::Test::ReportParser.new
t << "Run test suite AnExampleTestSuite"
t << "Test Suite 'AnExampleTestSuite' started at 2012-02-10 00:37:04 +0000"

t << "Run test case anExampleTest1"
t << "Test Case '-[AnExampleTestSuite anExampleTest1]' started."
t << "Test Case '-[AnExampleTestSuite anExampleTest1]' passed (0.003 seconds)."

t << "Run test case anExampleTest2"
t << "Test Case '-[AnExampleTestSuite anExampleTest2]' started."
t << "Test Case '-[AnExampleTestSuite anExampleTest2]' passed (0.003 seconds)."

yield(t) if block_given?

t << "Test Suite 'AnExampleTestSuite' finished at 2012-02-10 00:37:04 +0000."
t << "Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) seconds"

t
end

def example_failing_report
example_report do |t|
t << "Test Case '-[AnExampleTestSuite aFailingTest]' started."
t << "Test Case '-[AnExampleTestSuite aFailingTest]' failed (2 seconds)."
end
end

it "should create a test case" do
t = example_report
t.reports.count.should==1
t.reports.first.name.should=="AnExampleTestSuite"
t.reports.first.start_time.should==Time.parse("2012-02-10 00:37:04 +0000")
t.reports.first.end_time.should==Time.parse("2012-02-10 00:37:04 +0000")
end

it "should set the exist status to 0" do
t = example_report
t.exit_code.should==0
end

it "should set the exit status to non 0" do
t = example_failing_report
t.exit_code.should_not==0
end

it "should record a failure" do
t = example_failing_report
t.reports.first.total_failed_tests.should==1
end

it "should create a test case with some tests" do
t = example_report

t.reports.count.should==1
t.reports.first.tests.count.should==2
t.reports.first.tests[0].name.should=='anExampleTest1'
t.reports.first.tests[0].time.should==0.003
t.reports.first.tests[0].passed?.should==true

t.reports.first.tests[1].name.should=='anExampleTest2'
t.reports.first.tests[1].time.should==0.003
t.reports.first.tests[1].passed?.should==true
end

it "should write out reports in junit format" do
report_dir = "#{File.dirname(__FILE__)}/test-reports"
FileUtils.rm_rf report_dir

t = example_report
t.write(report_dir, :junit)

files = Dir["#{report_dir}/*.xml"]
files.count.should==1
files.first.should=~/TEST-AnExampleTestSuite.xml$/

# FIXME: parse the report
end

end
1 change: 1 addition & 0 deletions xcoder.gemspec
Expand Up @@ -21,4 +21,5 @@ Gem::Specification.new do |s|
s.add_runtime_dependency "json"
s.add_runtime_dependency "plist"
s.add_runtime_dependency "nokogiri"
s.add_runtime_dependency "builder"
end

0 comments on commit e79795a

Please sign in to comment.