learn-xcpretty 0.1.11

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.kick +17 -0
  4. data/.travis.yml +18 -0
  5. data/CHANGELOG.md +152 -0
  6. data/CONTRIBUTING.md +60 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +61 -0
  9. data/README.md +143 -0
  10. data/Rakefile +24 -0
  11. data/assets/report.html.erb +155 -0
  12. data/bin/learn-xcpretty +80 -0
  13. data/features/custom_formatter.feature +15 -0
  14. data/features/fixtures/xcodebuild.log +5963 -0
  15. data/features/html_report.feature +40 -0
  16. data/features/json_compilation_database_report.feature +21 -0
  17. data/features/junit_report.feature +44 -0
  18. data/features/knock_format.feature +11 -0
  19. data/features/simple_format.feature +172 -0
  20. data/features/steps/formatting_steps.rb +268 -0
  21. data/features/steps/html_steps.rb +23 -0
  22. data/features/steps/json_steps.rb +37 -0
  23. data/features/steps/junit_steps.rb +38 -0
  24. data/features/steps/report_steps.rb +21 -0
  25. data/features/steps/xcpretty_steps.rb +31 -0
  26. data/features/support/env.rb +108 -0
  27. data/features/tap_format.feature +31 -0
  28. data/features/test_format.feature +39 -0
  29. data/features/xcpretty.feature +14 -0
  30. data/learn-xcpretty.gemspec +37 -0
  31. data/lib/xcpretty/ansi.rb +71 -0
  32. data/lib/xcpretty/formatters/formatter.rb +134 -0
  33. data/lib/xcpretty/formatters/knock.rb +34 -0
  34. data/lib/xcpretty/formatters/rspec.rb +27 -0
  35. data/lib/xcpretty/formatters/simple.rb +155 -0
  36. data/lib/xcpretty/formatters/tap.rb +39 -0
  37. data/lib/xcpretty/parser.rb +421 -0
  38. data/lib/xcpretty/printer.rb +20 -0
  39. data/lib/xcpretty/reporters/html.rb +73 -0
  40. data/lib/xcpretty/reporters/json_compilation_database.rb +58 -0
  41. data/lib/xcpretty/reporters/junit.rb +99 -0
  42. data/lib/xcpretty/reporters/learn.rb +154 -0
  43. data/lib/xcpretty/snippet.rb +34 -0
  44. data/lib/xcpretty/syntax.rb +20 -0
  45. data/lib/xcpretty/version.rb +3 -0
  46. data/lib/xcpretty.rb +39 -0
  47. data/spec/fixtures/NSStringTests.m +64 -0
  48. data/spec/fixtures/constants.rb +546 -0
  49. data/spec/fixtures/custom_formatter.rb +17 -0
  50. data/spec/fixtures/oneliner.m +1 -0
  51. data/spec/fixtures/raw_kiwi_compilation_fail.txt +24 -0
  52. data/spec/fixtures/raw_kiwi_fail.txt +1896 -0
  53. data/spec/fixtures/raw_specta_fail.txt +3110 -0
  54. data/spec/spec_helper.rb +6 -0
  55. data/spec/support/matchers/colors.rb +20 -0
  56. data/spec/xcpretty/ansi_spec.rb +46 -0
  57. data/spec/xcpretty/formatters/formatter_spec.rb +113 -0
  58. data/spec/xcpretty/formatters/rspec_spec.rb +55 -0
  59. data/spec/xcpretty/formatters/simple_spec.rb +129 -0
  60. data/spec/xcpretty/parser_spec.rb +421 -0
  61. data/spec/xcpretty/printer_spec.rb +53 -0
  62. data/spec/xcpretty/snippet_spec.rb +39 -0
  63. data/spec/xcpretty/syntax_spec.rb +35 -0
  64. data/vendor/json_pure/COPYING +57 -0
  65. data/vendor/json_pure/LICENSE +340 -0
  66. data/vendor/json_pure/generator.rb +443 -0
  67. data/vendor/json_pure/parser.rb +364 -0
  68. 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
+