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
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ ![logo](http://i.imgur.com/i2fElxx.png)
2
+
3
+ __`xcpretty` is a fast and flexible formatter for `xcodebuild`__.<br/>
4
+ It does one thing, and it should do it well.
5
+
6
+ [![Gem version](http://img.shields.io/gem/v/xcpretty.svg)](http://rubygems.org/gems/xcpretty)
7
+ [![Build Status](https://travis-ci.org/supermarin/xcpretty.svg?branch=master)](https://travis-ci.org/supermarin/xcpretty)
8
+ [![Code Climate](http://img.shields.io/codeclimate/github/supermarin/xcpretty.svg)](https://codeclimate.com/github/supermarin/xcpretty)
9
+
10
+ ## Installation
11
+ ``` bash
12
+ $ gem install xcpretty
13
+ ```
14
+
15
+ ## Usage
16
+ ``` bash
17
+ $ xcodebuild [flags] | xcpretty -c
18
+ ```
19
+ `xcpretty` is designed to be piped with `xcodebuild` and thus keeping 100%
20
+ compatibility with it. It's even a bit faster than `xcodebuild` itself, since
21
+ it saves your terminal some prints.
22
+
23
+ __Important:__ If you're running `xcpretty` on a CI like Travis or Jenkins, you
24
+ may want to exit with same status code as `xcodebuild`.
25
+ CI systems usually use status codes to determine if the build has failed.
26
+
27
+ ``` bash
28
+ $ set -o pipefail && xcodebuild [flags] | xcpretty -c
29
+ #
30
+ # OR
31
+ #
32
+ $ xcodebuild [flags] | xcpretty -c && exit ${PIPESTATUS[0]}
33
+ ```
34
+
35
+ ## Raw xcodebuild output
36
+ You might want to use `xcpretty` together with `tee` to store the raw log in a
37
+ file, and get the pretty output in the terminal. This might be useful if you
38
+ want to inspect a failure in detail and aren't able to tell from the pretty
39
+ output.
40
+
41
+ Here's a way of doing it:
42
+ ``` bash
43
+ $ xcodebuild [flags] | tee xcodebuild.log | xcpretty -c
44
+ ```
45
+
46
+ ## Formats
47
+
48
+ - `--[no-]color`: Show build icons in color. (you can add it to `--simple` or `--test` format).
49
+ Defaults to auto-detecting color availability.
50
+ - `--[no-]utf`: Use unicode characters in build output or only ASCII.
51
+ Defaults to auto-detecting the current locale
52
+
53
+ - `--simple`, `-s` (default)
54
+ ![xcpretty --simple](http://i.imgur.com/LdmozBS.gif)
55
+
56
+ - `--test`, `-t` (RSpec style)
57
+ ![xcpretty alpha](http://i.imgur.com/VeTQQub.gif)
58
+ - `--tap` ([Test Anything Protocol](http://testanything.org)-compatible output)
59
+ - `--knock`, `-k` (a [simplified version](https://github.com/chneukirchen/knock) of the Test Anything Protocol)
60
+
61
+
62
+ ## Reporters
63
+
64
+ - `--report junit`, `-r junit`: Creates a JUnit-style XML report at `build/reports/junit.xml`, compatible with Jenkins and TeamCity CI.
65
+
66
+ - `--report html`, `-r html`: Creates a simple HTML report at `build/reports/tests.html`.
67
+ ![xcpretty html](http://i.imgur.com/0Rnux3v.gif)
68
+
69
+ - `--report json-compilation-database`, `-r json-compilation-database`: Creates a [JSON compilation database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) at `build/reports/compilation.json`. This is a format to replay single compilations independently of the build system.
70
+
71
+ Writing a report to a custom path can be specified using `--output PATH`.
72
+
73
+ ## Extensions
74
+
75
+ `xcpretty` supports custom formatters through the use of the
76
+ `--formatter` flag, which takes a path to a file as an argument. The
77
+ file must contain a Ruby subclass of `XCPretty::Formatter`, and
78
+ return that class at the end of te file. The class
79
+ can override the `format_*` methods to hook into output parsing
80
+ events.
81
+
82
+ ### Known extensions
83
+
84
+ * [xcpretty-travis-formatter](https://github.com/kattrali/xcpretty-travis-formatter): support for cleaner output in TravisCI using code folding
85
+
86
+ The recommended format is a gem containing the formatter and named
87
+ with an `xcpretty-` prefix, for easier discovery.
88
+
89
+
90
+ ## Team
91
+
92
+ - [Marin Usalj](http://github.com/supermarin) http://supermar.in
93
+ - [Delisa Mason](http://github.com/kattrali) http://delisa.me
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rubocop/rake_task'
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber/rake/task'
5
+
6
+ task :kick do
7
+ sh 'bundle exec kicker -r ruby'
8
+ end
9
+
10
+ Cucumber::Rake::Task.new(:cucumber) do |task|
11
+ end
12
+
13
+ RSpec::Core::RakeTask.new(:spec) do |task|
14
+ task.rspec_opts = %w(--color --format=doc)
15
+ end
16
+
17
+ RuboCop::RakeTask.new(:lint) do |task|
18
+ task.fail_on_error = false
19
+ end
20
+
21
+ task :ci do
22
+ Rake::Task[:spec].invoke
23
+ Rake::Task[:cucumber].invoke
24
+ Rake::Task[:lint].invoke
25
+ end
26
+
@@ -0,0 +1,172 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Test Results | xcpretty</title>
6
+ <style type="text/css">
7
+ body { font-family:Avenir Next, Helvetica Neue, sans-serif; color: #4A4A4A; background-color: #F0F3FB; margin:0;}
8
+ h1 { font-weight: normal; font-size: 24px; margin: 10px 0 0 0;}
9
+ h3 { font-weight: normal; margin: 2px; font-size: 1.1em;}
10
+ header { position: fixed;width: 100%;background: rgba(249, 254, 255, 0.9);margin: 0;padding: 10px;}
11
+ header:before, header:after { content:""; display:table;}
12
+ header:after { clear:both;}
13
+ a:link { color: #A1D761;}
14
+ footer { clear: both;position: relative;z-index: 10;height: 40px;margin-top: -10px; margin-left:30px; font-size:12px;}
15
+ table { width:100%; border-collapse: collapse;}
16
+ tr td:first-child { width:7%}
17
+ .left { float: left; margin-left:30px;}
18
+ .right { float: right; margin-right: 40px; margin-top: 0; margin-bottom:0;}
19
+ .test-suite { margin: 0 0 30px 0;}
20
+ .test-suite > .heading { font-family:Menlo, Monaco, monospace; font-weight: bold; border-color: #A1D761; background-color: #B8E986; border-width: 1px;}
21
+ .test-suite.failing > .heading { border-color: #C84F5E; background-color: #E58591;}
22
+ .test-suite > .heading > .title { margin-top: 4px; margin-left: 10px;}
23
+ .tests { overflow: scroll;margin: 0 30px 0 60px;}
24
+ .test, .test-suite > .heading { height: 30px; overflow: hidden; margin: 0 30px;}
25
+ .test, .test-suite > .heading { border-width: 1px; border-collapse: collapse; border-style: solid; }
26
+ .test { margin-left: 30px; border-top:none;}
27
+ .test.failing { border-color: #C84F5E; background-color: #F4DDE0;}
28
+ .test.passing { border-color: #A1D761;}
29
+ .test.failing { background-color: #E7A1AA;}
30
+ .test.passing { background-color: #CAF59F;}
31
+ .test.failing.odd { background-color: #EEC7CC;}
32
+ .test.passing.odd { background-color: #E5FBCF;}
33
+ .details { background-color: #F4DDE0; border: 1px solid #C84F5E;}
34
+ .test .test-detail:last-child { padding-bottom: 8px;}
35
+ .test .title { float: left; font-size: 0.9em; margin-top: 8px; font-family: Menlo, Monaco, monospace;}
36
+ .test .time { float: left;margin: 4px 10px 0 20px;}
37
+ .test-detail { font-family:Menlo, Monaco, monospace; font-size: 0.9em; margin: 5px 0 5px 0px;}
38
+ .screenshots { height: auto; overflow: hidden; padding: 4px 4px 0 4px; background-color: #B8E986; border: #A1D761; border-width: 0 1px; border-style: solid; }
39
+ .screenshots.failing { border-color: #C84F5E; background-color: #E58591; }
40
+ .screenshot { max-height: 60px; float: left; transition: max-height 0.2s; margin: 0 4px 4px 0 }
41
+ .screenshot.selected { max-height: 568px; }
42
+ #test-suites { display: inline-block; width: 100%;margin-top:100px;}
43
+ #segment-bar { margin-top: 10px;margin-left: 14px;float:right;}
44
+ #segment-bar a:first-child { border-radius: 9px 0 0 9px; border-right: none;}
45
+ #segment-bar a:last-child { border-radius: 0 9px 9px 0; border-left: none;}
46
+ #segment-bar > a { color: #565656; border: 2px solid #7B7B7B; width: 80px; font-weight: bold; display:inline-block;text-align:center; font-weight: normal;}
47
+ #segment-bar > a.selected { background-color: #979797; color: #F0F3FB;}
48
+ #counters { float: left;margin: 10px;text-align: right;}
49
+ #counters h2 { font-size: 16px; font-family: Avenir, sans-serif; font-weight: lighter; display:inline;}
50
+ #counters .number { font-size: 20px;}
51
+ #fail-count { color: #D0021B; margin-left:10px;}
52
+ @media (max-width: 640px) {
53
+ h1, #counters, #segment-bar { margin: 5px auto; text-align:center;}
54
+ header, #segment-bar { width: 100%; position: relative; background:none;}
55
+ .left, .right { float:none; margin:0;}
56
+ #test-suites { margin-top: 0;}
57
+ #counters { float:none;}
58
+ }
59
+ </style>
60
+ <script type="text/javascript">
61
+ var hide = function(element) { element.style.display = 'none';}
62
+ var show = function(element) { element.style.display = '';}
63
+ var isHidden = function(element) { return element.style.display == 'none';}
64
+ var isSelected = function(element) { return element.classList.contains("selected");}
65
+ var deselect = function(element) { return element.classList.remove("selected");}
66
+ var select = function(element) { return element.classList.add("selected");}
67
+ var toggle = function(element) { isHidden(element) ? show(element) : hide(element);};
68
+ var toggleTests = function(heading) { toggle(heading.parentNode.children[1]);};
69
+ var toggleDetails = function(detailClass) {
70
+ var details = document.querySelectorAll('.' + detailClass);
71
+ for (var i = details.length - 1; i >= 0; i--) { toggle(details[i]);};
72
+ };
73
+ var hideAll = function(collection) {
74
+ for (var i = collection.length - 1; i >= 0; i--) { hide(collection[i]); };
75
+ }
76
+ var showAll = function(collection) {
77
+ for (var i = collection.length - 1; i >= 0; i--) { show(collection[i]); };
78
+ }
79
+ var selectSegment = function(segment) {
80
+ if (isSelected(segment)) return;
81
+ var segments = document.querySelectorAll('#segment-bar > a');
82
+ for (var i = segments.length - 1; i >= 0; i--) { deselect(segments[i]);};
83
+ select(segment);
84
+ if (segment.id == "all-segment") {
85
+ showAll(document.querySelectorAll('.test-suite'));
86
+ showAll(document.querySelectorAll('.test'));
87
+ } else if (segment.id == "failing-segment") {
88
+ hideAll(document.querySelectorAll('.test.passing'));
89
+ showAll(document.querySelectorAll('.test.failing'));
90
+ hideAll(document.querySelectorAll('.test-suite.passing'));
91
+ showAll(document.querySelectorAll('.test-suite.failing'));
92
+ } else if (segment.id == "passing-segment") {
93
+ hideAll(document.querySelectorAll('.test.failing'));
94
+ showAll(document.querySelectorAll('.test.passing'));
95
+ hideAll(document.querySelectorAll('.test-suite.failing'));
96
+ showAll(document.querySelectorAll('.test-suite.passing'));
97
+ }
98
+ }
99
+ var toggleScreenshot = function(suiteName, index) {
100
+ var screenshot = document.getElementById("screenshot-" + suiteName + "-" + index);
101
+ isSelected(screenshot) ? deselect(screenshot) : select(screenshot);
102
+ }
103
+ </script>
104
+ </head>
105
+ <body>
106
+ <header>
107
+ <section class="left">
108
+ <h1>Test Results</h1>
109
+ </section>
110
+ <section class="right">
111
+ <section id="counters">
112
+ <h2 id="test-count"><span class="number"><%= test_count %></span> tests</h2>
113
+ <% if fail_count > 0 %>
114
+ <h2 id="fail-count"><span class="number"><%= fail_count %></span> failures</h2>
115
+ <% end %>
116
+ </section>
117
+ <section id="segment-bar">
118
+ <a id="all-segment" onclick="selectSegment(this);" class="selected">All</a><a id="failing-segment" onclick="selectSegment(this);">Failing</a><a id="passing-segment" onclick="selectSegment(this);">Passing</a>
119
+ </section>
120
+ </section>
121
+ </header>
122
+ <section id="test-suites">
123
+ <% test_suites.each do |name, info| %>
124
+ <% next unless info[:tests].size > 0 %>
125
+ <section class="test-suite <%= info[:failing] ? 'failing' : 'passing'%>" id="<%= name %>">
126
+ <section class="heading" onclick="toggleTests(this);">
127
+ <h3 class="title"><%= name %></h3>
128
+ </section>
129
+ <section class="tests">
130
+ <% unless info[:screenshots].empty? %>
131
+ <div class="screenshots <%= info[:failing] ? 'failing' : 'passing'%>">
132
+ <% info[:screenshots].each_with_index do |screenshot, index| %>
133
+ <a href="javascript:toggleScreenshot('<%=name %>', <%=index %>)">
134
+ <img class="screenshot" id="screenshot-<%=name %>-<%=index %>" src="<%=screenshot %>" />
135
+ </a>
136
+ <% end %>
137
+ </div>
138
+ <% end %>
139
+ <table>
140
+ <% info[:tests].each_with_index do |test, index| %>
141
+ <% detail_class = test[:name].gsub(/\s/,'') %>
142
+ <tr class="test <%= test[:failing] ? 'failing' : 'passing'%> <%= index % 2 != 0 ? 'odd' :''%>" onclick="toggleDetails('<%= detail_class %>');">
143
+ <td>
144
+ <% if test[:time] %>
145
+ <h3 class="time"><%= test[:time] %>s</h3>
146
+ <% end %>
147
+ </td>
148
+ <td><h3 class="title"><%= test[:name] %></h3></td>
149
+ </tr>
150
+ <% if test[:reason] || test[:snippet] %>
151
+ <tr class="details <%= detail_class %>">
152
+ <td></td>
153
+ <td>
154
+ <% if test[:reason] %>
155
+ <section class="test-detail reason"><%= test[:reason] %></section>
156
+ <% end %>
157
+ <% if test[:snippet] %>
158
+ <section class="test-detail snippet"><%= test[:snippet] %></section>
159
+ <section class="test-detail"><%= test[:file] %></section>
160
+ <% end %>
161
+ </td>
162
+ </tr>
163
+ <% end %>
164
+ <% end %>
165
+ </table>
166
+ </section>
167
+ </section>
168
+ <% end %>
169
+ </section>
170
+ <footer>Report generated with <a href="https://github.com/supermarin/xcpretty">xcpretty</a></footer>
171
+ </body>
172
+ </html>
data/bin/xcpretty ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if RUBY_VERSION < '2.0.0'
4
+ abort "error: XCPretty requires Ruby 2.0.0 or higher."
5
+ end
6
+
7
+ if $PROGRAM_NAME == __FILE__
8
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
9
+ end
10
+ require 'xcpretty'
11
+ require 'optparse'
12
+
13
+ report_options = []
14
+ report_classes = []
15
+ report_formats = {
16
+ "junit" => XCPretty::JUnit,
17
+ "html" => XCPretty::HTML,
18
+ "json-compilation-database" => XCPretty::JSONCompilationDatabase
19
+ }
20
+
21
+ printer_opts = {
22
+ unicode: XCPretty::Term.unicode?,
23
+ colorize: XCPretty::Term.color?,
24
+ formatter: XCPretty::Simple
25
+ }
26
+
27
+ OptionParser.new do |opts|
28
+ opts.banner = "Usage: xcodebuild [options] | xcpretty"
29
+ opts.on('-t', '--test', 'Use RSpec style output') do
30
+ printer_opts[:formatter] = XCPretty::RSpec
31
+ end
32
+ opts.on('-s', '--simple', 'Use simple output (default)') do
33
+ printer_opts[:formatter] = XCPretty::Simple
34
+ end
35
+ opts.on('-k', '--knock', 'Use knock output') do
36
+ printer_opts[:formatter] = XCPretty::Knock
37
+ end
38
+ opts.on('--tap', 'Use TAP output') do
39
+ printer_opts[:formatter] = XCPretty::TestAnything
40
+ end
41
+ opts.on('-f', '--formatter PATH', 'Use formatter returned from evaluating the specified Ruby file') do |path|
42
+ printer_opts[:formatter] = XCPretty.load_custom_formatter(path)
43
+ end
44
+ opts.on('--[no-]color', 'Use colorized output. Defaults is auto') do |value|
45
+ printer_opts[:colorize] = value
46
+ end
47
+ opts.on('--[no-]utf', 'Use unicode characters in output. Default is auto.') do |value|
48
+ printer_opts[:unicode] = value
49
+ end
50
+ opts.on("-r", "--report FORMAT", "Run FORMAT reporter",
51
+ " Choices: #{report_formats.keys.join(', ')}") do |format|
52
+ report_classes << report_formats[format]
53
+ report_options << {}
54
+ end
55
+ opts.on('-o', '--output PATH', 'Write report output to PATH') do |path|
56
+ unless opts = report_options.last
57
+ XCPretty.exit_with_error('Expected report format to be specified before output path')
58
+ end
59
+ opts[:path] = path
60
+ end
61
+ opts.on('--screenshots', 'Collect screenshots in the HTML report') do
62
+ unless opts = report_options.last
63
+ XCPretty.exit_with_error('Expected screenshot argument to be specified after report format')
64
+ end
65
+ opts[:screenshots] = true
66
+ end
67
+ opts.on_tail('-h', '--help', 'Show this message') { puts opts; exit }
68
+ opts.on_tail("-v", "--version", "Show version") { puts XCPretty::VERSION; exit }
69
+ opts.parse!
70
+
71
+ if STDIN.tty?
72
+ XCPretty.exit_with_error(opts.help)
73
+ end
74
+ end
75
+
76
+ printer = XCPretty::Printer.new(printer_opts)
77
+ reporters = report_classes.compact.each_with_index.map { |k, i| k.new(report_options[i]) }
78
+
79
+ STDIN.each_line do |line|
80
+ printer.pretty_print(line)
81
+ reporters.each { |r| r.handle(line) }
82
+ end
83
+
84
+ reporters.each(&:finish)
85
+
Binary file
@@ -0,0 +1,15 @@
1
+ Feature: Loading an arbitrary Ruby file as a formatter
2
+
3
+ Scenario: The file loaded does not contain a Ruby class
4
+ When I pipe to xcpretty with "--formatter /bin/bash"
5
+ Then the exit status code should be 1
6
+
7
+ Scenario: The file loaded contains a Ruby class
8
+ Given I have a file to compile
9
+ When I pipe to xcpretty with a custom formatter
10
+ Then the exit status code should be 0
11
+
12
+ Scenario: Showing output using a custom formatter
13
+ Given I have a file to compile
14
+ When I pipe to xcpretty with a custom formatter
15
+ Then I should see a custom compilation message