diff --git a/wp_config_snapshots/PluginSnapshot.php b/wp_config_snapshots/PluginSnapshot.php new file mode 100644 index 0000000..31fdf2b --- /dev/null +++ b/wp_config_snapshots/PluginSnapshot.php @@ -0,0 +1,35 @@ +current_version($wp_version); + $vco->supported_version('3.4.1'); + $vco->deprecated_message( + 'Notice: The Plugin Snapshot framework is only supported to WP version 3.4.1. Please check for updates.'); + $vco->set_block_ui(false); + return $vco; + } +} \ No newline at end of file diff --git a/wp_config_snapshots/Stamp.php b/wp_config_snapshots/Stamp.php new file mode 100644 index 0000000..63869d9 --- /dev/null +++ b/wp_config_snapshots/Stamp.php @@ -0,0 +1,388 @@ +tpl=$templ; + $this->id = $id; + if ($autofind) + $this->autoFindRegions(); + } + + protected $snippets = array(); + + + + /** + * Loads a template and returns an instance configured + * with this template; supports caching. + * + * @param string $filename template file path + * + * @return Stamp $instance instance of Stamp class + */ + public static function load($filename) { + + $hash = md5($filename); + if (isset(self::$tcache[$hash])) return self::$tcache[$hash]; + + if (!file_exists($filename)) { + throw new Exception("Could not find file $filename"); + } + if (!is_readable($filename)) { + throw new Exception("File $filename is not readable"); + } + + $str = file_get_contents($filename); + self::$tcache[ $hash ] = $str; + + return new self( $str ); + + } + + /** + * Finds a region in the template marked by the comment markers + * this area will be selected + * + * This method returns a Region Struct: + * + * array( + * "begin" => $begin -- begin of region (string position), + * "padBegin" => $padBegin -- begin of actual inner HTML (without the begin marker itself) + * "end" => $end -- end of region before end marker + * "padEnd" => -- end of region after end marker + * "copy" => -- inner HTML between markers + * + * ); + * + * @param string $id marker ID + * @param integer $next_pos starting position from which to search + * + * @return array $struct + */ + public function find($id, $nextPos = 0) { + $cacheKey = $id.'|'.$nextPos; + if (isset($this->fcache[$cacheKey])) { + return $this->fcache[$cacheKey]; + } + if($nextPos >= strlen($this->tpl)) return array(); + $fid = ""; + $fidEnd = ""; + $len = strlen($fid); + $begin = strpos($this->tpl, $fid, $nextPos); + $padBegin = $begin + $len; + $rest = substr($this->tpl, $padBegin); + $end = strpos($rest, $fidEnd); + $padEnd = $end + strlen($fidEnd); + $stamp = substr($rest, 0, $end); + $keys = array( "begin"=>$begin, "padBegin"=>$padBegin, "end"=>$end, "padEnd"=>$padEnd, "copy"=>$stamp ); + $this->fcache[$cacheKey] = $keys; + return $keys; + } + + + /** + * Automatically scans template and cuts out all regions prefixed + * with "cut:" + * + * @return void + */ + public function autoFindRegions() { + + + while((($pos = strpos($this->tpl,'',$pos); + $id = trim(substr($this->tpl,$pos+9,$end-($pos+9))); + $this->snippets[$id]=$this->cut($id); + $this->snippets[$id]->id = $id; + + } + + + $pos=0; + while((strpos($this->tpl,'',$pos); + $end2 = strpos($this->tpl,'(',$pos); + $sid = trim(substr($this->tpl,$pos+11,(min($end,$end2)-($pos+11)))); + if ($pos!==false) { + //found slot + $end = strpos($this->tpl,'-->',$pos); + $slot = substr($this->tpl,$pos,$end-$pos); + if (($respos=strpos($slot,'('))!==false) { + //has slot restriction from designer + $restrictions = substr($slot,$respos+1,strpos($slot,')')-($respos+1)); + $resIds = explode(',',$restrictions); + $resIdArray = array(); + foreach($resIds as $k=>$v) $resIdArray[$v]=true; + $this->restrictions[ $sid ] = $resIdArray; + } + } + $pos = $end; + + } + + + } + + /** + * Cuts a region from the template. + * + * @param string $id ID of the region that has to be cut + * + * @return Stamp $stamp instance of Stamp + */ + public function cut($id) { + $snippet = $this->copy('cut:'.$id); + $this->replace("cut:$id",""); + return $snippet; + } + + /** + * Returns a new Stamp Template, containing a copy of the + * specified region ID. + * + * @param string $id region id + * + * @return Stamp $stamp the new Stamp instance + */ + public function copy($id) { + $snip = $this->find($id); + return new Stamp( $snip["copy"] ); + } + + /** + * Cleans the contents of the current stamp + * + * @return Stamp + */ + public function clean() { + return $this->paste(""); + } + + /** + * Replace a region specified by $where region ID with string $paste. + * + * @param string $where region ID + * @param string $paste replacement string + * + * @return Stamp stamp + */ + public function replace( $where, $paste ) { + $nextPos = 0; + while($nextPos < strlen($this->tpl)) { + $keys = $this->find($where, $nextPos); + if(!$keys['begin']) break; + $nextPos = $keys['begin'] + strlen($paste); + $suffix = substr($this->tpl,$keys["padEnd"]+$keys["padBegin"]); + $prefix = substr($this->tpl,0,$keys["begin"]); + $copy = $prefix.$paste.$suffix; + $this->tpl = $copy; + } + return $this; + } + + + /** + * Puts $text in slot Slot ID, marker #slot# will be replaced. + * + * @param string $slotID slot identifier + * @param string $text + * + * @return Stamp + */ + public function put($slotID, $text) { + $text = htmlentities($text); + $slotFormat = "#$slotID#"; + $this->tpl = str_replace( $slotFormat, htmlspecialchars( $text ), $this->tpl); + return $this; + } + + /** + * Checks if the template contains slot Slot ID. + * + * @param string $slotID slot ID + * + * @return bool $containsSlot result of check + */ + public function hasSimpleSlot($slotID) { + if (strpos($this->tpl,"#".$slotID."#")!==false) return true; else return false; + } + + + /** + * Pastes the contents of string $paste after the first '>' + * + * @param $paste string HTML + * + * @return Stamp $chainable Chainable + */ + public function pastePad($paste) { + $beginOfTag = strpos($this->tpl,">"); + $endOfTag = strrpos($this->tpl,"<"); + $this->tpl = substr($this->tpl, 0, $beginOfTag+1).$paste.substr($this->tpl,$endOfTag); + return $this; + } + + + /** + * Fetches a previously cut snippet by id. + * + * @param string $id ID of the snippet you want to fetch. + * + * @return Stamp $stamp instance of Stamp + */ + public function fetch($id) { + $new = new self( $this->snippets[$id], $this->snippets[$id]->id); + $new->snippets = $this->snippets[$id]->snippets; + return $new; + } + + + /** + * Pastes a snippet in a region. + * + * @param string $sid Region ID + * @param string $snippet HTML Snippet to drop in the region + * + * @return void + */ + public function pasteIn($sid,$snippet) { + $id = $snippet->id; + if ( (isset($this->restrictions[$sid]) && + isset($this->restrictions[$sid][$id])) || + (!isset($this->restrictions[$sid])) + ) { + $slotPos = strpos($this->tpl,"#', $this->tpl, + $markersGroups); + $markersList = array(); + foreach($markersGroups[1] as $marker) { + if(strstr($this->tpl, "")) { + $markersList []= $marker; + } + } + return $markersList; + } + + /** + * Replaces all markers in current template with child's ones + * (like reverse (to make it chainable) of Django's {% extends %}) + * + * @return Stamp $chainable Chainable + */ + public function extendWith($childString) { + $child = new Stamp($childString); + $parent = $this; + foreach($child->getMarkersList() as $marker) { + $copyInChild = $child->copy($marker); + $parent->replace($marker, $copyInChild); + } + return $this; + } + + /** + * Removes all comments and redundant whitespaces from template. + * + * @return Stamp $self + */ + public function wash() { + $this->tpl = preg_replace("//m","",$this->tpl); + $this->tpl = preg_replace("/\n[\n\t\s]*\n/m","\n",$this->tpl); + $this->tpl = trim($this->tpl); + return $this; + } + +} + + + + + + + + + + + + + + + + + diff --git a/wp_config_snapshots/js/snapshots.js b/wp_config_snapshots/js/snapshots.js new file mode 100644 index 0000000..bc7fd11 --- /dev/null +++ b/wp_config_snapshots/js/snapshots.js @@ -0,0 +1,28 @@ +(function($, exports, undefined) { + $(document).ready(function() { + + $('.restore_snapshot').click(function() { + var snapshot = $(this).parent().find('select').val(), type = $(this).parent().parent().data('type'); + console.log(snapshot, type); + $.post(ajaxurl, {action: 'restore_wp_config_snapshot', snapshot_type: type, snapshot_name: snapshot}, function(r) { + var res = $.parseJSON(r); + if (res.status !== 'success' && res.message) return alert(res.message); + return window.location.reload(); + }); + }); + + $('.take_snapshot').click(function() { + var $el = $(this), snapshot = $(this).parent().find('.new_snapshot_name').val(), type = $(this).parent().parent().data('type'); + if (snapshot.trim() === '') return alert('Please enter a name for the new snapshot.'); + $(this).parent().find('.new_snapshot_name').val(''); + $.post(ajaxurl, {action: 'add_wp_config_snapshot', snapshot_type: type, snapshot_name: snapshot}, function(r) { + var res = $.parseJSON(r); + if (res.status !== 'success' && res.message) return alert(res.message); + $el.parent().parent().find('.wp_settings_snapshot_select') + .append('').val(snapshot); + return 1; + }); + return 1; + }); + }); +}(jQuery, window)) \ No newline at end of file diff --git a/wp_config_snapshots/plugin_snapshots.php b/wp_config_snapshots/plugin_snapshots.php new file mode 100644 index 0000000..a1c8777 --- /dev/null +++ b/wp_config_snapshots/plugin_snapshots.php @@ -0,0 +1,24 @@ +register_snapshot_module($ps); + } +} + +add_action('config_snapshot_plugin_loaded', array('PluginSnapshotPlugin', 'initialize')); \ No newline at end of file diff --git a/wp_config_snapshots/tests/EnhanceTestFramework.php b/wp_config_snapshots/tests/EnhanceTestFramework.php new file mode 100644 index 0000000..fb7c91d --- /dev/null +++ b/wp_config_snapshots/tests/EnhanceTestFramework.php @@ -0,0 +1,1620 @@ +discoverTests($path, $isRecursive, $excludeRules); + } + + public static function runTests($output = TemplateType::Html) + { + self::setInstance(); + self::$Instance->runTests($output); + } + + public static function getCodeCoverageWrapper($className, $args = null) + { + self::setInstance(); + self::$Instance->registerForCodeCoverage($className); + return new CodeCoverageWrapper($className, $args); + } + + public static function log($className, $methodName) + { + self::setInstance(); + self::$Instance->log($className, $methodName); + } + + public static function getScenario($className, $args = null) + { + return new Scenario($className, $args, self::$Language); + } + + public static function setInstance() + { + if (self::$Instance === null) { + self::$Instance = new EnhanceTestFramework(self::$Language); + } + } +} + +// Public API +class TestFixture +{ + +} + +// Public API +class MockFactory +{ + public static function createMock($typeName) + { + return new Mock($typeName, true, Core::getLanguage()); + } +} + +// Public API +class StubFactory +{ + public static function createStub($typeName) + { + return new Mock($typeName, false, Core::getLanguage()); + } +} + +// Public API +class Expect +{ + const AnyValue = 'ENHANCE_ANY_VALUE_WILL_DO'; + + public static function method($methodName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->method($methodName); + } + + public static function getProperty($propertyName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->getProperty($propertyName); + } + + public static function setProperty($propertyName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->setProperty($propertyName); + } +} + +// Public API +class Assert +{ + /** @var Assertions $EnhanceAssertions */ + private static $EnhanceAssertions; + + private static function GetEnhanceAssertionsInstance() + { + if(self::$EnhanceAssertions === null) { + self::$EnhanceAssertions = new Assertions(Core::getLanguage()); + } + return self::$EnhanceAssertions; + } + + public static function areIdentical($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->areIdentical($expected, $actual); + } + + public static function areNotIdentical($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->areNotIdentical($expected, $actual); + } + + public static function isTrue($actual) + { + self::GetEnhanceAssertionsInstance()->isTrue($actual); + } + + public static function isFalse($actual) + { + self::GetEnhanceAssertionsInstance()->isFalse($actual); + } + + public static function isNull($actual) + { + self::GetEnhanceAssertionsInstance()->isNull($actual); + } + + public static function isNotNull($actual) + { + self::GetEnhanceAssertionsInstance()->isNotNull($actual); + } + + public static function isArray($actual) + { + self::GetEnhanceAssertionsInstance()->isArray($actual); + } + + public static function isNotArray($actual) + { + self::GetEnhanceAssertionsInstance()->isNotArray($actual); + } + + public static function isBool($actual) + { + self::GetEnhanceAssertionsInstance()->isBool($actual); + } + + public static function isNotBool($actual) + { + self::GetEnhanceAssertionsInstance()->isNotBool($actual); + } + + public static function isFloat($actual) + { + self::GetEnhanceAssertionsInstance()->isFloat($actual); + } + + public static function isNotFloat($actual) + { + self::GetEnhanceAssertionsInstance()->isNotFloat($actual); + } + + public static function isInt($actual) + { + self::GetEnhanceAssertionsInstance()->isInt($actual); + } + + public static function isNotInt($actual) + { + self::GetEnhanceAssertionsInstance()->isNotInt($actual); + } + + public static function isNumeric($actual) + { + self::GetEnhanceAssertionsInstance()->isNumeric($actual); + } + + public static function isNotNumeric($actual) + { + self::GetEnhanceAssertionsInstance()->isNotNumeric($actual); + } + + public static function isObject($actual) + { + self::GetEnhanceAssertionsInstance()->isObject($actual); + } + + public static function isNotObject($actual) + { + self::GetEnhanceAssertionsInstance()->isNotObject($actual); + } + + public static function isResource($actual) + { + self::GetEnhanceAssertionsInstance()->isResource($actual); + } + + public static function isNotResource($actual) + { + self::GetEnhanceAssertionsInstance()->isNotResource($actual); + } + + public static function isScalar($actual) + { + self::GetEnhanceAssertionsInstance()->isScalar($actual); + } + + public static function isNotScalar($actual) + { + self::GetEnhanceAssertionsInstance()->isNotScalar($actual); + } + + public static function isString($actual) + { + self::GetEnhanceAssertionsInstance()->isString($actual); + } + + public static function isNotString($actual) + { + self::GetEnhanceAssertionsInstance()->isNotString($actual); + } + + public static function contains($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->contains($expected, $actual); + } + + public static function notContains($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->notContains($expected, $actual); + } + + public static function fail() + { + self::GetEnhanceAssertionsInstance()->fail(); + } + + public static function inconclusive() + { + self::GetEnhanceAssertionsInstance()->inconclusive(); + } + + public static function isInstanceOfType($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->isInstanceOfType($expected, $actual); + } + + public static function isNotInstanceOfType($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->isNotInstanceOfType($expected, $actual); + } + + public static function throws($class, $methodName, $args = null) + { + self::GetEnhanceAssertionsInstance()->throws($class, $methodName, $args); + } +} + +// Internal Workings +// You don't need to call any of these bits directly - use the public API above, which will +// use the stuff below to carry out your tests! + +class TextFactory +{ + public static $Text; + + public static function getLanguageText($language) + { + if (self::$Text === null) { + $languageClass = 'Enhance\Text' . $language; + self::$Text = new $languageClass(); + } + return self::$Text; + } +} + +class TextEn +{ + public $FormatForTestRunTook = 'Test run took {0} seconds'; + public $FormatForExpectedButWas = 'Expected {0} but was {1}'; + public $FormatForExpectedNotButWas = 'Expected NOT {0} but was {1}'; + public $FormatForExpectedContainsButWas = 'Expected to contain {0} but was {1}'; + public $FormatForExpectedNotContainsButWas = 'Expected NOT to contain {0} but was {1}'; + public $EnhanceTestFramework = 'Enhance Test Framework'; + public $EnhanceTestFrameworkFull = 'Enhance PHP Unit Testing Framework'; + public $TestResults = 'Test Results'; + public $Test = 'Test'; + public $TestPassed = 'Test Passed'; + public $TestFailed = 'Test Failed'; + public $Passed = 'Passed'; + public $Failed = 'Failed'; + public $ExpectationFailed = 'Expectation failed'; + public $Expected = 'Expected'; + public $Called = 'Called'; + public $InconclusiveOrNotImplemented = 'Inconclusive or not implemented'; + public $Times = 'Times'; + public $MethodCoverage = 'Method Coverage'; + public $Copyright = 'Copyright'; + public $ExpectedExceptionNotThrown = 'Expected exception was not thrown'; + public $CannotCallVerifyOnStub = 'Cannot call VerifyExpectations on a stub'; + public $ReturnsOrThrowsNotBoth = 'You must only set a single return value (1 returns() or 1 throws())'; + public $ScenarioWithExpectMismatch = 'Scenario must be initialised with the same number of "with" and "expect" calls'; + public $LineFile = 'Line {0} in file {1}'; +} + +class TextDe +{ + public $FormatForTestRunTook = 'Fertig nach {0} Sekunden'; + public $FormatForExpectedButWas = 'Erwartet {0} aber war {1}'; + public $FormatForExpectedNotButWas = 'Erwartet nicht {0} aber war {1}'; + public $FormatForExpectedContainsButWas = 'Erwartet {0} zu enthalten, aber war {1}'; + public $FormatForExpectedNotContainsButWas = 'Erwartet {0} nicht zu enthalten, aber war {1}'; + public $EnhanceTestFramework = 'Enhance Test-Rahmen'; + public $EnhanceTestFrameworkFull = 'Maßeinheits-Prüfungs-Rahmen'; + public $TestResults = 'Testergebnisse'; + public $Test = 'Test'; + public $TestPassed = 'Test bestehen'; + public $TestFailed = 'Test durchfallen'; + public $Passed = 'Bestehen'; + public $Failed = 'Durchfallen'; + public $ExpectationFailed = 'Erwartung durchfallen'; + public $Expected = 'Erwartet'; + public $Called = 'Benannt'; + public $InconclusiveOrNotImplemented = 'Unbestimmt oder nicht durchgefuert'; + public $Times = 'Ereignisse'; + public $MethodCoverage = 'Methodenbehandlung'; + public $Copyright = 'Copyright'; + public $ExpectedExceptionNotThrown = 'Die Ausnahme wird nicht ausgeloest'; + public $CannotCallVerifyOnStub = 'Kann VerifyExpectations auf einem Stub nicht aufrufen'; + public $ReturnsOrThrowsNotBoth = 'Sie müssen einen einzelnen Return Value nur einstellen'; + public $ScenarioWithExpectMismatch = 'Szenarium muss mit der gleichen Zahl von "With" und "Expect" calls initialisiert werden'; + public $LineFile = 'Zeile {0} der Datei {1}'; +} + +class EnhanceTestFramework +{ + private $FileSystem; + private $Text; + private $Tests = array(); + private $Results = array(); + private $Errors = array(); + private $Duration; + private $MethodCalls = array(); + private $Language; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + $this->FileSystem = new FileSystem(); + $this->Language = $language; + } + + public function discoverTests($path, $isRecursive, $excludeRules) + { + $directory = rtrim($path, '/'); + if (is_dir($directory)) { + $phpFiles = $this->FileSystem->getFilesFromDirectory($directory, $isRecursive, $excludeRules); + foreach ($phpFiles as $file) { + /** @noinspection PhpIncludeInspection */ + include_once($file); + } + } + } + + public function runTests($output) + { + $this->getTestFixturesByParent(); + $this->run(); + + if(PHP_SAPI === 'cli' && $output != TemplateType::Tap) { + $output = TemplateType::Cli; + } + + $OutputTemplate = TemplateFactory::createOutputTemplate($output, $this->Language); + echo $OutputTemplate->get( + $this->Errors, + $this->Results, + $this->Text, + $this->Duration, + $this->MethodCalls + ); + + if (count($this->Errors) > 0) { + exit(1); + } else { + exit(0); + } + } + + public function log($className, $methodName) + { + $index = $this->getMethodIndex($className, $methodName); + if (array_key_exists($index ,$this->MethodCalls)) { + $this->MethodCalls[$index] = $this->MethodCalls[$index] + 1; + } + } + + public function registerForCodeCoverage($className) + { + $classMethods = get_class_methods($className); + foreach($classMethods as $methodName) { + $index = $this->getMethodIndex($className, $methodName); + if (!array_key_exists($index ,$this->MethodCalls)) { + $this->MethodCalls[$index] = 0; + } + } + } + + private function getMethodIndex($className, $methodName) + { + return $className . '#' . $methodName; + } + + private function getTestFixturesByParent() + { + $classes = get_declared_classes(); + foreach($classes as $className) { + $this->AddClassIfTest($className); + } + } + + private function AddClassIfTest($className) + { + $parentClassName = get_parent_class($className); + if ($parentClassName === 'Enhance\TestFixture') { + $instance = new $className(); + $this->addFixture($instance); + } else { + $ancestorClassName = get_parent_class($parentClassName); + if ($ancestorClassName === 'Enhance\TestFixture') { + $instance = new $className(); + $this->addFixture($instance); + } + } + } + + private function addFixture($class) + { + $classMethods = get_class_methods($class); + foreach($classMethods as $method) { + if (strtolower($method) !== 'setup' && strtolower($method) !== 'teardown') { + $reflection = new \ReflectionMethod($class, $method); + if ($reflection->isPublic()) { + $this->addTest($class, $method); + } + } + } + } + + private function addTest($class, $method) + { + $testMethod = new Test($class, $method); + $this->Tests[] = $testMethod; + } + + private function run() + { + $start = time(); + foreach($this->Tests as /** @var Test $test */ $test) { + $result = $test->run(); + if ($result) { + $message = $test->getTestName() . ' - ' . $this->Text->Passed; + $this->Results[] = new TestMessage($message, $test, true); + } else { + $message = '['. str_replace('{0}', $test->getLine(), str_replace('{1}', $test->getFile(), $this->Text->LineFile)) . '] ' . + $test->getTestName() . ' - ' . + $this->Text->Failed . ' - ' . $test->getMessage(); + $this->Errors[] = new TestMessage($message, $test, false); + } + } + $this->Duration = time() - $start; + } +} + +class FileSystem +{ + public function getFilesFromDirectory($directory, $isRecursive, $excludeRules) + { + $files = array(); + if ($handle = opendir($directory)) { + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..' && strpos($file, '.') !== 0) { + if ($this->isFolderExcluded($file, $excludeRules)){ + continue; + } + + if(is_dir($directory . '/' . $file)) { + if ($isRecursive) { + $dir2 = $directory . '/' . $file; + $files[] = $this->getFilesFromDirectory($dir2, $isRecursive, $excludeRules); + } + } else { + $files[] = $directory . '/' . $file; + } + } + } + closedir($handle); + } + return $this->flattenArray($files); + } + + private function isFolderExcluded($folder, $excludeRules) + { + $folder = substr($folder, strrpos($folder, '/')); + + foreach ($excludeRules as $excluded){ + if ($folder === $excluded){ + return true; + } + } + return false; + } + + public function flattenArray($array) + { + $merged = array(); + foreach($array as $a) { + if(is_array($a)) { + $merged = array_merge($merged, $this->flattenArray($a)); + } else { + $merged[] = $a; + } + } + return $merged; + } +} + +class TestMessage +{ + public $Message; + public $Test; + public $IsPass; + + public function __construct($message, $test, $isPass) + { + $this->Message = $message; + $this->Test = $test; + $this->IsPass = $isPass; + } +} + +class Test +{ + private $ClassName; + private $TestName; + private $TestMethod; + private $SetUpMethod; + private $TearDownMethod; + private $Message; + private $Line; + private $File; + + public function __construct($class, $method) + { + $className = get_class($class); + $this->ClassName = $className; + $this->TestMethod = array($className, $method); + $this->SetUpMethod = array($className, 'setUp'); + $this->TearDownMethod = array($className, 'tearDown'); + $this->TestName = $method; + } + + public function getTestName() + { + return $this->TestName; + } + + public function getClassName() + { + return $this->ClassName; + } + + public function getMessage() + { + return $this->Message; + } + + public function getLine() + { + return $this->Line; + } + + public function getFile() + { + return $this->File; + } + + public function run() + { + /** @var $testClass iTestable */ + $testClass = new $this->ClassName(); + + try { + if (is_callable($this->SetUpMethod)) { + $testClass->setUp(); + } + } catch (\Exception $e) { } + + try { + $testClass->{$this->TestName}(); + $result = true; + } catch (TestException $e) { + $this->Message = $e->getMessage(); + $this->Line = $e->getLine(); + $this->File = pathinfo($e->getFile(), PATHINFO_BASENAME); + $result = false; + } + + try { + if (is_callable($this->TearDownMethod)) { + $testClass->tearDown(); + } + } catch (\Exception $e) { } + + return $result; + } +} + +class CodeCoverageWrapper +{ + private $Instance; + private $ClassName; + + public function __construct($className, $args) + { + $this->ClassName = $className; + if ($args !== null) { + $rc = new \ReflectionClass($className); + $this->Instance = $rc->newInstanceArgs($args); + } else { + $this->Instance = new $className(); + } + Core::log($this->ClassName, $className); + Core::log($this->ClassName, '__construct'); + } + + public function __call($methodName, $args = null) + { + Core::log($this->ClassName, $methodName); + if ($args !== null) { + /** @noinspection PhpParamsInspection */ + return call_user_func_array(array($this->Instance, $methodName), $args); + } else { + return $this->Instance->{$methodName}(); + } + } + + public function __get($propertyName) + { + return $this->Instance->{$propertyName}; + } + + public function __set($propertyName, $value) + { + $this->Instance->{$propertyName} = $value; + } +} + +class Mock +{ + private $IsMock; + private $Text; + private $ClassName; + private $Expectations = array(); + + public function __construct($className, $isMock, $language) + { + $this->IsMock = $isMock; + $this->ClassName = $className; + $this->Text = TextFactory::getLanguageText($language); + } + + public function addExpectation($expectation) + { + $this->Expectations[] = $expectation; + } + + public function verifyExpectations() + { + if (!$this->IsMock) { + throw new \Exception( + $this->ClassName . ': ' . $this->Text->CannotCallVerifyOnStub + ); + } + + foreach ($this->Expectations as /** @var Expectation $expectation */ $expectation) { + if (!$expectation->verify()) { + $Arguments = ''; + if (isset($expectation->MethodArguments)) { + foreach($expectation->MethodArguments as $argument) { + if (isset($Arguments[0])) { + $Arguments .= ', '; + } + $Arguments .= $argument; + } + } + + throw new \Exception( + $this->Text->ExpectationFailed . ' ' . + $this->ClassName . '->' . $expectation->MethodName . '(' . $Arguments . ') ' . + $this->Text->Expected . ' #' . $expectation->ExpectedCalls . ' ' . + $this->Text->Called . ' #' . $expectation->ActualCalls, 0); + } + } + } + + public function __call($methodName, $args) + { + return $this->getReturnValue('method', $methodName, $args); + } + + public function __get($propertyName) + { + return $this->getReturnValue('getProperty', $propertyName, array()); + } + + public function __set($propertyName, $value) + { + $this->getReturnValue('setProperty', $propertyName, array($value)); + } + + private function getReturnValue($type, $methodName, $args) + { + $Expectation = $this->getMatchingExpectation($type, $methodName, $args); + $Expected = true; + if ($Expectation === null) { + $Expected = false; + } + + if ($Expected) { + ++$Expectation->ActualCalls; + if ($Expectation->ReturnException) { + throw new \Exception($Expectation->ReturnValue); + } + return $Expectation->ReturnValue; + } + + if ($this->IsMock) { + throw new \Exception( + $this->Text->ExpectationFailed . ' ' . + $this->ClassName . '->' . $methodName . '(' . $args . ') ' . + $this->Text->Expected . ' #0 ' . + $this->Text->Called . ' #1', 0); + } + return null; + } + + private function getMatchingExpectation($type, $methodName, $arguments) + { + foreach ($this->Expectations as $expectation) { + if ($expectation->Type === $type) { + if ($expectation->MethodName === $methodName) { + $isMatch = true; + if ($expectation->ExpectArguments) { + $isMatch = $this->argumentsMatch( + $expectation->MethodArguments, + $arguments + ); + } + if ($isMatch) { + return $expectation; + } + } + } + } + return null; + } + + private function argumentsMatch($arguments1, $arguments2) + { + $Count1 = count($arguments1); + $Count2 = count($arguments2); + $isMatch = true; + if ($Count1 === $Count2) { + for ($i = 0; $i < $Count1; ++$i) { + if ($arguments1[$i] === Expect::AnyValue + || $arguments2[$i] === Expect::AnyValue) { + // No need to match + } else { + if ($arguments1[$i] !== $arguments2[$i]) { + $isMatch = false; + } + } + } + } else { + $isMatch = false; + } + return $isMatch; + } +} + +class Scenario +{ + private $Text; + private $Class; + private $FunctionName; + private $Inputs = array(); + private $Expectations = array(); + + public function __construct($class, $functionName, $language) + { + $this->Class = $class; + $this->FunctionName = $functionName; + $this->Text = TextFactory::getLanguageText($language); + } + + public function with() + { + $this->Inputs[] = func_get_args(); + return $this; + } + + public function expect() + { + $this->Expectations[] = func_get_args(); + return $this; + } + + public function verifyExpectations() + { + if (count($this->Inputs) !== count($this->Expectations)) { + throw new \Exception($this->Text->ScenarioWithExpectMismatch); + } + + $exceptionText = ''; + + while(count($this->Inputs) > 0) { + $input = array_shift($this->Inputs); + $expected = array_shift($this->Expectations); + $expected = $expected[0]; + + $actual = call_user_func_array(array($this->Class, $this->FunctionName), $input); + + if (is_float($expected)) { + if ((string)$expected !== (string)$actual) { + $exceptionText .= str_replace('{0}', $expected, str_replace('{1}', $actual, $this->Text->FormatForExpectedButWas)); + } + } elseif ($expected != $actual) { + $exceptionText .= str_replace('{0}', $expected, str_replace('{1}', $actual, $this->Text->FormatForExpectedButWas)); + } + } + + if ($exceptionText !== ''){ + throw new \Exception($exceptionText, 0); + } + } +} + +class Expectation +{ + public $MethodName; + public $MethodArguments; + public $ReturnValue; + public $ReturnException; + public $ExpectedCalls; + public $ActualCalls; + public $ExpectArguments; + public $ExpectTimes; + public $Type; + public $Text; + + public function __construct($language) + { + $this->ExpectedCalls = -1; + $this->ActualCalls = 0; + $this->ExpectArguments = false; + $this->ExpectTimes = false; + $this->ReturnException = false; + $this->ReturnValue = null; + $textFactory = new TextFactory(); + $this->Text = $textFactory->getLanguageText($language); + } + + public function method($methodName) + { + $this->Type = 'method'; + $this->MethodName = $methodName; + return $this; + } + + public function getProperty($propertyName) + { + $this->Type = 'getProperty'; + $this->MethodName = $propertyName; + return $this; + } + + public function setProperty($propertyName) + { + $this->Type = 'setProperty'; + $this->MethodName = $propertyName; + return $this; + } + + public function with() + { + $this->ExpectArguments = true; + $this->MethodArguments = func_get_args(); + return $this; + } + + public function returns($returnValue) + { + if ($this->ReturnValue !== null) { + throw new \Exception($this->Text->ReturnsOrThrowsNotBoth); + } + $this->ReturnValue = $returnValue; + return $this; + } + + public function throws($errorMessage) + { + if ($this->ReturnValue !== null) { + throw new \Exception($this->Text->ReturnsOrThrowsNotBoth); + } + $this->ReturnValue = $errorMessage; + $this->ReturnException = true; + return $this; + } + + public function times($expectedCalls) + { + $this->ExpectTimes = true; + $this->ExpectedCalls = $expectedCalls; + return $this; + } + + public function verify() + { + $ExpectationMet = true; + if ($this->ExpectTimes) { + if ($this->ExpectedCalls !== $this->ActualCalls) { + $ExpectationMet = false; + } + } + return $ExpectationMet; + } +} + +class Assertions +{ + private $Text; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function areIdentical($expected, $actual) + { + if (is_float($expected)) { + if ((string)$expected !== (string)$actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } elseif ($expected !== $actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function areNotIdentical($expected, $actual) + { + if (is_float($expected)) { + if ((string)$expected === (string)$actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } elseif ($expected === $actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isTrue($actual) + { + if ($actual !== true) { + throw new TestException(str_replace('{0}', 'true', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isFalse($actual) + { + if ($actual !== false) { + throw new TestException(str_replace('{0}', 'false', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function contains($expected, $actual) + { + $result = strpos($actual, $expected); + if ($result === false) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedContainsButWas)), 0); + } + } + + public function notContains($expected, $actual) + { + $result = strpos($actual, $expected); + if ($result !== false) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotContainsButWas)), 0); + } + } + + public function isNull($actual) + { + if ($actual !== null) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotNull($actual) + { + if ($actual === null) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isArray($actual) + { + if (!is_array($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotArray($actual) + { + if (is_array($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isBool($actual) + { + if (!is_bool($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotBool($actual) + { + if (is_bool($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isFloat($actual) + { + if (!is_float($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotFloat($actual) + { + if (is_float($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isInt($actual) + { + if (!is_int($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotInt($actual) + { + if (is_int($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isNumeric($actual) + { + if (!is_numeric($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotNumeric($actual) + { + if (is_numeric($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isObject($actual) + { + if (!is_object($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotObject($actual) + { + if (is_object($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isResource($actual) + { + if (!is_resource($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotResource($actual) + { + if (is_resource($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isScalar($actual) + { + if (!is_scalar($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotScalar($actual) + { + if (is_scalar($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isString($actual) + { + if (!is_string($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotString($actual) + { + if (is_string($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function fail() + { + throw new TestException($this->Text->Failed, 0); + } + + public function inconclusive() + { + throw new TestException($this->Text->InconclusiveOrNotImplemented, 0); + } + + public function isInstanceOfType($expected, $actual) + { + $actualType = get_class($actual); + if ($expected !== $actualType) { + throw new TestException(str_replace('{0}', $expected, str_replace('{1}', $actualType, $this->Text->FormatForExpectedButWas)), 0); + }; + } + + public function isNotInstanceOfType($expected, $actual) + { + $actualType = get_class($actual); + if ($expected === $actualType) { + throw new TestException(str_replace('{0}', $expected, str_replace('{1}', $actualType, $this->Text->FormatForExpectedNotButWas)), 0); + }; + } + + public function throws($class, $methodName, $args = null) + { + $exception = false; + + try { + if ($args !== null) { + /** @noinspection PhpParamsInspection */ + call_user_func_array(array($class, $methodName), $args); + } else { + $class->{$methodName}(); + } + } catch (\Exception $e) { + $exception = true; + } + + if (!$exception) { + throw new TestException($this->Text->ExpectedExceptionNotThrown, 0); + } + } + + private function getDescription($mixed) + { + if (is_object($mixed)){ + return get_class($mixed); + } else if (is_bool($mixed)){ + return $mixed ? 'true' : 'false'; + } else { + return (string) $mixed; + } + } +} + +class TestException extends \Exception +{ + public function __construct($message = null, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $trace = $this->getTrace(); + + $this->line = $trace[1]['line']; + $this->file = $trace[1]['file']; + } +} + +interface iOutputTemplate +{ + public function getTemplateType(); + public function get($errors, $results, $text, $duration, $methodCalls); +} + +interface iTestable +{ + public function setUp(); + public function tearDown(); +} + +class HtmlTemplate implements iOutputTemplate +{ + private $Text; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function getTemplateType() + { + return TemplateType::Html; + } + + public function get($errors, $results, $text, $duration, $methodCalls) + { + $message = ''; + $failCount = count($errors); + $passCount = count($results); + $methodCallCount = count($methodCalls); + + $currentClass = ''; + if ($failCount > 0) { + $message .= '
' . str_replace('{0}', $duration, $text->FormatForTestRunTook) . '
'; + + return $this->getTemplateWithMessage($message); + } + + private function getTemplateWithMessage($content) + { + return str_replace('{0}', $content, ' + + + ++ + + + + +
++ + + +
+ +