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 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
@@ -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,3 @@
1
+ guard 'phpunit', :cli => '--colors' do
2
+ watch(%r{^.+Test\.php$})
3
+ end
@@ -0,0 +1,6 @@
1
+ module Guard
2
+ # Guard::PHPUnitVersion is used in the gem specification.
3
+ module PHPUnitVersion
4
+ VERSION = "0.0.1"
5
+ end
6
+ 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: