xcpretty-bb 0.1.12.bb1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +2 -0
  4. data/.kick +17 -0
  5. data/.rubocop.yml +239 -0
  6. data/.travis.yml +11 -0
  7. data/CHANGELOG.md +200 -0
  8. data/CONTRIBUTING.md +64 -0
  9. data/Gemfile +9 -0
  10. data/LICENSE.txt +61 -0
  11. data/README.md +93 -0
  12. data/Rakefile +26 -0
  13. data/assets/report.html.erb +172 -0
  14. data/bin/xcpretty +85 -0
  15. data/features/assets/RACCommandSpec, line 80, hello xcpretty.png +0 -0
  16. data/features/assets/apple_raw.png +0 -0
  17. data/features/custom_formatter.feature +15 -0
  18. data/features/fixtures/xcodebuild.log +5963 -0
  19. data/features/html_report.feature +54 -0
  20. data/features/json_compilation_database_report.feature +21 -0
  21. data/features/junit_report.feature +44 -0
  22. data/features/knock_format.feature +11 -0
  23. data/features/simple_format.feature +204 -0
  24. data/features/steps/formatting_steps.rb +330 -0
  25. data/features/steps/html_steps.rb +32 -0
  26. data/features/steps/json_steps.rb +37 -0
  27. data/features/steps/junit_steps.rb +39 -0
  28. data/features/steps/report_steps.rb +22 -0
  29. data/features/steps/xcpretty_steps.rb +31 -0
  30. data/features/support/env.rb +117 -0
  31. data/features/tap_format.feature +31 -0
  32. data/features/test_format.feature +49 -0
  33. data/features/xcpretty.feature +14 -0
  34. data/lib/xcpretty/ansi.rb +72 -0
  35. data/lib/xcpretty/formatters/formatter.rb +177 -0
  36. data/lib/xcpretty/formatters/knock.rb +35 -0
  37. data/lib/xcpretty/formatters/rspec.rb +33 -0
  38. data/lib/xcpretty/formatters/simple.rb +200 -0
  39. data/lib/xcpretty/formatters/tap.rb +40 -0
  40. data/lib/xcpretty/parser.rb +591 -0
  41. data/lib/xcpretty/printer.rb +24 -0
  42. data/lib/xcpretty/reporters/html.rb +98 -0
  43. data/lib/xcpretty/reporters/json_compilation_database.rb +62 -0
  44. data/lib/xcpretty/reporters/junit.rb +102 -0
  45. data/lib/xcpretty/snippet.rb +38 -0
  46. data/lib/xcpretty/syntax.rb +51 -0
  47. data/lib/xcpretty/term.rb +14 -0
  48. data/lib/xcpretty/version.rb +4 -0
  49. data/lib/xcpretty.rb +37 -0
  50. data/spec/fixtures/NSStringTests.m +64 -0
  51. data/spec/fixtures/constants.rb +600 -0
  52. data/spec/fixtures/custom_formatter.rb +18 -0
  53. data/spec/fixtures/oneliner.m +1 -0
  54. data/spec/fixtures/raw_kiwi_compilation_fail.txt +24 -0
  55. data/spec/fixtures/raw_kiwi_fail.txt +1896 -0
  56. data/spec/fixtures/raw_specta_fail.txt +3110 -0
  57. data/spec/spec_helper.rb +7 -0
  58. data/spec/support/matchers/colors.rb +21 -0
  59. data/spec/xcpretty/ansi_spec.rb +47 -0
  60. data/spec/xcpretty/formatters/formatter_spec.rb +140 -0
  61. data/spec/xcpretty/formatters/rspec_spec.rb +56 -0
  62. data/spec/xcpretty/formatters/simple_spec.rb +173 -0
  63. data/spec/xcpretty/parser_spec.rb +542 -0
  64. data/spec/xcpretty/printer_spec.rb +55 -0
  65. data/spec/xcpretty/snippet_spec.rb +46 -0
  66. data/spec/xcpretty/syntax_spec.rb +39 -0
  67. data/spec/xcpretty/term_spec.rb +26 -0
  68. data/xcpretty.gemspec +37 -0
  69. metadata +237 -0
