xcpretty 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -1
- data/CHANGELOG.md +24 -0
- data/README.md +3 -1
- data/Rakefile +6 -1
- data/bin/xcpretty +51 -16
- data/features/custom_formatter.feature +15 -0
- data/features/junit_report.feature +9 -1
- data/features/simple_format.feature +68 -4
- data/features/steps/formatting_steps.rb +87 -4
- data/features/steps/junit_steps.rb +18 -6
- data/features/steps/xcpretty_steps.rb +7 -0
- data/features/support/env.rb +18 -15
- data/features/test_format.feature +1 -0
- data/features/xcpretty.feature +12 -0
- data/lib/xcpretty.rb +25 -3
- data/lib/xcpretty/ansi.rb +1 -0
- data/lib/xcpretty/formatters/formatter.rb +90 -0
- data/lib/xcpretty/formatters/rspec.rb +22 -0
- data/lib/xcpretty/formatters/simple.rb +137 -0
- data/lib/xcpretty/parser.rb +283 -0
- data/lib/xcpretty/printer.rb +7 -112
- data/lib/xcpretty/reporters/junit.rb +53 -45
- data/lib/xcpretty/syntax.rb +22 -0
- data/lib/xcpretty/version.rb +1 -1
- data/spec/fixtures/constants.rb +63 -15
- data/spec/fixtures/custom_formatter.rb +17 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/matchers/colors.rb +1 -1
- data/spec/xcpretty/formatters/formatter_spec.rb +56 -0
- data/spec/xcpretty/formatters/rspec_spec.rb +46 -0
- data/spec/xcpretty/formatters/simple_spec.rb +132 -0
- data/spec/xcpretty/parser_spec.rb +258 -0
- data/spec/xcpretty/printer_spec.rb +39 -74
- data/spec/xcpretty/syntax_spec.rb +35 -0
- data/xcpretty.gemspec +1 -1
- metadata +40 -25
- data/lib/xcpretty/printers/rspec.rb +0 -23
- data/lib/xcpretty/printers/simple.rb +0 -153
- data/spec/xcpretty/printers/printer_spec.rb +0 -117
- data/spec/xcpretty/printers/rspec_spec.rb +0 -52
- data/spec/xcpretty/printers/simple_spec.rb +0 -125
@@ -1,21 +1,33 @@
|
|
1
|
+
Given(/^I have tests in my suite from 2 classes$/) do
|
2
|
+
add_run_input SAMPLE_OCUNIT_TEST
|
3
|
+
add_run_input SAMPLE_KIWI_TEST
|
4
|
+
end
|
5
|
+
|
1
6
|
Then(/^I should see a failed test node in my report$/) do
|
2
|
-
|
7
|
+
junit_report_root.elements.to_a.detect do |node|
|
3
8
|
element = node.elements.to_a.first
|
4
9
|
element && element.name == "failure"
|
5
10
|
end.should_not be_nil
|
6
11
|
end
|
7
12
|
|
8
13
|
Then(/^I should see a passing test node in my report$/) do
|
9
|
-
|
14
|
+
junit_report_root.elements.to_a.detect do |node|
|
10
15
|
node.attributes["time"] != nil
|
11
16
|
end.should_not be_nil
|
12
17
|
end
|
13
18
|
|
14
19
|
Then(/^I should see a test suite node$/) do
|
15
|
-
|
20
|
+
junit_report_root.elements.to_a.first.should_not be_nil
|
16
21
|
end
|
17
22
|
|
18
23
|
Then(/^I should see (\d+) tests in my report$/) do |test_count|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
24
|
+
junit_report_root.attributes["tests"].should == test_count
|
25
|
+
junit_report_root.elements.to_a.count.should == test_count.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
Then(/^I should see (\d+) test suites$/) do |count|
|
29
|
+
suites = junit_report.root.elements.to_a
|
30
|
+
suites.size.should == count.to_i
|
31
|
+
suites.select {|s| s.name == 'testsuite' }.size.should == count.to_i
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Given(/^the build has failed$/) do
|
2
|
+
add_run_input "/Users/musalj/code/OSS/ObjectiveSugar/Example/ObjectiveSugarTests/NSArrayCategoriesTests.m:53:13: error: use of undeclared identifier 'something'"
|
3
|
+
end
|
4
|
+
|
5
|
+
Then(/^the exit status code should be (\d)$/) do |numbah|
|
6
|
+
$?.exitstatus.should == numbah.to_i
|
7
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
$:.unshift File.expand_path('../../..', __FILE__)
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
3
|
+
require 'tempfile'
|
4
|
+
require 'spec/fixtures/constants'
|
5
|
+
require 'spec/support/matchers/colors'
|
6
|
+
require 'lib/xcpretty/ansi'
|
7
|
+
require 'lib/xcpretty/syntax'
|
8
|
+
require 'rexml/document'
|
9
|
+
require 'lib/xcpretty/formatters/formatter'
|
10
|
+
require 'lib/xcpretty/reporters/junit'
|
10
11
|
|
11
12
|
include XCPretty::ANSI
|
12
13
|
|
@@ -17,10 +18,8 @@ TEST_PATH_MATCHER = %r{[\w/\-\s]+:\d+}
|
|
17
18
|
PASSING_TEST_NAME_MATCHER = %r{\w+\s\(\d+\.\d+\sseconds\)}
|
18
19
|
FAILING_TEST_NAME_MATCHER = %r{\w+, expected:}
|
19
20
|
|
20
|
-
def run_xcpretty
|
21
|
-
|
22
|
-
add_run_input SAMPLE_EXECUTED_TESTS
|
23
|
-
input_file = Tempfile.new("xcpretty_input")
|
21
|
+
def run_xcpretty(flags)
|
22
|
+
input_file = Tempfile.new('xcpretty_input')
|
24
23
|
File.open(input_file.path, 'w') do |file|
|
25
24
|
file.print run_input
|
26
25
|
end
|
@@ -28,24 +27,28 @@ def run_xcpretty flags
|
|
28
27
|
input_file.unlink
|
29
28
|
end
|
30
29
|
|
31
|
-
def add_run_input
|
30
|
+
def add_run_input(text)
|
32
31
|
run_input << "\n#{text}"
|
33
32
|
end
|
34
33
|
|
35
34
|
def run_input
|
36
|
-
@input ||=
|
35
|
+
@input ||= ''
|
37
36
|
end
|
38
37
|
|
39
38
|
def run_output
|
40
|
-
@output ||=
|
39
|
+
@output ||= ''
|
41
40
|
end
|
42
41
|
|
43
42
|
def junit_report
|
44
43
|
REXML::Document.new(File.open(XCPretty::JUnit::FILEPATH, 'r').read)
|
45
44
|
end
|
46
45
|
|
46
|
+
def junit_report_root
|
47
|
+
junit_report.root.elements.to_a.first
|
48
|
+
end
|
49
|
+
|
47
50
|
Before do
|
48
|
-
|
51
|
+
self.colorize = true
|
49
52
|
end
|
50
53
|
|
51
54
|
After do
|
@@ -12,6 +12,7 @@ Feature: Showing RSpec-style test output
|
|
12
12
|
|
13
13
|
Scenario: Showing some tests failed with color
|
14
14
|
Given I have a failing test in my suite
|
15
|
+
And the test suite has finished
|
15
16
|
When I pipe to xcpretty with "--test --color"
|
16
17
|
Then I should see a red failed test icon
|
17
18
|
And I should see the path of a failed test
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Status codes
|
2
|
+
|
3
|
+
Scenario: Xcode tests have failed
|
4
|
+
Given I have a failing test in my suite
|
5
|
+
When I pipe to xcpretty
|
6
|
+
Then the exit status code should be 1
|
7
|
+
|
8
|
+
Scenario: Xcode build has failed
|
9
|
+
Given the build has failed
|
10
|
+
When I pipe to xcpretty
|
11
|
+
Then the exit status code should be 1
|
12
|
+
|
data/lib/xcpretty.rb
CHANGED
@@ -1,9 +1,31 @@
|
|
1
1
|
require "xcpretty/version"
|
2
2
|
require "xcpretty/printer"
|
3
|
-
require "xcpretty/
|
4
|
-
require "xcpretty/
|
3
|
+
require "xcpretty/syntax"
|
4
|
+
require "xcpretty/formatters/formatter"
|
5
|
+
require "xcpretty/formatters/simple"
|
6
|
+
require "xcpretty/formatters/rspec"
|
5
7
|
require "xcpretty/reporters/junit"
|
6
8
|
|
7
9
|
module XCPretty
|
8
|
-
|
10
|
+
class ExitStatus
|
11
|
+
|
12
|
+
include XCPretty::Matchers
|
13
|
+
|
14
|
+
POSSIBLE_FAILURES = [
|
15
|
+
FAILING_TEST_MATCHER,
|
16
|
+
/\serror:\s/
|
17
|
+
]
|
18
|
+
|
19
|
+
def self.code
|
20
|
+
$exit_status || 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.handle(text)
|
24
|
+
POSSIBLE_FAILURES.detect do |failure|
|
25
|
+
$exit_status = 1 if text =~ failure
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
9
30
|
end
|
31
|
+
|
data/lib/xcpretty/ansi.rb
CHANGED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'xcpretty/ansi'
|
2
|
+
require 'xcpretty/parser'
|
3
|
+
|
4
|
+
module XCPretty
|
5
|
+
|
6
|
+
# Making a new formatter is easy.
|
7
|
+
# Just make a subclass of Formatter, and override any of these methods.
|
8
|
+
module FormatMethods
|
9
|
+
EMPTY_STRING = ''
|
10
|
+
def format_analyze(file_name, file_path); EMPTY_STRING; end
|
11
|
+
def format_build_target(target, project, configuration); EMPTY_STRING; end
|
12
|
+
def format_check_dependencies; EMPTY_STRING; end
|
13
|
+
def format_clean(project, target, configuration); EMPTY_STRING; end
|
14
|
+
def format_clean_target(target, project, configuration); EMPTY_STRING; end
|
15
|
+
def format_clean_remove; EMPTY_STRING; end
|
16
|
+
def format_compile(file_name, file_path); EMPTY_STRING; end
|
17
|
+
def format_compile_error(file_name, file_path, reason, line, cursor); EMPTY_STRING; end
|
18
|
+
def format_compile_xib(file_name, file_path); EMPTY_STRING; end
|
19
|
+
def format_copy_strings_file(file_name); EMPTY_STRING; end
|
20
|
+
def format_cpresource(file); EMPTY_STRING; end
|
21
|
+
def format_error(message); EMPTY_STRING; end
|
22
|
+
def format_generate_dsym(dsym); EMPTY_STRING; end
|
23
|
+
def format_linking(file, build_variant, arch); EMPTY_STRING; end
|
24
|
+
def format_libtool(library); EMPTY_STRING; end
|
25
|
+
def format_passing_test(suite, test, time); EMPTY_STRING; end
|
26
|
+
def format_failing_test(suite, test, time, file_path); EMPTY_STRING; end
|
27
|
+
def format_process_pch(file); EMPTY_STRING; end
|
28
|
+
def format_phase_script_execution(script_name); EMPTY_STRING; end
|
29
|
+
def format_process_info_plist(file_name, file_path); EMPTY_STRING; end
|
30
|
+
def format_codesign(file); EMPTY_STRING; end
|
31
|
+
def format_preprocess(file); EMPTY_STRING; end
|
32
|
+
def format_pbxcp(file); EMPTY_STRING; end
|
33
|
+
def format_test_run_started(name); EMPTY_STRING; end
|
34
|
+
def format_test_run_finished(name, time); EMPTY_STRING; end
|
35
|
+
def format_test_suite_started(name); EMPTY_STRING; end
|
36
|
+
def format_test_summary(message, failures_per_suite); EMPTY_STRING; end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Formatter
|
40
|
+
|
41
|
+
include ANSI
|
42
|
+
include FormatMethods
|
43
|
+
|
44
|
+
attr_reader :parser
|
45
|
+
|
46
|
+
def initialize(use_unicode, colorize)
|
47
|
+
@use_unicode = use_unicode
|
48
|
+
@colorize = colorize
|
49
|
+
@parser = Parser.new(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Override if you want to catch something specific with your regex
|
53
|
+
def pretty_format(text)
|
54
|
+
parser.parse(text)
|
55
|
+
end
|
56
|
+
|
57
|
+
# If you want to print inline, override #optional_newline with ''
|
58
|
+
def optional_newline
|
59
|
+
"\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
def use_unicode?
|
63
|
+
!!@use_unicode
|
64
|
+
end
|
65
|
+
|
66
|
+
# Will be printed by default. Override with '' if you don't want summary
|
67
|
+
def format_test_summary(executed_message, failures_per_suite)
|
68
|
+
failures = format_failures(failures_per_suite)
|
69
|
+
final_message = failures.empty? ? green(executed_message) : red(executed_message)
|
70
|
+
|
71
|
+
text = [failures, final_message].join("\n\n\n").strip
|
72
|
+
"\n\n#{text}"
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def format_failures(failures_per_suite)
|
79
|
+
failures_per_suite.map do |suite, failures|
|
80
|
+
formatted_failures = failures.map do |f|
|
81
|
+
" #{f[:test_case]}, #{red(f[:reason])}\n #{cyan(f[:file])}"
|
82
|
+
end.join("\n\n")
|
83
|
+
|
84
|
+
"\n#{suite}\n#{formatted_failures}"
|
85
|
+
end.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module XCPretty
|
2
|
+
|
3
|
+
class RSpec < Formatter
|
4
|
+
|
5
|
+
FAIL = "F"
|
6
|
+
PASS = "."
|
7
|
+
|
8
|
+
def optional_newline
|
9
|
+
''
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_passing_test(suite, test_case, time)
|
13
|
+
green(PASS)
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_failing_test(test_suite, test_case, reason, file)
|
17
|
+
red(FAIL)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module XCPretty
|
5
|
+
|
6
|
+
class Simple < Formatter
|
7
|
+
|
8
|
+
PASS = "✓"
|
9
|
+
FAIL = "✗"
|
10
|
+
ERROR = "⌦ "
|
11
|
+
|
12
|
+
ASCII_PASS = "."
|
13
|
+
ASCII_FAIL = "x"
|
14
|
+
ASCII_ERROR = "[!]"
|
15
|
+
COMPLETION = "▸"
|
16
|
+
ASCII_COMPLETION = ">"
|
17
|
+
|
18
|
+
def format_analyze(file_name, file_path)
|
19
|
+
format("Analyzing", file_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_build_target(target, project, configuration)
|
23
|
+
format("Building", "#{project}/#{target} [#{configuration}]")
|
24
|
+
end
|
25
|
+
|
26
|
+
def format_clean_target(target, project, configuration)
|
27
|
+
format("Cleaning", "#{project}/#{target} [#{configuration}]")
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_compile(file_name, file_path)
|
31
|
+
format("Compiling", file_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_compile_xib(file_name, file_path)
|
35
|
+
format("Compiling", file_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_copy_strings_file(file)
|
39
|
+
format("Copying", file)
|
40
|
+
end
|
41
|
+
|
42
|
+
def format_cpresource(resource)
|
43
|
+
format("Copying", resource)
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_error(message)
|
47
|
+
status_symbol(:error) + " " + red(message)
|
48
|
+
end
|
49
|
+
|
50
|
+
def format_compile_error(file, file_path, reason, line, cursor)
|
51
|
+
"\n#{file_path}: #{red(reason)}\n\n#{line}\n#{cyan(cursor)}\n\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
def format_generate_dsym(dsym)
|
55
|
+
format("Generating '#{dsym}'")
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_libtool(library)
|
59
|
+
format("Building library", library)
|
60
|
+
end
|
61
|
+
|
62
|
+
def format_linking(target, build_variants, arch)
|
63
|
+
format("Linking", target)
|
64
|
+
end
|
65
|
+
|
66
|
+
def format_failing_test(suite, test_case, reason, file)
|
67
|
+
format_test("#{test_case}, #{reason}", false)
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_passing_test(suite, test_case, time)
|
71
|
+
format_test("#{test_case} (#{time} seconds)")
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_phase_script_execution(script_name)
|
75
|
+
format("Running script", "'#{script_name}'")
|
76
|
+
end
|
77
|
+
|
78
|
+
def format_process_info_plist(file_name, file_path)
|
79
|
+
format("Processing", file_name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_process_pch(file)
|
83
|
+
format("Precompiling", file)
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_codesign(file)
|
87
|
+
format("Signing", file)
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_preprocess(file)
|
91
|
+
format("Preprocessing", file)
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_pbxcp(file)
|
95
|
+
format("Copying", file)
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_test_run_started(name)
|
99
|
+
heading("Test Suite", name, "started")
|
100
|
+
end
|
101
|
+
|
102
|
+
def format_test_suite_started(name)
|
103
|
+
heading("", name, "")
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def heading(prefix, text, description)
|
110
|
+
[prefix, white(text), description].join(" ").strip
|
111
|
+
end
|
112
|
+
|
113
|
+
def format(command, argument_text="", success=true)
|
114
|
+
[status_symbol(success ? :completion : :fail), white(command), argument_text].join(" ").strip
|
115
|
+
end
|
116
|
+
|
117
|
+
def format_test(test_case, success=true)
|
118
|
+
[status_symbol(success ? :pass : :fail), test_case].join(" ").strip
|
119
|
+
end
|
120
|
+
|
121
|
+
def status_symbol(status)
|
122
|
+
case status
|
123
|
+
when :pass
|
124
|
+
green(use_unicode? ? PASS : ASCII_PASS)
|
125
|
+
when :fail
|
126
|
+
red(use_unicode? ? FAIL : ASCII_FAIL)
|
127
|
+
when :error
|
128
|
+
red(use_unicode? ? ERROR : ASCII_ERROR)
|
129
|
+
when :completion
|
130
|
+
yellow(use_unicode? ? COMPLETION : ASCII_COMPLETION)
|
131
|
+
else
|
132
|
+
""
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
module XCPretty
|
2
|
+
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# @regex Captured groups
|
6
|
+
# $1 file_path
|
7
|
+
# $2 file_name
|
8
|
+
ANALYZE_MATCHER = /^Analyze(?:Shallow)?\s(.*\/(.*\.m))*/
|
9
|
+
|
10
|
+
# @regex Captured groups
|
11
|
+
# $1 target
|
12
|
+
# $2 project
|
13
|
+
# $3 configuration
|
14
|
+
BUILD_TARGET_MATCHER = /^=== BUILD TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/
|
15
|
+
|
16
|
+
# @regex Nothing returned here for now
|
17
|
+
CHECK_DEPENDENCIES_MATCHER = /^Check dependencies/
|
18
|
+
|
19
|
+
# @regex Nothing returned here for now
|
20
|
+
CLEAN_REMOVE_MATCHER = /^Clean.Remove/
|
21
|
+
|
22
|
+
# @regex Captured groups
|
23
|
+
# $1 target
|
24
|
+
# $2 project
|
25
|
+
# $3 configuration
|
26
|
+
CLEAN_TARGET_MATCHER = /^=== CLEAN TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH CONFIGURATION\s(.*)\s===/
|
27
|
+
|
28
|
+
# @regex Captured groups
|
29
|
+
# $1 = file
|
30
|
+
CODESIGN_MATCHER = /^CodeSign\s((?:\\ |[^ ])*)$/
|
31
|
+
|
32
|
+
# @regex Captured groups
|
33
|
+
# $1 = file
|
34
|
+
CODESIGN_FRAMEWORK_MATCHER = /^CodeSign\s((?:\\ |[^ ])*.framework)\/Versions/
|
35
|
+
|
36
|
+
# @regex Captured groups
|
37
|
+
# $1 file_path
|
38
|
+
# $2 file_name (e.g. KWNull.m)
|
39
|
+
COMPILE_MATCHER = /^CompileC\s.*\s(.*\/(.*\.m))\s.*/
|
40
|
+
|
41
|
+
# @regex Captured groups
|
42
|
+
# $1 = file_path
|
43
|
+
# $2 = file_name
|
44
|
+
# $3 = reason
|
45
|
+
COMPILE_ERROR_MATCHER = /^(.+\/(.*\.[h,m,c]).*):\serror:\s(.*)$/
|
46
|
+
|
47
|
+
# @regex Captured groups
|
48
|
+
# $1 file_path
|
49
|
+
# $2 file_name (e.g. MainMenu.xib)
|
50
|
+
COMPILE_XIB_MATCHER = /^CompileXIB\s(.*\/(.*\.xib))/
|
51
|
+
|
52
|
+
# @regex Captured groups
|
53
|
+
# $1 file
|
54
|
+
COPY_STRINGS_MATCHER = /^CopyStringsFile.*\/(.*.strings)/
|
55
|
+
|
56
|
+
# @regex Captured groups
|
57
|
+
# $1 resource
|
58
|
+
CPRESOURCE_MATCHER = /^CpResource\s(.*)\s\//
|
59
|
+
|
60
|
+
# @regex Captured groups
|
61
|
+
# $1 cursor (with whitespaces and tildes)
|
62
|
+
CURSOR_MATCHER = /^([\s~]*\^[\s~]*)$/
|
63
|
+
|
64
|
+
# @regex Captured groups
|
65
|
+
#
|
66
|
+
EXECUTED_MATCHER = /^Executed/
|
67
|
+
|
68
|
+
# @regex Captured groups
|
69
|
+
# $1 = file
|
70
|
+
# $2 = test_suite
|
71
|
+
# $3 = test_case
|
72
|
+
# $4 = reason
|
73
|
+
FAILING_TEST_MATCHER = /^(.+:\d+):\serror:\s[\+\-]\[(.*)\s(.*)\]\s:(?:\s'.*'\s\[FAILED\],)?\s(.*)/
|
74
|
+
|
75
|
+
# @regex Captured groups
|
76
|
+
# $1 = dsym
|
77
|
+
GENERATE_DSYM_MATCHER = /^GenerateDSYMFile \/.*\/(.*\.dSYM)/
|
78
|
+
|
79
|
+
# @regex Captured groups
|
80
|
+
# $1 = library
|
81
|
+
LIBTOOL_MATCHER = /^Libtool.*\/(.*\.a)/
|
82
|
+
|
83
|
+
# @regex Captured groups
|
84
|
+
# $1 = target
|
85
|
+
# $2 = build_variants (normal, profile, debug)
|
86
|
+
# $3 = architecture
|
87
|
+
LINKING_MATCHER = /^Ld \/.*\/(.*) (.*) (.*)$/
|
88
|
+
|
89
|
+
# @regex Captured groups
|
90
|
+
# $1 = suite
|
91
|
+
# $2 = test_case
|
92
|
+
# $3 = time
|
93
|
+
PASSING_TEST_MATCHER = /^Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/
|
94
|
+
|
95
|
+
# @regex Captured groups
|
96
|
+
# $1 = script_name
|
97
|
+
PHASE_SCRIPT_EXECUTION_MATCHER = /^PhaseScriptExecution\s(.*)\s\//
|
98
|
+
|
99
|
+
# @regex Captured groups
|
100
|
+
PODS_ERROR_MATCHER = /^error:\s(.*)/
|
101
|
+
|
102
|
+
# @regex Captured groups
|
103
|
+
# $1 = file
|
104
|
+
PROCESS_PCH_MATCHER = /^ProcessPCH\s.*\s(.*.pch)/
|
105
|
+
|
106
|
+
# @regex Captured groups
|
107
|
+
# $1 = file
|
108
|
+
PREPROCESS_MATCHER = /^Preprocess\s(?:(?:\\ |[^ ])*)\s((?:\\ |[^ ])*)$/
|
109
|
+
|
110
|
+
# @regex Captured groups
|
111
|
+
# $1 = file
|
112
|
+
PBXCP_MATCHER = /^PBXCp\s((?:\\ |[^ ])*)/
|
113
|
+
|
114
|
+
# @regex Captured groups
|
115
|
+
# $1 = file
|
116
|
+
PROCESS_INFO_PLIST_MATCHER = /^ProcessInfoPlistFile\s.*\.plist\s(.*\/+(.*\.plist))/
|
117
|
+
# @regex Captured groups
|
118
|
+
# $1 = suite
|
119
|
+
# $2 = time
|
120
|
+
TESTS_RUN_COMPLETION_MATCHER = /Test Suite '(?:.*\/)?(.*[ox]ctest.*)' finished at (.*)/
|
121
|
+
|
122
|
+
# @regex Captured groups
|
123
|
+
# $1 = suite
|
124
|
+
# $2 = time
|
125
|
+
TESTS_RUN_START_MATCHER = /Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/
|
126
|
+
|
127
|
+
# @regex Captured groups
|
128
|
+
# $1 test suite name
|
129
|
+
TEST_SUITE_START_MATCHER = /Test Suite '(.*)' started at/
|
130
|
+
end
|
131
|
+
|
132
|
+
class Parser
|
133
|
+
|
134
|
+
include Matchers
|
135
|
+
attr_reader :formatter
|
136
|
+
|
137
|
+
def initialize(formatter)
|
138
|
+
@formatter = formatter
|
139
|
+
end
|
140
|
+
|
141
|
+
def parse(text)
|
142
|
+
update_test_state(text)
|
143
|
+
update_error_state(text)
|
144
|
+
|
145
|
+
return format_error if should_format_error?
|
146
|
+
|
147
|
+
case text
|
148
|
+
when ANALYZE_MATCHER
|
149
|
+
formatter.format_analyze($2, $1)
|
150
|
+
when BUILD_TARGET_MATCHER
|
151
|
+
formatter.format_build_target($1, $2, $3)
|
152
|
+
when CLEAN_REMOVE_MATCHER
|
153
|
+
formatter.format_clean_remove
|
154
|
+
when CLEAN_TARGET_MATCHER
|
155
|
+
formatter.format_clean_target($1, $2, $3)
|
156
|
+
when COPY_STRINGS_MATCHER
|
157
|
+
formatter.format_copy_strings_file($1)
|
158
|
+
when CHECK_DEPENDENCIES_MATCHER
|
159
|
+
formatter.format_check_dependencies
|
160
|
+
when COMPILE_MATCHER
|
161
|
+
formatter.format_compile($2, $1)
|
162
|
+
when COMPILE_XIB_MATCHER
|
163
|
+
formatter.format_compile_xib($2, $1)
|
164
|
+
when CPRESOURCE_MATCHER
|
165
|
+
formatter.format_cpresource($1)
|
166
|
+
when EXECUTED_MATCHER
|
167
|
+
format_summary_if_needed(text)
|
168
|
+
when FAILING_TEST_MATCHER
|
169
|
+
formatter.format_failing_test($2, $3, $4, $1)
|
170
|
+
when GENERATE_DSYM_MATCHER
|
171
|
+
formatter.format_generate_dsym($1)
|
172
|
+
when LIBTOOL_MATCHER
|
173
|
+
formatter.format_libtool($1)
|
174
|
+
when LINKING_MATCHER
|
175
|
+
formatter.format_linking($1, $2, $3)
|
176
|
+
when PASSING_TEST_MATCHER
|
177
|
+
formatter.format_passing_test($1, $2, $3)
|
178
|
+
when PODS_ERROR_MATCHER
|
179
|
+
formatter.format_error($1)
|
180
|
+
when PROCESS_INFO_PLIST_MATCHER
|
181
|
+
formatter.format_process_info_plist(*unescaped($2, $1))
|
182
|
+
when PHASE_SCRIPT_EXECUTION_MATCHER
|
183
|
+
formatter.format_phase_script_execution(*unescaped($1))
|
184
|
+
when PROCESS_PCH_MATCHER
|
185
|
+
formatter.format_process_pch($1)
|
186
|
+
when CODESIGN_FRAMEWORK_MATCHER
|
187
|
+
formatter.format_codesign($1)
|
188
|
+
when CODESIGN_MATCHER
|
189
|
+
formatter.format_codesign($1)
|
190
|
+
when PREPROCESS_MATCHER
|
191
|
+
formatter.format_preprocess($1)
|
192
|
+
when PBXCP_MATCHER
|
193
|
+
formatter.format_pbxcp($1)
|
194
|
+
when TESTS_RUN_COMPLETION_MATCHER
|
195
|
+
formatter.format_test_run_finished($1, $2)
|
196
|
+
when TESTS_RUN_START_MATCHER
|
197
|
+
formatter.format_test_run_started($1)
|
198
|
+
when TEST_SUITE_START_MATCHER
|
199
|
+
formatter.format_test_suite_started($1)
|
200
|
+
else
|
201
|
+
""
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
def update_test_state(text)
|
208
|
+
case text
|
209
|
+
when TESTS_RUN_START_MATCHER
|
210
|
+
@tests_done = false
|
211
|
+
@formatted_summary = false
|
212
|
+
@failures = {}
|
213
|
+
when TESTS_RUN_COMPLETION_MATCHER
|
214
|
+
@tests_done = true
|
215
|
+
when FAILING_TEST_MATCHER
|
216
|
+
store_failure($1, $2, $3, $4)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# @ return Hash { :file_name, :file_path, :reason, :line }
|
221
|
+
def update_error_state(text)
|
222
|
+
if text =~ COMPILE_ERROR_MATCHER
|
223
|
+
@formatting_error = true
|
224
|
+
current_error[:reason] = $3
|
225
|
+
current_error[:file_path] = $1
|
226
|
+
current_error[:file_name] = $2
|
227
|
+
elsif text =~ CURSOR_MATCHER
|
228
|
+
@formatting_error = false
|
229
|
+
current_error[:cursor] = $1.chomp
|
230
|
+
else
|
231
|
+
current_error[:line] = text.chomp if @formatting_error
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# TODO: clean up the mess around all this
|
236
|
+
def should_format_error?
|
237
|
+
current_error[:reason] && current_error[:cursor] && current_error[:line]
|
238
|
+
end
|
239
|
+
|
240
|
+
def current_error
|
241
|
+
@current_error ||= {}
|
242
|
+
end
|
243
|
+
|
244
|
+
def format_error
|
245
|
+
error = current_error.dup
|
246
|
+
@current_error = {}
|
247
|
+
formatter.format_compile_error(error[:file_name],
|
248
|
+
error[:file_path],
|
249
|
+
error[:reason],
|
250
|
+
error[:line],
|
251
|
+
error[:cursor])
|
252
|
+
end
|
253
|
+
|
254
|
+
def store_failure(file, test_suite, test_case, reason)
|
255
|
+
failures_per_suite[test_suite] ||= []
|
256
|
+
failures_per_suite[test_suite] << {
|
257
|
+
:file => file,
|
258
|
+
:reason => reason,
|
259
|
+
:test_case => test_case,
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
def failures_per_suite
|
264
|
+
@failures ||= {}
|
265
|
+
end
|
266
|
+
|
267
|
+
def format_summary_if_needed(executed_message)
|
268
|
+
return "" unless should_format_summary?
|
269
|
+
|
270
|
+
@formatted_summary = true
|
271
|
+
formatter.format_test_summary(executed_message, failures_per_suite)
|
272
|
+
end
|
273
|
+
|
274
|
+
def should_format_summary?
|
275
|
+
@tests_done && !@formatted_summary
|
276
|
+
end
|
277
|
+
|
278
|
+
def unescaped(*escaped_values)
|
279
|
+
escaped_values.map { |v| v.gsub('\\', '') }
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
end
|