Skip to content

Instantly share code, notes, and snippets.

@faultier
Created September 30, 2009 10:14
Show Gist options
  • Save faultier/197966 to your computer and use it in GitHub Desktop.
Save faultier/197966 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# vim: encoding=utf-8 filetype=ruby
require 'rubygems'
require 'optparse'
require 'yaml'
options = YAML.load(<<CONF)
color: true
verbose: false
target: 'UnitTest'
config: 'Release'
sdkversion: '3.0'
format: 'simple'
CONF
if FileTest.exist?('.rocurc')
options.merge!(YAML.load_file('.rocurc'))
end
OptionParser.new { |opt|
opt.on('-o','--options=FILE') { |f| options.merge!(YAML.load_file(f)) }
opt.on('--[no-]color') { |c| options['color'] = c }
opt.on('--[no-]verbose') { |v| options['verbose'] = v }
opt.on('-t','--target=TARGET') { |t| options['target'] = t }
opt.on('--sdk-version=VERSION') { |v| options['sdkversion'] = v }
opt.on('-f','--format=FORMAT') { |f| options['format'] = v }
opt.parse!(ARGV)
}
TESTCMD = "xcodebuild -target #{options['target']} -configuration #{options['config']} -sdk iphonesimulator#{options['sdkversion']} 2>&1"
module DummyANSIColor
def bold; self; end
def negative; self; end
def red; self; end
def green; self; end
def yellow; self; end
def blue; self; end
end
if options['color']
begin
require 'term/ansicolor'
class String
include Term::ANSIColor
end
rescue LoadError
warn "#{$0} depends on Term::ANSIColor!"
end
end
unless String.instance_methods.include?(:bold)
class String
include DummyANSIColor
end
end
module XBTools
class Filter
Terminator = /\A\*\* BUILD/
def self.process(io, opts={})
new(io, opts).process
end
def initialize(io,opts={})
@input = io
@log = []
@opts = opts
end
def parse
line = @input.gets
case line
when Terminator
false
else
@log.push({ :message => line, :type => :unknown })
end
end
def fold
@log.map{ |l|
line = l[:position] ? "#{l[:message]} (#{l[:position]})" : l[:message]
line = case l[:type]
when :header then line.bold
when :passed then line.green
when :failed then line.red
else line end
line = case l[:level]
when :error then line.red.bold
when :warning then line.yellow
else line end
l[:negative] ? line.negative : line
}.join("\n")
end
def process
while parse; end
fold
end
end
class BuildFilter < XBTools::Filter
Terminator = /\APhaseScriptExecution/
def initialize(io,opts={})
super
@in_submsg = false
end
def parse
return false unless super
line = @log.pop
return false unless line
case line[:message]
when /\A(?:#{Dir.pwd}\/)?(.+); In function (.+)/
@log.push( :message => $2, :position => $1, :type => :info )
when /\A(?:#{Dir.pwd}\/)?(.+[0-9]+): (warning|error): (.+)/
pos = $1
level = $2.intern
msg = $3
if @in_submsg
@log.last[:message] << msg
@in_submsg = false if msg =~ /\)(\n|\z)/
elsif msg =~ /\A\(/
@log.last[:message] << ' ' << msg
@in_submsg = true
else
@log.push( :message => msg, :position => pos, :level => level )
end
when Terminator
return false
end
true
end
end
class OCUnitFilter < Filter
def initialize(io,opts={})
super
@in_subtest = false
@failures = []
@opts[:format] ||= 'simple'
end
def fold
if @opts[:format] == 'specdoc'
super
else
log = {}
current = ''
result = nil
if (@log.last)[:message] =~ /Executed/
result = @log.pop
end
@log.each do |l|
if l[:type] == :header
current = l[:message]
log[current] = {
:results => [],
:errors => []
}
else
section = log[current]
if !!section
msg = l[:position] ? "#{l[:message]} (#{l[:position]})" : l[:message]
msg.gsub!(/\t/,'') unless msg.nil?
case l[:type]
when :passed
section[:results] << '.'.green
when :failed
section[:results] << 'E'.red
section[:errors] << msg.red.bold
else
case l[:level]
when :error
section[:errors] << msg.red.bold
when :warning
section[:errors] << msg.yellow
end
end
end
end
end
output = ""
errors = []
log.each_pair do |key,val|
output << val[:results].join('')
unless val[:errors].empty?
errors << [key.bold, *val[:errors]].join("\n")
end
end
output = "#{output}\n\n#{errors.join("\n\n")}"
output << "\n\n#{(result[:type] == :passed ? result[:message].green : result[:message].red).negative}" if !!result
output
end
end
def parse
return false unless super
line = @log.pop
return false unless line
case line[:message]
when /\A(?:#{Dir.pwd}\/)?(.+[0-9]+): error: -\[.+\] : (.+)/
pos = $1
msg = $2
if @failures.last && @failures.last[:type] == 'assert' && [email protected][:message]
@failures.last[:message] = "Assertion failure. #{msg} (#{@failures.last[:position]})"
elsif pos =~ /Unknown.m:0/
@failures << { :type => 'runtime', :message => msg }
else
@failures << { :type => 'test', :message => "#{msg} (#{pos})" }
end
when /\*\*\* Assertion failure in (?:-|\+)\[.+\], (?:#{Dir.pwd}\/)?(.+)/
@failures.push( :type => 'assert', :position => $1 )
when /\AExecuted/
if @in_subtest
@in_subtest = false
else
msg = line[:message]
@log.push(
:message => msg,
:type => msg =~ /with 0 failure/ ? :passed : :failed,
:negative => true
)
end
when /\ATest Suite '([a-zA-Z0-9]+)' started/
@log.push( :message => "#{$1}:", :type => :header )
@in_subtest = true
when /\ATest Suite '(.+)' started/
@log.push( :message => "#{$1}:", :type => :header )
when /\ATest Case '(-\[.+\])' passed/
@log.push( :message => "\t#{$1}", :type => :passed )
when /\ATest Case '(-\[.+\])' failed/
@log.push( :message => "\t#{$1}", :type => :failed )
@failures.each do |assert|
case assert[:type]
when 'test'
@log.push( :message => "\t\t#{assert[:message]}", :type => :failed )
when 'assert'
@log.push( :message => "\t\t#{assert[:message]}", :level => :warning )
when 'runtime'
@log.push( :message => "\t\t#{assert[:message]}", :level => :error )
end
end
@failures = []
when /\ATest Suite '.+' finished/
@log.push( :message => 'All test finished.' ) unless @in_subtest
end
true
end
def message(m)
end
private :message
end
end
if $0 == __FILE__
IO.popen(TESTCMD) { |io|
puts '=== BUILD PHASE ==='
puts XBTools::BuildFilter.process(io)
puts '=== TEST PHASE ==='
puts XBTools::OCUnitFilter.process(io, :format => options['format'])
}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment