. /** * Utility functions to make unit testing easier. * * These functions, particularly the the database ones, are quick and * dirty methods for getting things done in test cases. None of these * methods should be used outside test code. * * Major Contirbutors * - T.J.Hunt@open.ac.uk * * @package tool * @subpackage unittest * @copyright © 2006 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Includes */ require_once($CFG->libdir . '/simpletestlib/simpletest.php'); require_once($CFG->libdir . '/simpletestlib/unit_tester.php'); require_once($CFG->libdir . '/simpletestlib/expectation.php'); require_once($CFG->libdir . '/simpletestlib/reporter.php'); require_once($CFG->libdir . '/simpletestlib/web_tester.php'); require_once($CFG->libdir . '/simpletestlib/mock_objects.php'); /** * Recursively visit all the files in the source tree. Calls the callback * function with the pathname of each file found. * * @param $path the folder to start searching from. * @param $callback the function to call with the name of each file found. * @param $fileregexp a regexp used to filter the search (optional). * @param $exclude If true, pathnames that match the regexp will be ingored. If false, * only files that match the regexp will be included. (default false). * @param array $ignorefolders will not go into any of these folders (optional). */ function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { $files = scandir($path); foreach ($files as $file) { $filepath = $path .'/'. $file; if (strpos($file, '.') === 0) { /// Don't check hidden files. continue; } else if (is_dir($filepath)) { if (!in_array($filepath, $ignorefolders)) { recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); } } else if ($exclude xor preg_match($fileregexp, $filepath)) { call_user_func($callback, $filepath); } } } /** * An expectation for comparing strings ignoring whitespace. * * @package moodlecore * @subpackage simpletestex * @copyright © 2006 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class IgnoreWhitespaceExpectation extends SimpleExpectation { var $expect; function IgnoreWhitespaceExpectation($content, $message = '%s') { $this->SimpleExpectation($message); $this->expect=$this->normalise($content); } function test($ip) { return $this->normalise($ip)==$this->expect; } function normalise($text) { return preg_replace('/\s+/m',' ',trim($text)); } function testMessage($ip) { return "Input string [$ip] doesn't match the required value."; } } /** * An Expectation that two arrays contain the same list of values. * * @package moodlecore * @subpackage simpletestex * @copyright © 2006 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ArraysHaveSameValuesExpectation extends SimpleExpectation { var $expect; function ArraysHaveSameValuesExpectation($expected, $message = '%s') { $this->SimpleExpectation($message); if (!is_array($expected)) { trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' . 'with an expected value that is not an array.'); } $this->expect = $this->normalise($expected); } function test($actual) { return $this->normalise($actual) == $this->expect; } function normalise($array) { sort($array); return $array; } function testMessage($actual) { return 'Array [' . implode(', ', $actual) . '] does not contain the expected list of values [' . implode(', ', $this->expect) . '].'; } } /** * An Expectation that compares to objects, and ensures that for every field in the * expected object, there is a key of the same name in the actual object, with * the same value. (The actual object may have other fields to, but we ignore them.) * * @package moodlecore * @subpackage simpletestex * @copyright © 2006 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class CheckSpecifiedFieldsExpectation extends SimpleExpectation { var $expect; function CheckSpecifiedFieldsExpectation($expected, $message = '%s') { $this->SimpleExpectation($message); if (!is_object($expected)) { trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' . 'with an expected value that is not an object.'); } $this->expect = $expected; } function test($actual) { foreach ($this->expect as $key => $value) { if (isset($value) && isset($actual->$key) && $actual->$key == $value) { // OK } else if (is_null($value) && is_null($actual->$key)) { // OK } else { return false; } } return true; } function testMessage($actual) { $mismatches = array(); foreach ($this->expect as $key => $value) { if (isset($value) && isset($actual->$key) && $actual->$key == $value) { // OK } else if (is_null($value) && is_null($actual->$key)) { // OK } else if (!isset($actual->$key)) { $mismatches[] = $key . ' (expected [' . $value . '] but was missing.'; } else { $mismatches[] = $key . ' (expected [' . $value . '] got [' . $actual->$key . '].'; } } return 'Actual object does not have all the same fields with the same values as the expected object (' . implode(', ', $mismatches) . ').'; } } abstract class XMLStructureExpectation extends SimpleExpectation { /** * Parse a string as XML and return a DOMDocument; * @param $html * @return unknown_type */ protected function load_xml($html) { $prevsetting = libxml_use_internal_errors(true); $parser = new DOMDocument(); if (!$parser->loadXML('' . $html . '')) { $parser = new DOMDocument(); } libxml_clear_errors(); libxml_use_internal_errors($prevsetting); return $parser; } function testMessage($html) { $parsererrors = $this->load_xml($html); if (is_array($parsererrors)) { foreach ($parsererrors as $key => $message) { $parsererrors[$key] = $message->message; } return 'Could not parse XML [' . $html . '] errors were [' . implode('], [', $parsererrors) . ']'; } return $this->customMessage($html); } } /** * An Expectation that looks to see whether some HMTL contains a tag with a certain attribute. * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ContainsTagWithAttribute extends XMLStructureExpectation { protected $tag; protected $attribute; protected $value; function __construct($tag, $attribute, $value, $message = '%s') { parent::__construct($message); $this->tag = $tag; $this->attribute = $attribute; $this->value = $value; } function test($html) { $parser = $this->load_xml($html); if (is_array($parser)) { return false; } $list = $parser->getElementsByTagName($this->tag); foreach ($list as $node) { if ($node->attributes->getNamedItem($this->attribute)->nodeValue === (string) $this->value) { return true; } } return false; } function customMessage($html) { return 'Content [' . $html . '] does not contain the tag [' . $this->tag . '] with attribute [' . $this->attribute . '="' . $this->value . '"].'; } } /** * An Expectation that looks to see whether some HMTL contains a tag with an array of attributes. * All attributes must be present and their values must match the expected values. * A third parameter can be used to specify attribute=>value pairs which must not be present in a positive match. * * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ContainsTagWithAttributes extends XMLStructureExpectation { /** * @var string $tag The name of the Tag to search */ protected $tag; /** * @var array $expectedvalues An associative array of parameters, all of which must be matched */ protected $expectedvalues = array(); /** * @var array $forbiddenvalues An associative array of parameters, none of which must be matched */ protected $forbiddenvalues = array(); /** * @var string $failurereason The reason why the test failed: nomatch or forbiddenmatch */ protected $failurereason = 'nomatch'; function __construct($tag, $expectedvalues, $forbiddenvalues=array(), $message = '%s') { parent::__construct($message); $this->tag = $tag; $this->expectedvalues = $expectedvalues; $this->forbiddenvalues = $forbiddenvalues; } function test($html) { $parser = $this->load_xml($html); if (is_array($parser)) { return false; } $list = $parser->getElementsByTagName($this->tag); $foundamatch = false; // Iterating through inputs foreach ($list as $node) { if (empty($node->attributes) || !is_a($node->attributes, 'DOMNamedNodeMap')) { continue; } // For the current expected attribute under consideration, check that values match $allattributesmatch = true; foreach ($this->expectedvalues as $expectedattribute => $expectedvalue) { if ($node->getAttribute($expectedattribute) === '' && $expectedvalue !== '') { $this->failurereason = 'nomatch'; continue 2; // Skip this tag, it doesn't have all the expected attributes } if ($node->getAttribute($expectedattribute) !== (string) $expectedvalue) { $allattributesmatch = false; $this->failurereason = 'nomatch'; } } if ($allattributesmatch) { $foundamatch = true; // Now make sure this node doesn't have any of the forbidden attributes either $nodeattrlist = $node->attributes; foreach ($nodeattrlist as $domattrname => $domattr) { if (array_key_exists($domattrname, $this->forbiddenvalues) && $node->getAttribute($domattrname) === (string) $this->forbiddenvalues[$domattrname]) { $this->failurereason = "forbiddenmatch:$domattrname:" . $node->getAttribute($domattrname); $foundamatch = false; } } } } return $foundamatch; } function customMessage($html) { $output = 'Content [' . $html . '] '; if (preg_match('/forbiddenmatch:(.*):(.*)/', $this->failurereason, $matches)) { $output .= "contains the tag $this->tag with the forbidden attribute=>value pair: [$matches[1]=>$matches[2]]"; } else if ($this->failurereason == 'nomatch') { $output .= 'does not contain the tag [' . $this->tag . '] with attributes ['; foreach ($this->expectedvalues as $var => $val) { $output .= "$var=\"$val\" "; } $output = rtrim($output); $output .= '].'; } return $output; } } /** * An Expectation that looks to see whether some HMTL contains a tag with an array of attributes. * All attributes must be present and their values must match the expected values. * A third parameter can be used to specify attribute=>value pairs which must not be present in a positive match. * * @copyright 2010 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ContainsSelectExpectation extends XMLStructureExpectation { /** * @var string $tag The name of the Tag to search */ protected $name; /** * @var array $expectedvalues An associative array of parameters, all of which must be matched */ protected $choices; /** * @var array $forbiddenvalues An associative array of parameters, none of which must be matched */ protected $selected; /** * @var string $failurereason The reason why the test failed: nomatch or forbiddenmatch */ protected $enabled; function __construct($name, $choices, $selected = null, $enabled = null, $message = '%s') { parent::__construct($message); $this->name = $name; $this->choices = $choices; $this->selected = $selected; $this->enabled = $enabled; } function test($html) { $parser = $this->load_xml($html); if (is_array($parser)) { return false; } $list = $parser->getElementsByTagName('select'); // Iterating through inputs foreach ($list as $node) { if (empty($node->attributes) || !is_a($node->attributes, 'DOMNamedNodeMap')) { continue; } if ($node->getAttribute('name') != $this->name) { continue; } if ($this->enabled === true && $node->getAttribute('disabled')) { continue; } else if ($this->enabled === false && $node->getAttribute('disabled') != 'disabled') { continue; } $options = $node->getElementsByTagName('option'); reset($this->choices); foreach ($options as $option) { if ($option->getAttribute('value') != key($this->choices)) { continue 2; } if ($option->firstChild->wholeText != current($this->choices)) { continue 2; } if ($option->getAttribute('value') === $this->selected && !$option->hasAttribute('selected')) { continue 2; } next($this->choices); } if (current($this->choices) !== false) { // The HTML did not contain all the choices. return false; } return true; } return false; } function customMessage($html) { if ($this->enabled === true) { $state = 'an enabled'; } else if ($this->enabled === false) { $state = 'a disabled'; } else { $state = 'a'; } $output = 'Content [' . $html . '] does not contain ' . $state . '