1 <?php
2 3 4 5
6
7
8 function hasPhpUnit() {
9 $paths = explode(PATH_SEPARATOR, ini_get('include_path'));
10 foreach($paths as $path) {
11 if(substr($path,-1) == DIRECTORY_SEPARATOR) $path = substr($path,0,-1);
12 if(@file_exists("$path/PHPUnit/Framework.php")) return true;
13 }
14 return false;
15 }
16
17 18
19 if(hasPhpUnit()) {
20 require_once 'PHPUnit/Framework.php';
21 require_once 'PHPUnit/Util/Report.php';
22 require_once 'PHPUnit/TextUI/TestRunner.php';
23 }
24
25 26 27 28 29 30 31 32 33 34 35
36 class TestRunner extends Controller {
37
38 private static $default_reporter;
39
40 static $url_handlers = array(
41 '' => 'browse',
42 'coverage/module/$ModuleName' => 'coverageModule',
43 'coverage/$TestCase!' => 'coverageOnly',
44 'coverage' => 'coverageAll',
45 'startsession' => 'startsession',
46 'endsession' => 'endsession',
47 'cleanupdb' => 'cleanupdb',
48 'module/$ModuleName' => 'module',
49 'all' => 'all',
50 'build' => 'build',
51 '$TestCase' => 'only',
52 );
53
54 55 56 57 58
59 static function set_reporter($reporter) {
60 if (is_string($reporter)) $reporter = new $reporter;
61 self::$default_reporter = $reporter;
62 }
63
64 function init() {
65 parent::init();
66
67 $canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN"));
68 if(!$canAccess) return Security::permissionFailure($this);
69
70 if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView');
71
72 if(!hasPhpUnit()) {
73 die("Please install PHPUnit using pear");
74 }
75 }
76
77 public function Link() {
78 return Controller::join_links(Director::absoluteBaseURL(), 'dev/tests/');
79 }
80
81 82 83 84
85 function all($coverage = false) {
86 ManifestBuilder::load_test_manifest();
87 $tests = ClassInfo::subclassesFor('SapphireTest');
88 array_shift($tests);
89 unset($tests['FunctionalTest']);
90
91
92 unset($tests['PhpSyntaxTest']);
93
94 foreach($tests as $class => $v) {
95 $reflection = new ReflectionClass($class);
96 if(!$reflection->isInstantiable()) unset($tests[$class]);
97 }
98
99 $this->runTests($tests, $coverage);
100 }
101
102 103 104
105 function build() {
106 ManifestBuilder::load_test_manifest();
107 $tests = ClassInfo::subclassesFor('SapphireTest');
108 array_shift($tests);
109 unset($tests['FunctionalTest']);
110 foreach($tests as $class => $v) {
111 $reflection = new ReflectionClass($class);
112 if(!$reflection->isInstantiable()) unset($tests[$class]);
113 }
114
115 $this->runTests($tests);
116
117 }
118
119
120 121 122
123 function browse() {
124 ManifestBuilder::load_test_manifest();
125 self::$default_reporter->writeHeader();
126 self::$default_reporter->writeInfo('Available Tests', false);
127 if(Director::is_cli()) {
128 $tests = ClassInfo::subclassesFor('SapphireTest');
129 $relativeLink = Director::makeRelative($this->Link());
130 echo "sake {$relativeLink}all: Run all " . count($tests) . " tests\n";
131 echo "sake {$relativeLink}coverage: Runs all tests and make test coverage report\n";
132 echo "sake {$relativeLink}module/<modulename>: Runs all tests in a module folder\n";
133 foreach ($tests as $test) {
134 echo "sake {$relativeLink}$test: Run $test\n";
135 }
136 } else {
137 echo '<div class="trace">';
138 $tests = ClassInfo::subclassesFor('SapphireTest');
139 asort($tests);
140 echo "<h3><a href=\"" . $this->Link() . "all\">Run all " . count($tests) . " tests</a></h3>";
141 echo "<h3><a href=\"" . $this->Link() . "coverage\">Runs all tests and make test coverage report</a></h3>";
142 echo "<hr />";
143 foreach ($tests as $test) {
144 echo "<h3><a href=\"" . $this->Link() . "$test\">Run $test</a></h3>";
145 }
146 echo '</div>';
147 }
148
149 self::$default_reporter->writeFooter();
150 }
151
152 153 154
155 function coverageAll() {
156 ManifestBuilder::load_all_classes();
157 $this->all(true);
158 }
159
160 161 162
163 function coverageOnly($request) {
164 $this->only($request, true);
165 }
166
167 168 169 170
171 function coverageModule($request) {
172 $this->module($request, true);
173 }
174
175 function cleanupdb() {
176 SapphireTest::delete_all_temp_dbs();
177 }
178
179 180 181
182 function only($request, $coverage = false) {
183 ManifestBuilder::load_test_manifest();
184 if($request->param('TestCase') == 'all') {
185 $this->all();
186 } else {
187 $classNames = explode(',', $request->param('TestCase'));
188 foreach($classNames as $className) {
189 if(!class_exists($className) || !is_subclass_of($className, 'SapphireTest')) {
190 user_error("TestRunner::only(): Invalid TestCase '$className', cannot find matching class", E_USER_ERROR);
191 }
192 }
193
194 $this->runTests($classNames, $coverage);
195 }
196 }
197
198 199 200 201
202 function module($request, $coverage = false) {
203 ManifestBuilder::load_test_manifest();
204 $classNames = array();
205 $moduleNames = explode(',', $request->param('ModuleName'));
206 foreach($moduleNames as $moduleName) {
207 $classesForModule = ClassInfo::classes_for_folder($moduleName);
208 if($classesForModule) foreach($classesForModule as $class) {
209 if(class_exists($class) && is_subclass_of($class, 'SapphireTest')) {
210 $classNames[] = $class;
211 }
212 }
213 }
214
215 $this->runTests($classNames, $coverage);
216 }
217
218 219 220 221
222 function runTests($classList, $coverage = false) {
223 $startTime = microtime(true);
224
225
226 if(function_exists('xdebug_disable')) xdebug_disable();
227
228 ini_set('max_execution_time', 0);
229
230 $this->setUp();
231
232
233 $skipTests = array();
234 if($this->request->getVar('SkipTests')) {
235 $skipTests = explode(',', $this->request->getVar('SkipTests'));
236 }
237 $classList = array_diff($classList, $skipTests);
238
239
240 $suite = new PHPUnit_Framework_TestSuite();
241 natcasesort($classList);
242 foreach($classList as $className) {
243
244 class_exists($className);
245 $suite->addTest(new SapphireTestSuite($className));
246 }
247
248
249 restore_error_handler();
250
251
252 if(Director::is_cli()) $reporter = new CliTestReporter();
253 else $reporter = new SapphireTestReporter();
254
255 self::$default_reporter->writeHeader("Sapphire Test Runner");
256 if (count($classList) > 1) {
257 self::$default_reporter->writeInfo("All Tests", "Running test cases: ",implode(", ", $classList));
258 } else {
259 self::$default_reporter->writeInfo($classList[0], "");
260 }
261
262 $results = new PHPUnit_Framework_TestResult();
263 $results->addListener($reporter);
264
265 if($coverage === true) {
266 $results->collectCodeCoverageInformation(true);
267 $suite->run($results);
268
269 if(!file_exists(ASSETS_PATH . '/coverage-report')) mkdir(ASSETS_PATH . '/coverage-report');
270 PHPUnit_Util_Report::render($results, ASSETS_PATH . '/coverage-report/');
271 $coverageApp = ASSETS_PATH . '/coverage-report/' . preg_replace('/[^A-Za-z0-9]/','_',preg_replace('/(\/$)|(^\/)/','',Director::baseFolder())) . '.html';
272 $coverageTemplates = ASSETS_PATH . '/coverage-report/' . preg_replace('/[^A-Za-z0-9]/','_',preg_replace('/(\/$)|(^\/)/','',realpath(TEMP_FOLDER))) . '.html';
273 echo "<p>Coverage reports available here:<ul>
274 <li><a href=\"$coverageApp\">Coverage report of the application</a></li>
275 <li><a href=\"$coverageTemplates\">Coverage report of the templates</a></li>
276 </ul>";
277 } else {
278 $suite->run($results);
279 }
280
281 if(!Director::is_cli()) echo '<div class="trace">';
282 $reporter->writeResults();
283
284 $endTime = microtime(true);
285 if(Director::is_cli()) echo "\n\nTotal time: " . round($endTime-$startTime,3) . " seconds\n";
286 else echo "<p>Total time: " . round($endTime-$startTime,3) . " seconds</p>\n";
287
288 if(!Director::is_cli()) echo '</div>';
289
290
291 Debug::loadErrorHandlers();
292
293 if(!Director::is_cli()) self::$default_reporter->writeFooter();
294
295 $this->tearDown();
296
297
298 if(Director::is_cli() && ($results->failureCount() + $results->errorCount()) > 0) exit(2);
299 }
300
301 302 303 304 305
306 function startsession() {
307 if(!Director::isLive()) {
308 if(SapphireTest::using_temp_db()) {
309 $endLink = Director::baseURL() . "/dev/tests/endsession";
310 return "<p><a id=\"end-session\" href=\"$endLink\">You're in the middle of a test session; click here to end it.</a></p>";
311
312 } else if(!isset($_GET['fixture'])) {
313 $me = Director::baseURL() . "/dev/tests/startsession";
314 return <<<HTML
315 <form action="$me">
316 <p>Enter a fixture file name to start a new test session. Don't forget to visit dev/tests/endsession when you're done!</p>
317 <p>Fixture file (leave blank to start with default set-up): <input id="fixture-file" name="fixture" /></p>
318 <input type="hidden" name="flush" value="1">
319 <p><input id="start-session" value="Start test session" type="submit" /></p>
320 </form>
321 HTML;
322 } else {
323 $fixtureFile = $_GET['fixture'];
324
325 if($fixtureFile) {
326
327 $realFile = realpath(BASE_PATH.'/'.$fixtureFile);
328 $baseDir = realpath(Director::baseFolder());
329 if(!$realFile || !file_exists($realFile)) {
330 return "<p>Fixture file doesn't exist</p>";
331 } else if(substr($realFile,0,strlen($baseDir)) != $baseDir) {
332 return "<p>Fixture file must be inside $baseDir</p>";
333 } else if(substr($realFile,-4) != '.yml') {
334 return "<p>Fixture file must be a .yml file</p>";
335 } else if(!preg_match('/^([^\/.][^\/]+)\/tests\//', $fixtureFile)) {
336 return "<p>Fixture file must be inside the tests subfolder of one of your modules.</p>";
337 }
338 }
339
340 $dbname = SapphireTest::create_temp_db();
341
342 DB::set_alternative_database_name($dbname);
343
344
345 if($fixtureFile) {
346 $fixture = new YamlFixture($fixtureFile);
347 $fixture->saveIntoDatabase();
348
349
350 } else {
351 $dataClasses = ClassInfo::subclassesFor('DataObject');
352 array_shift($dataClasses);
353 foreach($dataClasses as $dataClass) singleton($dataClass)->requireDefaultRecords();
354 }
355
356 return "<p>Started testing session with fixture '$fixtureFile'. Time to start testing; where would you like to start?</p>
357 <ul>
358 <li><a id=\"home-link\" href=\"" .Director::baseURL() . "\">Homepage - published site</a></li>
359 <li><a id=\"draft-link\" href=\"" .Director::baseURL() . "?stage=Stage\">Homepage - draft site</a></li>
360 <li><a id=\"admin-link\" href=\"" .Director::baseURL() . "admin/\">CMS Admin</a></li>
361 <li><a id=\"endsession-link\" href=\"" .Director::baseURL() . "dev/tests/endsession\">End your test session</a></li>
362 </ul>";
363 }
364
365 } else {
366 return "<p>startession can only be used on dev and test sites</p>";
367 }
368 }
369
370 function endsession() {
371 SapphireTest::kill_temp_db();
372 DB::set_alternative_database_name(null);
373
374 return "<p>Test session ended.</p>
375 <ul>
376 <li><a id=\"home-link\" href=\"" .Director::baseURL() . "\">Return to your site</a></li>
377 <li><a id=\"startsession-link\" href=\"" .Director::baseURL() . "dev/tests/startsession\">Start a new test session</a></li>
378 </ul>";
379 }
380
381 function setUp() {
382
383 SSViewer::flush_template_cache();
384 }
385
386 function tearDown() {
387 SapphireTest::kill_temp_db();
388 DB::set_alternative_database_name(null);
389 }
390 }
391
392
393 if(!hasPhpUnit()) {
394 395 396 397 398 399 400
401 class PHPUnit_Framework_TestCase {
402
403 }
404 }
405
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-