@@ -0,0 +1,591 @@
1
+ # encoding: utf-8
2
+
3
+ module XCPretty
4
+
5
+ module Matchers
6
+
7
+ # @regex Captured groups
8
+ # $1 file_path
9
+ # $2 file_name
10
+ ANALYZE_MATCHER = /^Analyze(?:Shallow)?\s(.*\/(.*\.m))*/
11
+
12
+ # @regex Captured groups
13
+ # $1 target
14
+ # $2 project
15
+ # $3 configuration
16
+ BUILD_TARGET_MATCHER = /^=== BUILD TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/
17
+
18
+ # @regex Captured groups
19
+ # $1 target
20
+ # $2 project
21
+ # $3 configuration
22
+ ANALYZE_TARGET_MATCHER = /^=== ANALYZE TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/
23
+
24
+ # @regex Nothing returned here for now
25
+ CHECK_DEPENDENCIES_MATCHER = /^Check dependencies/
26
+
27
+ # @regex Captured groups
28
+ # $1 command path
29
+ # $2 arguments
30
+ SHELL_COMMAND_MATCHER = /^\s{4}(cd|setenv|(?:[\w\/:\\\s\-.]+?\/)?[\w\-]+)\s(.*)$/
31
+
32
+ # @regex Nothing returned here for now
33
+ CLEAN_REMOVE_MATCHER = /^Clean.Remove/
34
+
35
+ # @regex Captured groups
36
+ # $1 target
37
+ # $2 project
38
+ # $3 configuration
39
+ CLEAN_TARGET_MATCHER = /^=== CLEAN TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH CONFIGURATION\s(.*)\s===/
40
+
41
+ # @regex Captured groups
42
+ # $1 = file
43
+ CODESIGN_MATCHER = /^CodeSign\s((?:\\ |[^ ])*)$/
44
+
45
+ # @regex Captured groups
46
+ # $1 = file
47
+ CODESIGN_FRAMEWORK_MATCHER = /^CodeSign\s((?:\\ |[^ ])*.framework)\/Versions/
48
+
49
+ # @regex Captured groups
50
+ # $1 file_path
51
+ # $2 file_name (e.g. KWNull.m)
52
+ COMPILE_MATCHER = /^Compile[\w]+\s.+?\s((?:\\.|[^ ])+\/((?:\\.|[^ ])+\.(?:m|mm|c|cc|cpp|cxx|swift)))\s.*/
53
+
54
+ # @regex Captured groups
55
+ # $1 compiler_command
56
+ # $2 file_path
57
+ COMPILE_COMMAND_MATCHER = /^\s*(.*\/usr\/bin\/clang\s.*\s\-c\s(.*\.(?:m|mm|c|cc|cpp|cxx))\s.*\.o)$/
58
+
59
+ # @regex Captured groups
60
+ # $1 file_path
61
+ # $2 file_name (e.g. MainMenu.xib)
62
+ COMPILE_XIB_MATCHER = /^CompileXIB\s(.*\/(.*\.xib))/
63
+
64
+ # @regex Captured groups
65
+ # $1 file_path
66
+ # $2 file_name (e.g. Main.storyboard)
67
+ COMPILE_STORYBOARD_MATCHER = /^CompileStoryboard\s(.*\/([^\/].*\.storyboard))/
68
+
69
+ # @regex Captured groups
70
+ # $1 source file
71
+ # $2 target file
72
+ COPY_HEADER_MATCHER = /^CpHeader\s(.*\.h)\s(.*\.h)/
73
+
74
+ # @regex Captured groups
75
+ # $1 source file
76
+ # $2 target file
77
+ COPY_PLIST_MATCHER = /^CopyPlistFile\s(.*\.plist)\s(.*\.plist)/
78
+
79
+ # $1 file
80
+ COPY_STRINGS_MATCHER = /^CopyStringsFile.*\/(.*.strings)/
81
+
82
+ # @regex Captured groups
83
+ # $1 resource
84
+ CPRESOURCE_MATCHER = /^CpResource\s(.*)\s\//
85
+
86
+ # @regex Captured groups
87
+ #
88
+ EXECUTED_MATCHER = /^\s*Executed/
89
+
90
+ # @regex Captured groups
91
+ # $1 = file
92
+ # $2 = test_suite
93
+ # $3 = test_case
94
+ # $4 = reason
95
+ FAILING_TEST_MATCHER = /^\s*(.+:\d+):\serror:\s[\+\-]\[(.*)\s(.*)\]\s:(?:\s'.*'\s\[FAILED\],)?\s(.*)/
96
+
97
+ # @regex Captured groups
98
+ # $1 = dsym
99
+ GENERATE_DSYM_MATCHER = /^GenerateDSYMFile \/.*\/(.*\.dSYM)/
100
+
101
+ # @regex Captured groups
102
+ # $1 = library
103
+ LIBTOOL_MATCHER = /^Libtool.*\/(.*\.a)/
104
+
105
+ # @regex Captured groups
106
+ # $1 = target
107
+ # $2 = build_variants (normal, profile, debug)
108
+ # $3 = architecture
109
+ LINKING_MATCHER = /^Ld \/.*\/(.*) (.*) (.*)$/
110
+
111
+ # @regex Captured groups
112
+ # $1 = suite
113
+ # $2 = test_case
114
+ # $3 = time
115
+ PASSING_TEST_MATCHER = /^\s*Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/
116
+
117
+ # @regex Captured groups
118
+ # $1 = suite
119
+ # $2 = test_case
120
+ PENDING_TEST_MATCHER = /^Test Case\s'-\[(.*)\s(.*)PENDING\]'\spassed/
121
+
122
+ # @regex Captured groups
123
+ # $1 = suite
124
+ # $2 = test_case
125
+ # $3 = time
126
+ MEASURING_TEST_MATCHER = /^[^:]*:[^:]*:\sTest Case\s'-\[(.*)\s(.*)\]'\smeasured\s\[Time,\sseconds\]\saverage:\s(\d*\.\d{3}),/
127
+
128
+ PHASE_SUCCESS_MATCHER = /^\*\*\s(.*)\sSUCCEEDED\s\*\*/
129
+
130
+ # @regex Captured groups
131
+ # $1 = file
132
+ PROCESS_PCH_MATCHER = /^ProcessPCH\s.*\s(.*.pch)/
133
+
134
+ # @regex Captured groups
135
+ # $1 file_path
136
+ PROCESS_PCH_COMMAND_MATCHER = /^\s*.*\/usr\/bin\/clang\s.*\s\-c\s(.*)\s\-o\s.*/
137
+
138
+ # @regex Captured groups
139
+ # $1 = file
140
+ PREPROCESS_MATCHER = /^Preprocess\s(?:(?:\\ |[^ ])*)\s((?:\\ |[^ ])*)$/
141
+
142
+ # @regex Captured groups
143
+ # $1 = file
144
+ PBXCP_MATCHER = /^PBXCp\s((?:\\ |[^ ])*)/
145
+
146
+ # @regex Captured groups
147
+ # $1 = file
148
+ PROCESS_INFO_PLIST_MATCHER = /^ProcessInfoPlistFile\s.*\.plist\s(.*\/+(.*\.plist))/
149
+
150
+ # @regex Captured groups
151
+ # $1 = suite
152
+ # $2 = time
153
+ TESTS_RUN_COMPLETION_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' (finished|passed|failed) at (.*)/
154
+
155
+ # @regex Captured groups
156
+ # $1 = suite
157
+ # $2 = time
158
+ TESTS_RUN_START_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/
159
+
160
+ # @regex Captured groups
161
+ # $1 test suite name
162
+ TEST_SUITE_START_MATCHER = /^\s*Test Suite '(.*)' started at/
163
+
164
+ # @regex Captured groups
165
+ # $1 file_name
166
+ TIFFUTIL_MATCHER = /^TiffUtil\s(.*)/
167
+
168
+ # @regex Captured groups
169
+ # $1 file_path
170
+ # $2 file_name
171
+ TOUCH_MATCHER = /^Touch\s(.*\/(.+))/
172
+
173
+ # @regex Captured groups
174
+ # $1 file_path
175
+ WRITE_FILE_MATCHER = /^write-file\s(.*)/
176
+
177
+ # @regex Captured groups
178
+ WRITE_AUXILIARY_FILES = /^Write auxiliary files/
179
+
180
+ module PhaseScript
181
+ # @regex Captured groups
182
+ # $1 = phase_details
183
+ PHASE_SCRIPT_EXECUTION_MATCHER = /^PhaseScriptExecution\s(.*)\s/
184
+
185
+ # @regex Captured groups
186
+ # $1 = file not found
187
+ PHASE_SCRIPT_NO_SUCH_FILE_OR_DIRECTORY_MATCHER = /: (.*): (No such file or directory)$/
188
+
189
+ # @regex Captured groups
190
+ # $1 = command
191
+ # $2 = exit code
192
+ PHASE_SCRIPT_COMMAND_FAILED_MATCHER = /^Command (.*) failed with exit code (\d+)/
193
+ end
194
+
195
+ module Warnings
196
+ # $1 = file_path
197
+ # $2 = file_name
198
+ # $3 = reason
199
+ COMPILE_WARNING_MATCHER = /^(\/.+\/(.*):.*:.*):\swarning:\s(.*)$/
200
+
201
+ # $1 = ld prefix
202
+ # $2 = warning message
203
+ LD_WARNING_MATCHER = /^(ld: )warning: (.*)/
204
+
205
+ # @regex Captured groups
206
+ # $1 = whole warning
207
+ GENERIC_WARNING_MATCHER = /^warning:\s(.*)$/
208
+ end
209
+
210
+ module Errors
211
+ # @regex Captured groups
212
+ # $1 = whole error
213
+ CLANG_ERROR_MATCHER = /^(clang: error:.*)$/
214
+
215
+ # @regex Captured groups
216
+ # $1 = whole error
217
+ CODESIGN_ERROR_MATCHER = /^(Code\s?Sign error:.*)$/
218
+
219
+ # @regex Captured groups
220
+ # $1 = file_path
221
+ # $2 = file_name
222
+ # $3 = reason
223
+ COMPILE_ERROR_MATCHER = /^(\/.+\/(.*):.*:.*):\s(?:fatal\s)?error:\s(.*)$/
224
+
225
+ # @regex Captured groups
226
+ # $1 cursor (with whitespaces and tildes)
227
+ CURSOR_MATCHER = /^([\s~]*\^[\s~]*)$/
228
+
229
+ # @regex Captured groups
230
+ # $1 = whole error.
231
+ # it varies a lot, not sure if it makes sense to catch everything separately
232
+ FATAL_ERROR_MATCHER = /^(fatal error:.*)$/
233
+
234
+ # @regex Captured groups
235
+ # $1 = whole error.
236
+ # $2 = file path
237
+ FILE_MISSING_ERROR_MATCHER = /^<unknown>:0:\s(error:\s.*)\s'(\/.+\/.*\..*)'$/
238
+
239
+ # $1 = whole error
240
+ LD_ERROR_MATCHER = /^(ld:.*)/
241
+
242
+ # @regex Captured groups
243
+ # $1 file path
244
+ LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER = /^\s+(\/.*\.o[\)]?)$/
245
+
246
+ # @regex Captured groups
247
+ # $1 reason
248
+ LINKER_DUPLICATE_SYMBOLS_MATCHER = /^(duplicate symbol .*):$/
249
+
250
+ # @regex Captured groups
251
+ # $1 symbol location
252
+ LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER = /^(.* in .*\.o)$/
253
+
254
+ # @regex Captured groups
255
+ # $1 reason
256
+ LINKER_UNDEFINED_SYMBOLS_MATCHER = /^(Undefined symbols for architecture .*):$/
257
+
258
+ # @regex Captured groups
259
+ # $1 reason
260
+ PODS_ERROR_MATCHER = /^(error:\s.*)/
261
+
262
+ # @regex Captured groups
263
+ # $1 = reference
264
+ SYMBOL_REFERENCED_FROM_MATCHER = /\s+"(.*)", referenced from:$/
265
+ end
266
+ end
267
+
268
+ class Parser
269
+
270
+ include Matchers
271
+ include Matchers::PhaseScript
272
+ include Matchers::Errors
273
+ include Matchers::Warnings
274
+
275
+ attr_reader :formatter
276
+
277
+ def initialize(formatter)
278
+ @formatter = formatter
279
+ end
280
+
281
+ def parse(text)
282
+ update_test_state(text)
283
+ update_error_state(text)
284
+ update_linker_failure_state(text)
285
+ update_phase_script_state(text)
286
+
287
+ return format_compile_error if should_format_error?
288
+ return format_compile_warning if should_format_warning?
289
+ return format_undefined_symbols if should_format_undefined_symbols?
290
+ return format_duplicate_symbols if should_format_duplicate_symbols?
291
+ return format_phase_script_error if should_format_phase_script_error?
292
+
293
+ case text
294
+ when ANALYZE_MATCHER
295
+ formatter.format_analyze($2, $1)
296
+ when BUILD_TARGET_MATCHER
297
+ formatter.format_build_target($1, $2, $3)
298
+ when ANALYZE_TARGET_MATCHER
299
+ formatter.format_analyze_target($1, $2, $3)
300
+ when CLEAN_REMOVE_MATCHER
301
+ formatter.format_clean_remove
302
+ when CLEAN_TARGET_MATCHER
303
+ formatter.format_clean_target($1, $2, $3)
304
+ when COPY_STRINGS_MATCHER
305
+ formatter.format_copy_strings_file($1)
306
+ when CHECK_DEPENDENCIES_MATCHER
307
+ formatter.format_check_dependencies
308
+ when CLANG_ERROR_MATCHER
309
+ formatter.format_error($1)
310
+ when CODESIGN_FRAMEWORK_MATCHER
311
+ formatter.format_codesign($1)
312
+ when CODESIGN_MATCHER
313
+ formatter.format_codesign($1)
314
+ when CODESIGN_ERROR_MATCHER
315
+ formatter.format_error($1)
316
+ when COMPILE_MATCHER
317
+ formatter.format_compile($2, $1)
318
+ when COMPILE_COMMAND_MATCHER
319
+ formatter.format_compile_command($1, $2)
320
+ when COMPILE_XIB_MATCHER
321
+ formatter.format_compile_xib($2, $1)
322
+ when COMPILE_STORYBOARD_MATCHER
323
+ formatter.format_compile_storyboard($2, $1)
324
+ when COPY_HEADER_MATCHER
325
+ formatter.format_copy_header_file($1, $2)
326
+ when COPY_PLIST_MATCHER
327
+ formatter.format_copy_plist_file($1, $2)
328
+ when CPRESOURCE_MATCHER
329
+ formatter.format_cpresource($1)
330
+ when EXECUTED_MATCHER
331
+ format_summary_if_needed(text)
332
+ when FAILING_TEST_MATCHER
333
+ formatter.format_failing_test($2, $3, $4, $1)
334
+ when FATAL_ERROR_MATCHER
335
+ formatter.format_error($1)
336
+ when FILE_MISSING_ERROR_MATCHER
337
+ formatter.format_file_missing_error($1, $2)
338
+ when GENERATE_DSYM_MATCHER
339
+ formatter.format_generate_dsym($1)
340
+ when LD_WARNING_MATCHER
341
+ formatter.format_ld_warning($1 + $2)
342
+ when LD_ERROR_MATCHER
343
+ formatter.format_error($1)
344
+ when LIBTOOL_MATCHER
345
+ formatter.format_libtool($1)
346
+ when LINKING_MATCHER
347
+ formatter.format_linking($1, $2, $3)
348
+ when MEASURING_TEST_MATCHER
349
+ formatter.format_measuring_test($1, $2, $3)
350
+ when PENDING_TEST_MATCHER
351
+ formatter.format_pending_test($1, $2)
352
+ when PASSING_TEST_MATCHER
353
+ formatter.format_passing_test($1, $2, $3)
354
+ when PODS_ERROR_MATCHER
355
+ formatter.format_error($1)
356
+ when PROCESS_INFO_PLIST_MATCHER
357
+ formatter.format_process_info_plist(*unescaped($2, $1))
358
+ when PHASE_SCRIPT_EXECUTION_MATCHER
359
+ formatter.format_phase_script_execution(*unescaped($1))
360
+ when PHASE_SUCCESS_MATCHER
361
+ formatter.format_phase_success($1)
362
+ when PROCESS_PCH_MATCHER
363
+ formatter.format_process_pch($1)
364
+ when PROCESS_PCH_COMMAND_MATCHER
365
+ formatter.format_process_pch_command($1)
366
+ when PREPROCESS_MATCHER
367
+ formatter.format_preprocess($1)
368
+ when PBXCP_MATCHER
369
+ formatter.format_pbxcp($1)
370
+ when TESTS_RUN_COMPLETION_MATCHER
371
+ formatter.format_test_run_finished($1, $3)
372
+ when TESTS_RUN_START_MATCHER
373
+ formatter.format_test_run_started($1)
374
+ when TEST_SUITE_START_MATCHER
375
+ formatter.format_test_suite_started($1)
376
+ when TIFFUTIL_MATCHER
377
+ formatter.format_tiffutil($1)
378
+ when TOUCH_MATCHER
379
+ formatter.format_touch($1, $2)
380
+ when WRITE_FILE_MATCHER
381
+ formatter.format_write_file($1)
382
+ when WRITE_AUXILIARY_FILES
383
+ formatter.format_write_auxiliary_files
384
+ when SHELL_COMMAND_MATCHER
385
+ formatter.format_shell_command($1, $2)
386
+ when GENERIC_WARNING_MATCHER
387
+ formatter.format_warning($1)
388
+ else
389
+ ""
390
+ end
391
+ end
392
+
393
+ private
394
+
395
+ def update_phase_script_state(text)
396
+ case text
397
+ when PHASE_SCRIPT_EXECUTION_MATCHER
398
+ @current_phase_script_text = text
399
+ when PHASE_SCRIPT_COMMAND_FAILED_MATCHER, PHASE_SCRIPT_NO_SUCH_FILE_OR_DIRECTORY_MATCHER
400
+ unless @current_phase_script_text.nil?
401
+ @current_phase_script_text += text
402
+ current_phase_script_failure[:text] = @current_phase_script_text
403
+ current_phase_script_failure[:error] = text
404
+ end
405
+ else
406
+ unless @current_phase_script_text.nil?
407
+ @current_phase_script_text += text
408
+ end
409
+ end
410
+ end
411
+
412
+ def update_test_state(text)
413
+ case text
414
+ when TESTS_RUN_START_MATCHER
415
+ @tests_done = false
416
+ @formatted_summary = false
417
+ @failures = {}
418
+ when TESTS_RUN_COMPLETION_MATCHER
419
+ @tests_done = true
420
+ when FAILING_TEST_MATCHER
421
+ store_failure($1, $2, $3, $4)
422
+ end
423
+ end
424
+
425
+ # @ return Hash { :file_name, :file_path, :reason, :line }
426
+ def update_error_state(text)
427
+ update_error = lambda {
428
+ current_issue[:reason] = $3
429
+ current_issue[:file_path] = $1
430
+ current_issue[:file_name] = $2
431
+ }
432
+ if text =~ COMPILE_ERROR_MATCHER
433
+ @formatting_error = true
434
+ update_error.call
435
+ elsif text =~ COMPILE_WARNING_MATCHER
436
+ @formatting_warning = true
437
+ update_error.call
438
+ elsif text =~ CURSOR_MATCHER
439
+ current_issue[:cursor] = $1.chomp
440
+ elsif @formatting_error || @formatting_warning
441
+ current_issue[:line] = text.chomp
442
+ end
443
+ end
444
+
445
+ def update_linker_failure_state(text)
446
+ if text =~ LINKER_UNDEFINED_SYMBOLS_MATCHER ||
447
+ text =~ LINKER_DUPLICATE_SYMBOLS_MATCHER
448
+
449
+ current_linker_failure[:message] = $1
450
+ @formatting_linker_failure = true
451
+ end
452
+ return unless @formatting_linker_failure
453
+
454
+ case text
455
+ when SYMBOL_REFERENCED_FROM_MATCHER
456
+ current_linker_failure[:symbol] = $1
457
+ when LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER
458
+ current_linker_failure[:reference] = text.strip
459
+ when LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER
460
+ current_linker_failure[:files] << $1
461
+ end
462
+ end
463
+
464
+ # TODO: clean up the mess around all this
465
+ def should_format_error?
466
+ @formatting_error && error_or_warning_is_present
467
+ end
468
+
469
+ def should_format_warning?
470
+ @formatting_warning && error_or_warning_is_present
471
+ end
472
+
473
+ def error_or_warning_is_present
474
+ current_issue[:reason] && current_issue[:cursor] && current_issue[:line]
475
+ end
476
+
477
+ def should_format_undefined_symbols?
478
+ current_linker_failure[:message] && current_linker_failure[:symbol] && current_linker_failure[:reference]
479
+ end
480
+
481
+ def should_format_duplicate_symbols?
482
+ current_linker_failure[:message] && current_linker_failure[:files].count > 1
483
+ end
484
+
485
+ def should_format_phase_script_error?
486
+ current_phase_script_failure[:error] && current_phase_script_failure[:text]
487
+ end
488
+
489
+ def current_issue
490
+ @current_issue ||= {}
491
+ end
492
+
493
+ def current_linker_failure
494
+ @linker_failure ||= {files: []}
495
+ end
496
+
497
+ def current_phase_script_failure
498
+ @phase_script_failure ||= {}
499
+ end
500
+
501
+ def format_compile_error
502
+ error = current_issue.dup
503
+ @current_issue = {}
504
+ @formatting_error = false
505
+ formatter.format_compile_error(error[:file_name],
506
+ error[:file_path],
507
+ error[:reason],
508
+ error[:line],
509
+ error[:cursor])
510
+ end
511
+
512
+ def format_compile_warning
513
+ warning = current_issue.dup
514
+ @current_issue = {}
515
+ @formatting_warning = false
516
+ formatter.format_compile_warning(warning[:file_name],
517
+ warning[:file_path],
518
+ warning[:reason],
519
+ warning[:line],
520
+ warning[:cursor])
521
+ end
522
+
523
+ def format_undefined_symbols
524
+ result = formatter.format_undefined_symbols(
525
+ current_linker_failure[:message],
526
+ current_linker_failure[:symbol],
527
+ current_linker_failure[:reference]
528
+ )
529
+ reset_linker_format_state
530
+ result
531
+ end
532
+
533
+ def format_duplicate_symbols
534
+ result = formatter.format_duplicate_symbols(
535
+ current_linker_failure[:message],
536
+ current_linker_failure[:files]
537
+ )
538
+ reset_linker_format_state
539
+ result
540
+ end
541
+
542
+ def format_phase_script_error
543
+ result = formatter.format_phase_script_error(current_phase_script_failure[:error],
544
+ current_phase_script_failure[:text]
545
+ )
546
+
547
+ reset_phase_script_state
548
+ result
549
+ end
550
+
551
+ def reset_phase_script_state
552
+ @phase_script_failure = nil
553
+ @current_phase_script_text = nil
554
+ end
555
+
556
+ def reset_linker_format_state
557
+ @linker_failure = nil
558
+ @formatting_linker_failure = false
559
+ end
560
+
561
+ def store_failure(file, test_suite, test_case, reason)
562
+ failures_per_suite[test_suite] ||= []
563
+ failures_per_suite[test_suite] << {
564
+ file_path: file,
565
+ reason: reason,
566
+ test_case: test_case
567
+ }
568
+ end
569
+
570
+ def failures_per_suite
571
+ @failures ||= {}
572
+ end
573
+
574
+ def format_summary_if_needed(executed_message)
575
+ return "" unless should_format_summary?
576
+
577
+ @formatted_summary = true
578
+ formatter.format_test_summary(executed_message, failures_per_suite)
579
+ end
580
+
581
+ def should_format_summary?
582
+ @tests_done && !@formatted_summary
583
+ end
584
+
585
+ def unescaped(*escaped_values)
586
+ escaped_values.map { |v| v.delete('\\') }
587
+ end
588
+
589
+ end
590
+ end
591
+
@@ -0,0 +1,24 @@
1
+ require "xcpretty/ansi"
2
+
3
+ module XCPretty
4
+
5
+ class Printer
6
+
7
+ attr_reader :formatter
8
+
9
+ def initialize(params)
10
+ klass = params[:formatter]
11
+ @formatter = klass.new(params[:unicode], params[:colorize])
12
+ end
13
+
14
+ def pretty_print(text)
15
+ formatted_text = formatter.pretty_format(text)
16
+ unless formatted_text.empty?
17
+ STDOUT.print(formatted_text + formatter.optional_newline)
18
+ STDOUT.flush
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,98 @@
1
+ module XCPretty
2
+ class HTML
3
+
4
+ include XCPretty::FormatMethods
5
+ FILEPATH = 'build/reports/tests.html'
6
+ TEMPLATE = File.expand_path('../../../../assets/report.html.erb', __FILE__)
7
+ SCREENSHOT_DIR = 'build/reports'
8
+
9
+ def load_dependencies
10
+ unless @@loaded ||= false
11
+ require 'fileutils'
12
+ require 'pathname'
13
+ require 'erb'
14
+ @@loaded = true
15
+ end
16
+ end
17
+
18
+ def initialize(options)
19
+ load_dependencies
20
+ @test_suites = {}
21
+ @filepath = options[:path] || FILEPATH
22
+ @parser = Parser.new(self)
23
+ @test_count = 0
24
+ @fail_count = 0
25
+ @collect_screenshots = options[:screenshots]
26
+ end
27
+
28
+ def handle(line)
29
+ @parser.parse(line)
30
+ end
31
+
32
+ def format_failing_test(suite, test_case, reason, file)
33
+ add_test(suite, name: test_case, failing: true,
34
+ reason: reason, file: file,
35
+ snippet: formatted_snippet(file))
36
+ end
37
+
38
+ def format_passing_test(suite, test_case, time)
39
+ add_test(suite, name: test_case, time: time)
40
+ end
41
+
42
+ def finish
43
+ FileUtils.mkdir_p(File.dirname(@filepath))
44
+ write_report
45
+ end
46
+
47
+ private
48
+
49
+ def formatted_snippet(filepath)
50
+ snippet = Snippet.from_filepath(filepath)
51
+ Syntax.highlight(snippet)
52
+ end
53
+
54
+
55
+ def add_test(suite_name, data)
56
+ @test_count += 1
57
+ @test_suites[suite_name] ||= {tests: [], screenshots: []}
58
+ @test_suites[suite_name][:tests] << data
59
+ if data[:failing]
60
+ @test_suites[suite_name][:failing] = true
61
+ @fail_count += 1
62
+ end
63
+ end
64
+
65
+ def write_report
66
+ if @collect_screenshots
67
+ load_screenshots
68
+ end
69
+ File.open(@filepath, 'w') do |f|
70
+ # WAT: get rid of these locals. BTW Cucumber fails if you remove them
71
+ test_suites = @test_suites
72
+ fail_count = @fail_count
73
+ test_count = @test_count
74
+ erb = ERB.new(File.open(TEMPLATE, 'r').read)
75
+ f.write erb.result(binding)
76
+ end
77
+ end
78
+
79
+ def load_screenshots
80
+ Dir.foreach(SCREENSHOT_DIR) do |item|
81
+ next if item == '.' || item == '..' || File.extname(item) != '.png'
82
+
83
+ suite_name = find_test_suite(item)
84
+ next if suite_name.nil?
85
+
86
+ @test_suites[suite_name][:screenshots] << item
87
+ end
88
+ end
89
+
90
+ def find_test_suite(image_name)
91
+ @test_suites.each do |key, value|
92
+ return key if image_name.start_with?(key)
93
+ end
94
+ nil
95
+ end
96
+ end
97
+ end
98
+