Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle uncaught exceptions #171

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions features/simple_format.feature
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,9 @@ Feature: Showing build output in simple format
When I pipe to xcpretty with "--simple --color"
Then I should see a yellow warning message

Scenario: Running tests crash because of an uncaught exception
Given tests fail with an uncaught exception
When I pipe to xcpretty with "--simple --color"
Then I should see a crash symbol and the test case in red
And I should see the exception name and reason
And I should see the call stack that led to the exception
19 changes: 19 additions & 0 deletions features/steps/formatting_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@
add_run_input SAMPLE_UNDEFINED_SYMBOLS
end

Given(/^tests fail with an uncaught exception$/) do
add_run_input SAMPLE_UNCAUGHT_EXCEPTION
end

Given(/^I have a pending test in my suite$/) do
add_run_input SAMPLE_PENDING_KIWI_TEST
end
Expand Down Expand Up @@ -296,6 +300,21 @@
run_output.should include("objc-class-ref in ATZRadialProgressControl.o")
end

Then(/^I should see a crash symbol and the test case in red$/) do
run_output.should include(red("💥 testRaisingUncaughtException"))
end

Then(/^I should see the exception name and reason$/) do
run_output.should include("NSInvalidArgumentException: -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]")
end

Then(/^I should see the call stack that led to the exception$/) do
run_output.should include("\t0 CoreFoundation 0x0000000109479a75 __exceptionPreprocess + 165")
run_output.should include("\t1 libobjc.A.dylib 0x0000000109112bb7 objc_exception_throw + 45")
run_output.should include("\t2 CoreFoundation 0x000000010938503f -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 383")
run_output.should include("\t3 CoreFoundation 0x0000000109397d8b +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59")
end

Then(/^I should see the name of a pending test$/) do
run_output.should =~ PENDING_TEST_NAME_MATCHER
end
Expand Down
15 changes: 15 additions & 0 deletions lib/xcpretty/formatters/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def format_compile_error(file_name, file_path, reason,
def format_error(message); EMPTY; end
def format_undefined_symbols(message, symbol, reference); EMPTY; end
def format_duplicate_symbols(message, file_paths); EMPTY; end
def format_uncaught_exception(test_case, name, reason,
call_stack); EMPTY; end
def format_warning(message); message; end

# TODO: see how we can unify format_error and format_compile_error,
Expand Down Expand Up @@ -106,6 +108,9 @@ def format_test_summary(executed_message, failures_per_suite)
ERROR = '❌ '
ASCII_ERROR = '[x]'

CRASH = '💥 '
ASCII_CRASH = '***'

WARNING = '⚠️ '
ASCII_WARNING = '[!]'

Expand Down Expand Up @@ -138,6 +143,12 @@ def format_duplicate_symbols(message, file_paths)
"> #{file_paths.map { |path| path.split('/').last }.join("\n> ")}\n"
end

def format_uncaught_exception(test_case, name, reason, call_stack)
"\n#{red(crash_symbol + " " + test_case)}\n" \
"#{name}: #{reason}\n\n" +
call_stack.join("\n") + "\n"
end


private

Expand All @@ -162,6 +173,10 @@ def error_symbol
use_unicode? ? ERROR : ASCII_ERROR
end

def crash_symbol
use_unicode? ? CRASH : ASCII_CRASH
end

def warning_symbol
use_unicode? ? WARNING : ASCII_WARNING
end
Expand Down
50 changes: 50 additions & 0 deletions lib/xcpretty/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ module Matchers
# $1 test suite name
TEST_SUITE_START_MATCHER = /^\s*Test Suite '(.*)' started at/

# @regex Captured groups
# $1 test case name
TEST_CASE_START_MATCHER = /Test Case '-\[.* (.*)\]' started/

# @regex Captured groups
# $1 file_name
TIFFUTIL_MATCHER = /^TiffUtil\s(.*)/
Expand Down Expand Up @@ -245,6 +249,11 @@ module Errors
# @regex Captured groups
# $1 = reference
SYMBOL_REFERENCED_FROM_MATCHER = /\s+"(.*)", referenced from:$/

# @regex Captured groups
# $1 = exception name
# $2 = exception reason
UNCAUGHT_EXCEPTION_MATCHER = /Terminating app due to uncaught exception '(.*)', reason: '(?:\*\*\* )?(.*)'/
end
end

Expand All @@ -263,12 +272,14 @@ def initialize(formatter)
def parse(text)
update_test_state(text)
update_error_state(text)
update_exception_state(text)
update_linker_failure_state(text)

return format_compile_error if should_format_error?
return format_compile_warning if should_format_warning?
return format_undefined_symbols if should_format_undefined_symbols?
return format_duplicate_symbols if should_format_duplicate_symbols?
return format_uncaught_exception if should_format_uncaught_exception?

case text
when ANALYZE_MATCHER
Expand Down Expand Up @@ -403,6 +414,22 @@ def update_error_state(text)
end
end

def update_exception_state(text)
if text =~ TEST_CASE_START_MATCHER
current_exception[:test_case] = $1
elsif text =~ UNCAUGHT_EXCEPTION_MATCHER
current_exception[:name] = $1
current_exception[:reason] = $2
elsif text =~ /\($/ && current_exception[:name]
@gathering_exception_call_stack = true
elsif text =~ /\)$/ && current_exception[:name]
@gathering_exception_call_stack = false
current_exception[:complete] = true
elsif @gathering_exception_call_stack
current_exception[:call_stack] << text.chomp
end
end

def update_linker_failure_state(text)
if text =~ LINKER_UNDEFINED_SYMBOLS_MATCHER ||
text =~ LINKER_DUPLICATE_SYMBOLS_MATCHER
Expand Down Expand Up @@ -443,10 +470,18 @@ def should_format_duplicate_symbols?
current_linker_failure[:message] && current_linker_failure[:files].count > 1
end

def should_format_uncaught_exception?
current_exception[:complete]
end

def current_issue
@current_issue ||= {}
end

def current_exception
@uncaught_exception ||= {call_stack: [], complete: false}
end

def current_linker_failure
@linker_failure ||= {files: []}
end
Expand Down Expand Up @@ -497,6 +532,21 @@ def reset_linker_format_state
@formatting_linker_failure = false
end

def format_uncaught_exception
result = formatter.format_uncaught_exception(
current_exception[:test_case],
current_exception[:name],
current_exception[:reason],
current_exception[:call_stack]
)
reset_uncaught_exception_state
result
end

def reset_uncaught_exception_state
@uncaught_exception = nil
end

def store_failure(file, test_suite, test_case, reason)
failures_per_suite[test_suite] ||= []
failures_per_suite[test_suite] << {
Expand Down
13 changes: 13 additions & 0 deletions spec/fixtures/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,19 @@
~~ ^~~~~~~
)

SAMPLE_UNCAUGHT_EXCEPTION = %Q(
Test Case '-[TestCase testRaisingUncaughtException]' started.
2015-10-07 00:08:20.151 xctest[25555:6492261] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
*** First throw call stack:
(
\t0 CoreFoundation 0x0000000109479a75 __exceptionPreprocess + 165
\t1 libobjc.A.dylib 0x0000000109112bb7 objc_exception_throw + 45
\t2 CoreFoundation 0x000000010938503f -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 383
\t3 CoreFoundation 0x0000000109397d8b +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59
)
libc++abi.dylib: terminating with uncaught exception of type NSException
)


################################################################################
# WARNINGS
Expand Down
17 changes: 17 additions & 0 deletions spec/xcpretty/formatters/formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,23 @@ module XCPretty
)
end

