Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • CliTestReporter
  • FunctionalTest
  • InstallerTest
  • JSTestRunner
  • PHPUnit_Framework_TestCase
  • SapphireTest
  • SapphireTestReporter
  • SapphireTestSuite
  • TestRunner
  • TestSession
  • TestSession_STResponseWrapper
  • TestViewer

Interfaces

  • TestOnly

Functions

  • hasPhpUnit
  1 <?php
  2 /**
  3  * @package sapphire
  4  * @subpackage testing
  5  */
  6 
  7 // Check that PHPUnit is installed
  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  * Controller that executes PHPUnit tests.
 27  *
 28  * <h2>URL Options</h2>
 29  * - SkipTests: A comma-separated list of test classes to skip (useful when running dev/tests/all)
 30  * 
 31  * See {@link browse()} output for generic usage instructions.
 32  * 
 33  * @package sapphire
 34  * @subpackage testing
 35  */
 36 class TestRunner extends Controller {
 37     /** @ignore */
 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      * Override the default reporter with a custom configured subclass.
 56      *
 57      * @param string $reporter
 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      * Run test classes that should be run with every commit.
 83      * Currently excludes PhpSyntaxTest
 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         // Remove tests that don't need to be executed every time
 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      * Run test classes that should be run before build - i.e., everything possible.
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      * Browse all enabled test cases in the environment
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      * Run a coverage test across all modules
154      */
155     function coverageAll() {
156         ManifestBuilder::load_all_classes();
157         $this->all(true);
158     }
159     
160     /**
161      * Run only a single coverage test class or a comma-separated list of tests
162      */
163     function coverageOnly($request) {
164         $this->only($request, true);
165     }
166     
167     /**
168      * Run coverage tests for one or more "modules".
169      * A module is generally a toplevel folder, e.g. "mysite" or "sapphire".
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      * Run only a single test class or a comma-separated list of tests
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      * Run tests for one or more "modules".
200      * A module is generally a toplevel folder, e.g. "mysite" or "sapphire".
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      * @param array $classList
220      * @param boolean $coverage
221      */
222     function runTests($classList, $coverage = false) {
223         $startTime = microtime(true);
224         
225         // XDEBUG seem to cause problems with test execution :-(
226         if(function_exists('xdebug_disable')) xdebug_disable();
227         
228         ini_set('max_execution_time', 0);       
229         
230         $this->setUp();
231         
232         // Optionally skip certain tests
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         // run tests before outputting anything to the client
240         $suite = new PHPUnit_Framework_TestSuite();
241         natcasesort($classList);
242         foreach($classList as $className) {
243             // Ensure that the autoloader pulls in the test class, as PHPUnit won't know how to do this.
244             class_exists($className);
245             $suite->addTest(new SapphireTestSuite($className));
246         }
247 
248         // Remove the error handler so that PHPUnit can add its own
249         restore_error_handler();
250 
251         /*, array("reportDirectory" => "/Users/sminnee/phpunit-report")*/
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         // Put the error handlers back
291         Debug::loadErrorHandlers();
292         
293         if(!Director::is_cli()) self::$default_reporter->writeFooter();
294         
295         $this->tearDown();
296         
297         // Todo: we should figure out how to pass this data back through Director more cleanly
298         if(Director::is_cli() && ($results->failureCount() + $results->errorCount()) > 0) exit(2);
299     }
300     
301     /**
302      * Start a test session.
303      * Usage: visit dev/tests/startsession?fixture=(fixturefile).  A test database will be constructed, and your browser session will be amended
304      * to use this database.  This can only be run on dev and test sites.
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                     // Validate fixture file
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                 // Fixture
345                 if($fixtureFile) {
346                     $fixture = new YamlFixture($fixtureFile);
347                     $fixture->saveIntoDatabase();
348                     
349                 // If no fixture, then use defaults
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         // The first DB test will sort out the DB, we don't have to
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 // This class is here to help with documentation.
393 if(!hasPhpUnit()) {
394     /**
395      * PHPUnit is a testing framework that can be installed using PEAR.
396      * It's not bundled with Sapphire, you will need to install it yourself.
397      * 
398      * @package sapphire
399      * @subpackage testing
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. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0