1 <?php
2 require_once 'TestRunner.php';
3 if(hasPhpUnit()) {
4 require_once 'PHPUnit/Framework.php';
5 }
6
7 8 9 10 11 12 13
14 class SapphireTest extends PHPUnit_Framework_TestCase {
15 16 17 18 19 20 21 22
23 static $fixture_file = null;
24
25 protected $originalMailer;
26 protected $originalMemberPasswordValidator;
27 protected $originalRequirements;
28 protected $originalIsRunningTest;
29 protected $originalTheme;
30 protected $originalNestedURLsState;
31
32 protected $mailer;
33
34 protected static $is_running_test = false;
35
36 37 38 39 40
41 protected $requireDefaultRecordsFrom = array();
42
43
44 45 46 47 48 49 50
51 protected $illegalExtensions = array(
52 );
53
54 55 56 57 58 59 60
61 protected $requiredExtensions = array(
62 );
63
64 65 66 67 68
69 protected = array();
70
71 72 73 74 75 76
77 protected $backupGlobals = FALSE;
78
79 80 81
82 private $extensionsToReapply = array(), $extensionsToRemove = array();
83
84 public static function is_running_test() {
85 return self::$is_running_test;
86 }
87
88 89 90
91 protected $fixtures;
92
93 function setUp() {
94
95 $this->originalIsRunningTest = self::$is_running_test;
96 self::$is_running_test = true;
97
98
99 $this->originalMemberPasswordValidator = Member::password_validator();
100 $this->originalRequirements = Requirements::backend();
101 Member::set_password_validator(null);
102 Cookie::set_report_errors(false);
103
104 RootURLController::reset();
105 Translatable::reset();
106 Versioned::reset();
107 DataObject::reset();
108 SiteTree::reset();
109 Controller::curr()->setSession(new Session(array()));
110
111 $this->originalTheme = SSViewer::current_theme();
112
113
114 $this->originalNestedURLsState = SiteTree::nested_urls();
115
116 $className = get_class($this);
117 $fixtureFile = eval("return {$className}::\$fixture_file;");
118
119
120 if($fixtureFile || !self::using_temp_db()) {
121 if(substr(DB::getConn()->currentDatabase(),0,5) != 'tmpdb') {
122
123 self::create_temp_db();
124
125 }
126
127 singleton('DataObject')->flushCache();
128
129 self::empty_temp_db();
130
131 foreach($this->requireDefaultRecordsFrom as $className) {
132 $instance = singleton($className);
133 if (method_exists($instance, 'requireDefaultRecords')) $instance->requireDefaultRecords();
134 if (method_exists($instance, 'augmentDefaultRecords')) $instance->augmentDefaultRecords();
135 }
136
137 if($fixtureFile) {
138 $fixtureFiles = (is_array($fixtureFile)) ? $fixtureFile : array($fixtureFile);
139
140 $i = 0;
141 foreach($fixtureFiles as $fixtureFilePath) {
142 $fixture = new YamlFixture($fixtureFilePath);
143 $fixture->saveIntoDatabase();
144 $this->fixtures[] = $fixture;
145
146
147 if($i == 0) $this->fixture = $fixture;
148 $i++;
149 }
150 }
151
152 $this->logInWithPermission("ADMIN");
153 }
154
155
156 $this->originalMailer = Email::mailer();
157 $this->mailer = new TestMailer();
158 Email::set_mailer($this->mailer);
159 Email::send_all_emails_to(null);
160 }
161
162 163 164 165 166 167 168 169
170 function setUpOnce() {
171
172 foreach($this->illegalExtensions as $class => $extensions) {
173 foreach($extensions as $extension) {
174 if (Object::has_extension($class, $extension)) {
175 if(!isset($this->extensionsToReapply[$class])) $this->extensionsToReapply[$class] = array();
176 $this->extensionsToReapply[$class][] = $extension;
177 Object::remove_extension($class, $extension);
178 $isAltered = true;
179 }
180 }
181 }
182
183
184 foreach($this->requiredExtensions as $class => $extensions) {
185 $this->extensionsToRemove[$class] = array();
186 foreach($extensions as $extension) {
187 if(!Object::has_extension($class, $extension)) {
188 if(!isset($this->extensionsToRemove[$class])) $this->extensionsToReapply[$class] = array();
189 $this->extensionsToRemove[$class][] = $extension;
190 Object::add_extension($class, $extension);
191 $isAltered = true;
192 }
193 }
194 }
195
196
197 if($this->extensionsToReapply || $this->extensionsToRemove || $this->extraDataObjects) {
198 if(!self::using_temp_db()) self::create_temp_db();
199 $this->resetDBSchema(true);
200 }
201
202
203 global $_SINGLETONS;
204 $_SINGLETONS = array();
205 }
206
207 208 209
210 function tearDownOnce() {
211
212 if($this->extensionsToReapply || $this->extensionsToRemove) {
213
214 foreach($this->extensionsToRemove as $class => $extensions) {
215 foreach($extensions as $extension) {
216 Object::remove_extension($class, $extension);
217 }
218 }
219
220
221 foreach($this->extensionsToReapply as $class => $extensions) {
222 foreach($extensions as $extension) {
223 Object::add_extension($class, $extension);
224 }
225 }
226 }
227
228 if($this->extensionsToReapply || $this->extensionsToRemove || $this->extraDataObjects) {
229 $this->resetDBSchema();
230 }
231 }
232
233 234 235
236 protected $fixtureDictionary;
237
238
239 240 241 242 243 244
245 protected function idFromFixture($className, $identifier) {
246 if(!$this->fixtures) {
247 user_error("You've called idFromFixture() but you haven't specified static \$fixture_file.\n", E_USER_WARNING);
248 return;
249 }
250
251 foreach($this->fixtures as $fixture) {
252 $match = $fixture->idFromFixture($className, $identifier);
253 if($match) return $match;
254 }
255
256 $fixtureFiles = Object::get_static(get_class($this), 'fixture_file');
257 user_error(sprintf(
258 "Couldn't find object '%s' (class: %s) in files %s",
259 $identifier,
260 $className,
261 (is_array($fixtureFiles)) ? implode(',', $fixtureFiles) : $fixtureFiles
262 ), E_USER_ERROR);
263
264 return false;
265 }
266
267 268 269 270 271 272 273
274 protected function allFixtureIDs($className) {
275 if(!$this->fixtures) {
276 user_error("You've called allFixtureIDs() but you haven't specified static \$fixture_file.\n", E_USER_WARNING);
277 return;
278 }
279
280 $ids = array();
281 foreach($this->fixtures as $fixture) {
282 $ids += $fixture->allFixtureIDs($className);
283 }
284
285 return $ids;
286 }
287
288 289 290 291 292
293 protected function objFromFixture($className, $identifier) {
294 if(!$this->fixtures) {
295 user_error("You've called objFromFixture() but you haven't specified static \$fixture_file.\n", E_USER_WARNING);
296 return;
297 }
298
299 foreach($this->fixtures as $fixture) {
300 $match = $fixture->objFromFixture($className, $identifier);
301 if($match) return $match;
302 }
303
304 $fixtureFiles = Object::get_static(get_class($this), 'fixture_file');
305 user_error(sprintf(
306 "Couldn't find object '%s' (class: %s) in files %s",
307 $identifier,
308 $className,
309 (is_array($fixtureFiles)) ? implode(',', $fixtureFiles) : $fixtureFiles
310 ), E_USER_ERROR);
311
312 return false;
313 }
314
315 316 317 318 319 320 321
322 function loadFixture($fixtureFile) {
323 $parser = new Spyc();
324 $fixtureContent = $parser->load(Director::baseFolder().'/'.$fixtureFile);
325
326 $fixture = new YamlFixture($fixtureFile);
327 $fixture->saveIntoDatabase();
328 $this->fixtures[] = $fixture;
329 }
330
331 332 333 334
335 function clearFixtures() {
336 $this->fixtures = array();
337 }
338
339 function tearDown() {
340
341 Email::set_mailer($this->originalMailer);
342 $this->originalMailer = null;
343 $this->mailer = null;
344
345
346 Member::set_password_validator($this->originalMemberPasswordValidator);
347
348
349 Requirements::set_backend($this->originalRequirements);
350
351
352 self::$is_running_test = $this->originalIsRunningTest;
353 $this->originalIsRunningTest = null;
354
355
356 SSViewer::set_theme($this->originalTheme);
357
358
359 SS_Datetime::clear_mock_now();
360
361
362 if ( $this->originalNestedURLsState )
363 SiteTree::enable_nested_urls();
364 else
365 SiteTree::disable_nested_urls();
366
367
368
369
370 $controller = Controller::curr();
371 if ( $controller && $controller->response && $controller->response->getHeader('Location') ) {
372 $controller->response->setStatusCode(200);
373 $controller->response->removeHeader('Location');
374 }
375 }
376 377 378
379 function clearEmails() {
380 return $this->mailer->clearEmails();
381 }
382
383 384 385 386 387 388 389 390 391
392 function findEmail($to, $from = null, $subject = null, $content = null) {
393 return $this->mailer->findEmail($to, $from, $subject, $content);
394 }
395
396 397 398 399 400 401 402 403 404
405 function assertEmailSent($to, $from = null, $subject = null, $content = null) {
406
407 if(!$this->findEmail($to, $from, $subject, $content)) {
408
409 $infoParts = "";
410 $withParts = array();
411 if($to) $infoParts .= " to '$to'";
412 if($from) $infoParts .= " from '$from'";
413 if($subject) $withParts[] = "subject '$subject'";
414 if($content) $withParts[] = "content '$content'";
415 if($withParts) $infoParts .= " with " . implode(" and ", $withParts);
416
417 throw new PHPUnit_Framework_AssertionFailedError(
418 "Failed asserting that an email was sent$infoParts."
419 );
420 }
421 }
422
423
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
444 function assertDOSContains($matches, $dataObjectSet) {
445 $extracted = array();
446 foreach($dataObjectSet as $item) $extracted[] = $item->toMap();
447
448 foreach($matches as $match) {
449 $matched = false;
450 foreach($extracted as $i => $item) {
451 if($this->dataObjectArrayMatch($item, $match)) {
452
453 unset($extracted[$i]);
454 $matched = true;
455 break;
456 }
457 }
458
459
460 if(!$matched) {
461 throw new PHPUnit_Framework_AssertionFailedError(
462 "Failed asserting that the DataObjectSet contains an item matching "
463 . var_export($match, true) . "\n\nIn the following DataObjectSet:\n"
464 . $this->DOSSummaryForMatch($dataObjectSet, $match)
465 );
466 }
467
468 }
469 }
470
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
488 function assertDOSEquals($matches, $dataObjectSet) {
489 if(!$dataObjectSet) return false;
490
491 $extracted = array();
492 foreach($dataObjectSet as $item) $extracted[] = $item->toMap();
493
494 foreach($matches as $match) {
495 $matched = false;
496 foreach($extracted as $i => $item) {
497 if($this->dataObjectArrayMatch($item, $match)) {
498
499 unset($extracted[$i]);
500 $matched = true;
501 break;
502 }
503 }
504
505
506 if(!$matched) {
507 throw new PHPUnit_Framework_AssertionFailedError(
508 "Failed asserting that the DataObjectSet contains an item matching "
509 . var_export($match, true) . "\n\nIn the following DataObjectSet:\n"
510 . $this->DOSSummaryForMatch($dataObjectSet, $match)
511 );
512 }
513 }
514
515
516 if($extracted) {
517
518 throw new PHPUnit_Framework_AssertionFailedError(
519 "Failed asserting that the DataObjectSet contained only the given items, the "
520 . "following items were left over:\n" . var_export($extracted, true)
521 );
522 }
523 }
524
525 526 527 528 529 530 531 532 533 534 535 536
537 function assertDOSAllMatch($match, $dataObjectSet) {
538 $extracted = array();
539 foreach($dataObjectSet as $item) $extracted[] = $item->toMap();
540
541 foreach($extracted as $i => $item) {
542 if(!$this->dataObjectArrayMatch($item, $match)) {
543 throw new PHPUnit_Framework_AssertionFailedError(
544 "Failed asserting that the the following item matched "
545 . var_export($match, true) . ": " . var_export($item, true)
546 );
547 }
548 }
549 }
550
551 552 553
554 private function dataObjectArrayMatch($item, $match) {
555 foreach($match as $k => $v) {
556 if(!isset($item[$k]) || $item[$k] != $v) return false;
557 }
558 return true;
559 }
560
561 562 563
564 private function DOSSummaryForMatch($dataObjectSet, $match) {
565 $extracted = array();
566 foreach($dataObjectSet as $item) $extracted[] = array_intersect_key($item->toMap(), $match);
567 return var_export($extracted, true);
568 }
569
570 571 572
573 static function using_temp_db() {
574 $dbConn = DB::getConn();
575 return $dbConn && (substr($dbConn->currentDatabase(),0,5) == 'tmpdb');
576 }
577
578 579 580
581 static function kill_temp_db() {
582
583 if(self::using_temp_db()) {
584 $dbConn = DB::getConn();
585 $dbName = $dbConn->currentDatabase();
586 if($dbName && DB::getConn()->databaseExists($dbName)) {
587
588
589 foreach(ClassInfo::subclassesFor('DataObjectDecorator') as $class) {
590 $toCall = array($class, 'on_db_reset');
591 if(is_callable($toCall)) call_user_func($toCall);
592 }
593
594
595 $dbConn->dropDatabase();
596 }
597 }
598 }
599
600 601 602
603 static function empty_temp_db() {
604 if(self::using_temp_db()) {
605 $dbadmin = new DatabaseAdmin();
606 $dbadmin->clearAllData();
607
608
609
610 foreach(array_merge(ClassInfo::subclassesFor('DataObjectDecorator'), ClassInfo::subclassesFor('DataObject')) as $class) {
611 $toCall = array($class, 'on_db_reset');
612 if(is_callable($toCall)) call_user_func($toCall);
613 }
614 }
615 }
616
617 618 619
620 static function create_temp_db() {
621
622 $dbConn = DB::getConn();
623 $dbname = 'tmpdb' . rand(1000000,9999999);
624 while(!$dbname || $dbConn->databaseExists($dbname)) {
625 $dbname = 'tmpdb' . rand(1000000,9999999);
626 }
627
628 $dbConn->selectDatabase($dbname);
629 $dbConn->createDatabase();
630
631 $st = new SapphireTest();
632 $st->resetDBSchema();
633
634 return $dbname;
635 }
636
637 static function delete_all_temp_dbs() {
638 foreach(DB::getConn()->allDatabaseNames() as $dbName) {
639 if(preg_match('/^tmpdb[0-9]+$/', $dbName)) {
640 DB::getConn()->dropDatabaseByName($dbName);
641 echo "<li>Dropped databse \"$dbName\"\n";
642 flush();
643 }
644 }
645 }
646
647 648 649 650
651 function resetDBSchema($includeExtraDataObjects = false) {
652 if(self::using_temp_db()) {
653
654 global $_SINGLETONS;
655 $_SINGLETONS = array();
656
657 $dataClasses = ClassInfo::subclassesFor('DataObject');
658 array_shift($dataClasses);
659
660 $conn = DB::getConn();
661 $conn->beginSchemaUpdate();
662 DB::quiet();
663
664 foreach($dataClasses as $dataClass) {
665
666 if(class_exists($dataClass)) {
667 $SNG = singleton($dataClass);
668 if(!($SNG instanceof TestOnly)) $SNG->requireTable();
669 }
670 }
671
672
673 if($includeExtraDataObjects && $this->extraDataObjects) {
674 foreach($this->extraDataObjects as $dataClass) {
675 $SNG = singleton($dataClass);
676 if(singleton($dataClass) instanceof DataObject) $SNG->requireTable();
677 }
678 }
679
680 $conn->endSchemaUpdate();
681
682 ClassInfo::reset_db_cache();
683 singleton('DataObject')->flushCache();
684 }
685 }
686
687 688 689 690
691 function logInWithPermission($permCode = "ADMIN") {
692 if(!isset($this->cache_generatedMembers[$permCode])) {
693 $group = new Group();
694 $group->Title = "$permCode group";
695 $group->write();
696
697 $permission = new Permission();
698 $permission->Code = $permCode;
699 $permission->write();
700 $group->Permissions()->add($permission);
701
702 $member = DataObject::get_one('Member', sprintf('"Email" = \'%s\'', "$permCode@example.org"));
703 if(!$member) $member = new Member();
704
705 $member->FirstName = $permCode;
706 $member->Surname = "User";
707 $member->Email = "$permCode@example.org";
708 $member->write();
709 $group->Members()->add($member);
710
711 $this->cache_generatedMembers[$permCode] = $member;
712 }
713
714 $this->cache_generatedMembers[$permCode]->logIn();
715 }
716
717 718 719
720 protected $cache_generatedMembers = array();
721 }
722
723 ?>
724
[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.
-