learn-xcpretty 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.kick +17 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.md +152 -0
- data/CONTRIBUTING.md +60 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +61 -0
- data/README.md +143 -0
- data/Rakefile +24 -0
- data/assets/report.html.erb +155 -0
- data/bin/learn-xcpretty +80 -0
- data/features/custom_formatter.feature +15 -0
- data/features/fixtures/xcodebuild.log +5963 -0
- data/features/html_report.feature +40 -0
- data/features/json_compilation_database_report.feature +21 -0
- data/features/junit_report.feature +44 -0
- data/features/knock_format.feature +11 -0
- data/features/simple_format.feature +172 -0
- data/features/steps/formatting_steps.rb +268 -0
- data/features/steps/html_steps.rb +23 -0
- data/features/steps/json_steps.rb +37 -0
- data/features/steps/junit_steps.rb +38 -0
- data/features/steps/report_steps.rb +21 -0
- data/features/steps/xcpretty_steps.rb +31 -0
- data/features/support/env.rb +108 -0
- data/features/tap_format.feature +31 -0
- data/features/test_format.feature +39 -0
- data/features/xcpretty.feature +14 -0
- data/learn-xcpretty.gemspec +37 -0
- data/lib/xcpretty/ansi.rb +71 -0
- data/lib/xcpretty/formatters/formatter.rb +134 -0
- data/lib/xcpretty/formatters/knock.rb +34 -0
- data/lib/xcpretty/formatters/rspec.rb +27 -0
- data/lib/xcpretty/formatters/simple.rb +155 -0
- data/lib/xcpretty/formatters/tap.rb +39 -0
- data/lib/xcpretty/parser.rb +421 -0
- data/lib/xcpretty/printer.rb +20 -0
- data/lib/xcpretty/reporters/html.rb +73 -0
- data/lib/xcpretty/reporters/json_compilation_database.rb +58 -0
- data/lib/xcpretty/reporters/junit.rb +99 -0
- data/lib/xcpretty/reporters/learn.rb +154 -0
- data/lib/xcpretty/snippet.rb +34 -0
- data/lib/xcpretty/syntax.rb +20 -0
- data/lib/xcpretty/version.rb +3 -0
- data/lib/xcpretty.rb +39 -0
- data/spec/fixtures/NSStringTests.m +64 -0
- data/spec/fixtures/constants.rb +546 -0
- data/spec/fixtures/custom_formatter.rb +17 -0
- data/spec/fixtures/oneliner.m +1 -0
- data/spec/fixtures/raw_kiwi_compilation_fail.txt +24 -0
- data/spec/fixtures/raw_kiwi_fail.txt +1896 -0
- data/spec/fixtures/raw_specta_fail.txt +3110 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/matchers/colors.rb +20 -0
- data/spec/xcpretty/ansi_spec.rb +46 -0
- data/spec/xcpretty/formatters/formatter_spec.rb +113 -0
- data/spec/xcpretty/formatters/rspec_spec.rb +55 -0
- data/spec/xcpretty/formatters/simple_spec.rb +129 -0
- data/spec/xcpretty/parser_spec.rb +421 -0
- data/spec/xcpretty/printer_spec.rb +53 -0
- data/spec/xcpretty/snippet_spec.rb +39 -0
- data/spec/xcpretty/syntax_spec.rb +35 -0
- data/vendor/json_pure/COPYING +57 -0
- data/vendor/json_pure/LICENSE +340 -0
- data/vendor/json_pure/generator.rb +443 -0
- data/vendor/json_pure/parser.rb +364 -0
- metadata +261 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'xcpretty/ansi'
|
3
|
+
require 'xcpretty/parser'
|
4
|
+
|
5
|
+
module XCPretty
|
6
|
+
|
7
|
+
# Making a new formatter is easy.
|
8
|
+
# Just make a subclass of Formatter, and override any of these methods.
|
9
|
+
module FormatMethods
|
10
|
+
EMPTY = ''.freeze
|
11
|
+
|
12
|
+
def format_analyze(file_name, file_path); EMPTY; end
|
13
|
+
def format_build_target(target, project, configuration); EMPTY; end
|
14
|
+
def format_check_dependencies; EMPTY; end
|
15
|
+
def format_clean(project, target, configuration); EMPTY; end
|
16
|
+
def format_clean_target(target, project, configuration); EMPTY; end
|
17
|
+
def format_clean_remove; EMPTY; end
|
18
|
+
def format_compile(file_name, file_path); EMPTY; end
|
19
|
+
def format_compile_command(compiler_command); EMPTY; end
|
20
|
+
def format_compile_xib(file_name, file_path); EMPTY; end
|
21
|
+
def format_copy_strings_file(file_name); EMPTY; end
|
22
|
+
def format_cpresource(file); EMPTY; end
|
23
|
+
def format_generate_dsym(dsym); EMPTY; end
|
24
|
+
def format_linking(file, build_variant, arch); EMPTY; end
|
25
|
+
def format_libtool(library); EMPTY; end
|
26
|
+
def format_passing_test(suite, test, time); EMPTY; end
|
27
|
+
def format_pending_test(suite, test); EMPTY; end
|
28
|
+
def format_failing_test(suite, test, time, file_path); EMPTY; end
|
29
|
+
def format_process_pch(file); EMPTY; end
|
30
|
+
def format_phase_script_execution(script_name); EMPTY; end
|
31
|
+
def format_process_info_plist(file_name, file_path); EMPTY; end
|
32
|
+
def format_codesign(file); EMPTY; end
|
33
|
+
def format_preprocess(file); EMPTY; end
|
34
|
+
def format_pbxcp(file); EMPTY; end
|
35
|
+
def format_test_run_started(name); EMPTY; end
|
36
|
+
def format_test_run_finished(name, time); EMPTY; end
|
37
|
+
def format_test_suite_started(name); EMPTY; end
|
38
|
+
def format_test_summary(message, failures_per_suite); EMPTY; end
|
39
|
+
def format_touch(file_path, file_name); EMPTY; end
|
40
|
+
def format_tiffutil(file); EMPTY; end
|
41
|
+
|
42
|
+
# COMPILER / LINKER ERRORS
|
43
|
+
def format_compile_error(file_name, file_path, reason,
|
44
|
+
line, cursor); EMPTY; end
|
45
|
+
def format_error(message); EMPTY; end
|
46
|
+
def format_undefined_symbols(message, symbol, reference); EMPTY; end
|
47
|
+
def format_duplicate_symbols(message, file_paths); EMPTY; end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Formatter
|
51
|
+
|
52
|
+
include ANSI
|
53
|
+
include FormatMethods
|
54
|
+
|
55
|
+
attr_reader :parser
|
56
|
+
|
57
|
+
def initialize(use_unicode, colorize)
|
58
|
+
@use_unicode = use_unicode
|
59
|
+
@colorize = colorize
|
60
|
+
@parser = Parser.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Override if you want to catch something specific with your regex
|
64
|
+
def pretty_format(text)
|
65
|
+
parser.parse(text)
|
66
|
+
end
|
67
|
+
|
68
|
+
# If you want to print inline, override #optional_newline with ''
|
69
|
+
def optional_newline
|
70
|
+
"\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
def use_unicode?
|
74
|
+
!!@use_unicode
|
75
|
+
end
|
76
|
+
|
77
|
+
# Will be printed by default. Override with '' if you don't want summary
|
78
|
+
def format_test_summary(executed_message, failures_per_suite)
|
79
|
+
failures = format_failures(failures_per_suite)
|
80
|
+
final_message = failures.empty? ? green(executed_message) : red(executed_message)
|
81
|
+
|
82
|
+
text = [failures, final_message].join("\n\n\n").strip
|
83
|
+
"\n\n#{text}"
|
84
|
+
end
|
85
|
+
|
86
|
+
ERROR = "⌦"
|
87
|
+
ASCII_ERROR = "[!]"
|
88
|
+
|
89
|
+
def format_error(message)
|
90
|
+
"\n#{red(error_symbol + " " + message)}\n\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
def format_compile_error(file, file_path, reason, line, cursor)
|
94
|
+
"\n#{red(error_symbol + " ")}#{file_path}: #{red(reason)}\n\n" +
|
95
|
+
"#{line}\n#{cyan(cursor)}\n\n"
|
96
|
+
end
|
97
|
+
|
98
|
+
def format_undefined_symbols(message, symbol, reference)
|
99
|
+
"\n#{red(error_symbol + " " + message)}\n" +
|
100
|
+
"> Symbol: #{symbol}\n" +
|
101
|
+
"> Referenced from: #{reference}\n\n"
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_duplicate_symbols(message, file_paths)
|
105
|
+
"\n#{red(error_symbol + " " + message)}\n" +
|
106
|
+
"> #{file_paths.map { |path| path.split('/').last }.join("\n> ")}\n"
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def format_failures(failures_per_suite)
|
113
|
+
failures_per_suite.map do |suite, failures|
|
114
|
+
formatted_failures = failures.map do |failure|
|
115
|
+
format_failure(failure)
|
116
|
+
end.join("\n\n")
|
117
|
+
|
118
|
+
"\n#{suite}\n#{formatted_failures}"
|
119
|
+
end.join("\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
def format_failure(f)
|
123
|
+
" #{f[:test_case]}, #{red(f[:reason])}\n #{cyan(f[:file_path])}\n" +
|
124
|
+
" ```\n" +
|
125
|
+
Syntax.highlight(Snippet.from_filepath(f[:file_path])) +
|
126
|
+
" ```"
|
127
|
+
end
|
128
|
+
|
129
|
+
def error_symbol
|
130
|
+
use_unicode? ? ERROR : ASCII_ERROR
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module XCPretty
|
2
|
+
|
3
|
+
class Knock < Formatter
|
4
|
+
|
5
|
+
FAIL = 'not ok'
|
6
|
+
PASS = 'ok'
|
7
|
+
|
8
|
+
def format_passing_test(suite, test_case, time)
|
9
|
+
"#{PASS} - #{test_case}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_failing_test(test_suite, test_case, reason, file)
|
13
|
+
"#{FAIL} - #{test_case}: FAILED" +
|
14
|
+
format_failure_diagnostics(test_suite, test_case, reason, file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_test_summary(executed_message, failures_per_suite)
|
18
|
+
''
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_failure_diagnostics(test_suite, test_case, reason, file)
|
22
|
+
format_diagnostics(reason) +
|
23
|
+
format_diagnostics(" #{file}: #{test_suite} - #{test_case}")
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def format_diagnostics(text)
|
29
|
+
"\n# #{text}"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module XCPretty
|
2
|
+
|
3
|
+
class RSpec < Formatter
|
4
|
+
|
5
|
+
FAIL = "F"
|
6
|
+
PASS = "."
|
7
|
+
PENDING = "P"
|
8
|
+
|
9
|
+
def optional_newline
|
10
|
+
''
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_passing_test(suite, test_case, time)
|
14
|
+
green(PASS)
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_failing_test(test_suite, test_case, reason, file)
|
18
|
+
red(FAIL)
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_pending_test(suite, test_case)
|
22
|
+
yellow(PENDING)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module XCPretty
|
5
|
+
|
6
|
+
class Simple < Formatter
|
7
|
+
|
8
|
+
PASS = "✓"
|
9
|
+
FAIL = "✗"
|
10
|
+
PENDING = "⧖"
|
11
|
+
|
12
|
+
ASCII_PASS = "."
|
13
|
+
ASCII_FAIL = "x"
|
14
|
+
COMPLETION = "▸"
|
15
|
+
ASCII_PENDING = "P"
|
16
|
+
ASCII_COMPLETION = ">"
|
17
|
+
|
18
|
+
INDENT = " "
|
19
|
+
|
20
|
+
def format_analyze(file_name, file_path)
|
21
|
+
format("Analyzing", file_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_build_target(target, project, configuration)
|
25
|
+
format("Building", "#{project}/#{target} [#{configuration}]")
|
26
|
+
end
|
27
|
+
|
28
|
+
def format_clean_target(target, project, configuration)
|
29
|
+
format("Cleaning", "#{project}/#{target} [#{configuration}]")
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_compile(file_name, file_path)
|
33
|
+
format("Compiling", file_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def format_compile_xib(file_name, file_path)
|
37
|
+
format("Compiling", file_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def format_copy_strings_file(file)
|
41
|
+
format("Copying", file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_cpresource(resource)
|
45
|
+
format("Copying", resource)
|
46
|
+
end
|
47
|
+
|
48
|
+
def format_generate_dsym(dsym)
|
49
|
+
format("Generating '#{dsym}'")
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_libtool(library)
|
53
|
+
format("Building library", library)
|
54
|
+
end
|
55
|
+
|
56
|
+
def format_linking(target, build_variants, arch)
|
57
|
+
format("Linking", target)
|
58
|
+
end
|
59
|
+
|
60
|
+
def format_failing_test(suite, test_case, reason, file)
|
61
|
+
INDENT + format_test("#{test_case}, #{reason}", :fail)
|
62
|
+
end
|
63
|
+
|
64
|
+
def format_passing_test(suite, test_case, time)
|
65
|
+
INDENT + format_test("#{test_case} (#{colored_time(time)} seconds)", :pass)
|
66
|
+
end
|
67
|
+
|
68
|
+
def format_pending_test(suite, test_case)
|
69
|
+
INDENT + format_test("#{test_case} [PENDING]", :pending)
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_phase_script_execution(script_name)
|
73
|
+
format("Running script", "'#{script_name}'")
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_process_info_plist(file_name, file_path)
|
77
|
+
format("Processing", file_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_process_pch(file)
|
81
|
+
format("Precompiling", file)
|
82
|
+
end
|
83
|
+
|
84
|
+
def format_codesign(file)
|
85
|
+
format("Signing", file)
|
86
|
+
end
|
87
|
+
|
88
|
+
def format_preprocess(file)
|
89
|
+
format("Preprocessing", file)
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_pbxcp(file)
|
93
|
+
format("Copying", file)
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_test_run_started(name)
|
97
|
+
heading("Test Suite", name, "started")
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_test_suite_started(name)
|
101
|
+
heading("", name, "")
|
102
|
+
end
|
103
|
+
|
104
|
+
def format_touch(file_path, file_name)
|
105
|
+
format("Touching", file_name)
|
106
|
+
end
|
107
|
+
|
108
|
+
def format_tiffutil(file_name)
|
109
|
+
format("Validating", file_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def heading(prefix, text, description)
|
115
|
+
[prefix, white(text), description].join(" ").strip
|
116
|
+
end
|
117
|
+
|
118
|
+
def format(command, argument_text="", success=true)
|
119
|
+
[status_symbol(success ? :completion : :fail), white(command), argument_text].join(" ").strip
|
120
|
+
end
|
121
|
+
|
122
|
+
def format_test(test_case, status)
|
123
|
+
[status_symbol(status), test_case].join(" ").strip
|
124
|
+
end
|
125
|
+
|
126
|
+
def status_symbol(status)
|
127
|
+
case status
|
128
|
+
when :pass
|
129
|
+
green(use_unicode? ? PASS : ASCII_PASS)
|
130
|
+
when :fail
|
131
|
+
red(use_unicode? ? FAIL : ASCII_FAIL)
|
132
|
+
when :pending
|
133
|
+
yellow(use_unicode? ? PENDING : ASCII_PENDING)
|
134
|
+
when :error
|
135
|
+
red(use_unicode? ? ERROR : ASCII_ERROR)
|
136
|
+
when :completion
|
137
|
+
yellow(use_unicode? ? COMPLETION : ASCII_COMPLETION)
|
138
|
+
else
|
139
|
+
""
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def colored_time(time)
|
144
|
+
case time.to_f
|
145
|
+
when 0..0.025
|
146
|
+
time
|
147
|
+
when 0.026..0.100
|
148
|
+
yellow(time)
|
149
|
+
else
|
150
|
+
red(time)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module XCPretty
|
2
|
+
|
3
|
+
class TestAnything < Knock
|
4
|
+
|
5
|
+
attr_reader :counter
|
6
|
+
|
7
|
+
def initialize unicode, color
|
8
|
+
super
|
9
|
+
@counter = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_passing_test(suite, test_case, time)
|
13
|
+
increment_counter
|
14
|
+
"#{PASS} #{counter} - #{test_case}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_failing_test(test_suite, test_case, reason, file)
|
18
|
+
increment_counter
|
19
|
+
"#{FAIL} #{counter} - #{test_case}" +
|
20
|
+
format_failure_diagnostics(test_suite, test_case, reason, file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def format_pending_test(test_suite, test_case)
|
24
|
+
increment_counter
|
25
|
+
"#{FAIL} #{counter} - #{test_case} # TODO Not written yet"
|
26
|
+
end
|
27
|
+
|
28
|
+
def format_test_summary(executed_message, failures_per_suite)
|
29
|
+
counter > 0 ? "1..#{counter}" : ''
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def increment_counter
|
35
|
+
@counter += 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,421 @@
|
|
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|mm|c|cc|cpp|cxx)))\s.*/
|
40
|
+
|
41
|
+
# @regex Captured groups
|
42
|
+
# $1 compiler_command
|
43
|
+
COMPILE_COMMAND_MATCHER = /^\s*(.*\/usr\/bin\/clang\s.*\.o)$/
|
44
|
+
|
45
|
+
# @regex Captured groups
|
46
|
+
# $1 file_path
|
47
|
+
# $2 file_name (e.g. MainMenu.xib)
|
48
|
+
COMPILE_XIB_MATCHER = /^CompileXIB\s(.*\/(.*\.xib))/
|
49
|
+
|
50
|
+
# @regex Captured groups
|
51
|
+
# $1 file
|
52
|
+
COPY_STRINGS_MATCHER = /^CopyStringsFile.*\/(.*.strings)/
|
53
|
+
|
54
|
+
# @regex Captured groups
|
55
|
+
# $1 resource
|
56
|
+
CPRESOURCE_MATCHER = /^CpResource\s(.*)\s\//
|
57
|
+
|
58
|
+
# @regex Captured groups
|
59
|
+
#
|
60
|
+
EXECUTED_MATCHER = /^\s*Executed/
|
61
|
+
|
62
|
+
# @regex Captured groups
|
63
|
+
# $1 = file
|
64
|
+
# $2 = test_suite
|
65
|
+
# $3 = test_case
|
66
|
+
# $4 = reason
|
67
|
+
FAILING_TEST_MATCHER = /^\s*(.+:\d+):\serror:\s[\+\-]\[(.*)\s(.*)\]\s:(?:\s'.*'\s\[FAILED\],)?\s(.*)/
|
68
|
+
|
69
|
+
# @regex Captured groups
|
70
|
+
# $1 = dsym
|
71
|
+
GENERATE_DSYM_MATCHER = /^GenerateDSYMFile \/.*\/(.*\.dSYM)/
|
72
|
+
|
73
|
+
# @regex Captured groups
|
74
|
+
# $1 = library
|
75
|
+
LIBTOOL_MATCHER = /^Libtool.*\/(.*\.a)/
|
76
|
+
|
77
|
+
# @regex Captured groups
|
78
|
+
# $1 = target
|
79
|
+
# $2 = build_variants (normal, profile, debug)
|
80
|
+
# $3 = architecture
|
81
|
+
LINKING_MATCHER = /^Ld \/.*\/(.*) (.*) (.*)$/
|
82
|
+
|
83
|
+
# @regex Captured groups
|
84
|
+
# $1 = suite
|
85
|
+
# $2 = test_case
|
86
|
+
# $3 = time
|
87
|
+
PASSING_TEST_MATCHER = /^\s*Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/
|
88
|
+
|
89
|
+
# @regex Captured groups
|
90
|
+
# $1 = suite
|
91
|
+
# $2 = test_case
|
92
|
+
PENDING_TEST_MATCHER = /^Test Case\s'-\[(.*)\s(.*)PENDING\]'\spassed/
|
93
|
+
|
94
|
+
# @regex Captured groups
|
95
|
+
# $1 = script_name
|
96
|
+
PHASE_SCRIPT_EXECUTION_MATCHER = /^PhaseScriptExecution\s(.*)\s\//
|
97
|
+
|
98
|
+
# @regex Captured groups
|
99
|
+
# $1 = file
|
100
|
+
PROCESS_PCH_MATCHER = /^ProcessPCH\s.*\s(.*.pch)/
|
101
|
+
|
102
|
+
# @regex Captured groups
|
103
|
+
# $1 = file
|
104
|
+
PREPROCESS_MATCHER = /^Preprocess\s(?:(?:\\ |[^ ])*)\s((?:\\ |[^ ])*)$/
|
105
|
+
|
106
|
+
# @regex Captured groups
|
107
|
+
# $1 = file
|
108
|
+
PBXCP_MATCHER = /^PBXCp\s((?:\\ |[^ ])*)/
|
109
|
+
|
110
|
+
# @regex Captured groups
|
111
|
+
# $1 = file
|
112
|
+
PROCESS_INFO_PLIST_MATCHER = /^ProcessInfoPlistFile\s.*\.plist\s(.*\/+(.*\.plist))/
|
113
|
+
|
114
|
+
# @regex Captured groups
|
115
|
+
# $1 = suite
|
116
|
+
# $2 = time
|
117
|
+
TESTS_RUN_COMPLETION_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' finished at (.*)/
|
118
|
+
|
119
|
+
# @regex Captured groups
|
120
|
+
# $1 = suite
|
121
|
+
# $2 = time
|
122
|
+
TESTS_RUN_START_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/
|
123
|
+
|
124
|
+
# @regex Captured groups
|
125
|
+
# $1 test suite name
|
126
|
+
TEST_SUITE_START_MATCHER = /^\s*Test Suite '(.*)' started at/
|
127
|
+
|
128
|
+
# @regex Captured groups
|
129
|
+
# $1 file_name
|
130
|
+
TIFFUTIL_MATCHER = /^TiffUtil\s(.*)/
|
131
|
+
|
132
|
+
# @regex Captured groups
|
133
|
+
# $1 file_path
|
134
|
+
# $2 file_name
|
135
|
+
TOUCH_MATCHER = /^Touch\s(.*\/([\w+\.]+))/
|
136
|
+
|
137
|
+
module Errors
|
138
|
+
# @regex Captured groups
|
139
|
+
# $1 = whole error
|
140
|
+
CLANG_ERROR_MATCHER = /^(clang: error:.*)$/
|
141
|
+
|
142
|
+
# @regex Captured groups
|
143
|
+
# $1 = whole error
|
144
|
+
CODESIGN_ERROR_MATCHER = /^(Code\s?Sign error:.*)$/
|
145
|
+
|
146
|
+
# @regex Captured groups
|
147
|
+
# $1 = file_path
|
148
|
+
# $2 = file_name
|
149
|
+
# $3 = reason
|
150
|
+
COMPILE_ERROR_MATCHER = /^(\/.+\/(.*):.*:.*):(?:\sfatal)?\serror:\s(.*)$/
|
151
|
+
|
152
|
+
# @regex Captured groups
|
153
|
+
# $1 cursor (with whitespaces and tildes)
|
154
|
+
CURSOR_MATCHER = /^([\s~]*\^[\s~]*)$/
|
155
|
+
|
156
|
+
# @regex Captured groups
|
157
|
+
# $1 = whole error.
|
158
|
+
# it varies a lot, not sure if it makes sense to catch everything separately
|
159
|
+
FATAL_ERROR_MATCHER = /^(fatal error:.*)$/
|
160
|
+
|
161
|
+
# @regex Captured groups
|
162
|
+
# $1 = whole error
|
163
|
+
LD_ERROR_MATCHER = /^(ld:.*not found for.*)/
|
164
|
+
|
165
|
+
# @regex Captured groups
|
166
|
+
# $1 file path
|
167
|
+
LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER = /^\s+(\/.*\.o[\)]?)$/
|
168
|
+
|
169
|
+
# @regex Captured groups
|
170
|
+
# $1 reason
|
171
|
+
LINKER_DUPLICATE_SYMBOLS_MATCHER = /^(duplicate symbol .*):$/
|
172
|
+
|
173
|
+
# @regex Captured groups
|
174
|
+
# $1 symbol location
|
175
|
+
LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER = /^(.* in .*\.o)$/
|
176
|
+
|
177
|
+
# @regex Captured groups
|
178
|
+
# $1 reason
|
179
|
+
LINKER_UNDEFINED_SYMBOLS_MATCHER = /^(Undefined symbols for architecture .*):$/
|
180
|
+
|
181
|
+
# @regex Captured groups
|
182
|
+
PODS_ERROR_MATCHER = /^error:\s(.*)/
|
183
|
+
|
184
|
+
# @regex Captured groups
|
185
|
+
# $1 = reference
|
186
|
+
SYMBOL_REFERENCED_FROM_MATCHER = /\s+"(.*)", referenced from:$/
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Parser
|
191
|
+
|
192
|
+
include Matchers
|
193
|
+
include Matchers::Errors
|
194
|
+
|
195
|
+
attr_reader :formatter
|
196
|
+
|
197
|
+
def initialize(formatter)
|
198
|
+
@formatter = formatter
|
199
|
+
end
|
200
|
+
|
201
|
+
def parse(text)
|
202
|
+
update_test_state(text)
|
203
|
+
update_error_state(text)
|
204
|
+
update_linker_failure_state(text)
|
205
|
+
|
206
|
+
return format_compile_error if should_format_error?
|
207
|
+
return format_undefined_symbols if should_format_undefined_symbols?
|
208
|
+
return format_duplicate_symbols if should_format_duplicate_symbols?
|
209
|
+
|
210
|
+
case text
|
211
|
+
when ANALYZE_MATCHER
|
212
|
+
formatter.format_analyze($2, $1)
|
213
|
+
when BUILD_TARGET_MATCHER
|
214
|
+
formatter.format_build_target($1, $2, $3)
|
215
|
+
when CLEAN_REMOVE_MATCHER
|
216
|
+
formatter.format_clean_remove
|
217
|
+
when CLEAN_TARGET_MATCHER
|
218
|
+
formatter.format_clean_target($1, $2, $3)
|
219
|
+
when COPY_STRINGS_MATCHER
|
220
|
+
formatter.format_copy_strings_file($1)
|
221
|
+
when CHECK_DEPENDENCIES_MATCHER
|
222
|
+
formatter.format_check_dependencies
|
223
|
+
when CLANG_ERROR_MATCHER
|
224
|
+
formatter.format_error($1)
|
225
|
+
when CODESIGN_FRAMEWORK_MATCHER
|
226
|
+
formatter.format_codesign($1)
|
227
|
+
when CODESIGN_MATCHER
|
228
|
+
formatter.format_codesign($1)
|
229
|
+
when CODESIGN_ERROR_MATCHER
|
230
|
+
formatter.format_error($1)
|
231
|
+
when COMPILE_MATCHER
|
232
|
+
formatter.format_compile($2, $1)
|
233
|
+
when COMPILE_COMMAND_MATCHER
|
234
|
+
formatter.format_compile_command($1)
|
235
|
+
when COMPILE_XIB_MATCHER
|
236
|
+
formatter.format_compile_xib($2, $1)
|
237
|
+
when CPRESOURCE_MATCHER
|
238
|
+
formatter.format_cpresource($1)
|
239
|
+
when EXECUTED_MATCHER
|
240
|
+
format_summary_if_needed(text)
|
241
|
+
when FAILING_TEST_MATCHER
|
242
|
+
formatter.format_failing_test($2, $3, $4, $1)
|
243
|
+
when FATAL_ERROR_MATCHER
|
244
|
+
formatter.format_error($1)
|
245
|
+
when GENERATE_DSYM_MATCHER
|
246
|
+
formatter.format_generate_dsym($1)
|
247
|
+
when LD_ERROR_MATCHER
|
248
|
+
formatter.format_error($1)
|
249
|
+
when LIBTOOL_MATCHER
|
250
|
+
formatter.format_libtool($1)
|
251
|
+
when LINKING_MATCHER
|
252
|
+
formatter.format_linking($1, $2, $3)
|
253
|
+
when PENDING_TEST_MATCHER
|
254
|
+
formatter.format_pending_test($1, $2)
|
255
|
+
when PASSING_TEST_MATCHER
|
256
|
+
formatter.format_passing_test($1, $2, $3)
|
257
|
+
when PODS_ERROR_MATCHER
|
258
|
+
formatter.format_error($1)
|
259
|
+
when PROCESS_INFO_PLIST_MATCHER
|
260
|
+
formatter.format_process_info_plist(*unescaped($2, $1))
|
261
|
+
when PHASE_SCRIPT_EXECUTION_MATCHER
|
262
|
+
formatter.format_phase_script_execution(*unescaped($1))
|
263
|
+
when PROCESS_PCH_MATCHER
|
264
|
+
formatter.format_process_pch($1)
|
265
|
+
when PREPROCESS_MATCHER
|
266
|
+
formatter.format_preprocess($1)
|
267
|
+
when PBXCP_MATCHER
|
268
|
+
formatter.format_pbxcp($1)
|
269
|
+
when TESTS_RUN_COMPLETION_MATCHER
|
270
|
+
formatter.format_test_run_finished($1, $2)
|
271
|
+
when TESTS_RUN_START_MATCHER
|
272
|
+
formatter.format_test_run_started($1)
|
273
|
+
when TEST_SUITE_START_MATCHER
|
274
|
+
formatter.format_test_suite_started($1)
|
275
|
+
when TIFFUTIL_MATCHER
|
276
|
+
formatter.format_tiffutil($1)
|
277
|
+
when TOUCH_MATCHER
|
278
|
+
formatter.format_touch($1, $2)
|
279
|
+
else
|
280
|
+
""
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
def update_test_state(text)
|
287
|
+
case text
|
288
|
+
when TESTS_RUN_START_MATCHER
|
289
|
+
@tests_done = false
|
290
|
+
@formatted_summary = false
|
291
|
+
@failures = {}
|
292
|
+
when TESTS_RUN_COMPLETION_MATCHER
|
293
|
+
@tests_done = true
|
294
|
+
when FAILING_TEST_MATCHER
|
295
|
+
store_failure($1, $2, $3, $4)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# @ return Hash { :file_name, :file_path, :reason, :line }
|
300
|
+
def update_error_state(text)
|
301
|
+
if text =~ COMPILE_ERROR_MATCHER
|
302
|
+
@formatting_error = true
|
303
|
+
current_error[:reason] = $3
|
304
|
+
current_error[:file_path] = $1
|
305
|
+
current_error[:file_name] = $2
|
306
|
+
elsif text =~ CURSOR_MATCHER
|
307
|
+
@formatting_error = false
|
308
|
+
current_error[:cursor] = $1.chomp
|
309
|
+
elsif @formatting_error
|
310
|
+
current_error[:line] = text.chomp
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def update_linker_failure_state(text)
|
315
|
+
if text =~ LINKER_UNDEFINED_SYMBOLS_MATCHER ||
|
316
|
+
text =~ LINKER_DUPLICATE_SYMBOLS_MATCHER
|
317
|
+
|
318
|
+
current_linker_failure[:message] = $1
|
319
|
+
@formatting_linker_failure = true
|
320
|
+
end
|
321
|
+
return unless @formatting_linker_failure
|
322
|
+
|
323
|
+
case text
|
324
|
+
when SYMBOL_REFERENCED_FROM_MATCHER
|
325
|
+
current_linker_failure[:symbol] = $1
|
326
|
+
when LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER
|
327
|
+
current_linker_failure[:reference] = text.strip
|
328
|
+
when LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER
|
329
|
+
current_linker_failure[:files] << $1
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# TODO: clean up the mess around all this
|
334
|
+
def should_format_error?
|
335
|
+
current_error[:reason] && current_error[:cursor] && current_error[:line]
|
336
|
+
end
|
337
|
+
|
338
|
+
def should_format_undefined_symbols?
|
339
|
+
current_linker_failure[:message] &&
|
340
|
+
current_linker_failure[:symbol] &&
|
341
|
+
current_linker_failure[:reference]
|
342
|
+
end
|
343
|
+
|
344
|
+
def should_format_duplicate_symbols?
|
345
|
+
current_linker_failure[:message] &&
|
346
|
+
current_linker_failure[:files].count > 1
|
347
|
+
end
|
348
|
+
|
349
|
+
def current_error
|
350
|
+
@current_error ||= {}
|
351
|
+
end
|
352
|
+
|
353
|
+
def current_linker_failure
|
354
|
+
@linker_failure ||= { :files => [] }
|
355
|
+
end
|
356
|
+
|
357
|
+
def format_compile_error
|
358
|
+
error = current_error.dup
|
359
|
+
@current_error = {}
|
360
|
+
formatter.format_compile_error(error[:file_name],
|
361
|
+
error[:file_path],
|
362
|
+
error[:reason],
|
363
|
+
error[:line],
|
364
|
+
error[:cursor])
|
365
|
+
end
|
366
|
+
|
367
|
+
def format_undefined_symbols
|
368
|
+
result = formatter.format_undefined_symbols(
|
369
|
+
current_linker_failure[:message],
|
370
|
+
current_linker_failure[:symbol],
|
371
|
+
current_linker_failure[:reference]
|
372
|
+
)
|
373
|
+
reset_linker_format_state
|
374
|
+
result
|
375
|
+
end
|
376
|
+
|
377
|
+
def format_duplicate_symbols
|
378
|
+
result = formatter.format_duplicate_symbols(
|
379
|
+
current_linker_failure[:message],
|
380
|
+
current_linker_failure[:files]
|
381
|
+
)
|
382
|
+
reset_linker_format_state
|
383
|
+
result
|
384
|
+
end
|
385
|
+
|
386
|
+
def reset_linker_format_state
|
387
|
+
@linker_failure = nil
|
388
|
+
@formatting_linker_failure = false
|
389
|
+
end
|
390
|
+
|
391
|
+
def store_failure(file, test_suite, test_case, reason)
|
392
|
+
failures_per_suite[test_suite] ||= []
|
393
|
+
failures_per_suite[test_suite] << {
|
394
|
+
:file_path => file,
|
395
|
+
:reason => reason,
|
396
|
+
:test_case => test_case
|
397
|
+
}
|
398
|
+
end
|
399
|
+
|
400
|
+
def failures_per_suite
|
401
|
+
@failures ||= {}
|
402
|
+
end
|
403
|
+
|
404
|
+
def format_summary_if_needed(executed_message)
|
405
|
+
return "" unless should_format_summary?
|
406
|
+
|
407
|
+
@formatted_summary = true
|
408
|
+
formatter.format_test_summary(executed_message, failures_per_suite)
|
409
|
+
end
|
410
|
+
|
411
|
+
def should_format_summary?
|
412
|
+
@tests_done && !@formatted_summary
|
413
|
+
end
|
414
|
+
|
415
|
+
def unescaped(*escaped_values)
|
416
|
+
escaped_values.map { |v| v.gsub('\\', '') }
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|