it "formats uncaught exceptions" do
@formatter.format_uncaught_exception("testRaisingUncaughtException",
'NSInvalidArgumentException',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align the parameters of a method call if they span more than one line.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

'-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]',
[' 0 CoreFoundation 0x0000000109479a75 __exceptionPreprocess + 165',
' 1 libobjc.A.dylib 0x0000000109112bb7 objc_exception_throw + 45',
' 2 CoreFoundation 0x000000010938503f -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 383',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [135/80]
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

' 3 CoreFoundation 0x0000000109397d8b +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59']).should == %Q(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance you could reuse the fixture here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean, reuse SAMPLE_UNCAUGHT_EXCEPTION defined in constants.rb?
I don’t see how since format_uncaught_exception expects 4 arguments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SAMPLE_UNCAUGHT_EXCEPTION.split("\n") ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you might want to use [1..4] or something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be SAMPLE_UNCAUGHT_EXCEPTION.split("\n")[5..8]).should == %Q( but does it make sense to reuse SAMPLE_UNCAUGHT_EXCEPTION in format_uncaught_exception last argument but not in the == %Q() part?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [142/80]
Use % instead of %Q.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

#{@formatter.red("💥 testRaisingUncaughtException")}
NSInvalidArgumentException: -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [133/80]


\t0 CoreFoundation 0x0000000109479a75 __exceptionPreprocess + 165
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [88/80]

\t1 libobjc.A.dylib 0x0000000109112bb7 objc_exception_throw + 45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [86/80]

\t2 CoreFoundation 0x000000010938503f -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 383
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [126/80]

\t3 CoreFoundation 0x0000000109397d8b +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [118/80]

)
end

it "formats failures per suite" do
Syntax.stub(:highlight) { |snippet| snippet.contents }
Expand Down
14 changes: 14 additions & 0 deletions spec/xcpretty/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,20 @@ module XCPretty
end
end

it "parses uncaught exception errors" do
@formatter.should receive(:format_uncaught_exception).with(
'testRaisingUncaughtException',
'NSInvalidArgumentException',
'-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [118/80]
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

[' 0 CoreFoundation 0x0000000109479a75 __exceptionPreprocess + 165',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [101/80]
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

' 1 libobjc.A.dylib 0x0000000109112bb7 objc_exception_throw + 45',
' 2 CoreFoundation 0x000000010938503f -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 383',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [139/80]
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

' 3 CoreFoundation 0x0000000109397d8b +[NSDictionary dictionaryWithObjects:forKeys:count:] + 59'])
SAMPLE_UNCAUGHT_EXCEPTION.each_line do |line|
@parser.parse(line)
end
end

it "parses code sign error:" do
@formatter.should receive(:format_error).with(
'Code Sign error: No code signing identites found: No valid signing identities (i.e. certificate and private key pair) matching the team ID “CAT6HF57NJ” were found.'
Expand Down