xcpretty 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b78a4012d3b1df36cbd26eee764159f4a2ee5e14
4
- data.tar.gz: 8744a4e46db1e1983611a445b733208c4d12f177
3
+ metadata.gz: 46fc2cf7d4b723b36c5944ac2dae1e935f40987a
4
+ data.tar.gz: e703e40ee1a621283885b2efae96b6e8406f4f84
5
5
  SHA512:
6
- metadata.gz: 9e7ff5c8ce55088cd935aa120aec5bbe154f876201211d193345349372a5e3100a0f38c2d55c460e2c380ddebfee1f9f9d1e0eee1f0fc930d95a512e27fe1339
7
- data.tar.gz: b54c6a0c5d506252a6ae6eb07ee0ddbb40d468757f104decf4219b357527f40eb33f8ac8664a3b221de88d083c613b30f0c4534925489e85452f8443cc1d5daf
6
+ metadata.gz: a8d7effa24f7900014019d49d58c9bfc4b2bb4e9e0eec3db61e1432a4ac5f3a92a036c15c81e2a9092a549d6b47ff6c6cee57d97f22704392ee74c687230adf1
7
+ data.tar.gz: b39b856bbcc21faf8249df6014c6141861beb327d8236181169bc31ecebccb6eba02f8f4357652ba74f7a8ff28f1d4120a4d1a24e0b883c6443722ffa75bb98c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ ###### Enhancements
6
+
7
+ * Indented test runs by suite
8
+ * Added HTML reporter
9
+
10
+ ###### Misc
11
+
12
+ * removed the faux exit statuts hangling. use `exit ${PIPESTATUS[0]}`
13
+
14
+
3
15
  ## 0.1.2
4
16
 
5
17
  ###### Enhancements
data/README.md CHANGED
@@ -1,27 +1,28 @@
1
- # XCPretty
1
+ # xcpretty
2
2
 
3
- __XCPretty is a fast and flexible formatter for `xcodebuild`__.<br/>
3
+ __`xcpretty` is a fast and flexible formatter for `xcodebuild`__.<br/>
4
4
  It does one thing, and it should do it well.
5
5
 
