xcpretty 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
[](https://travis-ci.org/mneorr/xcpretty)
|
7
7
|
[](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
|

|
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
|
+

|
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
|