diff --git a/.gitignore b/.gitignore index cd889a05..b088c21a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.gem *.rbc .bundle +.vendor .config .yardoc .rspec diff --git a/.travis.yml b/.travis.yml index 7f3782cc..1fdca27c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,12 @@ language: ruby sudo: false -install: - - bundle install rvm: - 2.1.1 - 2.0.0 -script: rake ci +script: + - rake spec + - rake cucumber + - rake lint diff --git a/CHANGELOG.md b/CHANGELOG.md index aac84e9b..c255ba76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.3.0 + +###### Enhancements + +* Add catch-all formatter to allow formatting unrecognized text + | [Chris Ballinger](https://github.com/chrisballinger) + | [#327](https://github.com/supermarin/xcpretty/pull/327) + +* Support `ccache-clang` (and other commands) in formatted output and JSON + compilation database reports + | [Delisa Mason](https://github.com/kattrali) + | [#332](https://github.com/supermarin/xcpretty/pull/332) + + +## 0.2.6 + +* Codesigning matchers improvements +* Screenshots parsing fixes + +###### Bug fixes + +* Fix reporers crash by default ld warning implementation + | [iKiKi](https://github.com/iKiKi) + | [#187](https://github.com/supermarin/xcpretty/pull/187) + ## 0.2.1 ###### Bug fixes diff --git a/LICENSE.txt b/LICENSE.txt index 16fdc227..1a00bbff 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2013 Marin Usalj +Copyright (c) 2013-2016 Marin Usalj MIT License diff --git a/README.md b/README.md index 8fd9729a..269485f9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ It does one thing, and it should do it well. [![Gem version](http://img.shields.io/gem/v/xcpretty.svg)](http://rubygems.org/gems/xcpretty) [![Build Status](https://travis-ci.org/supermarin/xcpretty.svg?branch=master)](https://travis-ci.org/supermarin/xcpretty) [![Code Climate](http://img.shields.io/codeclimate/github/supermarin/xcpretty.svg)](https://codeclimate.com/github/supermarin/xcpretty) +[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) ## Installation ``` bash @@ -67,7 +68,7 @@ $ xcodebuild [flags] | tee xcodebuild.log | xcpretty - `--report html`, `-r html`: Creates a simple HTML report at `build/reports/tests.html`. ![xcpretty html](http://i.imgur.com/0Rnux3v.gif) -- `--report json-compilation-database`, `-r json-compilation-database`: Creates a [JSON compilation database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) at `build/reports/compilation.json`. This is a format to replay single compilations independently of the build system. +- `--report json-compilation-database`, `-r json-compilation-database`: Creates a [JSON compilation database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) at `build/reports/compilation_db.json`. This is a format to replay single compilations independently of the build system. Writing a report to a custom path can be specified using `--output PATH`. @@ -76,7 +77,7 @@ Writing a report to a custom path can be specified using `--output PATH`. `xcpretty` supports custom formatters through the use of the `--formatter` flag, which takes a path to a file as an argument. The file must contain a Ruby subclass of `XCPretty::Formatter`, and -return that class at the end of te file. The class +return that class at the end of the file. The class can override the `format_*` methods to hook into output parsing events. diff --git a/Rakefile b/Rakefile index 7400b0e8..18715863 100644 --- a/Rakefile +++ b/Rakefile @@ -1,25 +1,18 @@ -require "bundler/gem_tasks" -require 'rubocop/rake_task' -require 'rspec/core/rake_task' -require 'cucumber/rake/task' +require 'bundler/gem_tasks' task :kick do sh 'bundle exec kicker -r ruby' end -Cucumber::Rake::Task.new(:cucumber) do |task| +task :spec do + sh 'bundle exec rspec --color --format=doc' end -RSpec::Core::RakeTask.new(:spec) do |task| - task.rspec_opts = %w(--color --format=doc) +task :cucumber do + sh 'bundle exec cucumber' end -RuboCop::RakeTask.new(:lint) do |task| - task.fail_on_error = false +task :lint do + sh 'bundle exec rubocop' end -task :ci do - Rake::Task[:spec].invoke - Rake::Task[:cucumber].invoke - Rake::Task[:lint].invoke -end diff --git a/assets/report.html.erb b/assets/report.html.erb index ca26763e..50979c06 100644 --- a/assets/report.html.erb +++ b/assets/report.html.erb @@ -30,7 +30,8 @@ .test.passing { background-color: #CAF59F;} .test.failing.odd { background-color: #EEC7CC;} .test.passing.odd { background-color: #E5FBCF;} - .details { background-color: #F4DDE0; border: 1px solid #C84F5E;} + .details.failing { background-color: #F4DDE0; border: 1px solid #C84F5E;} + .details.passing { background-color: #E5F4DC; border: 1px solid #A1D761;} .test .test-detail:last-child { padding-bottom: 8px;} .test .title { float: left; font-size: 0.9em; margin-top: 8px; font-family: Menlo, Monaco, monospace;} .test .time { float: left;margin: 4px 10px 0 20px;} @@ -127,15 +128,6 @@

<%= name %>

