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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +27 -32
- data/assets/report.html.erb +155 -0
- data/bin/xcpretty +3 -4
- data/features/html_report.feature +40 -0
- data/features/junit_report.feature +1 -1
- data/features/steps/formatting_steps.rb +1 -5
- data/features/steps/html_steps.rb +23 -0
- data/features/steps/junit_steps.rb +4 -27
- data/features/steps/report_steps.rb +21 -0
- data/features/steps/xcpretty_steps.rb +0 -4
- data/features/support/env.rb +18 -0
- data/features/xcpretty.feature +0 -10
- data/lib/xcpretty/formatters/simple.rb +5 -3
- data/lib/xcpretty/reporters/html.rb +77 -0
- data/lib/xcpretty/syntax.rb +4 -5
- data/lib/xcpretty/version.rb +1 -1
- data/lib/xcpretty.rb +1 -20
- data/spec/xcpretty/formatters/simple_spec.rb +3 -3
- metadata +24 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46fc2cf7d4b723b36c5944ac2dae1e935f40987a
|
4
|
+
data.tar.gz: e703e40ee1a621283885b2efae96b6e8406f4f84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8d7effa24f7900014019d49d58c9bfc4b2bb4e9e0eec3db61e1432a4ac5f3a92a036c15c81e2a9092a549d6b47ff6c6cee57d97f22704392ee74c687230adf1
|
7
|
+
data.tar.gz: b39b856bbcc21faf8249df6014c6141861beb327d8236181169bc31ecebccb6eba02f8f4357652ba74f7a8ff28f1d4120a4d1a24e0b883c6443722ffa75bb98c
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
-
#
|
1
|
+
# xcpretty
|
2
2
|
|
3
|
-
|
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/
|
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
|
-
|
9
|
+
## Installation
|
10
|
+
``` bash
|
11
|
+
$ gem install xcpretty
|
12
|
+
```
|
13
13
|
|
14
14
|
## Usage
|
15
|
-
|
16
|
-
|
17
|
-
|
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:__
|
21
|
-
|
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
|
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
|
-
|
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
|
49
|
-
At this point
|
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
|
-
####
|
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
|
-
####
|
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
|
-
####
|
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
|
-
####
|
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
|
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
|
data/features/support/env.rb
CHANGED
@@ -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
|
data/features/xcpretty.feature
CHANGED
@@ -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
|
data/lib/xcpretty/syntax.rb
CHANGED
@@ -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
|
|
data/lib/xcpretty/version.rb
CHANGED
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.
|
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-
|
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
|
-
-
|
82
|
-
-
|
83
|
-
-
|
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.
|
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
|