guard-phpunit 0.0.1
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.
- data/LICENSE +19 -0
- data/README.md +91 -0
- data/lib/guard/phpunit/formatter.rb +110 -0
- data/lib/guard/phpunit/formatters/PHPUnit-Progress/PHPUnit/Extensions/Progress/ResultPrinter.php +498 -0
- data/lib/guard/phpunit/inspector.rb +54 -0
- data/lib/guard/phpunit/runner.rb +170 -0
- data/lib/guard/phpunit/templates/Guardfile +3 -0
- data/lib/guard/phpunit/version.rb +6 -0
- data/lib/guard/phpunit.rb +64 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by Maher Sallam
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
Guard::PHPUnit
|
2
|
+
==============
|
3
|
+
|
4
|
+
PHPUnit guard allows to automatically & intelligently launch tests when files
|
5
|
+
are modified.
|
6
|
+
|
7
|
+
Tested on MRI Ruby 1.8.7, 1.9.2 and 1.9.3.
|
8
|
+
|
9
|
+
Install
|
10
|
+
-------
|
11
|
+
|
12
|
+
Please be sure to have [Ruby][1] running on your machine.
|
13
|
+
The latest versions of Ruby come with a packages-manger called Gem. Gem can be used to
|
14
|
+
install various packages, including PHPUnit guard.
|
15
|
+
|
16
|
+
Before you continue, also make sure you have the [Guard][2] gem installed
|
17
|
+
|
18
|
+
To install the PHPUnit gem, run the following command in the terminal:
|
19
|
+
|
20
|
+
gem install guard-phpunit
|
21
|
+
|
22
|
+
Usage
|
23
|
+
-----
|
24
|
+
|
25
|
+
Please read the [Guard usage documentation][3].
|
26
|
+
|
27
|
+
Guardfile
|
28
|
+
---------
|
29
|
+
|
30
|
+
Guard::PHPUnit can be used with any kind of PHP projects that uses PHPUnit as
|
31
|
+
its testing framwork. Please read the [Guard documentation][3] for more information
|
32
|
+
about the Guardfile DSL.
|
33
|
+
|
34
|
+
By default, Guard::PHPUnit will use the current working directory (pwd) to
|
35
|
+
search for tests and run them on start (if you enabled the `:all_on_start` option).
|
36
|
+
|
37
|
+
### Example PHP project
|
38
|
+
|
39
|
+
The [PHPUnit documentaion][4] uses the [Object Freezer][5] library as an example on how
|
40
|
+
to organize tests. This project uses the `Tests` directory for its tests.
|
41
|
+
|
42
|
+
An example of the Guardfile for the same project would look
|
43
|
+
something like:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
guard 'phpunit', :tests_path => 'Tests', :cli => '--colors' do
|
47
|
+
# Watch tests files
|
48
|
+
watch(%r{^.+Test\.php$})
|
49
|
+
|
50
|
+
# Watch library files and run their tests
|
51
|
+
watch(%r{^Object/(.+)\.php}) { |m| "Tests/#{m[1]}Test.php" }
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Options
|
56
|
+
-------
|
57
|
+
|
58
|
+
The following options can be passed to Guard::PHPUnit:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
:all_on_start => false # Run all tests on startup.
|
62
|
+
# default: true
|
63
|
+
|
64
|
+
:tests_path # Relative path to the tests directory. This path
|
65
|
+
# is used when running all the tests.
|
66
|
+
# default: the current working directory (pwd)
|
67
|
+
|
68
|
+
:cli # The options passed to the phpunit command
|
69
|
+
# when running the tests.
|
70
|
+
# default: nil
|
71
|
+
```
|
72
|
+
|
73
|
+
Development
|
74
|
+
-----------
|
75
|
+
|
76
|
+
* Source hosted at [GitHub](https://github.com/Maher4Ever/guard-phpunit)
|
77
|
+
* Report issues/Questions/Feature requests on [GitHub Issues](https://github.com/Maher4Ever/guard-phpunit/issues)
|
78
|
+
|
79
|
+
Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change
|
80
|
+
you make.
|
81
|
+
|
82
|
+
Author
|
83
|
+
------
|
84
|
+
|
85
|
+
[Maher Sallam](https://github.com/Maher4Ever)
|
86
|
+
|
87
|
+
[1]:http://ruby-lang.org
|
88
|
+
[2]:https://github.com/guard/guard
|
89
|
+
[3]:https://github.com/guard/guard#readme
|
90
|
+
[4]:http://www.phpunit.de/manual/current/en/
|
91
|
+
[5]:https://github.com/sebastianbergmann/php-object-freezer/
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Guard
|
2
|
+
class PHPUnit
|
3
|
+
|
4
|
+
# The Guard::PHPUnit formatter parses the output
|
5
|
+
# of phpunit which gets printed by the progress
|
6
|
+
# printer and formats the parsed results
|
7
|
+
# for the notifier.
|
8
|
+
#
|
9
|
+
module Formatter
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Parses the tests output.
|
13
|
+
#
|
14
|
+
# @param [String] text the output of phpunit.
|
15
|
+
# @return [Hash] the parsed results
|
16
|
+
#
|
17
|
+
def parse_output(text)
|
18
|
+
results = {
|
19
|
+
:tests => look_for_words_in('test', text),
|
20
|
+
:failures => look_for_words_in('failure', text),
|
21
|
+
:errors => look_for_words_in('error', text),
|
22
|
+
:pending => look_for_words_in(['skipped', 'incomplete'], text),
|
23
|
+
:duration => look_for_duration_in(text)
|
24
|
+
}
|
25
|
+
results.freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
# Outputs a system notification.
|
29
|
+
#
|
30
|
+
# @param [Hash] test_results the parsed tests results
|
31
|
+
# @option test_results [Integer] :tests tests count
|
32
|
+
# @option test_results [Integer] :failures failures count
|
33
|
+
# @option test_results [Integer] :errors count count
|
34
|
+
# @option test_results [Integer] :pending pending tests count
|
35
|
+
# @option test_results [Integer] :duration tests duration
|
36
|
+
#
|
37
|
+
def notify(test_results)
|
38
|
+
::Guard::Notifier.notify(notifier_message(test_results), {
|
39
|
+
:title => 'PHPUnit results',
|
40
|
+
:image => notifier_image(test_results)
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Searches for a list of strings in the tests output
|
47
|
+
# and returns the total number assigned to these strings.
|
48
|
+
#
|
49
|
+
# @param [String, Array<String>] string_list the words
|
50
|
+
# @param [String] text the tests output
|
51
|
+
# @return [Integer] the total number assigned to the words
|
52
|
+
#
|
53
|
+
def look_for_words_in(strings_list, text)
|
54
|
+
count = 0
|
55
|
+
strings_list = Array(strings_list)
|
56
|
+
strings_list.each do |s|
|
57
|
+
text =~ %r{
|
58
|
+
(\d+) # count of what we are looking for
|
59
|
+
[ ] # then a space
|
60
|
+
#{s}s? # then the string
|
61
|
+
.* # then whatever
|
62
|
+
\Z # start looking at the end of the text
|
63
|
+
}x
|
64
|
+
count += $1.to_i unless $1.nil?
|
65
|
+
end
|
66
|
+
count
|
67
|
+
end
|
68
|
+
|
69
|
+
# Searches for the duration in the tests output
|
70
|
+
#
|
71
|
+
# @param [String] text the tests output
|
72
|
+
# @return [Integer] the duration
|
73
|
+
#
|
74
|
+
def look_for_duration_in(text)
|
75
|
+
text =~ %r{Finished in (\d)+ seconds?.*\Z}m
|
76
|
+
$1.nil? ? 0 : $1.to_i
|
77
|
+
end
|
78
|
+
|
79
|
+
# Formats the message for the Notifier.
|
80
|
+
#
|
81
|
+
# @param (see .notify)
|
82
|
+
# @return [String] the message
|
83
|
+
#
|
84
|
+
def notifier_message(results)
|
85
|
+
message = "#{results[:tests]} tests, #{results[:failures]} failures"
|
86
|
+
message << "\n#{results[:errors]} errors" if results[:errors] > 0
|
87
|
+
message << " (#{results[:pending]} pending)" if results[:pending] > 0
|
88
|
+
message << "\nin #{results[:duration]} seconds"
|
89
|
+
message
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the appropriate image for the tests results
|
93
|
+
#
|
94
|
+
# @param (see .notify)
|
95
|
+
# @return [Symbol] the image symbol
|
96
|
+
#
|
97
|
+
def notifier_image(results)
|
98
|
+
case
|
99
|
+
when results[:failures] + results[:errors] > 0
|
100
|
+
:failed
|
101
|
+
when results[:pending] > 0
|
102
|
+
:pending
|
103
|
+
else
|
104
|
+
:success
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/guard/phpunit/formatters/PHPUnit-Progress/PHPUnit/Extensions/Progress/ResultPrinter.php
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
<?php
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Prints tests' results in a similar way
|
5
|
+
* to rspec's progress formatter.
|
6
|
+
*
|
7
|
+
* @package PHPUnit
|
8
|
+
* @subpackage Progress
|
9
|
+
* @author Maher Sallam <maher@sallam.me>
|
10
|
+
* @copyright 2011 Maher Sallam <maher@sallam.me>
|
11
|
+
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
12
|
+
* @version 0.1
|
13
|
+
*/
|
14
|
+
class PHPUnit_Extensions_Progress_ResultPrinter extends PHPUnit_TextUI_ResultPrinter {
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Constructor.
|
18
|
+
*
|
19
|
+
* @param mixed $out
|
20
|
+
* @param boolean $verbose
|
21
|
+
* @param boolean $colors
|
22
|
+
* @param boolean $debug
|
23
|
+
*/
|
24
|
+
public function __construct($out = NULL, $verbose = FALSE, $colors = FALSE, $debug = FALSE) {
|
25
|
+
|
26
|
+
// Start capturing output
|
27
|
+
ob_start();
|
28
|
+
|
29
|
+
$argv = $_SERVER['argv'];
|
30
|
+
$colors = in_array('--colors', $argv) || $colors;
|
31
|
+
$verbose = in_array('--verbose', $argv) || in_array('-v', $argv) || $verbose;
|
32
|
+
$debug = in_array('--debug', $argv) || $debug;
|
33
|
+
|
34
|
+
parent::__construct($out, $verbose, $colors, $debug);
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* @param PHPUnit_Framework_TestResult $result
|
39
|
+
*/
|
40
|
+
public function printResult(PHPUnit_Framework_TestResult $result)
|
41
|
+
{
|
42
|
+
print "\n";
|
43
|
+
|
44
|
+
if ($result->errorCount() > 0) {
|
45
|
+
$this->printErrors($result);
|
46
|
+
}
|
47
|
+
|
48
|
+
if ($result->failureCount() > 0) {
|
49
|
+
$this->printFailures($result);
|
50
|
+
}
|
51
|
+
|
52
|
+
if ($this->verbose) {
|
53
|
+
if ($result->deprecatedFeaturesCount() > 0) {
|
54
|
+
if ($result->failureCount() > 0) {
|
55
|
+
print "\n--\n\nDeprecated PHPUnit features are being used";
|
56
|
+
}
|
57
|
+
|
58
|
+
foreach ($result->deprecatedFeatures() as $deprecatedFeature) {
|
59
|
+
$this->write($deprecatedFeature . "\n\n");
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
if ($result->notImplementedCount() > 0) {
|
64
|
+
$this->printIncompletes($result);
|
65
|
+
}
|
66
|
+
|
67
|
+
if ($result->skippedCount() > 0) {
|
68
|
+
$this->printSkipped($result);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
$this->printFooter($result);
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @param array $defects
|
77
|
+
* @param integer $count
|
78
|
+
* @param string $type
|
79
|
+
*/
|
80
|
+
protected function printDefects(array $defects, $count, $type)
|
81
|
+
{
|
82
|
+
if ($count == 0) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
|
86
|
+
$this->write("\n" . $type . ":\n");
|
87
|
+
|
88
|
+
$i = 1;
|
89
|
+
$failOrError = $type == 'Failures' || $type == 'Errors';
|
90
|
+
|
91
|
+
foreach ($defects as $defect) {
|
92
|
+
$this->printDefect($defect, $i++, $failOrError);
|
93
|
+
$this->write("\n");
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* @param PHPUnit_Framework_TestFailure $defect
|
99
|
+
* @param integer $count
|
100
|
+
* @param boolean $failOrError
|
101
|
+
*/
|
102
|
+
protected function printDefect(PHPUnit_Framework_TestFailure $defect, $count, $failOrError = true)
|
103
|
+
{
|
104
|
+
$this->printDefectHeader($defect, $count, $failOrError);
|
105
|
+
|
106
|
+
$padding = str_repeat(' ',
|
107
|
+
4 + ( $failOrError ? strlen((string)$count) : 0 )
|
108
|
+
);
|
109
|
+
|
110
|
+
$this->printDefectBody($defect, $count, $failOrError, $padding);
|
111
|
+
$this->printDefectTrace($defect, $padding);
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* @param PHPUnit_Framework_TestFailure $defect
|
116
|
+
* @param integer $count
|
117
|
+
* @param boolean $failOrError
|
118
|
+
*/
|
119
|
+
protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $count, $failOrError = true)
|
120
|
+
{
|
121
|
+
$failedTest = $defect->failedTest();
|
122
|
+
|
123
|
+
if ($failedTest instanceof PHPUnit_Framework_SelfDescribing) {
|
124
|
+
$testName = $failedTest->toString();
|
125
|
+
} else {
|
126
|
+
$testName = get_class($failedTest);
|
127
|
+
}
|
128
|
+
|
129
|
+
if ( $failOrError ) {
|
130
|
+
$this->write(
|
131
|
+
sprintf(
|
132
|
+
"\n %d) %s",
|
133
|
+
|
134
|
+
$count,
|
135
|
+
$testName
|
136
|
+
)
|
137
|
+
);
|
138
|
+
} else {
|
139
|
+
$this->write(
|
140
|
+
sprintf( " %s", $this->yellow($testName) )
|
141
|
+
);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* @param PHPUnit_Framework_TestFailure $defect
|
147
|
+
* @param integer $count
|
148
|
+
* @param boolean $failOrError
|
149
|
+
* @param string $padding
|
150
|
+
*/
|
151
|
+
protected function printDefectBody(PHPUnit_Framework_TestFailure $defect, $count, $failOrError, $padding)
|
152
|
+
{
|
153
|
+
$error = trim($defect->getExceptionAsString());
|
154
|
+
|
155
|
+
if ( !empty($error) ) {
|
156
|
+
$error = explode("\n", $error);
|
157
|
+
$error = "\n" . $padding . implode("\n " . $padding , $error);
|
158
|
+
|
159
|
+
$this->write( $failOrError ? $this->red($error) : $this->cyan($error) );
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
/**
|
164
|
+
* @param PHPUnit_Framework_TestFailure $defect
|
165
|
+
* @param string $padding
|
166
|
+
*/
|
167
|
+
protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect, $padding = 0)
|
168
|
+
{
|
169
|
+
$trace = trim(
|
170
|
+
PHPUnit_Util_Filter::getFilteredStacktrace(
|
171
|
+
$defect->thrownException()
|
172
|
+
)
|
173
|
+
);
|
174
|
+
|
175
|
+
if ( ! empty($trace) ) {
|
176
|
+
$trace = explode("\n", $trace);
|
177
|
+
$trace = "\n" . $padding . '# ' . implode("\n${padding}# ", $trace);
|
178
|
+
|
179
|
+
$this->write($this->cyan($trace));
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* @param PHPUnit_Framework_TestResult $result
|
185
|
+
*/
|
186
|
+
protected function printErrors(PHPUnit_Framework_TestResult $result)
|
187
|
+
{
|
188
|
+
$this->printDefects(
|
189
|
+
$result->errors(),
|
190
|
+
$result->errorCount(),
|
191
|
+
'Errors'
|
192
|
+
);
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* @param PHPUnit_Framework_TestResult $result
|
197
|
+
*/
|
198
|
+
protected function printFailures(PHPUnit_Framework_TestResult $result)
|
199
|
+
{
|
200
|
+
$this->printDefects(
|
201
|
+
$result->failures(),
|
202
|
+
$result->failureCount(),
|
203
|
+
'Failures'
|
204
|
+
);
|
205
|
+
}
|
206
|
+
|
207
|
+
/**
|
208
|
+
* @param PHPUnit_Framework_TestResult $result
|
209
|
+
*/
|
210
|
+
protected function printIncompletes(PHPUnit_Framework_TestResult $result)
|
211
|
+
{
|
212
|
+
$this->printDefects(
|
213
|
+
$result->notImplemented(),
|
214
|
+
$result->notImplementedCount(),
|
215
|
+
'Incomplete tests'
|
216
|
+
);
|
217
|
+
}
|
218
|
+
|
219
|
+
/**
|
220
|
+
* @param PHPUnit_Framework_TestResult $result
|
221
|
+
* @since Method available since Release 3.0.0
|
222
|
+
*/
|
223
|
+
protected function printSkipped(PHPUnit_Framework_TestResult $result)
|
224
|
+
{
|
225
|
+
$this->printDefects(
|
226
|
+
$result->skipped(),
|
227
|
+
$result->skippedCount(),
|
228
|
+
'Skipped tests'
|
229
|
+
);
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* @param PHPUnit_Framework_TestResult $result
|
234
|
+
*/
|
235
|
+
protected function printFooter(PHPUnit_Framework_TestResult $result)
|
236
|
+
{
|
237
|
+
|
238
|
+
$this->write( sprintf("\nFinished in %s\n", PHP_Timer::timeSinceStartOfRequest()) );
|
239
|
+
|
240
|
+
$resultsCount = count($result);
|
241
|
+
|
242
|
+
$footer = sprintf("%d test%s, %d assertion%s",
|
243
|
+
$resultsCount,
|
244
|
+
$resultsCount == 1 ? '' : 's',
|
245
|
+
$this->numAssertions,
|
246
|
+
$this->numAssertions == 1 ? '' : 's'
|
247
|
+
);
|
248
|
+
|
249
|
+
if ( $result->wasSuccessful() &&
|
250
|
+
$result->allCompletlyImplemented() &&
|
251
|
+
$result->noneSkipped() )
|
252
|
+
{
|
253
|
+
$this->write($this->green($footer));
|
254
|
+
}
|
255
|
+
|
256
|
+
else if ( ( !$result->allCompletlyImplemented() || !$result->noneSkipped() )
|
257
|
+
&&
|
258
|
+
$result->wasSuccessful() )
|
259
|
+
{
|
260
|
+
|
261
|
+
$footer .= sprintf(
|
262
|
+
"%s%s",
|
263
|
+
|
264
|
+
$this->getCountString(
|
265
|
+
$result->notImplementedCount(), 'incomplete'
|
266
|
+
),
|
267
|
+
$this->getCountString(
|
268
|
+
$result->skippedCount(), 'skipped'
|
269
|
+
)
|
270
|
+
);
|
271
|
+
|
272
|
+
$this->write($this->yellow($footer));
|
273
|
+
|
274
|
+
}
|
275
|
+
|
276
|
+
else {
|
277
|
+
|
278
|
+
$footer .= sprintf(
|
279
|
+
"%s%s%s%s",
|
280
|
+
|
281
|
+
$this->getCountString($result->failureCount(), 'failures'),
|
282
|
+
$this->getCountString($result->errorCount(), 'errors'),
|
283
|
+
$this->getCountString(
|
284
|
+
$result->notImplementedCount(), 'incomplete'
|
285
|
+
),
|
286
|
+
$this->getCountString($result->skippedCount(), 'skipped')
|
287
|
+
);
|
288
|
+
|
289
|
+
$footer = preg_replace('/,$/', '', $footer);
|
290
|
+
|
291
|
+
$this->write($this->red($footer));
|
292
|
+
}
|
293
|
+
|
294
|
+
if ( ! $this->verbose &&
|
295
|
+
$result->deprecatedFeaturesCount() > 0 )
|
296
|
+
{
|
297
|
+
$message = sprintf(
|
298
|
+
"Warning: Deprecated PHPUnit features are being used %s times!\n".
|
299
|
+
"Use --verbose for more information.\n",
|
300
|
+
$result->deprecatedFeaturesCount()
|
301
|
+
);
|
302
|
+
|
303
|
+
if ($this->colors) {
|
304
|
+
$message = "\x1b[37;41m\x1b[2K" . $message .
|
305
|
+
"\x1b[0m";
|
306
|
+
}
|
307
|
+
|
308
|
+
$this->write("\n" . $message);
|
309
|
+
}
|
310
|
+
|
311
|
+
$this->writeNewLine();
|
312
|
+
}
|
313
|
+
|
314
|
+
/**
|
315
|
+
* @param integer $count
|
316
|
+
* @param string $name
|
317
|
+
* @return string
|
318
|
+
* @since Method available since Release 3.0.0
|
319
|
+
*/
|
320
|
+
protected function getCountString($count, $name)
|
321
|
+
{
|
322
|
+
$string = '';
|
323
|
+
|
324
|
+
if ($count > 0) {
|
325
|
+
$string = sprintf(
|
326
|
+
', %d %s',
|
327
|
+
|
328
|
+
$count,
|
329
|
+
$name
|
330
|
+
);
|
331
|
+
}
|
332
|
+
|
333
|
+
return $string;
|
334
|
+
}
|
335
|
+
|
336
|
+
/**
|
337
|
+
* An error occurred.
|
338
|
+
*
|
339
|
+
* @param PHPUnit_Framework_Test $test
|
340
|
+
* @param Exception $e
|
341
|
+
* @param float $time
|
342
|
+
*/
|
343
|
+
public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
|
344
|
+
{
|
345
|
+
$this->writeProgress($this->red('E'));
|
346
|
+
$this->lastTestFailed = TRUE;
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* A failure occurred.
|
351
|
+
*
|
352
|
+
* @param PHPUnit_Framework_Test $test
|
353
|
+
* @param PHPUnit_Framework_AssertionFailedError $e
|
354
|
+
* @param float $time
|
355
|
+
*/
|
356
|
+
public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
|
357
|
+
{
|
358
|
+
$this->writeProgress($this->red('F'));
|
359
|
+
$this->lastTestFailed = TRUE;
|
360
|
+
}
|
361
|
+
|
362
|
+
/**
|
363
|
+
* Incomplete test.
|
364
|
+
*
|
365
|
+
* @param PHPUnit_Framework_Test $test
|
366
|
+
* @param Exception $e
|
367
|
+
* @param float $time
|
368
|
+
*/
|
369
|
+
public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
|
370
|
+
{
|
371
|
+
$this->writeProgress($this->yellow('I'));
|
372
|
+
$this->lastTestFailed = TRUE;
|
373
|
+
}
|
374
|
+
|
375
|
+
/**
|
376
|
+
* Skipped test.
|
377
|
+
*
|
378
|
+
* @param PHPUnit_Framework_Test $test
|
379
|
+
* @param Exception $e
|
380
|
+
* @param float $time
|
381
|
+
* @since Method available since Release 3.0.0
|
382
|
+
*/
|
383
|
+
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
|
384
|
+
{
|
385
|
+
$this->writeProgress($this->yellow('S'));
|
386
|
+
$this->lastTestFailed = TRUE;
|
387
|
+
}
|
388
|
+
|
389
|
+
/**
|
390
|
+
* A test ended.
|
391
|
+
*
|
392
|
+
* @param PHPUnit_Framework_Test $test
|
393
|
+
* @param float $time
|
394
|
+
*/
|
395
|
+
public function endTest(PHPUnit_Framework_Test $test, $time)
|
396
|
+
{
|
397
|
+
if (!$this->lastTestFailed) {
|
398
|
+
$this->writeProgress($this->green('.'));
|
399
|
+
}
|
400
|
+
|
401
|
+
if ($test instanceof PHPUnit_Framework_TestCase) {
|
402
|
+
$this->numAssertions += $test->getNumAssertions();
|
403
|
+
}
|
404
|
+
|
405
|
+
else if ($test instanceof PHPUnit_Extensions_PhptTestCase) {
|
406
|
+
$this->numAssertions++;
|
407
|
+
}
|
408
|
+
|
409
|
+
$this->lastTestFailed = FALSE;
|
410
|
+
|
411
|
+
if ($this->verbose && $test instanceof PHPUnit_Framework_TestCase) {
|
412
|
+
$this->write($test->getActualOutput());
|
413
|
+
}
|
414
|
+
|
415
|
+
}
|
416
|
+
|
417
|
+
/**
|
418
|
+
* @param string $progress
|
419
|
+
*/
|
420
|
+
protected function writeProgress($progress)
|
421
|
+
{
|
422
|
+
static $deletedHeader = false;
|
423
|
+
|
424
|
+
if ( ! $deletedHeader ) {
|
425
|
+
ob_clean();
|
426
|
+
$deletedHeader = true;
|
427
|
+
}
|
428
|
+
|
429
|
+
parent::writeProgress($progress);
|
430
|
+
}
|
431
|
+
|
432
|
+
/**
|
433
|
+
* Returns a colored string which can be used
|
434
|
+
* in the terminal.
|
435
|
+
*
|
436
|
+
* @param string $text
|
437
|
+
* @param integer $color_code
|
438
|
+
*/
|
439
|
+
protected function color($text, $color_code) {
|
440
|
+
return $this->colors ? "\033[${color_code}m" . $text . "\033[0m" : $text;
|
441
|
+
}
|
442
|
+
|
443
|
+
/**
|
444
|
+
* @param string $text
|
445
|
+
*/
|
446
|
+
protected function bold($text) {
|
447
|
+
return $this->color($text, "1");
|
448
|
+
}
|
449
|
+
|
450
|
+
/**
|
451
|
+
* @param string $text
|
452
|
+
*/
|
453
|
+
protected function red($text) {
|
454
|
+
return $this->color($text, "31");
|
455
|
+
}
|
456
|
+
|
457
|
+
/**
|
458
|
+
* @param string $text
|
459
|
+
*/
|
460
|
+
protected function green($text) {
|
461
|
+
return $this->color($text, "32");
|
462
|
+
}
|
463
|
+
|
464
|
+
/**
|
465
|
+
* @param string $text
|
466
|
+
*/
|
467
|
+
protected function yellow($text) {
|
468
|
+
return $this->color($text, "33");
|
469
|
+
}
|
470
|
+
|
471
|
+
/**
|
472
|
+
* @param string $text
|
473
|
+
*/
|
474
|
+
protected function blue($text) {
|
475
|
+
return $this->color($text, "34");
|
476
|
+
}
|
477
|
+
|
478
|
+
/**
|
479
|
+
* @param string $text
|
480
|
+
*/
|
481
|
+
protected function magenta($text) {
|
482
|
+
return $this->color($text, "35");
|
483
|
+
}
|
484
|
+
|
485
|
+
/**
|
486
|
+
* @param string $text
|
487
|
+
*/
|
488
|
+
protected function cyan($text) {
|
489
|
+
return $this->color($text, "36");
|
490
|
+
}
|
491
|
+
|
492
|
+
/**
|
493
|
+
* @param string $text
|
494
|
+
*/
|
495
|
+
protected function white($text) {
|
496
|
+
return $this->color($text, "37");
|
497
|
+
}
|
498
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Guard
|
2
|
+
class PHPUnit
|
3
|
+
|
4
|
+
# The Guard::PHPUnit inspector verfies that the changed paths
|
5
|
+
# are valid for Guard::PHPUnit.
|
6
|
+
#
|
7
|
+
module Inspector
|
8
|
+
class << self
|
9
|
+
|
10
|
+
attr_accessor :tests_path
|
11
|
+
|
12
|
+
# Clean the changed paths and return only valid
|
13
|
+
# PHPUnit tests files.
|
14
|
+
#
|
15
|
+
# @param [Array<String>] paths the changed paths
|
16
|
+
# @return [Array<String>] the valid tests files
|
17
|
+
#
|
18
|
+
def clean(paths)
|
19
|
+
paths.uniq!
|
20
|
+
paths.compact!
|
21
|
+
paths = paths.select { |p| test_file?(p) }
|
22
|
+
clear_tests_files_list
|
23
|
+
paths
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Checks if the paths is a valid test file.
|
29
|
+
#
|
30
|
+
# @param [String] path the test path
|
31
|
+
# @return [Boolean] whether the path a valid test or not
|
32
|
+
#
|
33
|
+
def test_file?(path)
|
34
|
+
tests_files.include?(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Scans the tests path and keeps a list of all
|
38
|
+
# tests paths.
|
39
|
+
#
|
40
|
+
def tests_files
|
41
|
+
@tests_files ||= Dir.glob( File.join(tests_path, '**', '*Test.php') )
|
42
|
+
end
|
43
|
+
|
44
|
+
# Clears the list of PHPUnit tests.
|
45
|
+
#
|
46
|
+
# @see #clean
|
47
|
+
#
|
48
|
+
def clear_tests_files_list
|
49
|
+
@tests_files = nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Guard
|
5
|
+
class PHPUnit
|
6
|
+
|
7
|
+
# The Guard::PHPUnit runner handles running the tests, displaying
|
8
|
+
# their output and notifying the user about the results.
|
9
|
+
#
|
10
|
+
module Runner
|
11
|
+
class << self
|
12
|
+
|
13
|
+
PHPUNIT_ERRORS_EXITCODE = 2
|
14
|
+
|
15
|
+
# Runs the PHPUnit tests and displays notifications
|
16
|
+
# about the results.
|
17
|
+
#
|
18
|
+
# @param [Array<Strings>] path to the tests files.
|
19
|
+
# @param (see PHPUnit#initialize)
|
20
|
+
# @return [Boolean] whether the tests were run successfully
|
21
|
+
#
|
22
|
+
def run(paths, options = {})
|
23
|
+
paths = Array(paths)
|
24
|
+
return false if paths.empty?
|
25
|
+
|
26
|
+
notify_start(paths, options)
|
27
|
+
output = run_tests(paths, options)
|
28
|
+
|
29
|
+
print_output output
|
30
|
+
|
31
|
+
# return false in case the system call fails with no status!
|
32
|
+
return false if $?.nil?
|
33
|
+
|
34
|
+
if $?.success? or tests_contain_errors?
|
35
|
+
notify_results(output, options)
|
36
|
+
else
|
37
|
+
notify_failure(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
$?.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Displays the start testing notification.
|
46
|
+
#
|
47
|
+
# @param (see #run)
|
48
|
+
# @param (see #run)
|
49
|
+
#
|
50
|
+
def notify_start(paths, options)
|
51
|
+
message = options[:message] || "Running: #{paths.join(' ')}"
|
52
|
+
UI.info(message, :reset => true)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Executes the testing commandon the tests
|
56
|
+
# and returns the output.
|
57
|
+
#
|
58
|
+
# @param (see #run)
|
59
|
+
# @param (see #run)
|
60
|
+
#
|
61
|
+
def run_tests(paths, options)
|
62
|
+
if paths.length == 1
|
63
|
+
tests_path = paths.first
|
64
|
+
output = execute_command phpunit_command(tests_path, options)
|
65
|
+
else
|
66
|
+
create_tests_folder_for(paths) do |tests_folder|
|
67
|
+
output = execute_command phpunit_command(tests_folder, options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
output
|
71
|
+
end
|
72
|
+
|
73
|
+
# Prints the tests output to the terminal.
|
74
|
+
#
|
75
|
+
# @param [String] output the tests output
|
76
|
+
#
|
77
|
+
def print_output(output)
|
78
|
+
UI.info output
|
79
|
+
end
|
80
|
+
|
81
|
+
# Displays a notification about the tests results.
|
82
|
+
#
|
83
|
+
# @param [String] output the tests output
|
84
|
+
# @param (see #run)
|
85
|
+
#
|
86
|
+
def notify_results(output, options)
|
87
|
+
return if options[:notification] == false
|
88
|
+
results = Formatter.parse_output(output)
|
89
|
+
Formatter.notify(results)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Displays a notification about failing to run the tests
|
93
|
+
#
|
94
|
+
# @param (see #run)
|
95
|
+
#
|
96
|
+
def notify_failure(options)
|
97
|
+
return if options[:notification] == false
|
98
|
+
::Guard::Notifier.notify('Failed! Check the console', :title => 'PHPUnit results', :image => :failed)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Checks the exitstatus of the phpunit command
|
102
|
+
# for a sign of errors in the tests.
|
103
|
+
#
|
104
|
+
# @return [Boolean] whether the tests contain errors or not
|
105
|
+
#
|
106
|
+
def tests_contain_errors?
|
107
|
+
$?.exitstatus == PHPUNIT_ERRORS_EXITCODE
|
108
|
+
end
|
109
|
+
|
110
|
+
# Creates a temporary folder which has links to
|
111
|
+
# the tests paths. This method is used because PHPUnit
|
112
|
+
# can't run multiple tests files at the same time and generate
|
113
|
+
# one result for them.
|
114
|
+
#
|
115
|
+
# @param (see #run)
|
116
|
+
# @yield [String] d the temporary dir for the tests
|
117
|
+
#
|
118
|
+
def create_tests_folder_for(paths)
|
119
|
+
Dir.mktmpdir('guard_phpunit') do |d|
|
120
|
+
symlink_paths_to_tests_folder(paths, d)
|
121
|
+
yield d
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Creates symbolic links inside the folder pointing
|
126
|
+
# back to the paths.
|
127
|
+
#
|
128
|
+
# @see #create_tests_folder_for
|
129
|
+
#
|
130
|
+
# @param (see #run)
|
131
|
+
# @param [String] the folder in which the links must be made
|
132
|
+
#
|
133
|
+
def symlink_paths_to_tests_folder(paths, folder)
|
134
|
+
paths.each do |p|
|
135
|
+
FileUtils.mkdir_p( File.join(folder, File.dirname(p) ) ) unless File.dirname(p) == '.'
|
136
|
+
FileUtils.ln_s(Pathname.new(p).realpath, File.join(folder, p))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Generates the phpunit command for the tests paths.
|
141
|
+
#
|
142
|
+
# @param (see #run)
|
143
|
+
# @param (see #run)
|
144
|
+
# @see #run_tests
|
145
|
+
#
|
146
|
+
def phpunit_command(path, options)
|
147
|
+
formatter_path = File.join( File.dirname(__FILE__), 'formatters', 'PHPUnit-Progress')
|
148
|
+
|
149
|
+
cmd_parts = []
|
150
|
+
cmd_parts << "phpunit"
|
151
|
+
cmd_parts << "--include-path #{formatter_path}"
|
152
|
+
cmd_parts << "--printer PHPUnit_Extensions_Progress_ResultPrinter"
|
153
|
+
cmd_parts << options[:cli] if options[:cli]
|
154
|
+
cmd_parts << path
|
155
|
+
|
156
|
+
cmd_parts.join(' ')
|
157
|
+
end
|
158
|
+
|
159
|
+
# Executes a system command and returns the output.
|
160
|
+
#
|
161
|
+
# @param [String] command the command to be run
|
162
|
+
# @return [String] the output of the executed command
|
163
|
+
#
|
164
|
+
def execute_command(command)
|
165
|
+
%x{#{command}}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'guard'
|
2
|
+
require 'guard/guard'
|
3
|
+
|
4
|
+
module Guard
|
5
|
+
|
6
|
+
# The PHPUnit guard gets notified about system
|
7
|
+
# events.
|
8
|
+
#
|
9
|
+
class PHPUnit < Guard
|
10
|
+
|
11
|
+
autoload :Inspector, 'guard/phpunit/inspector'
|
12
|
+
autoload :Formatter, 'guard/phpunit/formatter'
|
13
|
+
autoload :Runner, 'guard/phpunit/runner'
|
14
|
+
|
15
|
+
DEFAULT_OPTIONS = {
|
16
|
+
:all_on_start => true,
|
17
|
+
:tests_path => Dir.pwd
|
18
|
+
}
|
19
|
+
|
20
|
+
# Initialize Guard::PHPUnit.
|
21
|
+
#
|
22
|
+
# @param [Array<Guard::Watcher>] watchers the watchers in the Guard block
|
23
|
+
# @param [Hash] options the options for the Guard
|
24
|
+
# @option options [Boolean] :all_on_start run all tests on start
|
25
|
+
# @option options [String] :cli The CLI arguments passed to phpunit
|
26
|
+
# @option options [String] :tests_path the path where all tests exist
|
27
|
+
#
|
28
|
+
def initialize(watchers = [], options = {})
|
29
|
+
defaults = DEFAULT_OPTIONS.clone
|
30
|
+
@options = defaults.merge(options)
|
31
|
+
super(watchers, @options)
|
32
|
+
Inspector.tests_path = @options[:tests_path]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets called once when Guard starts.
|
36
|
+
#
|
37
|
+
# @raise [:task_has_failed] when stop has failed
|
38
|
+
#
|
39
|
+
def start
|
40
|
+
run_all if options[:all_on_start]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets called when all tests should be run.
|
44
|
+
#
|
45
|
+
# @raise (see #start)
|
46
|
+
#
|
47
|
+
def run_all
|
48
|
+
success = Runner.run(options[:tests_path], options.merge(
|
49
|
+
:message => 'Running all tests'
|
50
|
+
))
|
51
|
+
throw :task_has_failed unless success
|
52
|
+
end
|
53
|
+
|
54
|
+
# Gets called when the watched tests have changes.
|
55
|
+
#
|
56
|
+
# @param [Array<String>] paths to the changed tests
|
57
|
+
# @raise (see #start)
|
58
|
+
#
|
59
|
+
def run_on_change(paths)
|
60
|
+
success = Runner.run(paths, options)
|
61
|
+
throw :task_has_failed unless success
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: guard-phpunit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Maher Sallam
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: guard
|
16
|
+
requirement: &13084420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *13084420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bundler
|
27
|
+
requirement: &13083340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *13083340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &13082360 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.7'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *13082360
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: guard-rspec
|
49
|
+
requirement: &13081360 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.5'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *13081360
|
58
|
+
description: Guard::PHPUnit automatically run your unit-tests written with the PHPUnit
|
59
|
+
testing framework.
|
60
|
+
email:
|
61
|
+
- maher@sallam.me
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- lib/guard/phpunit.rb
|
67
|
+
- lib/guard/phpunit/formatter.rb
|
68
|
+
- lib/guard/phpunit/inspector.rb
|
69
|
+
- lib/guard/phpunit/runner.rb
|
70
|
+
- lib/guard/phpunit/templates/Guardfile
|
71
|
+
- lib/guard/phpunit/version.rb
|
72
|
+
- lib/guard/phpunit/formatters/PHPUnit-Progress/PHPUnit/Extensions/Progress/ResultPrinter.php
|
73
|
+
- LICENSE
|
74
|
+
- README.md
|
75
|
+
homepage: ''
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.3.6
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project: guard-phpunit
|
95
|
+
rubygems_version: 1.8.10
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Guard gem for PHPUnit
|
99
|
+
test_files: []
|
100
|
+
has_rdoc:
|