- <% unless info[:screenshots].empty? %> -
- <% info[:screenshots].each_with_index do |screenshot, index| %> - - - - <% end %> -
- <% end %> <% info[:tests].each_with_index do |test, index| %> <% detail_class = test[:name].gsub(/\s/,'') %> @@ -147,8 +139,8 @@ - <% if test[:reason] || test[:snippet] %> - + <% if test[:reason] || test[:snippet] || !test[:screenshots].empty? %> + <% end %> diff --git a/bin/xcpretty b/bin/xcpretty index 4a2db92b..c42f1b0c 100755 --- a/bin/xcpretty +++ b/bin/xcpretty @@ -39,17 +39,21 @@ OptionParser.new do |opts| printer_opts[:formatter] = XCPretty::TestAnything end opts.on('-f', '--formatter PATH', 'Use formatter returned from evaluating the specified Ruby file') do |path| - printer_opts[:formatter] = XCPretty.load_custom_formatter(path) + printer_opts[:formatter] = XCPretty.load_custom_class(path) end - opts.on('--[no-]color', 'Use colorized output. Defaults is auto') do |value| + opts.on('-c', '--[no-]color', 'Use colorized output. Default is auto') do |value| printer_opts[:colorize] = value end opts.on('--[no-]utf', 'Use unicode characters in output. Default is auto.') do |value| printer_opts[:unicode] = value end - opts.on("-r", "--report FORMAT", "Run FORMAT reporter", + opts.on("-r", "--report FORMAT or PATH", "Run FORMAT or PATH reporter", " Choices: #{report_formats.keys.join(', ')}") do |format| - report_classes << report_formats[format] + if report_formats.key?(format) + report_classes << report_formats[format] + else + report_classes << XCPretty.load_custom_class(format) + end report_options << {} end opts.on('-o', '--output PATH', 'Write report output to PATH') do |path| @@ -81,5 +85,6 @@ STDIN.each_line do |line| reporters.each { |r| r.handle(line) } end +printer.finish reporters.each(&:finish) diff --git a/features/assets/RACCommandSpec, line 80, hello xcpretty.png b/features/assets/RACCommandSpec_enabled_signal_should_send_YES_while_executing_is_YES.png similarity index 100% rename from features/assets/RACCommandSpec, line 80, hello xcpretty.png rename to features/assets/RACCommandSpec_enabled_signal_should_send_YES_while_executing_is_YES.png diff --git a/features/custom_reporter.feature b/features/custom_reporter.feature new file mode 100644 index 00000000..16d13107 --- /dev/null +++ b/features/custom_reporter.feature @@ -0,0 +1,29 @@ +Feature: Loading an arbitrary Ruby file as a reporter + + Background: + Given the tests have started running + + Scenario: The file loaded does not contain a Ruby class + When I pipe to xcpretty with "-r /bin/bash" + Then the exit status code should be 1 + + Scenario: The file loaded contains a Ruby class + Given I have a file to compile + When I pipe to xcpretty with a custom reporter + Then the exit status code should be 0 + + Scenario: Showing failed tests + Given I have a failing test in my suite + When I pipe to xcpretty with a custom reporter + Then I should see a failed test in my custom report + And the custom failure counter should show 1 test + + Scenario: Showing passing tests + Given I have a passing test in my suite + When I pipe to xcpretty with a custom reporter + Then I should see a passing test in my custom report + + Scenario: Showing pending tests + Given I have a pending test in my suite + When I pipe to xcpretty with a custom reporter + Then I should see a pending test in my custom report \ No newline at end of file diff --git a/features/json_compilation_database_report.feature b/features/json_compilation_database_report.feature index 6eaa7eb6..1c3b1578 100644 --- a/features/json_compilation_database_report.feature +++ b/features/json_compilation_database_report.feature @@ -7,6 +7,13 @@ Feature: Create a JSON compilation database Then the JSON compilation database should contain an entry with a directory Then the JSON compilation database should contain an entry with a file + Scenario: Showing file compilation with CCache + Given I have a file to compile with ccache + When I pipe to xcpretty with "--report json-compilation-database" and specify a custom path + Then the JSON compilation database should contain an entry with a command + Then the JSON compilation database should contain an entry with a directory + Then the JSON compilation database should contain an entry with a file + Scenario: Handling a complete xcodebuild session Given some big input When I pipe to xcpretty with "--report json-compilation-database" and specify a custom path diff --git a/features/junit_report.feature b/features/junit_report.feature index d71f49dd..b1527694 100644 --- a/features/junit_report.feature +++ b/features/junit_report.feature @@ -42,3 +42,8 @@ Feature: Creating a JUnit test report Scenario: Writing to multiple custom file paths When I pipe to xcpretty with two custom "junit" report paths Then I should have test reports in two custom paths + + Scenario: Showing tests with one having a swift fatal error + Given I have a swift fatal error in a test in my suite + When I pipe to xcpretty with "--report junit" + Then I should see a failed test node in my report diff --git a/features/simple_format.feature b/features/simple_format.feature index ddd14bea..5bc38cc8 100644 --- a/features/simple_format.feature +++ b/features/simple_format.feature @@ -5,6 +5,11 @@ Feature: Showing build output in simple format When I pipe to xcpretty with "--simple --no-color" Then I should see a successful compilation message + Scenario: Showing file compilation with CCache + Given I have a file to compile with ccache + When I pipe to xcpretty with "--simple --no-color" + Then I should see a successful compilation message + Scenario: Showing xib compilation Given I have a xib to compile When I pipe to xcpretty with "--simple --no-color" @@ -45,6 +50,11 @@ Feature: Showing build output in simple format When I pipe to xcpretty with "--simple --color" Then I should see a yellow completion icon + Scenario: Showing aggregate target + Given I have an aggregate target to build + When I pipe to xcpretty with "--simple --no-color" + Then I should see an aggregate target message + Scenario: Showing analyze Given I have a file to analyze When I pipe to xcpretty with "--simple --no-color" @@ -162,6 +172,11 @@ Feature: Showing build output in simple format When I pipe to xcpretty with "--simple --no-color" Then I should see a successful code signing message + Scenario: Showing target will not be code signed warning + Given I have a target which will not be code signed + When I pipe to xcpretty with "--simple --color" + Then I should see a target will not be code signed warning + Scenario: Showing preprocess Given I have a file to preprocess When I pipe to xcpretty with "--simple --no-color" @@ -202,3 +217,22 @@ Feature: Showing build output in simple format When I pipe to xcpretty with "--simple --color" Then I should see a yellow warning message + Scenario: Showing provisioning profile doesn't support capability + Given the provisioning profile doesn't support capability + When I pipe to xcpretty with "--simple --no-color" + Then I should see the profile doesn't support capability message + + Scenario: Showing provisioning profile doesn't include entitlement + Given the provisioning profile doesn't include entitlement + When I pipe to xcpretty with "--simple --no-color" + Then I should see the profile doesn't include entitlement message + + Scenario: Showing code signing is required error + Given the target requires code signing + When I pipe to xcpretty with "--simple --no-color" + Then I should see the code signing is requried message + + Scenario: Showing no profile matching error + Given the matching profile is missing + When I pipe to xcpretty with "--simple --no-color" + Then I should see the no profile matching message diff --git a/features/steps/custom_reporter_steps.rb b/features/steps/custom_reporter_steps.rb new file mode 100644 index 00000000..2348e2c4 --- /dev/null +++ b/features/steps/custom_reporter_steps.rb @@ -0,0 +1,16 @@ +Then(/^I should see a passing test in my custom report$/) do + custom_report.should include("WOW such PASS.") +end + +Then(/^I should see a failed test in my custom report$/) do + custom_report.should include("WOW such FAIL.") +end + +Then(/^I should see a pending test in my custom report$/) do + custom_report.should include("WOW such PENDING.") +end + +Then(/^the custom failure counter should show (\d+) tests?$/) do |fail_count| + custom_report.should include("Much 1 FAIL.") +end + diff --git a/features/steps/formatting_steps.rb b/features/steps/formatting_steps.rb index e273dc7e..fc4fbd7b 100644 --- a/features/steps/formatting_steps.rb +++ b/features/steps/formatting_steps.rb @@ -3,6 +3,10 @@ add_run_input SAMPLE_COMPILE end +Given(/^I have a file to compile with ccache$/) do + add_run_input SAMPLE_COMPILE_CCACHE +end + Given(/^I have a xib to compile$/) do add_run_input SAMPLE_COMPILE_XIB end @@ -15,6 +19,10 @@ add_run_input SAMPLE_PRECOMPILE end +Given(/^I have an aggregate target to build$/) do + add_run_input SAMPLE_AGGREGATE_TARGET +end + Given(/^I have a file to analyze$/) do add_run_input SAMPLE_ANALYZE end @@ -27,6 +35,10 @@ add_run_input SAMPLE_OLD_SPECTA_FAILURE end +Given(/^I have a swift fatal error in a test in my suite$/) do + add_run_input SAMPLE_SWIFT_FATAL_ERROR_IN_TEST_MAKE_TESTS_RESTARTING +end + Given(/^all of my tests will pass in my suite$/) do 3.times { add_run_input SAMPLE_OCUNIT_TEST } end @@ -64,6 +76,10 @@ add_run_input SAMPLE_CODESIGN_FRAMEWORK end +Given(/^I have a target which will not be code signed$/) do + add_run_input SAMPLE_WILL_NOT_BE_CODE_SIGNED +end + Given(/^I have a file to preprocess$/) do add_run_input SAMPLE_PREPROCESS end @@ -124,6 +140,22 @@ add_run_input SAMPLE_CLEAN_SUCCEEDED end +Given(/^the provisioning profile doesn't support capability$/) do + add_run_input SAMPLE_PROFILE_DOESNT_SUPPORT_CAPABILITY_ERROR +end + +Given(/^the provisioning profile doesn't include entitlement$/) do + add_run_input SAMPLE_PROFILE_DOESNT_INCLUDE_ENTITLEMENT_ERROR +end + +Given(/^the target requires code signing$/) do + add_run_input SAMPLE_CODE_SIGNING_IS_REQUIRED_ERROR +end + +Given(/^the matching profile is missing$/) do + add_run_input SAMPLE_NO_PROFILE_MATCHING_ERROR +end + Then(/^I should see a "(\w+)" completion message$/) do |phase| run_output.should start_with("▸ #{phase.capitalize} Succeeded") end @@ -169,6 +201,10 @@ run_output.should start_with("▸ Precompiling") end +Then(/^I should see an aggregate target message$/) do + run_output.should start_with("▸ Aggregate") +end + Then(/^I should see a successful analyze message$/) do run_output.should start_with("▸ Analyzing") end @@ -177,6 +213,10 @@ run_output.should start_with("▸ Signing") end +Then(/^I should see a target will not be code signed warning$/) do + run_output.should include(yellow("⚠️ FrameworkName will not be code signed because its settings don't specify a development team.")) +end + Then(/^I should see a successful preprocessing message$/) do run_output.should start_with("▸ Preprocessing") end @@ -328,3 +368,19 @@ run_output.lines.to_a.last.strip.should == text end +Then(/^I should see the profile doesn't support capability message$/) do + run_output.should include("Provisioning profile \"Profile Name\" doesn't support the Push Notifications capability.") +end + +Then(/^I should see the profile doesn't include entitlement message$/) do + run_output.should include("Provisioning profile \"Profile Name\" doesn't include the aps-environment entitlement.") +end + +Then(/^I should see the code signing is requried message$/) do + run_output.should include("Code signing is required for product type 'Application' in SDK 'iOS 10.0'") +end + +Then(/^I should see the no profile matching message$/) do + run_output.should include("No profile matching 'TargetName' found: Xcode couldn't find a profile matching 'TargetName'. Install the profile (by dragging and dropping it onto Xcode's dock item) or select a different one in the General tab of the target editor.") +end + diff --git a/features/steps/json_steps.rb b/features/steps/json_steps.rb index 0f98573c..cd102a5c 100644 --- a/features/steps/json_steps.rb +++ b/features/steps/json_steps.rb @@ -18,16 +18,19 @@ Then(/^the JSON compilation database should contain an entry with a command$/) do json_db.length.should == 1 - json_db[0]['command'].should start_with('/Applications/Xcode.app/Contents/Developer') + json_db[0]['command'].should start_with('/') + json_db[0]['command'].should include('clang ') + json_db[0]['command'].should include(' -c ') + json_db[0]['command'].should include(' -o ') json_db[0]['command'].should end_with('.o') end Then(/^the JSON compilation database should contain an entry with a file$/) do - json_db[0]['file'].should == '/Users/musalj/code/OSS/ObjectiveSugar/Classes/NSMutableArray+ObjectiveSugar.m' + json_db[0]['file'].should end_with('.m') end Then(/^the JSON compilation database should contain an entry with a directory$/) do - json_db[0]['directory'].should == '/' + json_db[0]['directory'].should start_with('/') end Then(/^the JSON compilation database should be complete$/) do @@ -39,3 +42,4 @@ entries = json_db.select { |entry| entry['command'].match(/-include\s+-/) } entries.length.should == 0 end + diff --git a/features/steps/junit_steps.rb b/features/steps/junit_steps.rb index ec1970d0..1e79753c 100644 --- a/features/steps/junit_steps.rb +++ b/features/steps/junit_steps.rb @@ -1,7 +1,7 @@ Then(/^I should see a failed test node in my report$/) do junit_report_root.elements.to_a.detect do |node| element = node.elements.to_a.first - element && element.name == "failure" + element && element.name == "failure" && node.attributes["time"] != nil end.should_not be_nil end diff --git a/features/steps/report_steps.rb b/features/steps/report_steps.rb index 13662b99..a9995262 100644 --- a/features/steps/report_steps.rb +++ b/features/steps/report_steps.rb @@ -20,3 +20,8 @@ step("I should have a test report at \"#{custom_report_path}\"") end +When(/^I pipe to xcpretty with a custom reporter$/) do + reporter_path = File.expand_path('../../../spec/fixtures/custom_reporter.rb', __FILE__) + run_xcpretty("-r #{reporter_path}") +end + diff --git a/features/support/env.rb b/features/support/env.rb index cddf7c54..a3feaf15 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -8,6 +8,7 @@ require 'lib/xcpretty/syntax' require 'rexml/document' require 'lib/xcpretty/formatters/formatter' +require 'lib/xcpretty/reporters/reporter' require 'lib/xcpretty/reporters/junit' require 'lib/xcpretty/reporters/html' require 'lib/xcpretty/reporters/json_compilation_database' @@ -79,6 +80,10 @@ def junit_report_root junit_report.root.elements.to_a.first end +def custom_report + @custom_report ||= File.open(XCPretty::Reporter::FILEPATH, 'r').read +end + def custom_report_path @custom_report_path ||= begin @custom_report_file1 = Tempfile.new('custom_report_path') @@ -111,7 +116,8 @@ def copy_file_to_screenshot_dir(screenshot_file) @json = nil FileUtils.rm_rf(XCPretty::JUnit::FILEPATH) FileUtils.rm_rf(XCPretty::HTML::FILEPATH) - FileUtils.rm_rf(XCPretty::JSONCompilationDatabase::FILE_PATH) + FileUtils.rm_rf(XCPretty::JSONCompilationDatabase::FILEPATH) + FileUtils.rm_rf(XCPretty::Reporter::FILEPATH) File.delete(@screenshot_file_path) if @screenshot_file_path end diff --git a/lib/xcpretty.rb b/lib/xcpretty.rb index 26c76549..70b4edcd 100644 --- a/lib/xcpretty.rb +++ b/lib/xcpretty.rb @@ -8,6 +8,7 @@ require 'xcpretty/formatters/rspec' require 'xcpretty/formatters/knock' require 'xcpretty/formatters/tap' +require 'xcpretty/reporters/reporter' require 'xcpretty/reporters/junit' require 'xcpretty/reporters/html' require 'xcpretty/reporters/json_compilation_database' @@ -21,11 +22,11 @@ def self.class_from_path(path) klass end - def self.load_custom_formatter(path) + def self.load_custom_class(path) $LOAD_PATH.unshift File.dirname(path) class_from_path(path) rescue SyntaxError => e - exit_with_error("Expected formatter source file to return a class. #{e}") + exit_with_error("Expected custom source file to return a class. #{e}") end def self.exit_with_error(message) diff --git a/lib/xcpretty/formatters/formatter.rb b/lib/xcpretty/formatters/formatter.rb index 48be608e..548bdbe5 100644 --- a/lib/xcpretty/formatters/formatter.rb +++ b/lib/xcpretty/formatters/formatter.rb @@ -11,6 +11,7 @@ module FormatMethods def format_analyze(file_name, file_path); EMPTY; end def format_build_target(target, project, configuration); EMPTY; end + def format_aggregate_target(target, project, configuration); EMPTY; end def format_analyze_target(target, project, configuration); EMPTY; end def format_check_dependencies; EMPTY; end def format_clean(project, target, configuration); EMPTY; end @@ -30,7 +31,7 @@ def format_libtool(library); EMPTY; end def format_passing_test(suite, test, time); EMPTY; end def format_pending_test(suite, test); EMPTY; end def format_measuring_test(suite, test, time); EMPTY; end - def format_failing_test(suite, test, time, file_path); EMPTY; end + def format_failing_test(suite, test, reason, file_path); EMPTY; end def format_process_pch(file); EMPTY; end def format_process_pch_command(file_path); EMPTY; end def format_phase_success(phase_name); EMPTY; end @@ -48,6 +49,7 @@ def format_touch(file_path, file_name); EMPTY; end def format_tiffutil(file); EMPTY; end def format_write_file(file); EMPTY; end def format_write_auxiliary_files; EMPTY; end + def format_other(text) EMPTY; end # COMPILER / LINKER ERRORS AND WARNINGS def format_compile_error(file_name, file_path, reason, @@ -78,6 +80,9 @@ def initialize(use_unicode, colorize) @parser = Parser.new(self) end + def finish + end + # Override if you want to catch something specific with your regex def pretty_format(text) parser.parse(text) @@ -144,6 +149,14 @@ def format_duplicate_symbols(message, file_paths) "> #{file_paths.map { |path| path.split('/').last }.join("\n> ")}\n" end + def format_will_not_be_code_signed(message) + "#{yellow(warning_symbol + " " + message)}" + end + + def format_other(text) + "" + end + private @@ -158,10 +171,20 @@ def format_failures(failures_per_suite) end def format_failure(f) - " #{f[:test_case]}, #{red(f[:reason])}\n #{cyan(f[:file_path])}\n" \ - " ```\n" + - Syntax.highlight(Snippet.from_filepath(f[:file_path])) + - " ```" + snippet = Snippet.from_filepath(f[:file_path]) + output = " #{f[:test_case]}, #{red(f[:reason])}" + output += "\n #{cyan(f[:file_path])}" + return output if snippet.contents.empty? + + output += "\n ```\n" + if @colorize + output += Syntax.highlight(snippet) + else + output += snippet.contents + end + + output += " ```" + output end def error_symbol diff --git a/lib/xcpretty/formatters/simple.rb b/lib/xcpretty/formatters/simple.rb index 65128da9..2cd812de 100644 --- a/lib/xcpretty/formatters/simple.rb +++ b/lib/xcpretty/formatters/simple.rb @@ -27,6 +27,10 @@ def format_build_target(target, project, configuration) format("Building", "#{project}/#{target} [#{configuration}]") end + def format_aggregate_target(target, project, configuration) + format("Aggregate", "#{project}/#{target} [#{configuration}]") + end + def format_analyze_target(target, project, configuration) format("Analyzing", "#{project}/#{target} [#{configuration}]") end diff --git a/lib/xcpretty/parser.rb b/lib/xcpretty/parser.rb index a2c31c70..eb4e888d 100644 --- a/lib/xcpretty/parser.rb +++ b/lib/xcpretty/parser.rb @@ -7,7 +7,7 @@ module Matchers # @regex Captured groups # $1 file_path # $2 file_name - ANALYZE_MATCHER = /^Analyze(?:Shallow)?\s(.*\/(.*\.m))*/ + ANALYZE_MATCHER = /^Analyze(?:Shallow)?\s(.*\/(.*\.(?:m|mm|cc|cpp|c|cxx)))\s*/ # @regex Captured groups # $1 target @@ -15,6 +15,12 @@ module Matchers # $3 configuration BUILD_TARGET_MATCHER = /^=== BUILD TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/ + # @regex Captured groups + # $1 target + # $2 project + # $3 configuration + AGGREGATE_TARGET_MATCHER = /^=== BUILD AGGREGATE TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/ + # @regex Captured groups # $1 target # $2 project @@ -54,7 +60,7 @@ module Matchers # @regex Captured groups # $1 compiler_command # $2 file_path - COMPILE_COMMAND_MATCHER = /^\s*(.*\/usr\/bin\/clang\s.*\s\-c\s(.*\.(?:m|mm|c|cc|cpp|cxx))\s.*\.o)$/ + COMPILE_COMMAND_MATCHER = /^\s*(.*clang\s.*\s\-c\s(.*\.(?:m|mm|c|cc|cpp|cxx))\s.*\.o)$/ # @regex Captured groups # $1 file_path @@ -94,6 +100,14 @@ module Matchers # $4 = reason FAILING_TEST_MATCHER = /^\s*(.+:\d+):\serror:\s[\+\-]\[(.*)\s(.*)\]\s:(?:\s'.*'\s\[FAILED\],)?\s(.*)/ + # @regex Captured groups + # $1 = file + # $2 = reason + UI_FAILING_TEST_MATCHER = /^\s{4}t = \s+\d+\.\d+s\s+Assertion Failure: (.*:\d+): (.*)$/ + + # @regex Captured groups + RESTARTING_TESTS_MATCHER = /^Restarting after unexpected exit or crash in.+$/ + # @regex Captured groups # $1 = dsym GENERATE_DSYM_MATCHER = /^GenerateDSYMFile \/.*\/(.*\.dSYM)/ @@ -106,24 +120,30 @@ module Matchers # $1 = target # $2 = build_variants (normal, profile, debug) # $3 = architecture - LINKING_MATCHER = /^Ld \/.*\/(.*) (.*) (.*)$/ + LINKING_MATCHER = /^Ld \/?.*\/(.*?) (.*) (.*)$/ # @regex Captured groups # $1 = suite # $2 = test_case # $3 = time - PASSING_TEST_MATCHER = /^\s*Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/ + TEST_CASE_PASSED_MATCHER = /^\s*Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/ + + + # @regex Captured groups + # $1 = suite + # $2 = test_case + TEST_CASE_STARTED_MATCHER = /^Test Case '-\[(.*) (.*)\]' started.$/ # @regex Captured groups # $1 = suite # $2 = test_case - PENDING_TEST_MATCHER = /^Test Case\s'-\[(.*)\s(.*)PENDING\]'\spassed/ + TEST_CASE_PENDING_MATCHER = /^Test Case\s'-\[(.*)\s(.*)PENDING\]'\spassed/ # @regex Captured groups # $1 = suite # $2 = test_case # $3 = time - MEASURING_TEST_MATCHER = /^[^:]*:[^:]*:\sTest Case\s'-\[(.*)\s(.*)\]'\smeasured\s\[Time,\sseconds\]\saverage:\s(\d*\.\d{3}),/ + TEST_CASE_MEASURED_MATCHER = /^[^:]*:[^:]*:\sTest Case\s'-\[(.*)\s(.*)\]'\smeasured\s\[Time,\sseconds\]\saverage:\s(\d*\.\d{3}),/ PHASE_SUCCESS_MATCHER = /^\*\*\s(.*)\sSUCCEEDED\s\*\*/ @@ -159,7 +179,7 @@ module Matchers # @regex Captured groups # $1 = suite # $2 = time - TESTS_RUN_START_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/ + TEST_SUITE_STARTED_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/ # @regex Captured groups # $1 test suite name @@ -194,6 +214,10 @@ module Warnings # @regex Captured groups # $1 = whole warning GENERIC_WARNING_MATCHER = /^warning:\s(.*)$/ + + # @regex Captured groups + # $1 = whole warning + WILL_NOT_BE_CODE_SIGNED_MATCHER = /^(.* will not be code signed because .*)$/ end module Errors @@ -203,7 +227,15 @@ module Errors # @regex Captured groups # $1 = whole error - CODESIGN_ERROR_MATCHER = /^(Code\s?Sign error:.*)$/ + CHECK_DEPENDENCIES_ERRORS_MATCHER = /^(Code\s?Sign error:.*|Code signing is required for product type .* in SDK .*|No profile matching .* found:.*|Provisioning profile .* doesn't .*|Swift is unavailable on .*|.?Use Legacy Swift Language Version.*)$/ + + # @regex Captured groups + # $1 = whole error + PROVISIONING_PROFILE_REQUIRED_MATCHER = /^(.*requires a provisioning profile.*)$/ + + # @regex Captured groups + # $1 = whole error + NO_CERTIFICATE_MATCHER = /^(No certificate matching.*)$/ # @regex Captured groups # $1 = file_path @@ -225,6 +257,7 @@ module Errors # $2 = file path FILE_MISSING_ERROR_MATCHER = /^:0:\s(error:\s.*)\s'(\/.+\/.*\..*)'$/ + # @regex Captured groups # $1 = whole error LD_ERROR_MATCHER = /^(ld:.*)/ @@ -251,6 +284,10 @@ module Errors # @regex Captured groups # $1 = reference SYMBOL_REFERENCED_FROM_MATCHER = /\s+"(.*)", referenced from:$/ + + # @regex Captured groups + # $1 = error reason + MODULE_INCLUDES_ERROR_MATCHER = /^\:.*?:.*?:\s(?:fatal\s)?(error:\s.*)$/ end end @@ -281,6 +318,8 @@ def parse(text) formatter.format_analyze($2, $1) when BUILD_TARGET_MATCHER formatter.format_build_target($1, $2, $3) + when AGGREGATE_TARGET_MATCHER + formatter.format_aggregate_target($1, $2, $3) when ANALYZE_TARGET_MATCHER formatter.format_analyze_target($1, $2, $3) when CLEAN_REMOVE_MATCHER @@ -297,7 +336,11 @@ def parse(text) formatter.format_codesign($1) when CODESIGN_MATCHER formatter.format_codesign($1) - when CODESIGN_ERROR_MATCHER + when CHECK_DEPENDENCIES_ERRORS_MATCHER + formatter.format_error($1) + when PROVISIONING_PROFILE_REQUIRED_MATCHER + formatter.format_error($1) + when NO_CERTIFICATE_MATCHER formatter.format_error($1) when COMPILE_MATCHER formatter.format_compile($2, $1) @@ -315,6 +358,10 @@ def parse(text) formatter.format_cpresource($1) when EXECUTED_MATCHER format_summary_if_needed(text) + when RESTARTING_TESTS_MATCHER + formatter.format_failing_test(@test_suite, @test_case, "Test crashed", "n/a") + when UI_FAILING_TEST_MATCHER + formatter.format_failing_test(@test_suite, @test_case, $2, $1) when FAILING_TEST_MATCHER formatter.format_failing_test($2, $3, $4, $1) when FATAL_ERROR_MATCHER @@ -331,11 +378,13 @@ def parse(text) formatter.format_libtool($1) when LINKING_MATCHER formatter.format_linking($1, $2, $3) - when MEASURING_TEST_MATCHER + when MODULE_INCLUDES_ERROR_MATCHER + formatter.format_error($1) + when TEST_CASE_MEASURED_MATCHER formatter.format_measuring_test($1, $2, $3) - when PENDING_TEST_MATCHER + when TEST_CASE_PENDING_MATCHER formatter.format_pending_test($1, $2) - when PASSING_TEST_MATCHER + when TEST_CASE_PASSED_MATCHER formatter.format_passing_test($1, $2, $3) when PODS_ERROR_MATCHER formatter.format_error($1) @@ -355,7 +404,7 @@ def parse(text) formatter.format_pbxcp($1) when TESTS_RUN_COMPLETION_MATCHER formatter.format_test_run_finished($1, $3) - when TESTS_RUN_START_MATCHER + when TEST_SUITE_STARTED_MATCHER formatter.format_test_run_started($1) when TEST_SUITE_START_MATCHER formatter.format_test_suite_started($1) @@ -371,8 +420,10 @@ def parse(text) formatter.format_shell_command($1, $2) when GENERIC_WARNING_MATCHER formatter.format_warning($1) + when WILL_NOT_BE_CODE_SIGNED_MATCHER + formatter.format_will_not_be_code_signed($1) else - "" + formatter.format_other(text) end end @@ -380,14 +431,21 @@ def parse(text) def update_test_state(text) case text - when TESTS_RUN_START_MATCHER + when TEST_SUITE_STARTED_MATCHER @tests_done = false @formatted_summary = false @failures = {} + when TEST_CASE_STARTED_MATCHER + @test_suite = $1 + @test_case = $2 when TESTS_RUN_COMPLETION_MATCHER @tests_done = true when FAILING_TEST_MATCHER - store_failure($1, $2, $3, $4) + store_failure(file: $1, test_suite: $2, test_case: $3, reason: $4) + when UI_FAILING_TEST_MATCHER + store_failure(file: $1, test_suite: @test_suite, test_case: @test_case, reason: $2) + when RESTARTING_TESTS_MATCHER + store_failure(file: "n/a", test_suite: @test_suite, test_case: @test_case, reason: "Test crashed") end end @@ -505,7 +563,7 @@ def reset_linker_format_state @formatting_linker_failure = false end - def store_failure(file, test_suite, test_case, reason) + def store_failure(file: nil, test_suite: nil, test_case: nil, reason: nil) failures_per_suite[test_suite] ||= [] failures_per_suite[test_suite] << { file_path: file, diff --git a/lib/xcpretty/printer.rb b/lib/xcpretty/printer.rb index 34dc5bd7..e0c7d7b0 100644 --- a/lib/xcpretty/printer.rb +++ b/lib/xcpretty/printer.rb @@ -11,6 +11,10 @@ def initialize(params) @formatter = klass.new(params[:unicode], params[:colorize]) end + def finish + @formatter.finish + end + def pretty_print(text) formatted_text = formatter.pretty_format(text) unless formatted_text.empty? diff --git a/lib/xcpretty/reporters/html.rb b/lib/xcpretty/reporters/html.rb index d8abaabb..5e304665 100644 --- a/lib/xcpretty/reporters/html.rb +++ b/lib/xcpretty/reporters/html.rb @@ -1,7 +1,6 @@ module XCPretty - class HTML + class HTML < Reporter - include XCPretty::FormatMethods FILEPATH = 'build/reports/tests.html' TEMPLATE = File.expand_path('../../../../assets/report.html.erb', __FILE__) SCREENSHOT_DIR = 'build/reports' @@ -16,12 +15,8 @@ def load_dependencies end def initialize(options) - load_dependencies + super(options) @test_suites = {} - @filepath = options[:path] || FILEPATH - @parser = Parser.new(self) - @test_count = 0 - @fail_count = 0 @collect_screenshots = options[:screenshots] end @@ -32,29 +27,24 @@ def handle(line) def format_failing_test(suite, test_case, reason, file) add_test(suite, name: test_case, failing: true, reason: reason, file: file, - snippet: formatted_snippet(file)) + snippet: formatted_snippet(file), + screenshots: []) end def format_passing_test(suite, test_case, time) - add_test(suite, name: test_case, time: time) - end - - def finish - FileUtils.mkdir_p(File.dirname(@filepath)) - write_report + add_test(suite, name: test_case, time: time, screenshots: []) end private def formatted_snippet(filepath) snippet = Snippet.from_filepath(filepath) - Syntax.highlight(snippet) + Syntax.highlight_html(snippet) end - def add_test(suite_name, data) @test_count += 1 - @test_suites[suite_name] ||= {tests: [], screenshots: []} + @test_suites[suite_name] ||= {tests: []} @test_suites[suite_name][:tests] << data if data[:failing] @test_suites[suite_name][:failing] = true @@ -80,16 +70,21 @@ def load_screenshots Dir.foreach(SCREENSHOT_DIR) do |item| next if item == '.' || item == '..' || File.extname(item) != '.png' - suite_name = find_test_suite(item) - next if suite_name.nil? + test = find_test(item) + next if test.nil? - @test_suites[suite_name][:screenshots] << item + test[:screenshots] << item end end - def find_test_suite(image_name) - @test_suites.each do |key, value| - return key if image_name.start_with?(key) + def find_test(image_name) + @test_suites.each do |name, info| + info[:tests].each do |test, index| + combined_name = name + '_' + test[:name] + test_name_matches = image_name.start_with?(test[:name]) + combined_name_matches = image_name.start_with?(combined_name) + return test if test_name_matches || combined_name_matches + end end nil end diff --git a/lib/xcpretty/reporters/json_compilation_database.rb b/lib/xcpretty/reporters/json_compilation_database.rb index 1a5f3bd7..60527971 100644 --- a/lib/xcpretty/reporters/json_compilation_database.rb +++ b/lib/xcpretty/reporters/json_compilation_database.rb @@ -1,8 +1,7 @@ module XCPretty - class JSONCompilationDatabase + class JSONCompilationDatabase < Reporter - include XCPretty::FormatMethods - FILE_PATH = 'build/reports/compilation_db.json' + FILEPATH = 'build/reports/compilation_db.json' def load_dependencies unless @@loaded ||= false @@ -14,19 +13,13 @@ def load_dependencies end def initialize(options) - load_dependencies - @file_path = options[:path] || FILE_PATH - @parser = Parser.new(self) + super(options) @compilation_units = [] @pch_path = nil @current_file = nil @current_path = nil end - def handle(line) - @parser.parse(line) - end - def format_process_pch_command(file_path) @pch_path = file_path end @@ -48,15 +41,8 @@ def format_compile_command(compiler_command, file_path) directory: directory} end - def finish - FileUtils.mkdir_p(File.dirname(@file_path)) - write_report - end - - private - def write_report - File.open(@file_path, 'w') do |f| + File.open(@filepath, 'w') do |f| f.write(@compilation_units.to_json) end end diff --git a/lib/xcpretty/reporters/junit.rb b/lib/xcpretty/reporters/junit.rb index ef3925fe..29137c77 100644 --- a/lib/xcpretty/reporters/junit.rb +++ b/lib/xcpretty/reporters/junit.rb @@ -1,7 +1,6 @@ module XCPretty - class JUnit + class JUnit < Reporter - include XCPretty::FormatMethods FILEPATH = 'build/reports/junit.xml' def load_dependencies @@ -15,15 +14,13 @@ def load_dependencies end def initialize(options) - load_dependencies - @filepath = options[:path] || FILEPATH + super(options) @directory = `pwd`.strip @document = REXML::Document.new @document << REXML::XMLDecl.new('1.0', 'UTF-8') @document.add_element('testsuites') - @parser = Parser.new(self) - @total_tests = 0 @total_fails = 0 + @total_tests = 0 end def handle(line) @@ -54,6 +51,8 @@ def format_failing_test(classname, test_case, reason, file) test_node = suite(classname).add_element('testcase') test_node.attributes['classname'] = classname test_node.attributes['name'] = test_case + # A fake time is added to let Sonar parse failed tests. + test_node.attributes['time'] = "0" fail_node = test_node.add_element('failure') fail_node.attributes['message'] = reason fail_node.text = file.sub(@directory + '/', '') @@ -65,13 +64,10 @@ def finish set_test_counters @document.root.attributes['tests'] = @total_tests @document.root.attributes['failures'] = @total_fails - write_report_file + super end - private - - def write_report_file - FileUtils.mkdir_p(File.dirname(@filepath)) + def write_report formatter = REXML::Formatters::Pretty.new(2) formatter.compact = true output_file = File.open(@filepath, 'w+') @@ -80,6 +76,8 @@ def write_report_file result end + private + def suite(classname) if @last_suite && @last_suite.attributes['name'] == classname return @last_suite diff --git a/lib/xcpretty/reporters/reporter.rb b/lib/xcpretty/reporters/reporter.rb new file mode 100644 index 00000000..e03d8cdc --- /dev/null +++ b/lib/xcpretty/reporters/reporter.rb @@ -0,0 +1,62 @@ +module XCPretty + + class Reporter < Formatter + FILEPATH = 'build/reports/tests.txt' + + attr_reader :tests + + def load_dependencies + unless @@loaded ||= false + require 'fileutils' + @@loaded = true + end + end + + def initialize(options) + super(true, true) + load_dependencies + @filepath = options[:path] || self.class::FILEPATH + @test_count = 0 + @fail_count = 0 + @tests = [] + end + + def handle(line) + @parser.parse(line) + end + + def finish + FileUtils.mkdir_p(File.dirname(@filepath)) + write_report + end + + def format_failing_test(suite, test_case, reason, file) + @test_count += 1 + @fail_count += 1 + @tests.push("#{test_case} in #{file} FAILED: #{reason}") + end + + def format_passing_test(suite, test_case, time) + @test_count += 1 + @tests.push("#{test_case} PASSED") + end + + def format_pending_test(classname, test_case) + @test_count += 1 + @tests.push("#{test_case} IS PENDING") + end + + def write_report + File.open(@filepath, 'w') do |f| + # WAT: get rid of these locals. BTW Cucumber fails if you remove them + output_string = @tests.join("\n") + output_string += + "\nFINISHED RUNNING #{@test_count} TESTS WITH #{@fail_count} FAILURES" + f.write output_string + end + end + + end + +end + diff --git a/lib/xcpretty/syntax.rb b/lib/xcpretty/syntax.rb index 4a47b93a..6778d1d5 100644 --- a/lib/xcpretty/syntax.rb +++ b/lib/xcpretty/syntax.rb @@ -12,7 +12,15 @@ module XCPretty module Syntax def self.highlight(snippet) return snippet.contents unless Rouge + highlight_with_formatter(snippet, Rouge::Formatters::Terminal256.new) + end + + def self.highlight_html(snippet) + return snippet.contents unless Rouge + highlight_with_formatter(snippet, Rouge::Formatters::HTML.new) + end + def self.highlight_with_formatter(snippet, formatter) if snippet.file_path.include?(':') filename = snippet.file_path.rpartition(':').first else @@ -21,7 +29,6 @@ def self.highlight(snippet) lexer = find_lexer(filename, snippet.contents) if lexer - formatter = Rouge::Formatters::Terminal256.new formatter.format(lexer.lex(snippet.contents)) else snippet.contents diff --git a/lib/xcpretty/version.rb b/lib/xcpretty/version.rb index 3eb34f36..8842e6d0 100644 --- a/lib/xcpretty/version.rb +++ b/lib/xcpretty/version.rb @@ -1,4 +1,4 @@ module XCPretty - VERSION = "0.2.2" + VERSION = "0.3.0" end diff --git a/spec/fixtures/constants.rb b/spec/fixtures/constants.rb index e2c24e52..2e2210be 100644 --- a/spec/fixtures/constants.rb +++ b/spec/fixtures/constants.rb @@ -17,12 +17,67 @@ SAMPLE_OCUNIT_SUITE_COMPLETION = "Test Suite '/Users/musalj/Library/Developer/Xcode/DerivedData/ReactiveCocoa-eznxkbqvgfsnrvetemqloysuwagb/Build/Products/Test/ReactiveCocoaTests.octest(Tests)' finished at 2013-12-08 22:09:37 +0000." SAMPLE_XCTEST_SUITE_COMPLETION = "Test Suite 'ObjectiveSugarTests.xctest' finished at 2013-12-09 04:42:13 +0000." +SAMPLE_UITEST_CASE_WITH_FAILURE = %Q(\ +Test Case '-[viewUITests.vmtAboutWindow testConnectToDesktop]' started. + t = 0.00s Start Test at 2016-08-18 09:07:17.822 + t = 0.00s Set Up + t = 0.00s Launch com.vmware.horizon + t = 1.38s Wait for app to idle +Wait for connect to desktop done + t = 19.06s Snapshot accessibility hierarchy for com.vmware.horizon + t = 20.31s Find: Descendants matching type Window + t = 20.32s Find: Elements matching predicate '"rdsh1" IN identifiers' +Connect to desktop done + t = 20.32s Click "Disconnect" Button + t = 20.32s Wait for app to idle + t = 20.39s Find the "Disconnect" Button + t = 20.39s Snapshot accessibility hierarchy for com.vmware.horizon + t = 20.53s Find: Descendants matching type Window + t = 20.53s Find: Elements matching predicate '"rdsh1" IN identifiers' + t = 21.54s Find the "Disconnect" Button (retry 1) + t = 21.54s Snapshot accessibility hierarchy for com.vmware.horizon + t = 21.80s Find: Descendants matching type Window + t = 21.80s Find: Elements matching predicate '"rdsh1" IN identifiers' + t = 21.81s Find: Descendants matching type Toolbar + t = 21.81s Find: Descendants matching type Button + t = 21.81s Find: Elements matching predicate '"Disconnect" IN identifiers' + t = 21.81s Synthesize event + t = 22.27s Assertion Failure: :0: UI Testing Failure - Unable to find hit point for element Button 0x608001165880: {{74.0, -54.0}, {44.0, 38.0}}, label: 'Disconnect' + t = 22.29s Tear Down +Test Case '-[viewUITests.vmtAboutWindow testConnectToDesktop]' failed (22.490 seconds). +) +SAMPLE_XCTEST_FAILURE = "" SAMPLE_KIWI_FAILURE = "/Users/musalj/code/OSS/ObjectiveSugar/Example/ObjectiveSugarTests/NSNumberTests.m:49: error: -[NumberAdditions Iterators_TimesIteratesTheExactNumberOfTimes] : 'Iterators, times: iterates the exact number of times' [FAILED], expected subject to equal 4, got 5" SAMPLE_OLD_SPECTA_FAILURE = "/Users/musalj/code/OSS/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoaTests/RACCommandSpec.m:458: error: -[RACCommandSpec enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES] : expected: 1, got: 0" SAMPLE_SPECTA_FAILURE = " Test Case '-[SKWelcomeViewControllerSpecSpec SKWelcomeViewController_When_a_user_opens_the_app_from_a_clean_installation_displays_the_welcome_screen]' started. \n/Users/vickeryj/Code/ipad-register/KIFTests/Specs/SKWelcomeViewControllerSpec.m:11: error: -[SKWelcomeViewControllerSpecSpec SKWelcomeViewController_When_a_user_opens_the_app_from_a_clean_installation_displays_the_welcome_screen] : The step timed out after 2.00 seconds: Failed to find accessibility element with the label \"The asimplest way to make smarter business decisions\"" +SAMPLE_SWIFT_FATAL_ERROR_IN_TEST_MAKE_TESTS_RESTARTING = %Q(\ +Test Suite 'All tests' started at 2017-07-10 15:01:12.049 +Test Suite 'xcprettyFatalErrorTests.xctest' started at 2017-07-10 15:01:12.049 +Test Suite 'xcprettyFatalErrorTests' started at 2017-07-10 15:01:12.049 +Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testExample]' started. +Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testExample]' passed (0.001 seconds). +Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testFatalError]' started. +fatal error: unexpectedly found nil while unwrapping an Optional value + +Restarting after unexpected exit or crash in xcprettyFatalErrorTests/testFatalError(); summary will include totals from previous launches. + +Test Suite 'Selected tests' started at 2017-07-10 15:01:17.698 +Test Suite 'xcprettyFatalErrorTests.xctest' started at 2017-07-10 15:01:17.698 +Test Suite 'xcprettyFatalErrorTests' started at 2017-07-10 15:01:17.699 +Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testPerformanceExample]' started. +/Users/kenji/Desktop/xcprettyFatalError/xcprettyFatalErrorTests/xcprettyFatalErrorTests.swift:40: Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testPerformanceExample]' measured [Time, seconds] average: 0.000, relative standard deviation: 174.575%, values: [0.000005, 0.000001, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100 +Test Case '-[xcprettyFatalErrorTests.xcprettyFatalErrorTests testPerformanceExample]' passed (0.258 seconds). +Test Suite 'xcprettyFatalErrorTests' failed at 2017-07-10 15:01:17.957. + Executed 3 tests, with 1 failure (0 unexpected) in 0.258 (0.259) seconds +Test Suite 'xcprettyFatalErrorTests.xctest' failed at 2017-07-10 15:01:17.958. + Executed 3 tests, with 1 failure (0 unexpected) in 0.258 (0.259) seconds +Test Suite 'Selected tests' failed at 2017-07-10 15:01:17.958. + Executed 3 tests, with 1 failure (0 unexpected) in 0.258 (0.260) seconds +) SAMPLE_BUILD = "=== BUILD TARGET The Spacer OF PROJECT Pods WITH THE DEFAULT CONFIGURATION Debug ===" SAMPLE_ANALYZE_TARGET = "=== ANALYZE TARGET The Spacer OF PROJECT Pods WITH THE DEFAULT CONFIGURATION Debug ===" +SAMPLE_AGGREGATE_TARGET = "=== BUILD AGGREGATE TARGET Be Aggro OF PROJECT AggregateExample WITH CONFIGURATION Debug ===" SAMPLE_CLEAN = "=== CLEAN TARGET Pods-ObjectiveSugar OF PROJECT Pods WITH CONFIGURATION Debug ===" SAMPLE_ANOTHER_CLEAN = "=== CLEAN TARGET Pods OF PROJECT Pods WITH CONFIGURATION Debug ===" SAMPLE_BUILD_SUCCEEDED = "** BUILD SUCCEEDED **" @@ -33,7 +88,7 @@ ) SAMPLE_EXECUTED_TESTS = "Executed 4 tests, with 0 failures (0 unexpected) in 0.003 (0.004) seconds" SAMPLE_SPECTA_EXECUTED_TESTS = " Executed 4 tests, with 0 failures (0 unexpected) in 10.192 (10.193) seconds" -SAMPLE_OCUNIT_TEST = "Test Case '-[RACCommandSpec enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES]' passed (0.001 seconds)." +SAMPLE_OCUNIT_TEST = "Test Case '-[RACCommandSpec enabled_signal_should_send_YES_while_executing_is_YES]' passed (0.001 seconds)." SAMPLE_SPECTA_TEST = " Test Case '-[SKWelcomeActivationViewControllerSpecSpec SKWelcomeActivationViewController_When_a_user_enters_their_details_lets_them_enter_a_valid_manager_code]' passed (0.725 seconds)." SAMPLE_SLOWISH_TEST = "Test Case '-[RACCommandSpec enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES]' passed (0.026 seconds)." SAMPLE_SLOW_TEST = "Test Case '-[RACCommandSpec enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES]' passed (0.101 seconds)." @@ -61,6 +116,13 @@ setenv PATH "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/Users/musalj/code/go/bin:/Users/musalj/.rbenv/shims:/Users/musalj/.rbenv/bin:/usr/local/share/npm/bin:/usr/local/bin:/Library/Python/2.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch i386 -fmessage-length=178 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -fcolor-diagnostics -std=c99 -fobjc-arc -Wno-trigraphs -fpascal-strings -O0 -Wno-missing-field-initializers -Wmissing-prototypes -Wno-implicit-atomic-properties -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wshorten-64-to-32 -Wpointer-sign -Wnewline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk -fexceptions -fasm-blocks -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -g -Wno-sign-conversion -fobjc-abi-version=2 -fobjc-legacy-dispatch -mios-simulator-version-min=5.0 -iquote /Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Kiwi-generated-files.hmap -I/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Kiwi-own-target-headers.hmap -I/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Kiwi-all-target-headers.hmap -iquote /Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Kiwi-project-headers.hmap -I/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Products/Debug-iphonesimulator/include -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include -I/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/DerivedSources/i386 -I/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/DerivedSources -Wall -F/Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Products/Debug-iphonesimulator -F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/Developer/Library/Frameworks -F/Applications/Xcode.app/Contents/Developer/Library/Frameworks -include /Users/musalj/code/OSS/Kiwi/Supporting\ Files/Kiwi-Prefix.pch -MMD -MT dependencies -MF /Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Objects-normal/i386/KWNull.d --serialize-diagnostics /Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Objects-normal/i386/KWNull.dia -c /Users/musalj/code/OSS/Kiwi/Classes/Core/KWNull.m -o /Users/musalj/Library/Developer/Xcode/DerivedData/Kiwi-guimpeiqlepzeaankpygesetdzsx/Build/Intermediates/Kiwi.build/Debug-iphonesimulator/Kiwi.build/Objects-normal/i386/KWNull.o ) +SAMPLE_COMPILE_CCACHE = %Q( +CompileC /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato-DMMTodoTxt.build/Objects-normal/armv7/DMMTodoTxtParser.o DMMTodoTxt/Pod/Classes/DMMTodoTxtParser.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler + cd /Users/delisa/Code/personal/Gelato/Pods + export LANG=en_US.US-ASCII + export PATH="/Applications/Xcode/Xcode-9.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Applications/Xcode/Xcode-9.3.app/Contents/Developer/usr/bin:/Users/delisa/.rbenv/shims:/Users/delisa/.pyenv/shims:/Users/delisa/.swiftenv/shims:/Users/delisa/bin:/usr/local/bin:/usr/local/sbin:/bin:/usr/bin:/usr/sbin:/sbin:/Users/delisa/tmp/go/bin:/Users/delisa/Library/Android/sdk/platform-tools/" + /Users/delisa/Code/personal/Gelato/ccache-clang -x objective-c -arch arm64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu99 -fobjc-arc -fmodules -fmodules-cache-path=/Users/delisa/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/Users/delisa/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -fapplication-extension -Wno-trigraphs -fpascal-strings -Os -Wno-missing-field-initializers -Wno-missing-prototypes -Wunreachable-code -Wno-implicit-atomic-properties -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wno-non-literal-null-conversion -Wno-objc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -DRELEASE=1 -DCOCOAPODS=1 -DNS_BLOCK_ASSERTIONS=1 -DOBJC_OLD_DISPATCH_PROTOTYPES=0 -isysroot /Applications/Xcode/Xcode-9.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.3.sdk -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -miphoneos-version-min=9.0 -g -Wno-sign-conversion -Wno-infinite-recursion -Wno-comma -Wno-block-capture-autoreleasing -Wno-strict-prototypes -fembed-bitcode-marker -iquote /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Pods-Gelato\ Today-DMMTodoTxt-generated-files.hmap -I/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Pods-Gelato\ Today-DMMTodoTxt-own-target-headers.hmap -I/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Pods-Gelato\ Today-DMMTodoTxt-all-target-headers.hmap -iquote /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Pods-Gelato\ Today-DMMTodoTxt-project-headers.hmap -I/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Products/Release-iphoneos/include -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Private -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Private/DMMTodoTxt -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/APAutocompleteTextField -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/Amplitude-iOS -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/Bugsnag -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/CGFloatType -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/DMMTodoTxt -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/Dropbox-iOS-SDK -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/FMDB -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/FXKeychain -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/KSCrash -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/Kiwi -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/PDGestureTableView -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/RJBlurAlertView -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/SpinKit -I/Users/delisa/Code/personal/Gelato/Pods/Headers/Public/UIImageEffects -I/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/DerivedSources/arm64 -I/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/DerivedSources -F/Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Products/Release-iphoneos -include /Users/delisa/Code/personal/Gelato/Pods/Target\ Support\ Files/Pods-Gelato\ Today-DMMTodoTxt/Pods-Gelato\ Today-DMMTodoTxt-prefix.pch -MMD -MT dependencies -MF /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Objects-normal/arm64/DMMTodoTxtParser.d --serialize-diagnostics /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Objects-normal/arm64/DMMTodoTxtParser.dia -c /Users/delisa/Code/personal/Gelato/Pods/DMMTodoTxt/Pod/Classes/DMMTodoTxtParser.m -o /Users/delisa/Library/Developer/Xcode/DerivedData/Gelato-cbyvaqwtjzrxxtfccjobyatrvvwh/Build/Intermediates.noindex/Pods.build/Release-iphoneos/Pods-Gelato\ Today-DMMTodoTxt.build/Objects-normal/arm64/DMMTodoTxtParser.o +) SAMPLE_SWIFT_COMPILE = %Q( CompileSwift normal arm64 /Users/paul/foo/bar/siesta/Source/Resource.swift cd /Users/paul/foo/bar/siesta @@ -104,6 +166,13 @@ setenv PATH "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/Users/musalj/code/go/bin:/Users/musalj/.rbenv/shims:/Users/musalj/.rbenv/bin:/usr/local/share/npm/bin:/usr/local/bin:/Library/Python/2.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch i386 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk -L/Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator -F/Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator -filelist /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Intermediates/ObjectiveSugar.build/Debug-iphonesimulator/ObjectiveSugar.build/Objects-normal/i386/ObjectiveSugar.LinkFileList -Xlinker -objc_abi_version -Xlinker 2 -ObjC -fobjc-arc -fobjc-link-runtime -Xlinker -no_implicit_dylibs -mios-simulator-version-min=4.3 -framework UIKit -framework Foundation -framework CoreGraphics -lPods -Xlinker -dependency_info -Xlinker /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Intermediates/ObjectiveSugar.build/Debug-iphonesimulator/ObjectiveSugar.build/Objects-normal/i386/ObjectiveSugar_dependency_info.dat -o /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator/ObjectiveSugar.app/ObjectiveSugar ) +SAMPLE_LD_RELATIVE = %Q( +Ld ../Build/Products/Debug-iphonesimulator/ObjectiveSugar.app/ObjectiveSugar normal i386 + cd /Users/musalj/code/OSS/ObjectiveSugar/Example + setenv IPHONEOS_DEPLOYMENT_TARGET 4.3 + setenv PATH "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/Users/musalj/code/go/bin:/Users/musalj/.rbenv/shims:/Users/musalj/.rbenv/bin:/usr/local/share/npm/bin:/usr/local/bin:/Library/Python/2.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch i386 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk -L/Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator -F/Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator -filelist /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Intermediates/ObjectiveSugar.build/Debug-iphonesimulator/ObjectiveSugar.build/Objects-normal/i386/ObjectiveSugar.LinkFileList -Xlinker -objc_abi_version -Xlinker 2 -ObjC -fobjc-arc -fobjc-link-runtime -Xlinker -no_implicit_dylibs -mios-simulator-version-min=4.3 -framework UIKit -framework Foundation -framework CoreGraphics -lPods -Xlinker -dependency_info -Xlinker /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Intermediates/ObjectiveSugar.build/Debug-iphonesimulator/ObjectiveSugar.build/Objects-normal/i386/ObjectiveSugar_dependency_info.dat -o /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator/ObjectiveSugar.app/ObjectiveSugar +).freeze SAMPLE_DSYM = %Q( GenerateDSYMFile /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator/ObjectiveSugarTests.octest.dSYM /Users/musalj/Library/Developer/Xcode/DerivedData/ObjectiveSugar-ayzdhqmmwtqgysdpznmovjlupqjy/Build/Products/Debug-iphonesimulator/ObjectiveSugarTests.octest/ObjectiveSugarTests cd /Users/musalj/code/OSS/ObjectiveSugar/Example @@ -473,6 +542,12 @@ setenv LANG en_US.US-ASCII /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch x86_64 -fmessage-length=107 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -fcolor-diagnostics -std=gnu99 -fobjc-arc -Wno-trigraphs -fpascal-strings -Os -Werror -Wmissing-field-initializers -Wmissing-prototypes -Wno-implicit-atomic-properties -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wmissing-braces -Wparentheses -Wswitch -Wunused-function -Wunused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wshadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wsign-compare -Wshorten-64-to-32 -Wpointer-sign -Wnewline-eof -Wno-selector -Wstrict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk -fasm-blocks -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -mmacosx-version-min=10.7 -g -fvisibility=hidden -Wno-sign-conversion -D__clang_analyzer__ -Xclang -analyzer-output=plist-multi-file -Xclang -analyzer-config -Xclang path-diagnostics-alternate=true -Xclang -analyzer-config -Xclang report-in-main-source-file=true -Xclang -analyzer-config -Xclang mode=shallow -Xclang -analyzer-checker -Xclang security.insecureAPI.UncheckedReturn -Xclang -analyzer-checker -Xclang security.insecureAPI.getpw -Xclang -analyzer-checker -Xclang security.insecureAPI.gets -Xclang -analyzer-checker -Xclang security.insecureAPI.mkstemp -Xclang -analyzer-checker -Xclang security.insecureAPI.mktemp -Xclang -analyzer-disable-checker -Xclang security.insecureAPI.rand -Xclang -analyzer-disable-checker -Xclang security.insecureAPI.strcpy -Xclang -analyzer-checker -Xclang security.insecureAPI.vfork -iquote /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-generated-files.hmap -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-own-target-headers.hmap -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-all-target-headers.hmap -iquote /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-project-headers.hmap -I/Users/dustin/Source/CocoaChip/build/Release/include -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/DerivedSources/x86_64 -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/DerivedSources -F/Users/dustin/Source/CocoaChip/build/Release -include /var/folders/nn/s88k060x7016ccml2bc6f5_h0000gn/C/com.apple.DeveloperTools/5.0.2-5A3005/Xcode/SharedPrecompiledHeaders/CocoaChip-Prefix-dzmrdzscqkbvvrafvxsbjwbxtmlz/CocoaChip-Prefix.pch --analyze /Users/dustin/Source/CocoaChip/CocoaChip/CCChip8DisplayView.m -o /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/StaticAnalyzer/CocoaChip/CocoaChip/normal/x86_64/CCChip8DisplayView.plist ) +SAMPLE_ANALYZE_CPP = %Q( +Analyze CocoaChip/CCChip8DisplayView.cpp + cd /Users/dustin/Source/CocoaChip + setenv LANG en_US.US-ASCII + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x objective-c -arch x86_64 -fmessage-length=107 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -fcolor-diagnostics -std=gnu99 -fobjc-arc -Wno-trigraphs -fpascal-strings -Os -Werror -Wmissing-field-initializers -Wmissing-prototypes -Wno-implicit-atomic-properties -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak -Wduplicate-method-match -Wmissing-braces -Wparentheses -Wswitch -Wunused-function -Wunused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wshadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wsign-compare -Wshorten-64-to-32 -Wpointer-sign -Wnewline-eof -Wno-selector -Wstrict-selector-match -Wundeclared-selector -Wno-deprecated-implementations -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk -fasm-blocks -fstrict-aliasing -Wprotocol -Wdeprecated-declarations -mmacosx-version-min=10.7 -g -fvisibility=hidden -Wno-sign-conversion -D__clang_analyzer__ -Xclang -analyzer-output=plist-multi-file -Xclang -analyzer-config -Xclang path-diagnostics-alternate=true -Xclang -analyzer-config -Xclang report-in-main-source-file=true -Xclang -analyzer-checker -Xclang security.insecureAPI.UncheckedReturn -Xclang -analyzer-checker -Xclang security.insecureAPI.getpw -Xclang -analyzer-checker -Xclang security.insecureAPI.gets -Xclang -analyzer-checker -Xclang security.insecureAPI.mkstemp -Xclang -analyzer-checker -Xclang security.insecureAPI.mktemp -Xclang -analyzer-disable-checker -Xclang security.insecureAPI.rand -Xclang -analyzer-disable-checker -Xclang security.insecureAPI.strcpy -Xclang -analyzer-checker -Xclang security.insecureAPI.vfork -iquote /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-generated-files.hmap -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-own-target-headers.hmap -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-all-target-headers.hmap -iquote /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/CocoaChip-project-headers.hmap -I/Users/dustin/Source/CocoaChip/build/Release/include -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/DerivedSources/x86_64 -I/Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/DerivedSources -F/Users/dustin/Source/CocoaChip/build/Release -include /var/folders/nn/s88k060x7016ccml2bc6f5_h0000gn/C/com.apple.DeveloperTools/5.0.2-5A3005/Xcode/SharedPrecompiledHeaders/CocoaChip-Prefix-cqqikmscxiepjgdcrgpysqicucyu/CocoaChip-Prefix.pch --analyze /Users/dustin/Source/CocoaChip/CocoaChip/CCChip8DisplayView.m -o /Users/dustin/Source/CocoaChip/build/CocoaChip.build/Release/CocoaChip.build/StaticAnalyzer/CocoaChip/CocoaChip/normal/x86_64/CCChip8DisplayView.plist +).freeze SAMPLE_COMPILE_XIB = %Q( CompileXIB CocoaChip/en.lproj/MainMenu.xib cd /Users/dustin/Source/CocoaChip @@ -511,7 +586,7 @@ warning: skipping copy phase strip, binary is code signed: /Users/dustin/Source/CocoaChip/build/Release/CocoaChipCore.framework/Versions/A/CocoaChipCore ) -SAMPLE_SCREENSHOT_FILE = 'RACCommandSpec, line 80, hello xcpretty.png' +SAMPLE_SCREENSHOT_FILE = 'RACCommandSpec_enabled_signal_should_send_YES_while_executing_is_YES.png' SAMPLE_UNRELATED_IMAGE_FILE = 'apple_raw.png' ################################################################################ @@ -538,6 +613,14 @@ CodeSign error: code signing is required for product type 'Application' in SDK 'iOS 7.0' ) +SAMPLE_REQUIRES_PROVISION = %Q( +PROJECT_NAME requires a provisioning profile. Select a provisioning profile for the "Debug" build configuration in the project editor. +) + +SAMPLE_NO_CERTIFICATE = %Q( +No certificate matching 'iPhone Distribution: Name (B89SBB0AV9)' for team 'B89SBB0AV9': Select a different signing certificate for CODE_SIGN_IDENTITY, a team that matches your selected certificate, or switch to automatic provisioning. +) + SAMPLE_FATAL_COMPILE_ERROR = %Q( In file included from /Users/musalj/code/OSS/SampleApp/Pods/SuperCoolPod/SuperAwesomeClass.m:12: In file included from /Users/musalj/code/OSS/SampleApp/Pods/../LessCoolPod/LessCoolClass.h:9: @@ -585,6 +668,27 @@ ~~ ^~~~~~~ ) +SAMPLE_PROFILE_DOESNT_SUPPORT_CAPABILITY_ERROR = %Q( +Provisioning profile "Profile Name" doesn't support the Push Notifications capability. +) + +SAMPLE_PROFILE_DOESNT_INCLUDE_ENTITLEMENT_ERROR = %Q( +Provisioning profile "Profile Name" doesn't include the aps-environment entitlement. +) + +SAMPLE_CODE_SIGNING_IS_REQUIRED_ERROR = %Q( +Code signing is required for product type 'Application' in SDK 'iOS 10.0' +) + +SAMPLE_NO_PROFILE_MATCHING_ERROR = %Q( +No profile matching 'TargetName' found: Xcode couldn't find a profile matching 'TargetName'. Install the profile (by dragging and dropping it onto Xcode's dock item) or select a different one in the General tab of the target editor. +) + +SAMPLE_SWIFT_UNAVAILABLE = "Swift is unavailable on iOS earlier than 7.0; please set IPHONEOS_DEPLOYMENT_TARGET to 7.0 or later (currently it is '6.0')." + +SAMPLE_USE_LEGACY_SWIFT = "“Use Legacy Swift Language Version” (SWIFT_VERSION) is required to be configured correctly for targets which use Swift. Use the [Edit > Convert > To Current Swift Syntax…] menu to choose a Swift version or use the Build Settings editor to configure the build setting directly." + +SAMPLE_MODULE_INCLUDES_ERROR = ":1:1: error: umbrella header for module 'ModuleName' does not include header 'Header.h'" ################################################################################ # WARNINGS @@ -598,3 +702,6 @@ 1 warning generated. ) +SAMPLE_WILL_NOT_BE_CODE_SIGNED = "FrameworkName will not be code signed because its settings don't specify a development team." +SAMPLE_FORMAT_OTHER_UNRECOGNIZED_STRING = "This is a string that won't be matched by any other regex." + diff --git a/spec/fixtures/custom_reporter.rb b/spec/fixtures/custom_reporter.rb new file mode 100644 index 00000000..f16d4477 --- /dev/null +++ b/spec/fixtures/custom_reporter.rb @@ -0,0 +1,30 @@ +# encoding: utf-8 +class DogeReporter < XCPretty::Reporter + + def format_failing_test(suite, test_case, reason, file) + @test_count += 1 + @fail_count += 1 + @tests.push("WOW such FAIL. Many #{test_case}. Much #{reason}. Very #{file}.") + end + + def format_passing_test(suite, test_case, time) + @test_count += 1 + @tests.push("WOW such PASS. Many #{test_case}. Much green. Very success.") + end + + def format_pending_test(classname, test_case) + @test_count += 1 + @tests.push("WOW such PENDING. Many #{test_case}. Much stop. Very wait.") + end + + def write_report + File.open(@filepath, 'w') do |f| + output_string = @tests.join("\n") + output_string += "\nWOW such FINISH. Very #{@test_count}. Much #{@fail_count} FAIL. Very done." + f.write output_string + end + end +end + +DogeReporter + diff --git a/spec/xcpretty/formatters/formatter_spec.rb b/spec/xcpretty/formatters/formatter_spec.rb index 207bc60e..26935c92 100644 --- a/spec/xcpretty/formatters/formatter_spec.rb +++ b/spec/xcpretty/formatters/formatter_spec.rb @@ -12,11 +12,11 @@ module XCPretty end it "initializes with unicode" do - @formatter.use_unicode?.should be_truthy + @formatter.use_unicode?.should == true end it "initializes with color" do - @formatter.colorize?.should be_truthy + @formatter.colorize?.should == true end it "outputs to new lines by default" do @@ -91,6 +91,10 @@ module XCPretty ) end + it "formats will not be code signed warnings" do + @formatter.format_will_not_be_code_signed(SAMPLE_WILL_NOT_BE_CODE_SIGNED).should == "#{@formatter.yellow("⚠️ FrameworkName will not be code signed because its settings don't specify a development team.")}" + end + it "formats failures per suite" do Syntax.stub(:highlight) { |snippet| snippet.contents } @@ -99,18 +103,21 @@ module XCPretty second_path = File.expand_path('spec/fixtures/NSStringTests.m:57') failures = { - 'CarSpec' => [ - { - file_path: first_path, - reason: "just doesn't work", - test_case: 'Starting the car' - }], - 'StringSpec' => [ - { - file_path: second_path, - reason: "doesn't split", - test_case: 'Splitting the string' - }] + 'CarSpec' => [{ + file_path: first_path, + reason: "just doesn't work", + test_case: 'Starting the car' + }], + 'StringSpec' => [{ + file_path: second_path, + reason: "doesn't split", + test_case: 'Splitting the string' + }], + 'UI spec' => [{ + file_path: ":0", + reason: "ui test failed", + test_case: 'yolo' + }] } @formatter.format_test_summary(SAMPLE_EXECUTED_TESTS, failures).should == %Q( @@ -132,6 +139,10 @@ module XCPretty }); ``` +UI spec + yolo, #{@formatter.red("ui test failed")} + #{@formatter.cyan(":0")} + #{@formatter.red(SAMPLE_EXECUTED_TESTS)}) end diff --git a/spec/xcpretty/formatters/simple_spec.rb b/spec/xcpretty/formatters/simple_spec.rb index edc8a284..bc90cc28 100644 --- a/spec/xcpretty/formatters/simple_spec.rb +++ b/spec/xcpretty/formatters/simple_spec.rb @@ -20,6 +20,11 @@ module XCPretty "> Building Pods/The Spacer [Debug]" end + it "formats build target/project/configuration with target" do + @formatter.format_aggregate_target("Be Aggro", "AggregateExample", "Debug").should == + "> Aggregate AggregateExample/Be Aggro [Debug]" + end + it "formats analyze target/project/configuration with target" do @formatter.format_analyze_target("The Spacer", "Pods", "Debug").should == "> Analyzing Pods/The Spacer [Debug]" diff --git a/spec/xcpretty/parser_spec.rb b/spec/xcpretty/parser_spec.rb index 614a91cd..8c76421a 100644 --- a/spec/xcpretty/parser_spec.rb +++ b/spec/xcpretty/parser_spec.rb @@ -23,11 +23,21 @@ module XCPretty @parser.parse(SAMPLE_ANALYZE_SHALLOW) end + it "parses analyze for a C++ target" do + @formatter.should receive(:format_analyze).with("CCChip8DisplayView.cpp", "CocoaChip/CCChip8DisplayView.cpp") + @parser.parse(SAMPLE_ANALYZE_CPP) + end + it "parses build target" do @formatter.should receive(:format_build_target).with("The Spacer", "Pods", "Debug") @parser.parse(SAMPLE_BUILD) end + it "parses aggregate target" do + @formatter.should receive(:format_aggregate_target).with("Be Aggro", "AggregateExample", "Debug") + @parser.parse(SAMPLE_AGGREGATE_TARGET) + end + it "parses analyze target" do @formatter.should receive(:format_analyze_target).with("The Spacer", "Pods", "Debug") @parser.parse(SAMPLE_ANALYZE_TARGET) @@ -148,11 +158,26 @@ module XCPretty @parser.parse(SAMPLE_LD) end + it "parses Ld with relative path" do + @formatter.should receive(:format_linking).with('ObjectiveSugar', 'normal', 'i386') + @parser.parse(SAMPLE_LD_RELATIVE) + end + it "parses Libtool" do @formatter.should receive(:format_libtool).with('libPods-ObjectiveSugarTests-Kiwi.a') @parser.parse(SAMPLE_LIBTOOL) end + it "parses uitest failing tests" do + @formatter.should receive(:format_failing_test).with( + "viewUITests.vmtAboutWindow", + "testConnectToDesktop", + "UI Testing Failure - Unable to find hit point for element Button 0x608001165880: {{74.0, -54.0}, {44.0, 38.0}}, label: 'Disconnect'", + ":0" + ) + @parser.parse(SAMPLE_UITEST_CASE_WITH_FAILURE) + end + it "parses specta failing tests" do @formatter.should receive(:format_failing_test).with("SKWelcomeViewControllerSpecSpec", "SKWelcomeViewController_When_a_user_opens_the_app_from_a_clean_installation_displays_the_welcome_screen", @@ -176,7 +201,7 @@ module XCPretty it "parses passing ocunit tests" do @formatter.should receive(:format_passing_test).with('RACCommandSpec', - 'enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES', + 'enabled_signal_should_send_YES_while_executing_is_YES', '0.001') @parser.parse(SAMPLE_OCUNIT_TEST) end @@ -336,6 +361,13 @@ module XCPretty @parser.parse(SAMPLE_SPECTA_SUITE_BEGINNING) end + it "parses unknown strings" do + @formatter.should receive(:format_other).with( + SAMPLE_FORMAT_OTHER_UNRECOGNIZED_STRING + ) + @parser.parse(SAMPLE_FORMAT_OTHER_UNRECOGNIZED_STRING) + end + context "errors" do it "parses clang errors" do @formatter.should receive(:format_error).with(SAMPLE_CLANG_ERROR) @@ -427,6 +459,41 @@ module XCPretty @parser.parse(SAMPLE_CODESIGN_ERROR_NO_SPACES) end + it "parses No profile matching error:" do + @formatter.should receive(:format_error).with( + "No profile matching 'TargetName' found: Xcode couldn't find a profile matching 'TargetName'. Install the profile (by dragging and dropping it onto Xcode's dock item) or select a different one in the General tab of the target editor." + ) + @parser.parse(SAMPLE_NO_PROFILE_MATCHING_ERROR) + end + + it "parses requires provision error:" do + @formatter.should receive(:format_error).with( + 'PROJECT_NAME requires a provisioning profile. Select a provisioning profile for the "Debug" build configuration in the project editor.' + ) + @parser.parse(SAMPLE_REQUIRES_PROVISION) + end + + it "parses no certificate error:" do + @formatter.should receive(:format_error).with( + "No certificate matching 'iPhone Distribution: Name (B89SBB0AV9)' for team 'B89SBB0AV9': Select a different signing certificate for CODE_SIGN_IDENTITY, a team that matches your selected certificate, or switch to automatic provisioning." + ) + @parser.parse(SAMPLE_NO_CERTIFICATE) + end + + it "parses swift unavailable error:" do + @formatter.should receive(:format_error).with( + SAMPLE_SWIFT_UNAVAILABLE + ) + @parser.parse(SAMPLE_SWIFT_UNAVAILABLE) + end + + it "parses use legacy swift error:" do + @formatter.should receive(:format_error).with( + SAMPLE_USE_LEGACY_SWIFT + ) + @parser.parse(SAMPLE_USE_LEGACY_SWIFT) + end + it "parses ld library errors" do @formatter.should receive(:format_error).with( SAMPLE_LD_LIBRARY_ERROR @@ -448,6 +515,28 @@ module XCPretty @formatter.should_not receive(:format_compile_error) @parser.parse("hohohoooo") end + + it "parses provisioning profile doesn't support capability error" do + @formatter.should receive(:format_error) + @parser.parse(SAMPLE_PROFILE_DOESNT_SUPPORT_CAPABILITY_ERROR) + end + + it "parses provisioning profile doesn't include entitlement error" do + @formatter.should receive(:format_error) + @parser.parse(SAMPLE_PROFILE_DOESNT_INCLUDE_ENTITLEMENT_ERROR) + end + + it "parses code signing is required error" do + @formatter.should receive(:format_error) + @parser.parse(SAMPLE_CODE_SIGNING_IS_REQUIRED_ERROR) + end + + it "parses module includes error" do + @formatter.should receive(:format_error).with( + "error: umbrella header for module 'ModuleName' does not include header 'Header.h'" + ) + @parser.parse(SAMPLE_MODULE_INCLUDES_ERROR) + end end context "warnings" do @@ -472,6 +561,11 @@ module XCPretty @formatter.should receive(:format_ld_warning).with("ld: embedded dylibs/frameworks only run on iOS 8 or later") @parser.parse("ld: warning: embedded dylibs/frameworks only run on iOS 8 or later") end + + it "parses will not be code signed warnings" do + @formatter.should receive(:format_will_not_be_code_signed).with(SAMPLE_WILL_NOT_BE_CODE_SIGNED) + @parser.parse(SAMPLE_WILL_NOT_BE_CODE_SIGNED) + end end context "summary" do diff --git a/spec/xcpretty/reporters/junit_spec.rb b/spec/xcpretty/reporters/junit_spec.rb index cce3e905..1fe9f64c 100644 --- a/spec/xcpretty/reporters/junit_spec.rb +++ b/spec/xcpretty/reporters/junit_spec.rb @@ -17,3 +17,4 @@ module XCPretty end end end + diff --git a/spec/xcpretty/reporters/reporter_spec.rb b/spec/xcpretty/reporters/reporter_spec.rb new file mode 100644 index 00000000..0cd86287 --- /dev/null +++ b/spec/xcpretty/reporters/reporter_spec.rb @@ -0,0 +1,40 @@ +# encoding: utf-8 + +require 'xcpretty' +require 'fixtures/constants' + +module XCPretty + + describe Reporter do + + before(:each) do + @reporter = Reporter.new(path: "example_file") + end + + it "reports a passing test" do + @reporter.format_passing_test("RACCommandSpec", "_tupleByAddingObject__should_add_a_non_nil_object", "0.001") + @reporter.tests.should include("_tupleByAddingObject__should_add_a_non_nil_object PASSED") + end + + it "reports a failing test" do + @reporter.format_failing_test("RACCommandSpec", "enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES", "expected: 1, got: 0", 'path/to/file') + @reporter.tests.should include("enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES in path/to/file FAILED: expected: 1, got: 0") + end + + it "reports a pending test" do + @reporter.format_pending_test("RACCommandSpec", "_tupleByAddingObject__should_add_a_non_nil_object") + @reporter.tests.should include("_tupleByAddingObject__should_add_a_non_nil_object IS PENDING") + end + + it "writes to disk" do + @reporter.format_passing_test("RACCommandSpec", "_tupleByAddingObject__should_add_a_non_nil_object", "0.001") + file = double("file stub") + File.should_receive(:open).with("example_file", "w").and_yield(file) + file.should_receive(:write).with("_tupleByAddingObject__should_add_a_non_nil_object PASSED\nFINISHED RUNNING 1 TESTS WITH 0 FAILURES") + @reporter.write_report + + end + + end +end + diff --git a/xcpretty.gemspec b/xcpretty.gemspec index 5f30feb9..4179108a 100644 --- a/xcpretty.gemspec +++ b/xcpretty.gemspec @@ -26,12 +26,12 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.add_dependency 'rouge', '~> 1.8' + spec.add_dependency 'rouge', '~> 2.0.7' spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "rubocop", "~> 0.34.0" - spec.add_development_dependency "rspec", "~> 2.0" + spec.add_development_dependency "rspec", "2.99.0" spec.add_development_dependency "cucumber", "~> 1.0" end

<%= test[:name] %>

<% if test[:reason] %> @@ -158,6 +150,15 @@
<%= test[:snippet] %>
<%= test[:file] %>
<% end %> + <% if !test[:screenshots].empty? %> +
+ <% test[:screenshots].each_with_index do |screenshot, idx| %> + + + + <% end %> +
+ <% end %>