6
- [![Build Status](https://travis-ci.org/mneorr/XCPretty.png?branch=master)](https://travis-ci.org/mneorr/XCPretty)
6
+ [![Build Status](https://travis-ci.org/mneorr/xcpretty.png?branch=master)](https://travis-ci.org/mneorr/xcpretty)
7
7
  [![Code Climate](https://codeclimate.com/github/mneorr/XCPretty.png)](https://codeclimate.com/github/mneorr/XCPretty)
8
- ## Installation
9
-
10
- $ gem install xcpretty
11
8
 
12
- XCPretty requires Ruby 1.8.7 or above.
9
+ ## Installation
10
+ ``` bash
11
+ $ gem install xcpretty
12
+ ```
13
13
 
14
14
  ## Usage
15
-
16
- XCPretty is designed to be piped with `xcodebuild` and thus keeping 100% compatibility with it.
17
- This means, when `xcodebuild` works, `xcpretty` works.
15
+ ``` bash
16
+ $ xcodebuild [flags] | xcpretty -c
17
+ ```
18
+ `xcpretty` is designed to be piped with `xcodebuild` and thus keeping 100% compatibility with it.
18
19
  It's even a bit faster than `xcodebuild` only, since it saves your terminal some prints.
19
20
 
20
- __Important:__ lots of CIs are using exit status for determining if build has
21
- failed. You probably want to exit with the same status code as `xcodebuild`.
21
+ __Important:__ If you're running `xcpretty` on a CI like Travis or Jenkins, you may want to exit with same status code as `xcodebuild`.
22
+ CI uses the status code to determine if build has failed.
22
23
 
23
- ```
24
- xcodebuild ... | xcpretty -c; exit ${PIPESTATUS[0]}
24
+ ``` bash
25
+ $ xcodebuild [flags] | xcpretty -c; exit ${PIPESTATUS[0]}
25
26
  ```
26
27
 
27
28
  ## Formats
@@ -33,37 +34,31 @@ xcodebuild ... | xcpretty -c; exit ${PIPESTATUS[0]}
33
34
  - `--test`, `-t` (RSpec style)
34
35
  ![xcpretty alpha](http://i.imgur.com/VeTQQub.gif)
35
36
 
36
- - tun / tap (not yet implemented. possible solution for most CI servers)
37
37
 
38
38
  ## Reporters
39
39
 
40
40
  - `--report junit`, `-r junit`: Creates a JUnit-style XML report at `build/reports/junit.xml`, compatible with Jenkins CI.
41
41
 
42
- ## Have you just cloned xctool?
42
+ - `--report html`, `-r html`: Creates a simple HTML report at `build/reports/tests.html`.
43
+ ![xcpretty html](http://i.imgur.com/0Rnux3v.gif)
44
+
45
+ Writing a report to a custom path can be specified using `--output PATH`.
46
+
47
+ ## Did you just clone xctool?
43
48
 
44
49
  Unlike [xctool](https://github.com/facebook/xctool), `xcpretty` isn't a build tool.
45
50
  It relies on `xcodebuild` to do the build process, and it formats the output.
46
51
 
47
52
  By the time when [xctool](https://github.com/facebook/xctool) was made, `xcodebuild`
48
- wasn't aware of the `test` command. That means, running tests in general via CLI was a pain.
49
- At this point, `xcodebuild` has got improved a lot, and it's ready to be used directly.
50
-
51
- ## Why?
52
-
53
- There are many usages of this tool. Let me give you some ideas:
54
- - Xcode's test tools are close to useless. Failures in a sidebar, non-dettachable console,... You can use `xcpretty` to build your next Xcode test runner plugin
55
- - Run tests each time you hit save. Use [xclisten](https://github.com/mneorr/xclisten) for that
56
- - Mine Bitcoins. You can't with this tool, but you'll be so productive that you can earn all the money and buy them!!!1!
53
+ wasn't aware of the `test` command, thus running tests in general via CLI was a pain.
54
+ At this point `xcodebuild` has been improved significantly, and is ready to be used directly.
57
55
 
58
- ## Roadmap
59
- - Improve test reporting, group tests semantically
60
- - Write original xcodebuild output with -o flag
61
56
 
62
57
  ## Benchmark
63
58
 
64
59
  A smaller project ([ObjectiveSugar](https://github.com/mneorr/objectivesugar)) with a fast suite
65
60
 
66
- #### XCPretty
61
+ #### xcpretty
67
62
  ```
68
63
  $ time xcodebuild -workspace ObjectiveSugar.xcworkspace -scheme ObjectiveSugar -sdk iphonesimulator test | xcpretty -tc
69
64
  ....................................................................................
@@ -80,7 +75,7 @@ Executed 84 tests, with 0 failures (0 unexpected) in 0.103 (0.129) seconds
80
75
 
81
76
  4.35 real 6.07 user 2.21 sys
82
77
  ```
83
- #### XCtool
78
+ #### xctool
84
79
  ```
85
80
  $ time xctool -workspace ObjectiveSugar.xcworkspace -scheme ObjectiveSugar -sdk iphonesimulator test
86
81
  ... ommitted output ...
@@ -91,7 +86,7 @@ $ time xctool -workspace ObjectiveSugar.xcworkspace -scheme ObjectiveSugar -sdk
91
86
 
92
87
  A bit bigger project, without CocoaPods ([ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa))
93
88
 
94
- #### XCPretty
89
+ #### xcpretty
95
90
  ```
96
91
  $ time xcodebuild -project ReactiveCocoa.xcodeproj -scheme ReactiveCocoa test | xcpretty -tc
97
92
  ..........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
@@ -108,7 +103,7 @@ Executed 922 tests, with 0 failures (0 unexpected) in 6.542 (6.913) seconds
108
103
 
109
104
  8.82 real 5.65 user 0.75 sys
110
105
  ```
111
- #### XCtool
106
+ #### xctool
112
107
  ```
113
108
  $ time xctool -project ReactiveCocoa.xcodeproj -scheme ReactiveCocoa test
114
109
  ... ommitted output ...
@@ -0,0 +1,155 @@
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
+ #test-suites { display: inline-block; width: 100%;margin-top:100px;}
39
+ #segment-bar { margin-top: 10px;margin-left: 14px;float:right;}
40
+ #segment-bar a:first-child { border-radius: 9px 0 0 9px; border-right: none;}
41
+ #segment-bar a:last-child { border-radius: 0 9px 9px 0; border-left: none;}
42
+ #segment-bar > a { color: #565656; border: 2px solid #7B7B7B; width: 80px; font-weight: bold; display:inline-block;text-align:center; font-weight: normal;}
43
+ #segment-bar > a.selected { background-color: #979797; color: #F0F3FB;}
44
+ #counters { float: left;margin: 10px;text-align: right;}
45
+ #counters h2 { font-size: 16px; font-family: Avenir, sans-serif; font-weight: lighter; display:inline;}
46
+ #counters .number { font-size: 20px;}
47
+ #fail-count { color: #D0021B; margin-left:10px;}
48
+ @media (max-width: 640px) {
49
+ h1, #counters, #segment-bar { margin: 5px auto; text-align:center;}
50
+ header, #segment-bar { width: 100%; position: relative; background:none;}
51
+ .left, .right { float:none; margin:0;}
52
+ #test-suites { margin-top: 0;}
53
+ #counters { float:none;}
54
+ }
55
+ </style>
56
+ <script type="text/javascript">
57
+ var hide = function(element) { element.style.display = 'none';}
58
+ var show = function(element) { element.style.display = '';}
59
+ var isHidden = function(element) { return element.style.display == 'none';}
60
+ var isSelected = function(element) { return element.classList.contains("selected");}
61
+ var deselect = function(element) { return element.classList.remove("selected");}
62
+ var select = function(element) { return element.classList.add("selected");}
63
+ var toggle = function(element) { isHidden(element) ? show(element) : hide(element);};
64
+ var toggleTests = function(heading) { toggle(heading.parentNode.children[1]);};
65
+ var toggleDetails = function(detailClass) {
66
+ var details = document.querySelectorAll('.' + detailClass);
67
+ for (var i = details.length - 1; i >= 0; i--) { toggle(details[i]);};
68
+ };
69
+ var hideAll = function(collection) {
70
+ for (var i = collection.length - 1; i >= 0; i--) { hide(collection[i]); };
71
+ }
72
+ var showAll = function(collection) {
73
+ for (var i = collection.length - 1; i >= 0; i--) { show(collection[i]); };
74
+ }
75
+ var selectSegment = function(segment) {
76
+ if (isSelected(segment)) return;
77
+ var segments = document.querySelectorAll('#segment-bar > a');
78
+ for (var i = segments.length - 1; i >= 0; i--) { deselect(segments[i]);};
79
+ select(segment);
80
+ if (segment.id == "all-segment") {
81
+ showAll(document.querySelectorAll('.test-suite'));
82
+ showAll(document.querySelectorAll('.test'));
83
+ } else if (segment.id == "failing-segment") {
84
+ hideAll(document.querySelectorAll('.test.passing'));
85
+ showAll(document.querySelectorAll('.test.failing'));
86
+ hideAll(document.querySelectorAll('.test-suite.passing'));
87
+ showAll(document.querySelectorAll('.test-suite.failing'));
88
+ } else if (segment.id == "passing-segment") {
89
+ hideAll(document.querySelectorAll('.test.failing'));
90
+ showAll(document.querySelectorAll('.test.passing'));
91
+ hideAll(document.querySelectorAll('.test-suite.failing'));
92
+ showAll(document.querySelectorAll('.test-suite.passing'));
93
+ }
94
+ }
95
+ </script>
96
+ </head>
97
+ <body>
98
+ <header>
99
+ <section class="left">
100
+ <h1>Test Results</h1>
101
+ </section>
102
+ <section class="right">
103
+ <section id="counters">
104
+ <h2 id="test-count"><span class="number"><%= test_count %></span> tests</h2>
105
+ <% if fail_count > 0 %>
106
+ <h2 id="fail-count"><span class="number"><%= fail_count %></span> failures</h2>
107
+ <% end %>
108
+ </section>
109
+ <section id="segment-bar">
110
+ <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>
111
+ </section>
112
+ </section>
113
+ </header>
114
+ <section id="test-suites">
115
+ <% test_suites.each do |name, info| %>
116
+ <% next unless info[:tests].size > 0 %>
117
+ <section class="test-suite <%= info[:failing] ? 'failing' : 'passing'%>" id="<%= name %>">
118
+ <section class="heading" onclick="toggleTests(this);">
119
+ <h3 class="title"><%= name %></h3>
120
+ </section>
121
+ <section class="tests">
122
+ <table>
123
+ <% info[:tests].each_with_index do |test, index| %>
124
+ <% detail_class = test[:name].gsub(/\s/,'') %>
125
+ <tr class="test <%= test[:failing] ? 'failing' : 'passing'%> <%= index % 2 != 0 ? 'odd' :''%>" onclick="toggleDetails('<%= detail_class %>');">
126
+ <td>
127
+ <% if test[:time] %>
128
+ <h3 class="time"><%= test[:time] %>s</h3>
129
+ <% end %>
130
+ </td>
131
+ <td><h3 class="title"><%= test[:name] %></h3></td>
132
+ </tr>
133
+ <% if test[:reason] || test[:snippet] %>
134
+ <tr class="details <%= detail_class %>">
135
+ <td></td>
136
+ <td>
137
+ <% if test[:reason] %>
138
+ <section class="test-detail reason"><%= test[:reason] %></section>
139
+ <% end %>
140
+ <% if test[:snippet] %>
141
+ <section class="test-detail snippet"><%= test[:snippet] %></section>
142
+ <section class="test-detail"><%= test[:file] %></section>
143
+ <% end %>
144
+ </td>
145
+ </tr>
146
+ <% end %>
147
+ <% end %>
148
+ </table>
149
+ </section>
150
+ </section>
151
+ <% end %>
152
+ </section>
153
+ <footer>Report generated with <a href="https://github.com/mneorr/xcpretty">xcpretty</a></footer>
154
+ </body>
155
+ </html>
data/bin/xcpretty CHANGED
@@ -13,7 +13,8 @@ require 'optparse'
13
13
  report_options = []
14
14
  report_classes = []
15
15
  report_formats = {
16
- "junit" => XCPretty::JUnit
16
+ "junit" => XCPretty::JUnit,
17
+ "html" => XCPretty::HTML
17
18
  }
18
19
 
19
20
  printer_opts = {
@@ -46,7 +47,7 @@ OptionParser.new do |opts|
46
47
  end
47
48
  opts.on('-o', '--output PATH', 'Write report output to PATH') do |path|
48
49
  unless opts = report_options.last
49
- XCPretty.exit_with_error('Expected report format to be speficied before before output path')
50
+ XCPretty.exit_with_error('Expected report format to be specified before before output path')
50
51
  end
51
52
  opts[:path] = path
52
53
  end
@@ -63,11 +64,9 @@ printer = XCPretty::Printer.new(printer_opts)
63
64
  reporters = report_classes.compact.each_with_index.map {|k,i| k.new(report_options[i])}
64
65
 
65
66
  ARGF.each_line do |line|
66
- XCPretty::ExitStatus.handle(line)
67
67
  printer.pretty_print(line)
68
68
  reporters.each { |r| r.handle(line) }
69
69
  end
70
70
 
71
71
  reporters.each(&:finish)
72
- exit(XCPretty::ExitStatus.code)
73
72
 
@@ -0,0 +1,40 @@
1
+ Feature: Creating a HTML test report
2
+
3
+ Background:
4
+ Given the tests have started running
5
+
6
+ Scenario: Showing a test suite
7
+ Given I have a passing test in my suite
8
+ When I pipe to xcpretty with "--report html"
9
+ Then I should see a test suite section in HTML
10
+
11
+ Scenario: Showing failed tests
12
+ Given I have a failing test in my suite
13
+ When I pipe to xcpretty with "--report html"
14
+ Then I should see a failed test in HTML
15
+ And the failure counter should show 1 test
16
+
17
+ Scenario: Showing passing tests
18
+ Given I have a passing test in my suite
19
+ When I pipe to xcpretty with "--report html"
20
+ Then I should see a passing test in HTML
21
+
22
+ Scenario: Counting tests
23
+ Given I have a passing test in my suite
24
+ And I have a failing test in my suite
25
+ And the test suite has finished
26
+ When I pipe to xcpretty with "--report html"
27
+ Then I should see 2 tests in HTML
28
+
29
+ Scenario: Having many test classes
30
+ Given I have tests in my suite from 2 classes
31
+ When I pipe to xcpretty with "--report html"
32
+ Then I should see 2 test suite sections in HTML
33
+
34
+ Scenario: Writing to a custom file path
35
+ When I pipe to xcpretty with "--report html" and specify a custom path
36
+ Then I should have a test report in a custom path
37
+
38
+ Scenario: Writing to multiple custom file paths
39
+ When I pipe to xcpretty with two custom "html" report paths
40
+ Then I should have test reports in two custom paths
@@ -35,5 +35,5 @@ Feature: Creating a JUnit test report
35
35
  Then I should have a test report in a custom path
36
36
 
37
37
  Scenario: Writing to multiple custom file paths
38
- When I pipe to xcpretty with two custom report paths
38
+ When I pipe to xcpretty with two custom "junit" report paths
39
39
  Then I should have test reports in two custom paths
@@ -109,10 +109,6 @@ When(/^I pipe to xcpretty with a custom formatter$/) do
109
109
  run_xcpretty("-f #{formatter_path}")
110
110
  end
111
111
 
112
- When(/^I pipe to xcpretty$/) do
113
- run_xcpretty("")
114
- end
115
-
116
112
  Then(/^I should see a custom compilation message$/) do
117
113
  run_output.should start_with("😎 Compilation party time")
118
114
  end
@@ -211,7 +207,7 @@ Then(/^I should see a green passing test mark$/) do
211
207
  end
212
208
 
213
209
  Then(/^I should see a non-utf prefixed output$/) do
214
- run_output.should start_with(green("."))
210
+ run_output.should start_with(" " + green("."))
215
211
  end
216
212
 
217
213
  Then(/^I should not see the name of the test group$/) do
@@ -0,0 +1,23 @@
1
+ Then(/^I should see a test suite section in HTML$/) do
2
+ html_test_suites.first.should_not be_nil
3
+ end
4
+
5
+ Then(/^I should see a failed test in HTML$/) do
6
+ html_report_body.get_elements("//*[contains(@class, 'test failing')]/").to_a.size.should_not == 0
7
+ end
8
+
9
+ Then(/^the failure counter should show (\d+) tests?$/) do |fail_count|
10
+ html_report_body.get_elements("//*[@id='fail-count']/").first.elements.to_a.first.text.to_i.should == fail_count.to_i
11
+ end
12
+
13
+ Then(/^I should see a passing test in HTML$/) do
14
+ html_report_body.get_elements("//*[contains(@class, 'test passing')]/").to_a.size.should_not == 0
15
+ end
16
+
17
+ Then(/^I should see (\d+) tests in HTML$/) do |test_count|
18
+ html_report_body.get_elements("//*[contains(@class, 'test ')]/").size.should == test_count.to_i
19
+ end
20
+
21
+ Then(/^I should see (\d+) test suite sections? in HTML$/) do |section_count|
22
+ html_test_suites.size.should == section_count.to_i
23
+ end
@@ -1,30 +1,3 @@
1
- Given(/^I have tests in my suite from 2 classes$/) do
2
- add_run_input SAMPLE_OCUNIT_TEST
3
- add_run_input SAMPLE_KIWI_TEST
4
- end
5
-
6
- When(/^I pipe to xcpretty with "(.*?)" and specify a custom path$/) do |args|
7
- step("I pipe to xcpretty with \"#{args} --output #{custom_report_path}\"")
8
- end
9
-
10
- When(/^I pipe to xcpretty with two custom report paths$/) do
11
- step("I pipe to xcpretty with \"--report junit --output #{custom_report_path} --report junit --output #{other_custom_report_path}\"")
12
- end
13
-
14
- Then(/^I should have test reports in two custom paths$/) do
15
- step("I should have a test report at \"#{custom_report_path}\"")
16
- step("I should have a test report at \"#{other_custom_report_path}\"")
17
- end
18
-
19
- Then(/^I should have a test report in a custom path$/) do
20
- step("I should have a test report at \"#{custom_report_path}\"")
21
- end
22
-
23
- Then(/^I should have a test report at "(.*?)"$/) do |path|
24
- doc = REXML::Document.new(File.open(path, 'r').read)
25
- doc.root.should_not be_nil
26
- end
27
-
28
1
  Then(/^I should see a failed test node in my report$/) do
29
2
  junit_report_root.elements.to_a.detect do |node|
30
3
  element = node.elements.to_a.first
@@ -53,3 +26,7 @@ Then(/^I should see (\d+) test suites$/) do |count|
53
26
  suites.select {|s| s.name == 'testsuite' }.size.should == count.to_i
54
27
  end
55
28
 
29
+ Then(/^I should have a test report at "(.*?)"$/) do |path|
30
+ doc = REXML::Document.new(File.open(path, 'r').read)
31
+ doc.root.should_not be_nil
32
+ end
@@ -0,0 +1,21 @@
1
+ Given(/^I have tests in my suite from 2 classes$/) do
2
+ add_run_input SAMPLE_OCUNIT_TEST
3
+ add_run_input SAMPLE_KIWI_TEST
4
+ end
5
+
6
+ When(/^I pipe to xcpretty with "(.*?)" and specify a custom path$/) do |args|
7
+ step("I pipe to xcpretty with \"#{args} --output #{custom_report_path}\"")
8
+ end
9
+
10
+ When(/^I pipe to xcpretty with two custom "(.*?)" report paths$/) do |type|
11
+ step("I pipe to xcpretty with \"--report #{type} --output #{custom_report_path} --report #{type} --output #{other_custom_report_path}\"")
12
+ end
13
+
14
+ Then(/^I should have test reports in two custom paths$/) do
15
+ step("I should have a test report at \"#{custom_report_path}\"")
16
+ step("I should have a test report at \"#{other_custom_report_path}\"")
17
+ end
18
+
19
+ Then(/^I should have a test report in a custom path$/) do
20
+ step("I should have a test report at \"#{custom_report_path}\"")
21
+ end
@@ -1,7 +1,3 @@
1
- Given(/^the build has failed$/) do
2
- add_run_input "/Users/musalj/code/OSS/ObjectiveSugar/Example/ObjectiveSugarTests/NSArrayCategoriesTests.m:53:13: error: use of undeclared identifier 'something'"
3
- end
4
-
5
1
  When(/^I run xcpretty$/) do
6
2
  @output = `bin/xcpretty 2>&1`
7
3
  end
@@ -9,6 +9,7 @@ require 'lib/xcpretty/syntax'
9
9
  require 'rexml/document'
10
10
  require 'lib/xcpretty/formatters/formatter'
11
11
  require 'lib/xcpretty/reporters/junit'
12
+ require 'lib/xcpretty/reporters/html'
12
13
 
13
14
  include XCPretty::ANSI
14
15
 
@@ -41,6 +42,21 @@ def run_output
41
42
  @output ||= ''
42
43
  end
43
44
 
45
+ def html_report
46
+ @html_report ||= REXML::Document.new(File.open(XCPretty::HTML::FILEPATH, 'r').read.sub("<!DOCTYPE html>",""))
47
+ end
48
+
49
+ def html_report_body
50
+ html_report.root.get_elements('//body').first
51
+ end
52
+
53
+ def html_test_suites
54
+ parent = html_report_body.get_elements("//*[@id='test-suites']/").first
55
+ parent.elements.to_a.select do |e|
56
+ e.attributes['class'] && e.attributes['class'].include?('test-suite')
57
+ end
58
+ end
59
+
44
60
  def junit_report
45
61
  REXML::Document.new(File.open(XCPretty::JUnit::FILEPATH, 'r').read)
46
62
  end
@@ -72,5 +88,7 @@ After do
72
88
  @output = ""
73
89
  @custom_report_file1.unlink if @custom_report_file1
74
90
  @custom_report_file2.unlink if @custom_report_file2
91
+ @html_report = nil
75
92
  FileUtils.rm_rf(XCPretty::JUnit::FILEPATH)
93
+ FileUtils.rm_rf(XCPretty::HTML::FILEPATH)
76
94
  end
@@ -1,15 +1,5 @@
1
1
  Feature: CLI behavior
2
2
 
3
- Scenario: Xcode tests have failed
4
- Given I have a failing test in my suite
5
- When I pipe to xcpretty
6
- Then the exit status code should be 1
7
-
8
- Scenario: Xcode build has failed
9
- Given the build has failed
10
- When I pipe to xcpretty
11
- Then the exit status code should be 1
12
-
13
3
  Scenario: Starting xcpretty without any flags
14
4
  When I run xcpretty
15
5
  Then I should see the help banner
@@ -15,6 +15,8 @@ module XCPretty
15
15
  ASCII_PENDING = "P"
16
16
  ASCII_COMPLETION = ">"
17
17
 
18
+ INDENT = " "
19
+
18
20
  def format_analyze(file_name, file_path)
19
21
  format("Analyzing", file_name)
20
22
  end
@@ -56,15 +58,15 @@ module XCPretty
56
58
  end
57
59
 
58
60
  def format_failing_test(suite, test_case, reason, file)
59
- format_test("#{test_case}, #{reason}", :fail)
61
+ INDENT + format_test("#{test_case}, #{reason}", :fail)
60
62
  end
61
63
 
62
64
  def format_passing_test(suite, test_case, time)
63
- format_test("#{test_case} (#{colored_time(time)} seconds)", :pass)
65
+ INDENT + format_test("#{test_case} (#{colored_time(time)} seconds)", :pass)
64
66
  end
65
67
 
66
68
  def format_pending_test(suite, test_case)
67
- format_test("#{test_case} [PENDING]", :pending)
69
+ INDENT + format_test("#{test_case} [PENDING]", :pending)
68
70
  end
69
71
 
70
72
  def format_phase_script_execution(script_name)
@@ -0,0 +1,77 @@
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
+
8
+ def load_dependencies
9
+ unless @@loaded ||= false
10
+ require 'fileutils'
11
+ require 'pathname'
12
+ require 'erb'
13
+ @@loaded = true
14
+ end
15
+ end
16
+
17
+ def initialize(options)
18
+ load_dependencies
19
+ @test_suites = {}
20
+ @filepath = options[:path] || FILEPATH
21
+ @parser = Parser.new(self)
22
+ @test_count = 0
23
+ @fail_count = 0
24
+ end
25
+
26
+ def handle(line)
27
+ @parser.parse(line)
28
+ end
29
+
30
+ def format_failing_test(suite, test_case, reason, file)
31
+ add_test(suite, {:name => test_case, :failing => true,
32
+ :reason => reason, :file => file, :snippet => formatted_snippet(file)})
33
+ end
34
+
35
+ def format_passing_test(suite, test_case, time)
36
+ add_test(suite, {:name => test_case, :time => time})
37
+ end
38
+
39
+ def finish
40
+ FileUtils.mkdir_p(File.dirname(@filepath))
41
+ write_report
42
+ end
43
+
44
+ private
45
+
46
+ def formatted_snippet filepath
47
+ file, line = filepath.split(':')
48
+ f = File.open(file)
49
+ line.to_i.times { f.gets }
50
+ text = $_.strip
51
+ f.close
52
+ Syntax.highlight(text, "-f html -O style=colorful -O noclasses")
53
+ rescue
54
+ nil
55
+ end
56
+
57
+ def add_test(suite_name, data)
58
+ @test_count += 1
59
+ @test_suites[suite_name] ||= {:tests => []}
60
+ @test_suites[suite_name][:tests] << data
61
+ if data[:failing]
62
+ @test_suites[suite_name][:failing] = true
63
+ @fail_count += 1
64
+ end
65
+ end
66
+
67
+ def write_report
68
+ File.open(@filepath, 'w') do |f|
69
+ test_suites = @test_suites
70
+ fail_count = @fail_count
71
+ test_count = @test_count
72
+ erb = ERB.new(File.open(TEMPLATE, 'r').read)
73
+ f.write erb.result(binding)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,8 +1,8 @@
1
1
  module XCPretty
2
2
  class Syntax
3
3
 
4
- def self.highlight(code)
5
- pygments_available? ? pygmentize(code) : code
4
+ def self.highlight(code, options="")
5
+ pygments_available? ? pygmentize(code, options) : code
6
6
  end
7
7
 
8
8
 
@@ -13,10 +13,9 @@ module XCPretty
13
13
  @available
14
14
  end
15
15
 
16
- def self.pygmentize(code)
17
- `echo "#{code}" | pygmentize -l objc`
16
+ def self.pygmentize(code, options)
17
+ `echo "#{code}" | pygmentize -l objc #{options}`
18
18
  end
19
-
20
19
  end
21
20
  end
22
21
 
@@ -1,3 +1,3 @@
1
1
  module XCPretty
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/xcpretty.rb CHANGED
@@ -5,28 +5,9 @@ require "xcpretty/formatters/formatter"
5
5
  require "xcpretty/formatters/simple"
6
6
  require "xcpretty/formatters/rspec"
7
7
  require "xcpretty/reporters/junit"
8
+ require "xcpretty/reporters/html"
8
9
 
9
10
  module XCPretty
10
- module ExitStatus
11
-
12
- include XCPretty::Matchers
13
-
14
- POSSIBLE_FAILURES = [
15
- FAILING_TEST_MATCHER,
16
- /\serror:\s/
17
- ]
18
-
19
- def self.code
20
- $exit_status || 0
21
- end
22
-
23
- def self.handle(text)
24
- POSSIBLE_FAILURES.detect do |failure|
25
- $exit_status = 1 if text =~ failure
26
- end
27
- end
28
-
29
- end
30
11
 
31
12
  def self.class_from_path(path)
32
13
  source = File.read(path)
@@ -67,17 +67,17 @@ module XCPretty
67
67
 
68
68
  it "formats failing tests" do
69
69
  @formatter.format_failing_test("RACCommandSpec", "enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES", "expected: 1, got: 0", 'path/to/file').should ==
70
- "x enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES, expected: 1, got: 0"
70
+ " x enabled_signal_should_send_YES_while_executing_is_YES_and_allowsConcurrentExecution_is_YES, expected: 1, got: 0"
71
71
  end
72
72
 
73
73
  it "formats passing tests" do
74
74
  @formatter.format_passing_test("RACCommandSpec", "_tupleByAddingObject__should_add_a_non_nil_object", "0.001").should ==
75
- ". _tupleByAddingObject__should_add_a_non_nil_object (0.001 seconds)"
75
+ " . _tupleByAddingObject__should_add_a_non_nil_object (0.001 seconds)"
76
76
  end
77
77
 
78
78
  it "formats pending tests" do
79
79
  @formatter.format_pending_test("RACCommandSpec", "_tupleByAddingObject__should_add_a_non_nil_object").should ==
80
- "P _tupleByAddingObject__should_add_a_non_nil_object [PENDING]"
80
+ " P _tupleByAddingObject__should_add_a_non_nil_object [PENDING]"
81
81
  end
82
82
 
83
83
  it "formats Phase Script Execution" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcpretty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marin Usalj
@@ -9,62 +9,62 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-20 00:00:00.000000000 Z
12
+ date: 2014-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ~>
19
19
  - !ruby/object:Gem::Version
20
20
  version: '1.3'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "~>"
25
+ - - ~>
26
26
  - !ruby/object:Gem::Version
27
27
  version: '1.3'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rake
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - '>='
33
33
  - !ruby/object:Gem::Version
34
34
  version: '0'
35
35
  type: :development
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - '>='
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rspec
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ">="
46
+ - - '>='
47
47
  - !ruby/object:Gem::Version
48
48
  version: '0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ">="
53
+ - - '>='
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: cucumber
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - ">="
60
+ - - '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - ">="
67
+ - - '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  description: "\n Xcodebuild formatter designed to be piped with `xcodebuild`,\n and
@@ -78,22 +78,26 @@ executables:
78
78
  extensions: []
79
79
  extra_rdoc_files: []
80
80
  files:
81
- - ".gitignore"
82
- - ".kick"
83
- - ".travis.yml"
81
+ - .gitignore
82
+ - .kick
83
+ - .travis.yml
84
84
  - CHANGELOG.md
85
85
  - CONTRIBUTING.md
86
86
  - Gemfile
87
87
  - LICENSE.txt
88
88
  - README.md
89
89
  - Rakefile
90
+ - assets/report.html.erb
90
91
  - bin/xcpretty
91
92
  - features/custom_formatter.feature
92
93
  - features/fixtures/xcodebuild.log
94
+ - features/html_report.feature
93
95
  - features/junit_report.feature
94
96
  - features/simple_format.feature
95
97
  - features/steps/formatting_steps.rb
98
+ - features/steps/html_steps.rb
96
99
  - features/steps/junit_steps.rb
100
+ - features/steps/report_steps.rb
97
101
  - features/steps/xcpretty_steps.rb
98
102
  - features/support/env.rb
99
103
  - features/test_format.feature
@@ -105,6 +109,7 @@ files:
105
109
  - lib/xcpretty/formatters/simple.rb
106
110
  - lib/xcpretty/parser.rb
107
111
  - lib/xcpretty/printer.rb
112
+ - lib/xcpretty/reporters/html.rb
108
113
  - lib/xcpretty/reporters/junit.rb
109
114
  - lib/xcpretty/syntax.rb
110
115
  - lib/xcpretty/version.rb
@@ -133,27 +138,30 @@ require_paths:
133
138
  - lib
134
139
  required_ruby_version: !ruby/object:Gem::Requirement
135
140
  requirements:
136
- - - ">="
141
+ - - '>='
137
142
  - !ruby/object:Gem::Version
138
143
  version: 1.8.7
139
144
  required_rubygems_version: !ruby/object:Gem::Requirement
140
145
  requirements:
141
- - - ">="
146
+ - - '>='
142
147
  - !ruby/object:Gem::Version
143
148
  version: '0'
144
149
  requirements: []
145
150
  rubyforge_project:
146
- rubygems_version: 2.2.0
151
+ rubygems_version: 2.0.3
147
152
  signing_key:
148
153
  specification_version: 4
149
154
  summary: xcodebuild formatter done right
150
155
  test_files:
151
156
  - features/custom_formatter.feature
152
157
  - features/fixtures/xcodebuild.log
158
+ - features/html_report.feature
153
159
  - features/junit_report.feature
154
160
  - features/simple_format.feature
155
161
  - features/steps/formatting_steps.rb
162
+ - features/steps/html_steps.rb
156
163
  - features/steps/junit_steps.rb
164
+ - features/steps/report_steps.rb
157
165
  - features/steps/xcpretty_steps.rb
158
166
  - features/support/env.rb
159
167
  - features/test_format.feature