From ecdc28d6198e942249273bb343dd6acf5ae8b547 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 21 May 2014 13:38:03 +0000 Subject: [PATCH 01/15] splitting out web test case and unit test case --- islandora.info | 6 +- tests/datastream_validator_tests.test | 9 +- tests/datastream_validators.inc | 781 -------------------------- tests/islandora_web_test_case.inc | 604 -------------------- 4 files changed, 8 insertions(+), 1392 deletions(-) delete mode 100644 tests/datastream_validators.inc delete mode 100644 tests/islandora_web_test_case.inc diff --git a/islandora.info b/islandora.info index e06d0de9..e19f4937 100644 --- a/islandora.info +++ b/islandora.info @@ -13,7 +13,10 @@ files[] = includes/dublin_core.inc files[] = includes/tuque.inc files[] = includes/tuque_wrapper.inc files[] = includes/object.entity_controller.inc -files[] = tests/islandora_web_test_case.inc +files[] = tests/includes/datastream_validators.inc +files[] = tests/includes/islandora_web_test_case.inc +files[] = tests/includes/islandora_unit_test_case.inc +files[] = tests/includes/utilities.inc files[] = tests/authtokens.test files[] = tests/hooks.test files[] = tests/ingest.test @@ -22,6 +25,5 @@ files[] = tests/islandora_manage_permissions.test files[] = tests/datastream_versions.test files[] = tests/datastream_cache.test files[] = tests/derivatives.test -files[] = tests/islandora_manage_temp_file.test files[] = tests/datastream_validator_tests.test php = 5.3 diff --git a/tests/datastream_validator_tests.test b/tests/datastream_validator_tests.test index 79e1df7f..40615ae4 100644 --- a/tests/datastream_validator_tests.test +++ b/tests/datastream_validator_tests.test @@ -4,8 +4,6 @@ * Tests for things that test tests. Madness. */ -include 'datastream_validators.inc'; - /** * A test DatastreamValidator for the DatastreamValidatorResultTestCase. */ @@ -62,7 +60,7 @@ class DatastreamValidatorResultTestCase extends IslandoraWebTestCase { * Generates a generic DatastreamValidatorResult and grabs its properties. */ public function testDatastreamValidatorResult() { - $result = new DatastreamValidatorResult(TRUE, 'true', array()); + $result = new IslandoraTestUtilityResult(TRUE, 'true', array()); $this->assertEqual($result->getMessage(), 'true', "Result message generated correctly.", 'Islandora'); $this->assertEqual($result->getType(), TRUE, "Result type generated correctly.", 'Islandora'); $this->assertEqual($result->getCaller(), array(), "Result caller generated correctly.", 'Islandora'); @@ -111,10 +109,11 @@ class DatastreamValidatorResultTestCase extends IslandoraWebTestCase { $this->assertTrue(substr($first_caller['file'], -48) === substr($second_caller['file'], -48), "Fail caller file matches the pass caller file.", 'Islandora'); $this->assertTrue($first_caller['function'] === 'TestDatastreamValidator->assertSomethingSuccessfully()', "Correct pass caller function returned (actual: {$first_caller['function']}; expected: TestDatastreamValidator->assertSomethingSuccessfully()).", 'Islandora'); $this->assertTrue($second_caller['function'] === 'TestDatastreamValidator->assertSomethingFailed()', "Correct fail caller function returned (actual: {$second_caller['function']}; expected: TestDatastreamValidator->assertSomethingFailed()).", 'Islandora'); - $this->assertTrue($first_caller['line'] === 18, "Correct pass line number returned (actual: {$first_caller['line']}; expected: 9).", 'Islandora'); - $this->assertTrue($second_caller['line'] === 25, "Correct fail line number returned (actual: {$second_caller['line']}; expected: 13).", 'Islandora'); + $this->assertTrue($first_caller['line'] === 16, "Correct pass line number returned (actual: {$first_caller['line']}; expected: 16).", 'Islandora'); + $this->assertTrue($second_caller['line'] === 23, "Correct fail line number returned (actual: {$second_caller['line']}; expected: 23).", 'Islandora'); } } + } /** diff --git a/tests/datastream_validators.inc b/tests/datastream_validators.inc deleted file mode 100644 index 151ec676..00000000 --- a/tests/datastream_validators.inc +++ /dev/null @@ -1,781 +0,0 @@ -message = $message; - $this->caller = $caller; - $this->type = $type; - } - - /** - * Get the message for this result. - * - * @return string - * The message for this result. - */ - public function getMessage() { - return $this->message; - } - - /** - * Get the caller for this result. - * - * @return string - * The caller for this result. - */ - public function getCaller() { - return $this->caller; - } - - /** - * Get the type of result. - * - * @return bool - * The type of pass (TRUE for pass, FALSE for fail). - */ - public function getType() { - return $this->type; - } -} - -/** - * Abstraction for datastream validators. - * - * Classes extended from DatastreamValidator don't require much to be useful. - * They accept an IslandoraFedoraObject and a DSID to perform assertions on; - * all you have to do is place a series of functions inside the extended class - * using the naming convention assertThing(); each of these functions should - * ideally assert one thing and one thing only (for simplicity's sake), and - * should generate either a pass or a fail message by calling addPass() or - * addFail(). That's it, really; they don't have to return any values, as - * addPass() and addFail() just add messages to the overall pass/fail array. - * - * As long as you use those rules and naming conventions, all the magic is done - * when you instantiate the new datastream validator object. - * - * The IslandoraWebTestCase::assertDatastreams() function accepts paired DSIDs - * and datastream validator names in order to do the rest of the work. It grabs - * all the test results using getPasses() and getFails() and transforms those - * into something that DrupalWebTestCase can use. - */ -abstract class DatastreamValidator { - - /** - * The IslandoraFedoraObject containing the datastream to test. - * - * @var IslandoraFedoraObject - */ - public $object; - - /** - * The DSID of the string to test. - * - * @var string - */ - public $datastream; - - /** - * The content of the datastream. - * - * @var string[] - */ - public $datastreamContent; - - /** - * An array of DatastreamValidatorResults. - * - * These should be generated using $this->addResult. - * - * @var DatastreamValidatorResult[] - */ - public $results = array(); - - /** - * An array of additional required parameters. - * - * @var array - */ - public $params = array(); - - /** - * Constructs a DatastreamValidator. - * - * @param IslandoraFedoraObject $object - * The object to grab the datastream from. - * @param string $datastream - * The DSID of the datastream itself. - * @param array $params - * An extra array of parameters the validator might need. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - $this->object = $object; - $this->datastream = $datastream; - $this->params = $params; - if ($object[$datastream]) { - $this->datastreamContent = $object[$datastream]->content; - } - else { - drupal_set_message(t("Error grabbing content from datastream %datastream in object %id", array( - '%datastream' => $datastream, - '%id' => $object->id, - )), 'error'); - } - } - - /** - * Helper function to run all the validators in a class. - * - * On DatastreamValidator::__construct(), this looks for any functions - * within the class beginning in "assert" and runs them. In all current cases - * (and realistically in all future cases), this adds one or more passes or - * fails to $this->passes and/or $this->fails. - */ - public function runValidators() { - $methods = get_class_methods($this); - foreach ($methods as $method) { - if (substr($method, 0, 6) === 'assert') { - $this->$method(); - } - } - } - - /** - * Returns an array of DatastreamValidatorResults. - * - * @return DatastreamValidatorResult[] - * The results. - */ - public function getResults() { - if (empty($this->results)) { - $this->runValidators(); - } - return $this->results; - } - - /** - * Adds a result to $this->results. - * - * @param bool $type - * The type of result (TRUE for pass, FALSE for fail). - * @param string $message - * The message to put in the result. - */ - public function addResult($type, $message) { - $result = new DatastreamValidatorResult($type, $message, $this->getAssertionCall()); - $this->results[] = $result; - } - - /** - * Cycles through backtrace until the first non-assertion method is found. - * - * This is a manipulated version of DrupalWebTestCase::getAssertionCall(). - * We use it here so that we can pass back assertion calls from - * DatastreamValidator assertions instead of less useful TestCase functions. - * - * @return array - * Array representing the true caller. - */ - protected function getAssertionCall() { - $backtrace = debug_backtrace(); - - // While the current caller's function starts with 'assert', and another one - // exists after this function, keep poppin' em off. - while (substr($backtrace[1]['function'], 0, 6) !== 'assert' && isset($backtrace[2])) { - array_shift($backtrace); - } - - return _drupal_get_last_caller($backtrace); - } - -} - -/** - * Asserts that an object's given datastreams are common-type image files. - * - * Uses PHPGD to run the assertion check. This means that only certain kinds - * of image files can be checked. Please check the documentation for the PHPGD - * imagecreatefromstring() function to determine what filetypes are valid. - */ -class ImageDatastreamValidator extends DatastreamValidator { - - /** - * Asserts the validity of an image using PHPGD. - */ - protected function assertImageGeneration() { - $assertion = imagecreatefromstring($this->datastreamContent) !== FALSE; - $pass = "Image datastream {$this->datastream} is valid."; - $fail = "Image datastream {$this->datastream} is either invalid or corrupt."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - -} - -/** - * Asserts the validity of any .tif/.tiff datastream. - */ -class TIFFDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the TIFF contains an appropriate header. - */ - public function assertTIFFHeaderHex() { - $datastream_header_hex = self::getTIFFHeaderHex(); - if ($datastream_header_hex == "49492a00") { - // In this case, the ingested TIFF is designated as using the "Intel - // byte-order" (i.e. little-endian) by starting with the characters "II" - // (repeated so that byte order does not yet need to be significant). - // The number that follows is '42' in little-endian hex, a number of - // 'deep philosophical significance' to the TIFF format creators.' - $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Intel-byte-orderded TIF/TIFF file."); - } - elseif ($datastream_header_hex == "4d4d002a") { - // In this case, the ingested TIFF is designated as using the "Motorola - // byte-order" (i.e. big-endian) by starting with the characters "MM" - // instead. 42 follows once again, this time in big-endian hex. - $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Motorola-byte-ordered TIF/TIFF file."); - } - else { - $this->addResult(FALSE, "{$this->datastream} datastream does not assert that it is a valid TIF/TIFF file."); - } - } - - /** - * Grabs the first 8 characters from the TIFF datastream's hex. - * - * @return string - * The ... thing I just wrote up there. - */ - protected function getTIFFHeaderHex() { - return substr(bin2hex($this->datastreamContent), 0, 8); - } - -} - -/** - * Asserts the validity of a JP2 datastream. - */ -class JP2DatastreamValidator extends DatastreamValidator { - - /** - * Asserts the hex values at the head of the JP2 file. - * - * JP2 files begin with an offset header at the second 32-bit integer, - * 0x6A502020. This header is in all .jp2s, and we check for it here. - */ - protected function assertJP2Header() { - $assertion = substr(bin2hex($this->datastreamContent), 8, 8) == '6a502020'; - $pass = "Datastream {$this->datastream} contains the appropriate JP2 header."; - $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 header."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts the marker at the end of the JP2 file. - * - * JP2 files have their codestream capped with a marker, 0xFFD9. We're just - * checking for it here to see if the .jp2 encoder finished okay. - */ - protected function assertJP2Marker() { - $assertion = substr(bin2hex($this->datastreamContent), strlen(bin2hex($this->datastreamContent)) - 4, 4) == 'ffd9'; - $pass = "Datastream {$this->datastream} contains the appropriate JP2 ending marker."; - $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 ending marker. If this is the only JP2 validator that failed, it is likely that derivative generation was interrupted."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Asserts the validity of a PDF datastream. - */ -class PDFDatastreamValidator extends DatastreamValidator { - - /** - * Validates the PDF signature. - */ - protected function assertPDFSignature() { - $assertion = substr($this->datastreamContent, 0, 5) == '%PDF-'; - $pdf_version = substr($this->datastreamContent, 5, 3); - $pass = "{$this->datastream} datastream asserts that it is a valid PDF file using PDF version {$pdf_version}"; - $fail = "{$this->datastream} datastream binary header appears to be corrupt and missing a valid PDF signature."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Counts the number of signatures in this PDF file and asserts there are any. - */ - protected function assertPDFStreamCount() { - $pdf_stream_count = substr_count(bin2hex($this->datastreamContent), '0a73747265616d0a'); - $assertion = $pdf_stream_count !== 0; - $pass = "{$this->datastream} datastream reports the existence of {$pdf_stream_count} PDF streams. Note that an extremely low number could still indicate corruption."; - $fail = "{$this->datastream} datastream contains zero PDF streams, and is likely not a PDF file."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Validates the PDF closing tag. - * - * @return bool - * TRUE if it was present; FALSE otherwise. - */ - protected function assertPDFClosingTag() { - $assertion = strpos(bin2hex($this->datastreamContent), '0a2525454f460a') == TRUE; - $pass = "{$this->datastream} datastream reports the existence of the closing 'EOF' tag required at the end of PDFs"; - $fail = "{$this->datastream} datastream does not contain the closing 'EOF' tag. If this is the only PDF validation that failed, it is likely that derivative generation was interrupted."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Validates the number of times a string occurs in a datastream. - * - * Requires $this->params to be set to an array containing two keys - the first - * is the string we're looking to find in the datastream, and the second is an - * integer representing the number of times it should appear in the datastream. - */ -class TextDatastreamValidator extends DatastreamValidator { - /** - * Constructor override; blow up if we don't have our two values. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - if (count($params) < 2) { - throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.'); - } - parent::__construct($object, $datastream, $params); - } - - /** - * Asserts that the string given appears the correct number of times. - */ - protected function assertTextStringCount() { - $string_count = self::getTextStringCount(); - list($string, $expected) = $this->params; - $assertion = $string_count === $expected; - $this->addResult($assertion, "{$this->datastream} datastream contains the word(s) '{$string}' repeated {$string_count} time(s) (expected: {$expected})."); - } - - /** - * The number of times key [0] in $this->params appears in the datastream. - * - * @return int - * That count I just mentioned up there. - */ - protected function getTextStringCount() { - return substr_count($this->datastreamContent, $this->params[0]); - } -} - -/** - * Asserts the validity a WAV datastream. - * - * WAV files contain a rigidly detailed header that contains all sorts of fun - * information we can use to validate things against other things. So, we check - * rigorously that the header contains properly constructed data by looking to - * see if certain values are at their expected byte offset. We also compare - * declared chunk sizes against actual sizes. If any of these are off, WAV - * players will fail to function. - */ -class WAVDatastreamValidator extends DatastreamValidator { - - /** - * We need a special constructor here to get the hex datastream content. - * - * @param IslandoraFedoraObject $object - * The object to grab the datastream from. - * @param string $datastream - * The DSID of the datastream itself. - * @param array $params - * An extra array of parameters the validator might need. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - parent::__construct($object, $datastream, $params); - $this->datastreamContent = bin2hex($this->datastreamContent); - } - - /** - * Asserts that the datastream contains a valid WAV signature. - */ - protected function assertWAVSignature() { - $signatures = str_split(substr($this->datastreamContent, 0, 24), 8); - $assertion = $signatures[0] == '52494646' && $signatures[2] == '57415645'; - $pass = "Header of the {$this->datastream} datastream contains a valid file signature."; - $fail = "Header of the {$this->datastream} datastream contains corrupt file signature."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the chunksize in the header is correct. - */ - protected function assertWAVChunkSize() { - $assertion = islandora_hex2int(substr($this->datastreamContent, 8, 8)) === 36 + self::getDataSubChunkSize(); - $pass = "{$this->datastream} datastream chunksize in WAV header is correct"; - $fail = "{$this->datastream} datastream chunksize in WAV header does not match actual chunksize."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains a 'fmt' subchunk. - */ - protected function assertWAVFmtSubChunk() { - $assertion = substr($this->datastreamContent, 24, 8) === '666d7420'; - $pass = "{$this->datastream} datastream contains a 'fmt' subchunk."; - $fail = "{$this->datastream} datastream is missing the required 'fmt' subchunk."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the byterate reported by the WAV header is valid. - */ - protected function assertWAVByteRate() { - $wav_samplerate = islandora_hex2int(substr($this->datastreamContent, 48, 8)); - $assertion = islandora_hex2int(substr($this->datastreamContent, 56, 8)) === $wav_samplerate * self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream byterate in the WAV header is correct."; - $fail = "{$this->datastream} datastream byterate in the WAV header does not match actual calculated byterate."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the block alignment is correct. - */ - protected function assertWAVBlockAlignment() { - $assertion = islandora_hex2int(substr($this->datastreamContent, 64, 4)) === self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream block alignment is set correctly."; - $fail = "{$this->datastream} datastream block alignment is off."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts the existence of a 'data' subchunk. - * - * Also asserts that the subchunk size is correct. - */ - protected function assertWAVDataSubChunk() { - if (substr($this->datastreamContent, 72, 8) !== '64617461') { - $this->addResult(FALSE, "{$this->datastream} datastream is missing the 'data' subchunk."); - return; - } - else { - $this->addResult(TRUE, "{$this->datastream} datastream contains 'data' subchunk."); - $wav_numsamples = strlen(substr($this->datastreamContent, 88)) / self::getNumChannels() / self::getBytesPerSample() / 2; - $assertion = self::getDataSubChunkSize() === $wav_numsamples * self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream 'data' chunk is the correct size."; - $fail = "{$this->datastream} datastream 'data' chunk is sized incorrectly."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - } - - /** - * Gets the number of channels reported by the WAV header. - * - * @return int - * The number of channels reported by the datastream header. - */ - protected function getNumChannels() { - return islandora_hex2int(substr($this->datastreamContent, 44, 4)); - } - - /** - * Gets the reported number of byte rates per sample. - * - * @return int - * The number of bytes per sample reported by the datastream header. - */ - protected function getBytesPerSample() { - return islandora_hex2int(substr($this->datastreamContent, 68, 4)) / 8; - } - - /** - * Gets the size of the 'data' subchunk. - * - * @return int - * The size of the 'data' subchunk. - */ - protected function getDataSubChunkSize() { - return islandora_hex2int(substr($this->datastreamContent, 80, 8)); - } -} - -/** - * Asserts the validity of any .mp3 datastream. - * - * Our default setup tries to create an MP3 using VBR, but we do some extra - * checks in case someone turns that off. If the header contains the characters - * 'Xing', it is flagged as VBR, and we can do an in-depth check on each of the - * VBR settings. Otherwise, we look for the basic MP3 signature 'fffa' or 'fffb' - * at the start of the binary. - */ -class MP3DatastreamValidator extends DatastreamValidator { - - /** - * Asserts the validity of the MP3. - * - * The MP3 file format is a bit of a mess; the entire makeup of the file - * depends on whether it uses variable bit rate or static bit rate. So, I'm - * breaking my own rules here and using a single assert function so that I - * can handle the weird logic. - */ - protected function assertValidMP3() { - $this->datastreamContent = bin2hex($this->datastreamContent); - - // If it's not a VBR MP3, we don't have to check much, so let's get that - // out of the way first before we go doing a bunch of potentially pointless - // math. Check to see if the VBR flag (58696e67) isn't there. - if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffa') { - $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid MPEG-1 Layer 3 file with CRC protection"); - return; - } - if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffb') { - $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid unprotected MPEG-1 Layer 3 file"); - return; - } - - // And what if the flag IS set? - if (strpos($this->datastreamContent, '58696e67')) { - // Check the field flags. VBR-formatted MP3 files contain a 32-bit - // integer (stored as $mp3_flag_value) that is a combination of four - // bits, each one indicating the on-off status of a VBR setting, via - // logical OR. Rather than disassembling this value into individual - // bits, we use the algorithm "if (binary_total+bit_value*2)/bit_value*2 - // is greater than or equal to bit_value, that bit is turned on" to find - // the status of each bit, so we know whether to offset the rest. - $mp3_field_offset = array(0, 0, 0); - $mp3_vbrheader = substr($this->datastreamContent, strpos($this->datastreamContent, '58696e67'), 240); - $mp3_flag_value = hexdec(substr($mp3_vbrheader, 8, 8)); - - // We can't use the first flag, but we still need to offset the rest. - if (($mp3_flag_value + 1) % 2 == 0) { - $mp3_field_offset[0] += 8; - $mp3_field_offset[1] += 8; - $mp3_field_offset[2] += 8; - } - - // The second flag leads us to filesize data, which we can verify. - if (($mp3_flag_value + 4) % 4 > 1) { - $mp3_field_bytes = hexdec(substr($mp3_vbrheader, $mp3_field_offset[0] + 16, 8)); - $mp3_size = strlen($this->datastreamContent) / 2; - $assertion = $mp3_size == $mp3_field_bytes; - $pass = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes matches size field value of {$mp3_field_bytes}"; - $fail = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes does not match size field value of {$mp3_field_bytes}"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - $mp3_field_offset[1] += 8; - $mp3_field_offset[2] += 8; - } - - // We can't use the third flag for anything, but we still have to offset. - if (($mp3_flag_value + 8) % 8 > 3) { - $mp3_field_offset[2] += 200; - } - - // The fourth flag leads us to VBR quality data, which we can validate. - if ($mp3_flag_value > 7) { - $mp3_field_quality = hexdec(substr($mp3_vbrheader, $mp3_field_offset[2] + 16, 8)); - $assertion = $mp3_field_quality <= 100 && $mp3_field_quality >= 0; - $pass = "{$this->datastream} datastream reports valid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; - $fail = "{$this->datastream} datastream reports invalid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - } - - // If none of that works out, fail. - else { - $this->addResult(FALSE, "{$this->datastream} datastream is corrupt and does not identify as a valid MP3."); - } - } - -} - -/** - * Attempts to validate an .mp4 datastream. - * - * MP4 files are a subset of the ISO file format specification, and as such need - * to contain a 64-bit declaration of type within the first eight eight bytes of - * the file. This declaration is comprised of the characters 'ftyp', followed by - * a four-character filetype code. Here, we look for 'ftyp', and then pass the - * filetype code to the test message. - */ -class MP4DatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream is ISO-formatted video. - */ - protected function assertISOVideo() { - $mp4_ftyp = substr(strpos($this->datastreamContent, 'ftyp'), 4, 4); - $assertion = strpos($this->datastreamContent, 'ftyp') !== 0; - $pass = "{$this->datastream} datastream asserts that it is a valid ISO-formatted video file using ftyp {$mp4_ftyp}"; - $fail = "{$this->datastream} datastream is not a valid ISO-formatted video"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Attempts to validate an .ogg/ogv datastream using Vorbis and Theora encoding. - * - * OGG files are made up of several 'pages' of OGG data, each prefaced with an - * OGG marker - the letters 'OggS'. The file header also contains information on - * what encoders were used to create the file. Here, we're looking for at least - * one OGG page, and confirming that the file asserts the Theora and Vorbis - * codecs were used to create the file. - */ -class OGGDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream contains ogg pages. - */ - protected function assertOGGPages() { - $ogg_pages = substr_count($this->datastreamContent, 'OggS'); - $assertion = $ogg_pages !== 0; - $pass = "{$this->datastream} datastream asserts that it contains {$ogg_pages} Ogg pages (even a very small file should contain several)."; - $fail = "{$this->datastream} datastream contains no Ogg pages."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains Theora-encoded video. - */ - protected function assertTheoraVideo() { - $assertion = substr_count($this->datastreamContent, 'theora') !== 0; - $pass = "{$this->datastream} datastream asserts that it contains Theora-encoded video data."; - $fail = "{$this->datastream} datastream contains no marker indicating the presence of Theora-encoded video data."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains Vorbis-encoded audio. - */ - protected function assertVorbisAudio() { - $assertion = substr_count($this->datastreamContent, 'vorbis') !== 0; - $pass = "{$this->datastream} datastream asserts that it contains Vorbis-encoded audio data"; - $fail = "{$this->datastream} datastream contains no marker indicating the presence of Vorbis-encoded audio data."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Attempts to validate an .mkv datastream. - * - * There's not much we can do to check an MKV file, since the format is really, - * really loose. We do know a couple of things though - first, since MKV is an - * EBML format, the first four characters will always be the same. Since they're - * non-standard characters, we're looking at their hex values instead. And - * second, we know that the file will contain the declaration 'matroska' soon - * after. - */ -class MKVDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream is an EBML-format file. - */ - protected function assertEBMLFormat() { - $assertion = substr(bin2hex($this->datastreamContent), 0, 8) == '1a45dfa3'; - $pass = "{$this->datastream} datastream asserts that it is an EBML-formatted file"; - $fail = "{$this->datastream} datastream is not an EBML-formatted file."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains a matroska marker. - */ - protected function assertMatroskaMarker() { - $assertion = substr_count($this->datastreamContent, 'matroska') == 1; - $pass = "{$this->datastream} datastream asserts that its EBML DocType is Matroska"; - $fail = "{$this->datastream} datastream does not contain a Matroska EBML DocType marker."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} diff --git a/tests/islandora_web_test_case.inc b/tests/islandora_web_test_case.inc deleted file mode 100644 index 8a1e5ce8..00000000 --- a/tests/islandora_web_test_case.inc +++ /dev/null @@ -1,604 +0,0 @@ -configuration = $this->getTestConfiguration(); - if ($this->configuration['use_drupal_filter']) { - $this->backUpDrupalFilter(); - $this->setUpDrupalFilter(); - } - $this->admin = $this->createAdminUser(); - } - - /** - * Parses and returns the settings from the test configuration file. - * - * If no install specific test_config.ini file is found, it will use the - * assumed default configs found in default.test_config.ini. - * - * @return array - * The test configuration. - * - * @see parse_ini_file() - */ - protected function getTestConfiguration() { - $path = drupal_get_path('module', 'islandora'); - if (file_exists("$path/tests/test_config.ini")) { - $this->pass('Using custom test configuration.'); - return parse_ini_file("$path/tests/test_config.ini"); - } - elseif (file_exists("$path/tests/default.test_config.ini")) { - $this->pass('Using default test configuration.'); - return parse_ini_file("$path/tests/default.test_config.ini"); - } - throw new Exception('Required default.test_config.ini/test_config.ini file not found'); - } - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - protected function backUpDrupalFilter() { - if (file_exists($this->configuration['drupal_filter_file'])) { - $this->originalDrupalFilterContent = file_get_contents($this->configuration['drupal_filter_file']); - } - else { - throw new Exception('Failed to find the required Drupal Filter configuration file.'); - } - } - - /** - * Sets up a drupal filter that can read for the tests users table. - */ - protected function setUpDrupalFilter() { - $connection_info = Database::getConnectionInfo('default'); - $drupal_filter_dom = new DomDocument(); - $drupal_filter_dom->loadXML($this->originalDrupalFilterContent); - $server = $connection_info['default']['host']; - $dbname = $connection_info['default']['database']; - $user = $connection_info['default']['username']; - $password = $connection_info['default']['password']; - $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; - $prefix = $connection_info['default']['prefix']['default']; - $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); - $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); - $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); - $connection_node->setAttributeNode(new DOMAttr('server', $server)); - $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); - $connection_node->setAttributeNode(new DOMAttr('user', $user)); - $connection_node->setAttributeNode(new DOMAttr('password', $password)); - $connection_node->setAttributeNode(new DOMAttr('port', $port)); - $sql_node = $connection_node->appendChild(new DOMElement('sql')); - $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); - file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); - } - - /** - * Creates the a full fedora admin user with a repository connection. - */ - protected function createAdminUser() { - $roles = user_roles(); - $index = array_search('administrator', $roles); - $user = $this->drupalCreateUser(); - $user->roles[$index] = 'administrator'; - $user->name = $this->configuration['admin_user']; - $user->pass = $this->configuration['admin_pass']; - $user = user_save($user); - $url = variable_get('islandora_base_url', $this->configuration['fedora_url']); - $connection = islandora_get_tuque_connection($user, $url); - $user->repository = $connection->repository; - return $user; - } - - /** - * Logs in the given user, handles the special case where the user is admin. - * - * @see DrupalWebTestCase::drupalLogin() - */ - protected function drupalLogin(stdClass $account) { - if ($account->uid == $this->admin->uid) { - // Create password for Drupal. - $edit = array('pass' => user_password()); - $account = user_save($account, $edit); - // Raw password is used to login. - $account->pass_raw = $edit['pass']; - // We must login before changing the password for fedora. - parent::drupalLogin($account); - $account->name = $this->configuration['admin_user']; - $account->pass = $this->configuration['admin_pass']; - // Save the fedora admin credentials for later GET/POST requests. - $account = user_save($account); - } - else { - parent::drupalLogin($account); - $this->users[] = $account->name; - } - } - - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - protected function restoreDrupalFilter() { - $file = $this->configuration['drupal_filter_file']; - if (isset($this->originalDrupalFilterContent)) { - file_put_contents($file, $this->originalDrupalFilterContent); - } - elseif (file_exists($file)) { - // Remove if there was never an original. - drupal_unlink($file); - } - } - - /** - * Restores the original Drupal filter, frees any allocated resources. - * - * To safeguard against leaving test objects in the repository, tearDown() - * calls deleteUserCreatedObjects() every time by default. This feature can be - * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. - * - * @see DrupalWebTestCase::tearDown() - */ - public function tearDown() { - if ($this->deleteObjectsOnTeardown) { - foreach ($this->users as $user) { - $this->deleteUserCreatedObjects($user); - } - } - if ($this->configuration['use_drupal_filter']) { - $this->restoreDrupalFilter(); - } - unset($this->admin); - unset($this->configuration); - parent::tearDown(); - } - - /** - * Asserts that the given datastreams exist correctly on the object. - * - * @param AbstractObject $object - * The PID of the object - * @param array $datastreams - * An array of strings containing datastream names - * - * @return bool - * TRUE on success, FALSE on fail. - */ - public function assertDatastreams($object, array $datastreams) { - if (!is_object($object)) { - $this->fail("Failed. Object passed in is invalid.", 'Islandora'); - } - else { - $missing_datastreams = array_diff_key(array_flip($datastreams), $this->admin->repository->api->a->listDatastreams($object->id)); - - if (!empty($missing_datastreams)) { - $this->fail("Failed to find datastream(s) " . implode(', ', array_flip($missing_datastreams)) . " in object {$object->id}."); - return FALSE; - } - - $this->pass("Found required datastream(s) in object {$object->id}"); - return TRUE; - } - } - - /** - * Attempts to validate an array of datastreams, generally via binary checks. - * - * Datastream validation classes exist in, and can be added to, the file - * 'datastream_validators.inc', which is found in this folder. Datastream - * validator classes use the naming convention 'PrefixDatastreamValidator', - * and that 'Prefix' is what this function uses to determine what class to - * instantiate. - * - * $param IslandoraFedoraObject $object - * The object to load datastreams from. - * $param array $datastreams - * An array of arrays that pair DSIDs, DatastreamValidator class prefixes, - * and optional params. You can check some of the existing implementations - * for examples. - */ - public function validateDatastreams($object, array $datastreams) { - - if (!is_object($object)) { - $this->fail("Datastream validation failed; Object passed in is invalid.", 'Islandora'); - return; - } - - module_load_include('inc', 'islandora', 'tests/datastream_validators'); - - foreach ($datastreams as $datastream) { - // Let's give them conventional names. - $dsid = $datastream[0]; - $prefix = $datastream[1]; - $params = array(); - if (isset($datastream[2])) { - $params = $datastream[2]; - } - - // Legacy tests were created before the CamelCase conventions of the class - // system now in place. So, we need to automagically seek out prefixes - // that start with a lower-case letter and convert them to the proper - // format (rather than fixing every single legacy test). - if (ctype_lower(substr($prefix, 0, 1))) { - // Handle the case where the prefix is "image". - if ($prefix === 'image') { - $prefix = 'Image'; - } - // Handle the case where the prefix is "text". - elseif ($prefix === 'text') { - $prefix = 'Text'; - } - // All other cases involve just converting everything to caps. - else { - $prefix = strtoupper($prefix); - } - } - - // Instantiate the appropriate class, and grab the results. - $class_name = "{$prefix}DatastreamValidator"; - if (class_exists($class_name)) { - $validator = new $class_name($object, $dsid, $params); - foreach ($validator->getResults() as $result) { - $this->assert($result->getType(), $result->getMessage(), 'Islandora', $result->getCaller()); - } - } - else { - $this->fail("No DatastreamValidator class was found with the name '$class_name'; are you sure the prefix given to IslandoraWebTestCase->validateDatastreams() was entered correctly, or that such a validator exists?", 'Islandora'); - } - } - } - - /** - * Gets a tuque object from a path. - * - * @param string $path - * A full or partial path to an islandora object. - * - * @return AbstractObject - * The pid of the object or FALSE if a PID is not found. - */ - public function getObjectFromPath($path) { - $path_parts = explode('/', $path); - $array_length = count($path_parts); - for ($i = 0; $i < $array_length; $i++) { - if ($path_parts[$i] == 'islandora' && isset($path_parts[$i + 1]) && $path_parts[$i + 1] == 'object') { - if (isset($path_parts[$i + 2])) { - return islandora_object_load(urldecode($path_parts[$i + 2])); - } - } - } - $this->fail("Failed to parse path: $path."); - return FALSE; - } - - /** - * Deletes an object using the PID. This does the deletion using the UI. - * - * @param string $pid - * The PID of the collection to be deleted - * @param string $button - * The label of the first 'Delete' button - * @param bool $safety - * If TRUE, this will only delete objects owned by users in $this->users. - */ - public function deleteObject($pid, $button = NULL, $safety = TRUE) { - $object = islandora_object_load($pid); - if (!$safety || in_array($object->owner, $this->users)) { - $path = "islandora/object/$pid/manage/properties"; - if (isset($button)) { - $this->drupalPost($path, array(), $button); - } - else { - $object = islandora_object_load($pid); - $this->drupalPost($path, array(), "Permanently remove '{$object->label}' from repository"); - } - $this->drupalPost($this->url, array(), t('Delete')); - - $this->drupalGet("islandora/object/$pid"); - $this->assertResponse(404, "Object $pid successfully deleted."); - } - else { - $this->fail("Cannot delete object {$pid}; it is owned by non-test user {$object->owner}, and this function was called with the safety on."); - return FALSE; - } - } - - /** - * Constructs and ingests a Fedora object and datastream(s) via tuque. - * - * All keys inside the parameter arrays for this function are optional. it - * can be run simply by calling $this->ingestConstructedObject();. - * - * @param array $properties - * An array containing object information using these keys: - * 'label' - The object label; randomized if not set. - * 'pid' - 'namespace:pid', or just 'namespace' to generate the suffix. - * 'models' - An array that can contain multiple content model PIDs, or a - * string containing a single content model PID. - * 'owner' - The object's owner. Defaults to the currently logged-in user, - * if available. It is recommended to set this to a value that can be found - * in $this->users; otherwise, this object will have to be manually deleted. - * 'parent' - The PID of the parent collection. - * @param array $datastreams - * An array containing zero or more datastream arrays that use the keys: - * 'dsid' - the datastream ID; randomized if not set. - * 'path' - The path to the file to use; defaults to fixtures/test.jpg. - * 'control_group' - The single-letter control group identifier. - * 'mimetype' - The datastream's mimetype. - * - * @return bool|array - * FALSE if the object ingest failed, or the object array if successful. - */ - public function ingestConstructedObject(array $properties = array(), array $datastreams = array()) { - $tuque = islandora_get_tuque_connection($this->admin); - $repository = $tuque->repository; - if (!isset($properties['pid'])) { - $properties['pid'] = "islandora"; - } - $object = $repository->constructObject($properties['pid']); - - // Set the object properties before ingesting it. - if (isset($properties['label'])) { - $object->label = $properties['label']; - } - else { - $properties['label'] = $this->randomName(16); - $object->label = $properties['label']; - } - - if (isset($properties['owner'])) { - $object->owner = $properties['owner']; - } - elseif ($this->loggedInUser !== FALSE) { - $object->owner = $this->loggedInUser->name; - } - - if (isset($properties['models'])) { - try { - $object->models = (array) $properties['models']; - } - catch (Exception $e) { - $this->fail("Encountered an exception when trying to add content models to {$object->id}: $e"); - return FALSE; - } - } - - $repository->ingestObject($object); - if (!$object) { - $this->fail(t("Failed to ingest object."), 'Islandora'); - return FALSE; - } - else { - $this->pass(t("Ingested object %object", array('%object' => $object->id)), 'Islandora'); - } - - // Chuck in some datastreams. - if (!empty($datastreams)) { - foreach ($datastreams as $datastream) { - if (!isset($datastream['dsid'])) { - $datastream['dsid'] = $this->randomName(8); - } - if (!isset($datastream['path'])) { - $datastream['path'] = drupal_get_path('module', 'islandora') . '/tests/fixtures/test.jpg'; - } - if (!isset($datastream['control_group'])) { - $new_datastream = $object->constructDatastream($datastream['dsid']); - } - else { - $new_datastream = $object->constructDatastream($datastream['dsid'], $datastream['control_group']); - } - $new_datastream->label = $datastream['dsid']; - if (isset($datastream['mimetype'])) { - $new_datastream->mimetype = $datastream['mimetype']; - } - $new_datastream->setContentFromFile($datastream['path']); - $object->ingestDatastream($new_datastream); - } - } - - // Add a parent relationship, if necessary. - if (isset($properties['parent'])) { - $object->relationships->add(FEDORA_RELS_EXT_URI, 'isMemberOfCollection', $properties['parent']); - } - - return $object; - } - - /** - * Deletes all objects created by the given user. - * - * To safeguard against leaving test objects in the repository, this is called - * each time DrupalTestCase::run() calls tearDown(). This feature can be - * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. - * - * @param object $username - * The user whose objects we'd like to remove. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function deleteUserCreatedObjects($username) { - if ($username === $this->configuration['admin_user']) { - $this->fail("This function will under no circumstance attempt deletion of all objects owned by the configured Fedora admin user ({$this->configuration['admin_user']}), as this could irreparably damage the repository.", 'Islandora'); - return FALSE; - } - - $query = << WHERE -{ - ?object "$username" -} -QUERY; - - $objects = $this->admin->repository->ri->sparqlQuery($query); - foreach ($objects as $object) { - $loaded_object = islandora_object_load($object['object']['value']); - islandora_delete_object($loaded_object); - if ($this->assertFalse(islandora_object_load($object['object']['value']), "Object {$object['object']['value']} successfully removed from repository.", 'Islandora')) { - return FALSE; - } - return TRUE; - } - } - - /** - * These are a few quick helper functions to fill in a gap in the standard - * DrupalWebTestCase where no function exists to test for the simple existence - * or non-existence of an error. - */ - - /** - * Asserts that an error is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertError($message = '', $group = 'Other') { - if (!$message) { - $message = "Error found on current page when error was expected."; - } - return $this->assertRaw("
", $message, $group); - } - - /** - * Asserts that no error is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertNoError($message = '', $group = 'Other') { - if (!$message) { - $message = "No error found on current page when no error was expected."; - } - return $this->assertNoRaw("
", $message, $group); - } - - /** - * Asserts that a warning is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertWarning($message = '', $group = 'Other') { - if (!$message) { - $message = "Warning found on current page when warning was expected."; - } - return $this->assertRaw("
", $message, $group); - } - - /** - * Asserts that no warning is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertNoWarning($message = '', $group = 'Other') { - if (!$message) { - $message = "No warning found on current page when no warning was expected."; - } - return $this->assertNoRaw("
", $message, $group); - } - - /** - * Makes a drupalPost() request, using the form submit button's ID. - * - * Because drupalPost() is silly and doesn't let us choose which button we'd - * like to select if multiple buttons have the same value, this function - * allows us to select whatever button we'd like based on the ID instead. - * - * This is done via the absolutely hilarious method of fudging the actual - * button labels that don't have the ID we want, so that the only one left - * with the correct label is the one with the right ID. - * - * @see DrupalWebTestCase::drupalPost() - * - * @param string $path - * Location of the post form. - * @param array $edit - * Field data in an associative array. - * @param string $submit - * Value of the submit button whose click is to be emulated. - * @param string $id - * ID of the submit button whose click is to be emulated. - * @param array $options - * Options to be forwarded to url(). - * @param array $headers - * An array containing additional HTTP request headers, each formatted as - * "name: value". - * @param null $form_html_id - * (optional) HTML ID of the form to be submitted. - * @param null $extra_post - * (optional) A string of additional data to append to the POST submission. - * - * @return bool|string - * The content from the POST request's curlExec, or FALSE on fail. - */ - public function drupalPostByID($path, $edit, $submit, $id, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { - $buttons = $this->xpath("//input[@type=\"submit\" and @value=\"{$submit}\"]"); - if (empty($buttons)) { - $this->fail("No buttons found on the page with value '$submit'"); - return FALSE; - } - $i = 0; - foreach ($buttons as $button) { - if ($button['id'] !== $id) { - $button['value'] .= "_$i"; - $i++; - } - } - return $this->drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post); - } - -} From 23e8201afe68e9a36da876b2562b30dea65a52b5 Mon Sep 17 00:00:00 2001 From: qadan Date: Wed, 21 May 2014 13:43:36 +0000 Subject: [PATCH 02/15] er, add the includes too --- tests/includes/datastream_validators.inc | 699 ++++++++++++++++++++ tests/includes/islandora_unit_test_case.inc | 97 +++ tests/includes/islandora_web_test_case.inc | 340 ++++++++++ tests/includes/utilities.inc | 554 ++++++++++++++++ 4 files changed, 1690 insertions(+) create mode 100644 tests/includes/datastream_validators.inc create mode 100644 tests/includes/islandora_unit_test_case.inc create mode 100644 tests/includes/islandora_web_test_case.inc create mode 100644 tests/includes/utilities.inc diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc new file mode 100644 index 00000000..c0241c84 --- /dev/null +++ b/tests/includes/datastream_validators.inc @@ -0,0 +1,699 @@ +object = $object; + $this->datastream = $datastream; + $this->params = $params; + if ($object[$datastream]) { + $this->datastreamContent = $object[$datastream]->content; + } + else { + drupal_set_message(t("Error grabbing content from datastream %datastream in object %id", array( + '%datastream' => $datastream, + '%id' => $object->id, + )), 'error'); + } + } + + /** + * Helper function to run all the validators in a class. + * + * On DatastreamValidator::__construct(), this looks for any functions + * within the class beginning in "assert" and runs them. In all current cases + * (and realistically in all future cases), this adds one or more passes or + * fails to $this->passes and/or $this->fails. + */ + public function runValidators() { + $methods = get_class_methods($this); + foreach ($methods as $method) { + if (substr($method, 0, 6) === 'assert') { + $this->$method(); + } + } + } + + /** + * Returns an array of IslandoraTestUtilityResults. + * + * @return IslandoraTestUtilityResult[] + * The results. + */ + public function getResults() { + if (empty($this->results)) { + $this->runValidators(); + } + return $this->results; + } + + /** + * Adds a result to $this->results. + * + * @param bool $type + * The type of result (TRUE for pass, FALSE for fail). + * @param string $message + * The message to put in the result. + */ + public function addResult($type, $message) { + $result = new IslandoraTestUtilityResult($type, $message, $this->getAssertionCall()); + $this->results[] = $result; + } + + /** + * Cycles through backtrace until the first non-assertion method is found. + * + * This is a manipulated version of DrupalWebTestCase::getAssertionCall(). + * We use it here so that we can pass back assertion calls from + * DatastreamValidator assertions instead of less useful TestCase functions. + * + * @return array + * Array representing the true caller. + */ + public function getAssertionCall() { + $backtrace = debug_backtrace(); + + // While the current caller's function starts with 'assert', and another one + // exists after this function, keep poppin' em off. + while (substr($backtrace[1]['function'], 0, 6) !== 'assert' && isset($backtrace[2])) { + array_shift($backtrace); + } + + return _drupal_get_last_caller($backtrace); + } + +} + +/** + * Asserts that an object's given datastreams are common-type image files. + * + * Uses PHPGD to run the assertion check. This means that only certain kinds + * of image files can be checked. Please check the documentation for the PHPGD + * imagecreatefromstring() function to determine what filetypes are valid. + */ +class ImageDatastreamValidator extends DatastreamValidator { + + /** + * Asserts the validity of an image using PHPGD. + */ + protected function assertImageGeneration() { + $assertion = imagecreatefromstring($this->datastreamContent) !== FALSE; + $pass = "Image datastream {$this->datastream} is valid."; + $fail = "Image datastream {$this->datastream} is either invalid or corrupt."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + +} + +/** + * Asserts the validity of any .tif/.tiff datastream. + */ +class TIFFDatastreamValidator extends DatastreamValidator { + + /** + * Asserts that the TIFF contains an appropriate header. + */ + public function assertTIFFHeaderHex() { + $datastream_header_hex = self::getTIFFHeaderHex(); + if ($datastream_header_hex == "49492a00") { + // In this case, the ingested TIFF is designated as using the "Intel + // byte-order" (i.e. little-endian) by starting with the characters "II" + // (repeated so that byte order does not yet need to be significant). + // The number that follows is '42' in little-endian hex, a number of + // 'deep philosophical significance' to the TIFF format creators.' + $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Intel-byte-orderded TIF/TIFF file."); + } + elseif ($datastream_header_hex == "4d4d002a") { + // In this case, the ingested TIFF is designated as using the "Motorola + // byte-order" (i.e. big-endian) by starting with the characters "MM" + // instead. 42 follows once again, this time in big-endian hex. + $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Motorola-byte-ordered TIF/TIFF file."); + } + else { + $this->addResult(FALSE, "{$this->datastream} datastream does not assert that it is a valid TIF/TIFF file."); + } + } + + /** + * Grabs the first 8 characters from the TIFF datastream's hex. + * + * @return string + * The ... thing I just wrote up there. + */ + protected function getTIFFHeaderHex() { + return substr(bin2hex($this->datastreamContent), 0, 8); + } + +} + +/** + * Asserts the validity of a JP2 datastream. + */ +class JP2DatastreamValidator extends DatastreamValidator { + + /** + * Asserts the hex values at the head of the JP2 file. + * + * JP2 files begin with an offset header at the second 32-bit integer, + * 0x6A502020. This header is in all .jp2s, and we check for it here. + */ + protected function assertJP2Header() { + $assertion = substr(bin2hex($this->datastreamContent), 8, 8) == '6a502020'; + $pass = "Datastream {$this->datastream} contains the appropriate JP2 header."; + $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 header."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts the marker at the end of the JP2 file. + * + * JP2 files have their codestream capped with a marker, 0xFFD9. We're just + * checking for it here to see if the .jp2 encoder finished okay. + */ + protected function assertJP2Marker() { + $assertion = substr(bin2hex($this->datastreamContent), strlen(bin2hex($this->datastreamContent)) - 4, 4) == 'ffd9'; + $pass = "Datastream {$this->datastream} contains the appropriate JP2 ending marker."; + $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 ending marker. If this is the only JP2 validator that failed, it is likely that derivative generation was interrupted."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } +} + +/** + * Asserts the validity of a PDF datastream. + */ +class PDFDatastreamValidator extends DatastreamValidator { + + /** + * Validates the PDF signature. + */ + protected function assertPDFSignature() { + $assertion = substr($this->datastreamContent, 0, 5) == '%PDF-'; + $pdf_version = substr($this->datastreamContent, 5, 3); + $pass = "{$this->datastream} datastream asserts that it is a valid PDF file using PDF version {$pdf_version}"; + $fail = "{$this->datastream} datastream binary header appears to be corrupt and missing a valid PDF signature."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Counts the number of signatures in this PDF file and asserts there are any. + */ + protected function assertPDFStreamCount() { + $pdf_stream_count = substr_count(bin2hex($this->datastreamContent), '0a73747265616d0a'); + $assertion = $pdf_stream_count !== 0; + $pass = "{$this->datastream} datastream reports the existence of {$pdf_stream_count} PDF streams. Note that an extremely low number could still indicate corruption."; + $fail = "{$this->datastream} datastream contains zero PDF streams, and is likely not a PDF file."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Validates the PDF closing tag. + * + * @return bool + * TRUE if it was present; FALSE otherwise. + */ + protected function assertPDFClosingTag() { + $assertion = strpos(bin2hex($this->datastreamContent), '0a2525454f460a') == TRUE; + $pass = "{$this->datastream} datastream reports the existence of the closing 'EOF' tag required at the end of PDFs"; + $fail = "{$this->datastream} datastream does not contain the closing 'EOF' tag. If this is the only PDF validation that failed, it is likely that derivative generation was interrupted."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } +} + +/** + * Validates the number of times a string occurs in a datastream. + * + * Requires $this->params to be set to an array containing two keys - the first + * is the string we're looking to find in the datastream, and the second is an + * integer representing the number of times it should appear in the datastream. + */ +class TextDatastreamValidator extends DatastreamValidator { + /** + * Constructor override; blow up if we don't have our two values. + */ + public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + if (count($params) < 2) { + throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.'); + } + parent::__construct($object, $datastream, $params); + } + + /** + * Asserts that the string given appears the correct number of times. + */ + protected function assertTextStringCount() { + $string_count = self::getTextStringCount(); + list($string, $expected) = $this->params; + $assertion = $string_count === $expected; + $this->addResult($assertion, "{$this->datastream} datastream contains the word(s) '{$string}' repeated {$string_count} time(s) (expected: {$expected})."); + } + + /** + * The number of times key [0] in $this->params appears in the datastream. + * + * @return int + * That count I just mentioned up there. + */ + protected function getTextStringCount() { + return substr_count($this->datastreamContent, $this->params[0]); + } +} + +/** + * Asserts the validity a WAV datastream. + * + * WAV files contain a rigidly detailed header that contains all sorts of fun + * information we can use to validate things against other things. So, we check + * rigorously that the header contains properly constructed data by looking to + * see if certain values are at their expected byte offset. We also compare + * declared chunk sizes against actual sizes. If any of these are off, WAV + * players will fail to function. + */ +class WAVDatastreamValidator extends DatastreamValidator { + + /** + * We need a special constructor here to get the hex datastream content. + * + * @param IslandoraFedoraObject $object + * The object to grab the datastream from. + * @param string $datastream + * The DSID of the datastream itself. + * @param array $params + * An extra array of parameters the validator might need. + */ + public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + parent::__construct($object, $datastream, $params); + $this->datastreamContent = bin2hex($this->datastreamContent); + } + + /** + * Asserts that the datastream contains a valid WAV signature. + */ + protected function assertWAVSignature() { + $signatures = str_split(substr($this->datastreamContent, 0, 24), 8); + $assertion = $signatures[0] == '52494646' && $signatures[2] == '57415645'; + $pass = "Header of the {$this->datastream} datastream contains a valid file signature."; + $fail = "Header of the {$this->datastream} datastream contains corrupt file signature."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the chunksize in the header is correct. + */ + protected function assertWAVChunkSize() { + $assertion = islandora_hex2int(substr($this->datastreamContent, 8, 8)) === 36 + self::getDataSubChunkSize(); + $pass = "{$this->datastream} datastream chunksize in WAV header is correct"; + $fail = "{$this->datastream} datastream chunksize in WAV header does not match actual chunksize."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the datastream contains a 'fmt' subchunk. + */ + protected function assertWAVFmtSubChunk() { + $assertion = substr($this->datastreamContent, 24, 8) === '666d7420'; + $pass = "{$this->datastream} datastream contains a 'fmt' subchunk."; + $fail = "{$this->datastream} datastream is missing the required 'fmt' subchunk."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the byterate reported by the WAV header is valid. + */ + protected function assertWAVByteRate() { + $wav_samplerate = islandora_hex2int(substr($this->datastreamContent, 48, 8)); + $assertion = islandora_hex2int(substr($this->datastreamContent, 56, 8)) === $wav_samplerate * self::getNumChannels() * self::getBytesPerSample(); + $pass = "{$this->datastream} datastream byterate in the WAV header is correct."; + $fail = "{$this->datastream} datastream byterate in the WAV header does not match actual calculated byterate."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the block alignment is correct. + */ + protected function assertWAVBlockAlignment() { + $assertion = islandora_hex2int(substr($this->datastreamContent, 64, 4)) === self::getNumChannels() * self::getBytesPerSample(); + $pass = "{$this->datastream} datastream block alignment is set correctly."; + $fail = "{$this->datastream} datastream block alignment is off."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts the existence of a 'data' subchunk. + * + * Also asserts that the subchunk size is correct. + */ + protected function assertWAVDataSubChunk() { + if (substr($this->datastreamContent, 72, 8) !== '64617461') { + $this->addResult(FALSE, "{$this->datastream} datastream is missing the 'data' subchunk."); + return; + } + else { + $this->addResult(TRUE, "{$this->datastream} datastream contains 'data' subchunk."); + $wav_numsamples = strlen(substr($this->datastreamContent, 88)) / self::getNumChannels() / self::getBytesPerSample() / 2; + $assertion = self::getDataSubChunkSize() === $wav_numsamples * self::getNumChannels() * self::getBytesPerSample(); + $pass = "{$this->datastream} datastream 'data' chunk is the correct size."; + $fail = "{$this->datastream} datastream 'data' chunk is sized incorrectly."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + } + + /** + * Gets the number of channels reported by the WAV header. + * + * @return int + * The number of channels reported by the datastream header. + */ + protected function getNumChannels() { + return islandora_hex2int(substr($this->datastreamContent, 44, 4)); + } + + /** + * Gets the reported number of byte rates per sample. + * + * @return int + * The number of bytes per sample reported by the datastream header. + */ + protected function getBytesPerSample() { + return islandora_hex2int(substr($this->datastreamContent, 68, 4)) / 8; + } + + /** + * Gets the size of the 'data' subchunk. + * + * @return int + * The size of the 'data' subchunk. + */ + protected function getDataSubChunkSize() { + return islandora_hex2int(substr($this->datastreamContent, 80, 8)); + } +} + +/** + * Asserts the validity of any .mp3 datastream. + * + * Our default setup tries to create an MP3 using VBR, but we do some extra + * checks in case someone turns that off. If the header contains the characters + * 'Xing', it is flagged as VBR, and we can do an in-depth check on each of the + * VBR settings. Otherwise, we look for the basic MP3 signature 'fffa' or 'fffb' + * at the start of the binary. + */ +class MP3DatastreamValidator extends DatastreamValidator { + + /** + * Asserts the validity of the MP3. + * + * The MP3 file format is a bit of a mess; the entire makeup of the file + * depends on whether it uses variable bit rate or static bit rate. So, I'm + * breaking my own rules here and using a single assert function so that I + * can handle the weird logic. + */ + protected function assertValidMP3() { + $this->datastreamContent = bin2hex($this->datastreamContent); + + // If it's not a VBR MP3, we don't have to check much, so let's get that + // out of the way first before we go doing a bunch of potentially pointless + // math. Check to see if the VBR flag (58696e67) isn't there. + if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffa') { + $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid MPEG-1 Layer 3 file with CRC protection"); + return; + } + if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffb') { + $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid unprotected MPEG-1 Layer 3 file"); + return; + } + + // And what if the flag IS set? + if (strpos($this->datastreamContent, '58696e67')) { + // Check the field flags. VBR-formatted MP3 files contain a 32-bit + // integer (stored as $mp3_flag_value) that is a combination of four + // bits, each one indicating the on-off status of a VBR setting, via + // logical OR. Rather than disassembling this value into individual + // bits, we use the algorithm "if (binary_total+bit_value*2)/bit_value*2 + // is greater than or equal to bit_value, that bit is turned on" to find + // the status of each bit, so we know whether to offset the rest. + $mp3_field_offset = array(0, 0, 0); + $mp3_vbrheader = substr($this->datastreamContent, strpos($this->datastreamContent, '58696e67'), 240); + $mp3_flag_value = hexdec(substr($mp3_vbrheader, 8, 8)); + + // We can't use the first flag, but we still need to offset the rest. + if (($mp3_flag_value + 1) % 2 == 0) { + $mp3_field_offset[0] += 8; + $mp3_field_offset[1] += 8; + $mp3_field_offset[2] += 8; + } + + // The second flag leads us to filesize data, which we can verify. + if (($mp3_flag_value + 4) % 4 > 1) { + $mp3_field_bytes = hexdec(substr($mp3_vbrheader, $mp3_field_offset[0] + 16, 8)); + $mp3_size = strlen($this->datastreamContent) / 2; + $assertion = $mp3_size == $mp3_field_bytes; + $pass = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes matches size field value of {$mp3_field_bytes}"; + $fail = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes does not match size field value of {$mp3_field_bytes}"; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + $mp3_field_offset[1] += 8; + $mp3_field_offset[2] += 8; + } + + // We can't use the third flag for anything, but we still have to offset. + if (($mp3_flag_value + 8) % 8 > 3) { + $mp3_field_offset[2] += 200; + } + + // The fourth flag leads us to VBR quality data, which we can validate. + if ($mp3_flag_value > 7) { + $mp3_field_quality = hexdec(substr($mp3_vbrheader, $mp3_field_offset[2] + 16, 8)); + $assertion = $mp3_field_quality <= 100 && $mp3_field_quality >= 0; + $pass = "{$this->datastream} datastream reports valid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; + $fail = "{$this->datastream} datastream reports invalid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + } + + // If none of that works out, fail. + else { + $this->addResult(FALSE, "{$this->datastream} datastream is corrupt and does not identify as a valid MP3."); + } + } + +} + +/** + * Attempts to validate an .mp4 datastream. + * + * MP4 files are a subset of the ISO file format specification, and as such need + * to contain a 64-bit declaration of type within the first eight eight bytes of + * the file. This declaration is comprised of the characters 'ftyp', followed by + * a four-character filetype code. Here, we look for 'ftyp', and then pass the + * filetype code to the test message. + */ +class MP4DatastreamValidator extends DatastreamValidator { + + /** + * Asserts that the datastream is ISO-formatted video. + */ + protected function assertISOVideo() { + $mp4_ftyp = substr(strpos($this->datastreamContent, 'ftyp'), 4, 4); + $assertion = strpos($this->datastreamContent, 'ftyp') !== 0; + $pass = "{$this->datastream} datastream asserts that it is a valid ISO-formatted video file using ftyp {$mp4_ftyp}"; + $fail = "{$this->datastream} datastream is not a valid ISO-formatted video"; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } +} + +/** + * Attempts to validate an .ogg/ogv datastream using Vorbis and Theora encoding. + * + * OGG files are made up of several 'pages' of OGG data, each prefaced with an + * OGG marker - the letters 'OggS'. The file header also contains information on + * what encoders were used to create the file. Here, we're looking for at least + * one OGG page, and confirming that the file asserts the Theora and Vorbis + * codecs were used to create the file. + */ +class OGGDatastreamValidator extends DatastreamValidator { + + /** + * Asserts that the datastream contains ogg pages. + */ + protected function assertOGGPages() { + $ogg_pages = substr_count($this->datastreamContent, 'OggS'); + $assertion = $ogg_pages !== 0; + $pass = "{$this->datastream} datastream asserts that it contains {$ogg_pages} Ogg pages (even a very small file should contain several)."; + $fail = "{$this->datastream} datastream contains no Ogg pages."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the datastream contains Theora-encoded video. + */ + protected function assertTheoraVideo() { + $assertion = substr_count($this->datastreamContent, 'theora') !== 0; + $pass = "{$this->datastream} datastream asserts that it contains Theora-encoded video data."; + $fail = "{$this->datastream} datastream contains no marker indicating the presence of Theora-encoded video data."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the datastream contains Vorbis-encoded audio. + */ + protected function assertVorbisAudio() { + $assertion = substr_count($this->datastreamContent, 'vorbis') !== 0; + $pass = "{$this->datastream} datastream asserts that it contains Vorbis-encoded audio data"; + $fail = "{$this->datastream} datastream contains no marker indicating the presence of Vorbis-encoded audio data."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } +} + +/** + * Attempts to validate an .mkv datastream. + * + * There's not much we can do to check an MKV file, since the format is really, + * really loose. We do know a couple of things though - first, since MKV is an + * EBML format, the first four characters will always be the same. Since they're + * non-standard characters, we're looking at their hex values instead. And + * second, we know that the file will contain the declaration 'matroska' soon + * after. + */ +class MKVDatastreamValidator extends DatastreamValidator { + + /** + * Asserts that the datastream is an EBML-format file. + */ + protected function assertEBMLFormat() { + $assertion = substr(bin2hex($this->datastreamContent), 0, 8) == '1a45dfa3'; + $pass = "{$this->datastream} datastream asserts that it is an EBML-formatted file"; + $fail = "{$this->datastream} datastream is not an EBML-formatted file."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } + + /** + * Asserts that the datastream contains a matroska marker. + */ + protected function assertMatroskaMarker() { + $assertion = substr_count($this->datastreamContent, 'matroska') == 1; + $pass = "{$this->datastream} datastream asserts that its EBML DocType is Matroska"; + $fail = "{$this->datastream} datastream does not contain a Matroska EBML DocType marker."; + $message = $assertion ? $pass : $fail; + $this->addResult($assertion, $message); + } +} diff --git a/tests/includes/islandora_unit_test_case.inc b/tests/includes/islandora_unit_test_case.inc new file mode 100644 index 00000000..ce9216d8 --- /dev/null +++ b/tests/includes/islandora_unit_test_case.inc @@ -0,0 +1,97 @@ + FALSE); + $utilities = new IslandoraTestUtilities($this->configuration, $params); + if (!method_exists($utilities, $method)) { + $caller = $this->getAssertionCall(); + throw new BadMethodCallException("Exception: undefined method $method in {$caller['file']}, line {$caller['line']}."); + } + $result = call_user_func_array(array(&$utilities, $method), $args); + $this->parseUtilityResults($utilities); + return $result; + } + + /** + * Parses utility results and passes them to the test results as an assertion. + * + * @param IslandoraTestUtilities $utility + * An instance of IslandoraTestUtilities with populated results. + */ + public function parseUtilityResults($utility) { + foreach ($utility->getResults() as $result) { + $this->assert($result->getType(), $result->getMessage(), 'Islandora', $result->getCaller()); + } + } + + /** + * Sets up the Drupal filter to access this test Drupal instances database. + * + * @see DrupalWebTestCase::setUp() + */ + public function setUp() { + parent::setUp(); + + // It's possible test are running before class autoloading. + module_load_include('inc', 'islandora', 'includes/tuque'); + module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); + module_load_include('inc', 'islandora', 'tests/includes/utilities'); + + $this->configuration = islandora_get_test_configuration(); + if ($this->configuration['use_drupal_filter']) { + $this->filterManipulator = new IslandoraDrupalFilterManipulator($this->configuration); + $this->filterManipulator->setUpDrupalFilter(); + } + + $this->connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); + $api = new FedoraApi($this->connection); + $this->repository = new FedoraRepository($api, new SimpleCache()); + } + + /** + * Restores the original Drupal filter, frees any allocated resources. + * + * To safeguard against leaving test objects in the repository, tearDown() + * calls deleteUserCreatedObjects() every time by default. This feature can be + * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. + * + * @see DrupalWebTestCase::tearDown() + */ + public function tearDown() { + if ($this->configuration['use_drupal_filter']) { + $this->filterManipulator->restoreDrupalFilter(); + } + unset($this->configuration); + parent::tearDown(); + } + +} diff --git a/tests/includes/islandora_web_test_case.inc b/tests/includes/islandora_web_test_case.inc new file mode 100644 index 00000000..383e2b02 --- /dev/null +++ b/tests/includes/islandora_web_test_case.inc @@ -0,0 +1,340 @@ + $this->loggedInUser, + 'db_access' => TRUE, + ); + $utilities = new IslandoraTestUtilities($this->configuration, $params); + if (!method_exists($utilities, $method)) { + $caller = $this->getAssertionCall(); + throw new BadMethodCallException("Exception: undefined method $method in {$caller['file']}, line {$caller['line']}."); + } + $result = call_user_func_array(array(&$utilities, $method), $args); + $this->parseUtilityResults($utilities); + return $result; + } + + /** + * Parses utility results and passes them to the test results as an assertion. + * + * @param IslandoraTestUtilities $utility + * An instance of IslandoraTestUtilities with populated results. + */ + public function parseUtilityResults($utility) { + foreach ($utility->getResults() as $result) { + $this->assert($result->getType(), $result->getMessage(), 'Islandora', $result->getCaller()); + } + } + + /** + * Sets up the Drupal filter to access this test Drupal instances database. + * + * @see DrupalWebTestCase::setUp() + */ + public function setUp() { + $args = func_get_args(); + $args = (isset($args[0]) && is_array($args[0])) ? $args[0] : $args; + // Always enable islandora. + $args[] = 'islandora'; + parent::setUp($args); + + // It's possible test are running before class autoloading. + module_load_include('inc', 'islandora', 'includes/tuque'); + module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); + module_load_include('inc', 'islandora', 'tests/includes/utilities'); + + $this->configuration = islandora_get_test_configuration(); + if ($this->configuration['use_drupal_filter']) { + $this->filterManipulator = new IslandoraDrupalFilterManipulator($this->configuration); + $this->filterManipulator->setUpDrupalFilter(); + } + $this->admin = $this->createAdminUser(); + } + + /** + * Creates the a full fedora admin user with a repository connection. + */ + protected function createAdminUser() { + $roles = user_roles(); + $index = array_search('administrator', $roles); + $user = $this->drupalCreateUser(); + $user->roles[$index] = 'administrator'; + $user->name = $this->configuration['admin_user']; + $user->pass = $this->configuration['admin_pass']; + $user = user_save($user); + $url = variable_get('islandora_base_url', $this->configuration['fedora_url']); + $connection = islandora_get_tuque_connection($user, $url); + $user->repository = $connection->repository; + return $user; + } + + /** + * Logs in the given user, handles the special case where the user is admin. + * + * @see DrupalWebTestCase::drupalLogin() + */ + protected function drupalLogin(stdClass $account) { + if ($account->uid == $this->admin->uid) { + // Create password for Drupal. + $edit = array('pass' => user_password()); + $account = user_save($account, $edit); + // Raw password is used to login. + $account->pass_raw = $edit['pass']; + // We must login before changing the password for fedora. + parent::drupalLogin($account); + $account->name = $this->configuration['admin_user']; + $account->pass = $this->configuration['admin_pass']; + // Save the fedora admin credentials for later GET/POST requests. + $account = user_save($account); + } + else { + parent::drupalLogin($account); + $this->users[] = $account->name; + } + } + + /** + * Restores the original Drupal filter, frees any allocated resources. + * + * To safeguard against leaving test objects in the repository, tearDown() + * calls deleteUserCreatedObjects() every time by default. This feature can be + * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. + * + * @see DrupalWebTestCase::tearDown() + */ + public function tearDown() { + if ($this->deleteObjectsOnTeardown) { + foreach ($this->users as $user) { + $this->deleteUserCreatedObjects($user); + } + } + if ($this->configuration['use_drupal_filter']) { + $this->filterManipulator->restoreDrupalFilter(); + } + unset($this->admin); + unset($this->configuration); + parent::tearDown(); + } + + /** + * Gets a tuque object from a path. + * + * @param string $path + * A full or partial path to an islandora object. + * + * @return AbstractObject + * The pid of the object or FALSE if a PID is not found. + */ + public function getObjectFromPath($path) { + $path_parts = explode('/', $path); + $array_length = count($path_parts); + for ($i = 0; $i < $array_length; $i++) { + if ($path_parts[$i] == 'islandora' && isset($path_parts[$i + 1]) && $path_parts[$i + 1] == 'object') { + if (isset($path_parts[$i + 2])) { + return islandora_object_load(urldecode($path_parts[$i + 2])); + } + } + } + $this->fail("Failed to parse path: $path."); + return FALSE; + } + + /** + * Deletes an object using the PID. This does the deletion using the UI. + * + * @param string $pid + * The PID of the collection to be deleted + * @param string $button + * The label of the first 'Delete' button + * @param bool $safety + * If TRUE, this will only delete objects owned by users in $this->users. + */ + public function deleteObject($pid, $button = NULL, $safety = TRUE) { + $object = islandora_object_load($pid); + if (!$safety || in_array($object->owner, $this->users)) { + $path = "islandora/object/$pid/manage/properties"; + if (isset($button)) { + $this->drupalPost($path, array(), $button); + } + else { + $object = islandora_object_load($pid); + $this->drupalPost($path, array(), "Permanently remove '{$object->label}' from repository"); + } + $this->drupalPost($this->url, array(), t('Delete')); + + $this->drupalGet("islandora/object/$pid"); + $this->assertResponse(404, "Object $pid successfully deleted."); + } + else { + $this->fail("Cannot delete object {$pid}; it is owned by non-test user {$object->owner}, and this function was called with the safety on."); + return FALSE; + } + } + + /** + * These are a few quick helper functions to fill in a gap in the standard + * DrupalWebTestCase where no function exists to test for the simple existence + * or non-existence of an error. + */ + + /** + * Asserts that an error is found in $this->content. + * + * @param string $message + * The message to pass on to the results. + * @param string $group + * The group to place the result in. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function assertError($message = '', $group = 'Other') { + if (!$message) { + $message = "Error found on current page when error was expected."; + } + return $this->assertRaw("
", $message, $group); + } + + /** + * Asserts that no error is found in $this->content. + * + * @param string $message + * The message to pass on to the results. + * @param string $group + * The group to place the result in. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function assertNoError($message = '', $group = 'Other') { + if (!$message) { + $message = "No error found on current page when no error was expected."; + } + return $this->assertNoRaw("
", $message, $group); + } + + /** + * Asserts that a warning is found in $this->content. + * + * @param string $message + * The message to pass on to the results. + * @param string $group + * The group to place the result in. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function assertWarning($message = '', $group = 'Other') { + if (!$message) { + $message = "Warning found on current page when warning was expected."; + } + return $this->assertRaw("
", $message, $group); + } + + /** + * Asserts that no warning is found in $this->content. + * + * @param string $message + * The message to pass on to the results. + * @param string $group + * The group to place the result in. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function assertNoWarning($message = '', $group = 'Other') { + if (!$message) { + $message = "No warning found on current page when no warning was expected."; + } + return $this->assertNoRaw("
", $message, $group); + } + + /** + * Makes a drupalPost() request, using the form submit button's ID. + * + * Because drupalPost() is silly and doesn't let us choose which button we'd + * like to select if multiple buttons have the same value, this function + * allows us to select whatever button we'd like based on the ID instead. + * + * This is done via the absolutely hilarious method of fudging the actual + * button labels that don't have the ID we want, so that the only one left + * with the correct label is the one with the right ID. + * + * @see DrupalWebTestCase::drupalPost() + * + * @param string $path + * Location of the post form. + * @param array $edit + * Field data in an associative array. + * @param string $submit + * Value of the submit button whose click is to be emulated. + * @param string $id + * ID of the submit button whose click is to be emulated. + * @param array $options + * Options to be forwarded to url(). + * @param array $headers + * An array containing additional HTTP request headers, each formatted as + * "name: value". + * @param null $form_html_id + * (optional) HTML ID of the form to be submitted. + * @param null $extra_post + * (optional) A string of additional data to append to the POST submission. + * + * @return bool|string + * The content from the POST request's curlExec, or FALSE on fail. + */ + public function drupalPostByID($path, $edit, $submit, $id, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { + $buttons = $this->xpath("//input[@type=\"submit\" and @value=\"{$submit}\"]"); + if (empty($buttons)) { + $this->fail("No buttons found on the page with value '$submit'"); + return FALSE; + } + $i = 0; + foreach ($buttons as $button) { + if ($button['id'] !== $id) { + $button['value'] .= "_$i"; + $i++; + } + } + return $this->drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post); + } + +} diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc new file mode 100644 index 00000000..e0672fd8 --- /dev/null +++ b/tests/includes/utilities.inc @@ -0,0 +1,554 @@ +configuration = $configuration; + $this->backUpDrupalFilter(); + } + + /** + * Stores the content of the Drupal Filter for later restoration. + */ + public function restoreDrupalFilter() { + $file = $this->configuration['drupal_filter_file']; + if (isset($this->originalDrupalFilterContent)) { + file_put_contents($file, $this->originalDrupalFilterContent); + } + elseif (file_exists($file)) { + // Remove if there was never an original. + drupal_unlink($file); + } + } + + /** + * Sets up a drupal filter that can read for the tests users table. + */ + public function setUpDrupalFilter() { + $connection_info = Database::getConnectionInfo('default'); + $drupal_filter_dom = new DomDocument(); + $drupal_filter_dom->loadXML($this->originalDrupalFilterContent); + $server = $connection_info['default']['host']; + $dbname = $connection_info['default']['database']; + $user = $connection_info['default']['username']; + $password = $connection_info['default']['password']; + $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; + $prefix = $connection_info['default']['prefix']['default']; + $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); + $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); + $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); + $connection_node->setAttributeNode(new DOMAttr('server', $server)); + $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); + $connection_node->setAttributeNode(new DOMAttr('user', $user)); + $connection_node->setAttributeNode(new DOMAttr('password', $password)); + $connection_node->setAttributeNode(new DOMAttr('port', $port)); + $sql_node = $connection_node->appendChild(new DOMElement('sql')); + $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); + file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); + } + + /** + * Stores the content of the Drupal Filter for later restoration. + */ + protected function backUpDrupalFilter() { + if (file_exists($this->configuration['drupal_filter_file'])) { + $this->originalDrupalFilterContent = file_get_contents($this->configuration['drupal_filter_file']); + } + else { + throw new Exception('Failed to find the required Drupal Filter configuration file.'); + } + } +} + +/** + * A result from a utility method; $type defines TRUE/FALSE as pass/fail. + */ +class IslandoraTestUtilityResult { + + /** + * The message for this result. + * + * @var string + */ + protected $message; + + /** + * The caller for this result. + * + * @var array + */ + protected $caller; + + /** + * The type of result this is - TRUE for pass, FALSE for fail. + * + * @var bool + */ + protected $type; + + /** + * Constructs an IslandoraTestUtilityResult. + * + * @param bool $type + * Whether this result should indicate a pass (TRUE) or fail (FALSE). + * @param string $message + * The message that will be used by this result. + * @param array $caller + * The caller for this result. + */ + public function __construct($type, $message, array $caller) { + $this->message = $message; + $this->caller = $caller; + $this->type = $type; + } + + /** + * Get the message for this result. + * + * @return string + * The message for this result. + */ + public function getMessage() { + return $this->message; + } + + /** + * Get the caller for this result. + * + * @return string + * The caller for this result. + */ + public function getCaller() { + return $this->caller; + } + + /** + * Get the type of result. + * + * @return bool + * The type of pass (TRUE for pass, FALSE for fail). + */ + public function getType() { + return $this->type; + } +} + +/** + * Abstraction for test utility classes. + * + * This is to be implemented in any class that wants to have test utility + * functionality (i.e. that wants to pass back results to tests). Check out the + * datastream validator class for a good example of how this is implemented. + */ +abstract class IslandoraTestUtilityClass { + + /** + * An array of IslandoraTestUtilityResults. + * + * These should be generated using $this->addResult. + * + * @var IslandoraTestUtilityResult[] + */ + public $results = array(); + + /** + * Returns an array of IslandoraTestUtilityResults. + * + * The particular testing implementation you are using should use this to + * parse results from utilities and pass them through. + * + * @return IslandoraTestUtilityResult[] + * The results. + */ + abstract public function getResults(); + + /** + * Adds a result to $this->results. + * + * @param bool $type + * The type of result (TRUE for pass, FALSE for fail). + * @param string $message + * The message to put in the result. + */ + abstract public function addResult($type, $message); + + /** + * Gets the caller of the method that passed a result. + * + * @return array + * Array representing the true caller. + */ + abstract public function getAssertionCall(); + +} + +class IslandoraTestUtilities extends IslandoraTestUtilityClass { + + protected $configuration; + + protected $params; + + public $results = array(); + + protected $repository; + + /** + * Constructs an IslandoraTestUtilities object. + * + * @param array $configuration + * The parsed test configuration. + * @param array $params + * Any additional parameters the method called may need to function. + */ + public function __construct($configuration, array $params = array()) { + $this->configuration = $configuration; + $this->params = $params; + // If we have DB access, we want to use the Islandora Tuque wrappers. + if ($params['db_access']) { + $connection = islandora_get_tuque_connection(); + $this->repository = $connection->repository; + } + // Otherwise, get a generic Tuque repository. + else { + $connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); + $api = new FedoraApi($connection); + $this->repository = new FedoraRepository($api, new SimpleCache()); + } + } + + /** + * Returns an array of IslandoraTestUtilityResults. + * + * @return IslandoraTestUtilityResult[] + * The results. + */ + public function getResults() { + return $this->results; + } + + /** + * Adds a result to $this->results. + * + * @param bool $type + * The type of result (TRUE for pass, FALSE for fail). + * @param string $message + * The message to put in the result. + */ + public function addResult($type, $message) { + $result = new IslandoraTestUtilityResult($type, $message, $this->getAssertionCall()); + $this->results[] = $result; + } + + /** + * Cycles through backtrace until the first non-assertion method is found. + * + * This is a manipulated version of DrupalWebTestCase::getAssertionCall(). + * We use it here so that we can pass back assertion calls from + * DatastreamValidator assertions instead of less useful TestCase functions. + * + * @return array + * Array representing the true caller. + */ + public function getAssertionCall() { + $backtrace = debug_backtrace(); + array_shift($backtrace); + return _drupal_get_last_caller($backtrace); + } + + /** + * Asserts that the given datastreams exist correctly on the object. + * + * @param AbstractObject $object + * The PID of the object + * @param array $datastreams + * An array of strings containing datastream names + * + * @return bool + * TRUE on success, FALSE on fail. + */ + public function assertDatastreams($object, array $datastreams) { + if (!is_object($object)) { + $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); + } + else { + $missing_datastreams = array_diff_key(array_flip($datastreams), $object->repository->api->a->listDatastreams($object->id)); + + if (!empty($missing_datastreams)) { + $this->addResult(FALSE, "Failed to find datastream(s) " . implode(', ', array_flip($missing_datastreams)) . " in object {$object->id}."); + return FALSE; + } + + $this->addResult(TRUE, "Found required datastream(s) in object {$object->id}"); + return TRUE; + } + } + + /** + * Attempts to validate an array of datastreams, generally via binary checks. + * + * Datastream validation classes exist in, and can be added to, the file + * 'datastream_validators.inc', which is found in this folder. Datastream + * validator classes use the naming convention 'PrefixDatastreamValidator', + * and that 'Prefix' is what this function uses to determine what class to + * instantiate. + * + * $param IslandoraFedoraObject $object + * The object to load datastreams from. + * $param array $datastreams + * An array of arrays that pair DSIDs, DatastreamValidator class prefixes, + * and optional params. You can check some of the existing implementations + * for examples. + */ + public function validateDatastreams($object, array $datastreams) { + + if (!is_object($object)) { + $this->addResult(FALSE, "Datastream validation failed; Object passed in is invalid.", 'Islandora'); + return; + } + + module_load_include('inc', 'islandora', 'tests/includes/datastream_validators'); + + foreach ($datastreams as $datastream) { + // Let's give them conventional names. + $dsid = $datastream[0]; + $prefix = $datastream[1]; + $params = array(); + if (isset($datastream[2])) { + $params = $datastream[2]; + } + + // Legacy tests were created before the CamelCase conventions of the class + // system now in place. So, we need to automagically seek out prefixes + // that start with a lower-case letter and convert them to the proper + // format (rather than fixing every single legacy test). + if (ctype_lower(substr($prefix, 0, 1))) { + // Handle the case where the prefix is "image". + if ($prefix === 'image') { + $prefix = 'Image'; + } + // Handle the case where the prefix is "text". + elseif ($prefix === 'text') { + $prefix = 'Text'; + } + // All other cases involve just converting everything to caps. + else { + $prefix = strtoupper($prefix); + } + } + + // Instantiate the appropriate class, and grab the results. + $class_name = "{$prefix}DatastreamValidator"; + if (class_exists($class_name)) { + $validator = new $class_name($object, $dsid, $params); + foreach ($validator->getResults() as $result) { + $this->addResult($result->getType(), $result->getMessage()); + } + } + else { + $this->addResult(FALSE, "No DatastreamValidator class was found with the name '$class_name'; are you sure the prefix given to IslandoraWebTestCase->validateDatastreams() was entered correctly, or that such a validator exists?", 'Islandora'); + } + } + } + + /** + * Constructs and ingests a Fedora object and datastream(s) via tuque. + * + * All keys inside the parameter arrays for this function are optional. it + * can be run simply by calling the method with no arguments. + * + * If your test case supports logged in Drupal users, IslandoraTestUtilities + * can be instantiated with $params['logged_in_user'] as that user object, and + * this method will set the owner of the ingested object as that user by + * default. + * + * @param array $properties + * An array containing object information using these keys: + * 'label' - The object label; randomized if not set. + * 'pid' - 'namespace:pid', or just 'namespace' to generate the suffix. + * 'models' - An array that can contain multiple content model PIDs, or a + * string containing a single content model PID. + * 'owner' - The object's owner. Defaults to the currently logged-in user, + * if available. It is recommended to set this to a value that can be found + * in $this->users; otherwise, this object will have to be manually deleted. + * 'parent' - The PID of the parent collection. + * @param array $datastreams + * An array containing zero or more datastream arrays that use the keys: + * 'dsid' - the datastream ID; randomized if not set. + * 'path' - The path to the file to use; defaults to fixtures/test.jpg. + * 'control_group' - The single-letter control group identifier. + * 'mimetype' - The datastream's mimetype. + * + * @return bool|array + * FALSE if the object ingest failed, or the object array if successful. + */ + public function ingestConstructedObject(array $properties = array(), array $datastreams = array()) { + if (!isset($properties['pid'])) { + $properties['pid'] = "islandora"; + } + $object = $this->repository->constructObject($properties['pid']); + + // Set the object properties before ingesting it. + if (isset($properties['label'])) { + $object->label = $properties['label']; + } + else { + $properties['label'] = DrupalUnitTestCase::randomName(); + $object->label = $properties['label']; + } + + if (isset($properties['owner'])) { + $object->owner = $properties['owner']; + } + elseif (isset($this->params['logged_in_user'])) { + $object->owner = $this->params['logged_in_user']->name; + } + + if (isset($properties['models'])) { + try { + $object->models = (array) $properties['models']; + } + catch (Exception $e) { + $this->addResult(FALSE, "Encountered an exception when trying to add content models to {$object->id}: $e"); + return FALSE; + } + } + + // Chuck in some datastreams. + if (!empty($datastreams)) { + foreach ($datastreams as $datastream) { + if (!isset($datastream['dsid'])) { + $datastream['dsid'] = DrupalUnitTestCase::randomName(); + } + if (!isset($datastream['path'])) { + $datastream['path'] = drupal_get_path('module', 'islandora') . '/tests/fixtures/test.jpg'; + } + if (!isset($datastream['control_group'])) { + $new_datastream = $object->constructDatastream($datastream['dsid']); + } + else { + $new_datastream = $object->constructDatastream($datastream['dsid'], $datastream['control_group']); + } + $new_datastream->label = $datastream['dsid']; + if (isset($datastream['mimetype'])) { + $new_datastream->mimetype = $datastream['mimetype']; + } + $new_datastream->setContentFromFile($datastream['path']); + $object->ingestDatastream($new_datastream); + } + } + + $this->repository->ingestObject($object); + if (!$object) { + $this->addResult(FALSE, t("Failed to ingest object."), 'Islandora'); + return FALSE; + } + else { + $this->addResult(TRUE, t("Ingested object %object", array('%object' => $object->id)), 'Islandora'); + } + + // Add a parent relationship, if necessary. + if (isset($properties['parent'])) { + $object->relationships->add(FEDORA_RELS_EXT_URI, 'isMemberOfCollection', $properties['parent']); + } + + return $object; + } + + /** + * Deletes all objects created by the given user. + * + * To safeguard against leaving test objects in the repository, this is called + * each time DrupalTestCase::run() calls tearDown(). This feature can be + * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. + * + * @param object $username + * The user whose objects we'd like to remove. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function deleteUserCreatedObjects($username) { + if ($username === $this->configuration['admin_user']) { + $this->addResult(FALSE, "This function will under no circumstance attempt deletion of all objects owned by the configured Fedora admin user ({$this->configuration['admin_user']}), as this could irreparably damage the repository.", 'Islandora'); + return FALSE; + } + + $query = << WHERE +{ + ?object "$username" +} +QUERY; + + $objects = $this->repository->ri->sparqlQuery($query); + foreach ($objects as $object) { + $loaded_object = islandora_object_load($object['object']['value']); + $this->repository->api->m->purgeObject($loaded_object->id); + if (islandora_object_load($object['object']['value'])) { + $this->addResult(TRUE, "Object {$object['object']['value']} successfully removed from repository."); + return TRUE; + } + $this->addResult(FALSE, "Unable to remove object {$object['object']['value']} from the repository."); + return FALSE; + } + } + +} From 83960d6f02e6696dae238982fdee2a9eb7ca1043 Mon Sep 17 00:00:00 2001 From: qadan Date: Mon, 26 May 2014 15:22:49 -0300 Subject: [PATCH 03/15] better error handling --- tests/includes/datastream_validators.inc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc index c0241c84..49c090e5 100644 --- a/tests/includes/datastream_validators.inc +++ b/tests/includes/datastream_validators.inc @@ -128,12 +128,17 @@ abstract class DatastreamValidator extends IslandoraTestUtilityClass { * fails to $this->passes and/or $this->fails. */ public function runValidators() { - $methods = get_class_methods($this); - foreach ($methods as $method) { - if (substr($method, 0, 6) === 'assert') { - $this->$method(); + if ($this->object[$this->datastream]) { + $methods = get_class_methods($this); + foreach ($methods as $method) { + if (substr($method, 0, 6) === 'assert') { + $this->$method(); + } } } + else { + $this->addResult(FALSE, "Unable to load the requested datastream {$this->datastream} from object {$this->object->id}."); + } } /** From a13fa13451cc9c9e5da4d9e8690dd0f2f6cd7b48 Mon Sep 17 00:00:00 2001 From: qadan Date: Tue, 27 May 2014 11:06:03 -0300 Subject: [PATCH 04/15] accept objects with/without wrapper --- tests/includes/datastream_validators.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc index 49c090e5..d96166a3 100644 --- a/tests/includes/datastream_validators.inc +++ b/tests/includes/datastream_validators.inc @@ -69,7 +69,7 @@ abstract class DatastreamValidator extends IslandoraTestUtilityClass { /** * The IslandoraFedoraObject containing the datastream to test. * - * @var IslandoraFedoraObject + * @var IslandoraFedoraObject|FedoraObject */ public $object; @@ -97,14 +97,14 @@ abstract class DatastreamValidator extends IslandoraTestUtilityClass { /** * Constructs a DatastreamValidator. * - * @param IslandoraFedoraObject $object + * @param IslandoraFedoraObject|FedoraObject $object * The object to grab the datastream from. * @param string $datastream * The DSID of the datastream itself. * @param array $params * An extra array of parameters the validator might need. */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + public function __construct($object, $datastream, array $params = array()) { $this->object = $object; $this->datastream = $datastream; $this->params = $params; From 8b11d4a05129ca8424843d81369d78e911fb7368 Mon Sep 17 00:00:00 2001 From: qadan Date: Tue, 27 May 2014 11:20:54 -0300 Subject: [PATCH 05/15] erm, forgot one --- tests/includes/datastream_validators.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc index d96166a3..445205ab 100644 --- a/tests/includes/datastream_validators.inc +++ b/tests/includes/datastream_validators.inc @@ -343,7 +343,7 @@ class TextDatastreamValidator extends DatastreamValidator { /** * Constructor override; blow up if we don't have our two values. */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + public function __construct($object, $datastream, array $params = array()) { if (count($params) < 2) { throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.'); } From fc357e6f7df95c0a0dc2d85d0d68bc06fb352a88 Mon Sep 17 00:00:00 2001 From: qadan Date: Thu, 29 May 2014 12:32:12 +0000 Subject: [PATCH 06/15] last bit of bugfixes and cleanup --- includes/derivatives.inc | 36 +++++++++++--------- tests/includes/datastream_validators.inc | 16 ++++----- tests/includes/utilities.inc | 42 ++++++++++++++++-------- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/includes/derivatives.inc b/includes/derivatives.inc index 47ec4fad..8a36512e 100644 --- a/includes/derivatives.inc +++ b/includes/derivatives.inc @@ -21,25 +21,29 @@ function islandora_run_derivatives(AbstractObject $object, $dsid) { islandora_derivative_logging($logging_results); } else { - batch_set( - // Title won't show for batch in a batch. + $operations = islandora_do_batch_derivatives( + $object, array( - 'init_message' => t('Preparing derivatives for @label', array('@label' => $object->label)), - 'error_message' => t('An error occured creating derivatives.'), - 'progress_message' => t( - 'Creating derivatives for @label
Time elapsed: @elapsed
- Estimated time remaining @estimate.', - array('@label' => $object->label) - ), - 'file' => drupal_get_path('module', 'islandora') . '/includes/regenerate_derivatives.form.inc', - 'operations' => islandora_do_batch_derivatives( - $object, - array( - 'source_dsid' => $dsid, - ) - ), + 'source_dsid' => $dsid, ) ); + if ($operations) { + batch_set( + // Title won't show for batch in a batch. + array( + 'init_message' => t('Preparing derivatives for @label', array('@label' => $object->label)), + 'error_message' => t('An error occured creating derivatives.'), + 'progress_message' => t( + 'Creating derivatives for @label
Time elapsed: @elapsed
+ Estimated time remaining @estimate.', + array('@label' => $object->label) + ), + 'file' => drupal_get_path('module', 'islandora') . '/includes/regenerate_derivatives.form.inc', + 'operations' => $operations, + 'finished' => 'islandora_regenerate_derivative_batch_finished', + ) + ); + } } } diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc index 49c090e5..4768e989 100644 --- a/tests/includes/datastream_validators.inc +++ b/tests/includes/datastream_validators.inc @@ -48,7 +48,7 @@ function islandora_hex2int($hex) { * Abstraction for datastream validators. * * Classes extended from DatastreamValidator don't require much to be useful. - * They accept an IslandoraFedoraObject and a DSID to perform assertions on; + * They accept a Fedora object and a DSID to perform assertions on; * all you have to do is place a series of functions inside the extended class * using the naming convention assertThing(); each of these functions should * ideally assert one thing and one thing only (for simplicity's sake), and @@ -67,9 +67,9 @@ function islandora_hex2int($hex) { abstract class DatastreamValidator extends IslandoraTestUtilityClass { /** - * The IslandoraFedoraObject containing the datastream to test. + * The Fedora object containing the datastream to test. * - * @var IslandoraFedoraObject + * @var IslandoraFedoraObject|FedoraObject */ public $object; @@ -97,14 +97,14 @@ abstract class DatastreamValidator extends IslandoraTestUtilityClass { /** * Constructs a DatastreamValidator. * - * @param IslandoraFedoraObject $object + * @param IslandoraFedoraObject|FedoraObject $object * The object to grab the datastream from. * @param string $datastream * The DSID of the datastream itself. * @param array $params * An extra array of parameters the validator might need. */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + public function __construct($object, $datastream, array $params = array()) { $this->object = $object; $this->datastream = $datastream; $this->params = $params; @@ -343,7 +343,7 @@ class TextDatastreamValidator extends DatastreamValidator { /** * Constructor override; blow up if we don't have our two values. */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + public function __construct($object, $datastream, array $params = array()) { if (count($params) < 2) { throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.'); } @@ -386,14 +386,14 @@ class WAVDatastreamValidator extends DatastreamValidator { /** * We need a special constructor here to get the hex datastream content. * - * @param IslandoraFedoraObject $object + * @param IslandoraFedoraObject|FedoraObject $object * The object to grab the datastream from. * @param string $datastream * The DSID of the datastream itself. * @param array $params * An extra array of parameters the validator might need. */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { + public function __construct($object, $datastream, array $params = array()) { parent::__construct($object, $datastream, $params); $this->datastreamContent = bin2hex($this->datastreamContent); } diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index e0672fd8..e0682397 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -261,17 +261,9 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { public function __construct($configuration, array $params = array()) { $this->configuration = $configuration; $this->params = $params; - // If we have DB access, we want to use the Islandora Tuque wrappers. - if ($params['db_access']) { - $connection = islandora_get_tuque_connection(); - $this->repository = $connection->repository; - } - // Otherwise, get a generic Tuque repository. - else { - $connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); - $api = new FedoraApi($connection); - $this->repository = new FedoraRepository($api, new SimpleCache()); - } + $connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); + $api = new FedoraApi($connection); + $this->repository = new FedoraRepository($api, new SimpleCache()); } /** @@ -317,9 +309,9 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { * Asserts that the given datastreams exist correctly on the object. * * @param AbstractObject $object - * The PID of the object + * The object to check. * @param array $datastreams - * An array of strings containing datastream names + * An array of strings containing datastream names. * * @return bool * TRUE on success, FALSE on fail. @@ -341,6 +333,30 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { } } + /** + * Asserts that the given datastreams do not exist on the object. + * + * @param AbstractObject $object + * The object to check. + * @param array $datastreams + * An array of datastreams to confirm not present. + */ + public function assertNoDatastreams($object, array $datastreams) { + if (!is_object($object)) { + $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); + return; + } + $found_datastreams = array_intersect_key(array_flip($datastreams), $object->repository->api->a->listDatastreams($object->id)); + + if (!empty($found_datastreams)) { + $this->addResult(FALSE, "Found unwanted datastream(s)" . implode(', ', array_flip($found_datastreams)) . " in object {$object->id}."); + return FALSE; + } + + $this->addResult(TRUE, "Unwanted datastream(s) not found in object {$object->id}"); + return TRUE; + } + /** * Attempts to validate an array of datastreams, generally via binary checks. * From 02de114401a58e211366c053dbeaf7c5d437a06f Mon Sep 17 00:00:00 2001 From: qadan Date: Thu, 29 May 2014 16:45:16 +0000 Subject: [PATCH 07/15] better, simpler drupal filter handling --- islandora.module | 17 ++- tests/includes/islandora_unit_test_case.inc | 22 +--- tests/includes/islandora_web_test_case.inc | 14 +-- tests/includes/utilities.inc | 110 +++++--------------- 4 files changed, 37 insertions(+), 126 deletions(-) diff --git a/islandora.module b/islandora.module index ff077212..3f3d777b 100644 --- a/islandora.module +++ b/islandora.module @@ -1813,18 +1813,13 @@ function islandora_form_simpletest_test_form_alter(array &$form) { * Submit handler for islandora_form_simpletest_test_form_alter(). */ function islandora_repair_drupal_filter() { - - // Grab the config. - $path = drupal_get_path('module', 'islandora'); - if (file_exists("$path/tests/test_config.ini")) { - $configuration = parse_ini_file("$path/tests/test_config.ini"); - } - elseif (file_exists("$path/tests/default.test_config.ini")) { - $configuration = parse_ini_file("$path/tests/default.test_config.ini"); + module_load_include('inc', 'islandora', 'tests/includes/utilities'); + try { + $configuration = islandora_get_test_configuration(); } - else { - drupal_set_message(t('Required default.test_config.ini/test_config.ini file not found'), 'error'); - return FALSE; + catch (Exception $e) { + drupal_set_message("Unable to get the test configuration: $e", 'error'); + return; } // Xpath to the filter 'sql' elements. diff --git a/tests/includes/islandora_unit_test_case.inc b/tests/includes/islandora_unit_test_case.inc index ce9216d8..4cdf30bd 100644 --- a/tests/includes/islandora_unit_test_case.inc +++ b/tests/includes/islandora_unit_test_case.inc @@ -13,13 +13,6 @@ class IslandoraUnitTestCase extends DrupalUnitTestCase { */ protected $deleteObjectsOnTeardown = TRUE; - /** - * An instance of IslandoraDrupalFilterManipulator carried between tests. - * - * @var IslandoraDrupalFilterManipulator - */ - protected $filterManipulator; - /** * Defers to IslandoraTestUtilities for missing methods. * @@ -64,32 +57,19 @@ class IslandoraUnitTestCase extends DrupalUnitTestCase { // It's possible test are running before class autoloading. module_load_include('inc', 'islandora', 'includes/tuque'); module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); - module_load_include('inc', 'islandora', 'tests/includes/utilities'); $this->configuration = islandora_get_test_configuration(); - if ($this->configuration['use_drupal_filter']) { - $this->filterManipulator = new IslandoraDrupalFilterManipulator($this->configuration); - $this->filterManipulator->setUpDrupalFilter(); - } - $this->connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); $api = new FedoraApi($this->connection); $this->repository = new FedoraRepository($api, new SimpleCache()); } /** - * Restores the original Drupal filter, frees any allocated resources. - * - * To safeguard against leaving test objects in the repository, tearDown() - * calls deleteUserCreatedObjects() every time by default. This feature can be - * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. + * Frees any allocated resources. * * @see DrupalWebTestCase::tearDown() */ public function tearDown() { - if ($this->configuration['use_drupal_filter']) { - $this->filterManipulator->restoreDrupalFilter(); - } unset($this->configuration); parent::tearDown(); } diff --git a/tests/includes/islandora_web_test_case.inc b/tests/includes/islandora_web_test_case.inc index 383e2b02..211f0df0 100644 --- a/tests/includes/islandora_web_test_case.inc +++ b/tests/includes/islandora_web_test_case.inc @@ -21,13 +21,6 @@ class IslandoraWebTestCase extends DrupalWebTestCase { */ protected $deleteObjectsOnTeardown = TRUE; - /** - * An instance of IslandoraDrupalFilterManipulator carried between tests. - * - * @var IslandoraDrupalFilterManipulator - */ - protected $filterManipulator; - /** * Defers to IslandoraTestUtilities for missing methods. * @@ -65,7 +58,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { } /** - * Sets up the Drupal filter to access this test Drupal instances database. + * Sets up the web test case. * * @see DrupalWebTestCase::setUp() */ @@ -83,8 +76,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { $this->configuration = islandora_get_test_configuration(); if ($this->configuration['use_drupal_filter']) { - $this->filterManipulator = new IslandoraDrupalFilterManipulator($this->configuration); - $this->filterManipulator->setUpDrupalFilter(); + $this->setUpDrupalFilter(); } $this->admin = $this->createAdminUser(); } @@ -147,7 +139,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { } } if ($this->configuration['use_drupal_filter']) { - $this->filterManipulator->restoreDrupalFilter(); + islandora_repair_drupal_filter(); } unset($this->admin); unset($this->configuration); diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index e0682397..28179c05 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -35,89 +35,6 @@ function islandora_get_test_configuration() { throw new Exception('Required default.test_config.ini/test_config.ini file not found'); } -/** - * Methods specifically for working with the Drupal filter for tests. - */ -class IslandoraDrupalFilterManipulator { - - /** - * Parsed test configuration. - * - * @var array - */ - public $configuration; - - /** - * The original contents of the drupal filter. - * - * @var string - */ - protected $originalDrupalFilterContent; - - /** - * Constructs an IslandoraTestUtilities object. - * - * @param array $configuration - * The parsed test configuration. - */ - public function __construct($configuration) { - $this->configuration = $configuration; - $this->backUpDrupalFilter(); - } - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - public function restoreDrupalFilter() { - $file = $this->configuration['drupal_filter_file']; - if (isset($this->originalDrupalFilterContent)) { - file_put_contents($file, $this->originalDrupalFilterContent); - } - elseif (file_exists($file)) { - // Remove if there was never an original. - drupal_unlink($file); - } - } - - /** - * Sets up a drupal filter that can read for the tests users table. - */ - public function setUpDrupalFilter() { - $connection_info = Database::getConnectionInfo('default'); - $drupal_filter_dom = new DomDocument(); - $drupal_filter_dom->loadXML($this->originalDrupalFilterContent); - $server = $connection_info['default']['host']; - $dbname = $connection_info['default']['database']; - $user = $connection_info['default']['username']; - $password = $connection_info['default']['password']; - $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; - $prefix = $connection_info['default']['prefix']['default']; - $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); - $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); - $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); - $connection_node->setAttributeNode(new DOMAttr('server', $server)); - $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); - $connection_node->setAttributeNode(new DOMAttr('user', $user)); - $connection_node->setAttributeNode(new DOMAttr('password', $password)); - $connection_node->setAttributeNode(new DOMAttr('port', $port)); - $sql_node = $connection_node->appendChild(new DOMElement('sql')); - $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); - file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); - } - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - protected function backUpDrupalFilter() { - if (file_exists($this->configuration['drupal_filter_file'])) { - $this->originalDrupalFilterContent = file_get_contents($this->configuration['drupal_filter_file']); - } - else { - throw new Exception('Failed to find the required Drupal Filter configuration file.'); - } - } -} - /** * A result from a utility method; $type defines TRUE/FALSE as pass/fail. */ @@ -266,6 +183,33 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { $this->repository = new FedoraRepository($api, new SimpleCache()); } + /** + * Sets up a drupal filter that can read for the tests users table. + */ + public function setUpDrupalFilter() { + $original_drupal_filter_content = file_get_contents($this->configuration['drupal_filter_file']); + $connection_info = Database::getConnectionInfo('default'); + $drupal_filter_dom = new DomDocument(); + $drupal_filter_dom->loadXML($original_drupal_filter_content); + $server = $connection_info['default']['host']; + $dbname = $connection_info['default']['database']; + $user = $connection_info['default']['username']; + $password = $connection_info['default']['password']; + $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; + $prefix = $connection_info['default']['prefix']['default']; + $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); + $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); + $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); + $connection_node->setAttributeNode(new DOMAttr('server', $server)); + $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); + $connection_node->setAttributeNode(new DOMAttr('user', $user)); + $connection_node->setAttributeNode(new DOMAttr('password', $password)); + $connection_node->setAttributeNode(new DOMAttr('port', $port)); + $sql_node = $connection_node->appendChild(new DOMElement('sql')); + $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); + file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); + } + /** * Returns an array of IslandoraTestUtilityResults. * From 723a8f54d7db69ef3e4aac60f9df9bd46f75803a Mon Sep 17 00:00:00 2001 From: qadan Date: Thu, 29 May 2014 17:21:23 +0000 Subject: [PATCH 08/15] not using t() like some sorta madman --- islandora.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islandora.module b/islandora.module index 3f3d777b..dbca8844 100644 --- a/islandora.module +++ b/islandora.module @@ -1818,7 +1818,7 @@ function islandora_repair_drupal_filter() { $configuration = islandora_get_test_configuration(); } catch (Exception $e) { - drupal_set_message("Unable to get the test configuration: $e", 'error'); + drupal_set_message(t("Unable to get the test configuration: %e", array('%e' => $e)), 'error'); return; } From 16d24a3f9c6ee615df609934b0cf68bd8e624e26 Mon Sep 17 00:00:00 2001 From: Nigel Banks Date: Wed, 4 Jun 2014 15:32:01 -0300 Subject: [PATCH 09/15] Operator precedence allowed for strings that were not 4 or 8 characters long. --- tests/includes/datastream_validators.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/includes/datastream_validators.inc b/tests/includes/datastream_validators.inc index 4768e989..b8ff785e 100644 --- a/tests/includes/datastream_validators.inc +++ b/tests/includes/datastream_validators.inc @@ -28,7 +28,7 @@ function islandora_hex2int($hex) { drupal_set_message(t('String passed to islandora_hex2int() contains non-hexidecimal characters.'), 'error'); return FALSE; } - if (!strlen($hex) === 4 || !strlen($hex) === 8) { + if (!(strlen($hex) == 4 || strlen($hex) == 8)) { drupal_set_message(t('String passed to islandora_hex2int() cannot create a 16- or 32-bit little-endian signed integer'), 'error'); return FALSE; } From 1c0fa96099467e1cd157e25f0c61fd1b85b8d5eb Mon Sep 17 00:00:00 2001 From: qadan Date: Fri, 6 Jun 2014 17:37:09 +0000 Subject: [PATCH 10/15] moving around some stuffs --- islandora.info | 1 + islandora.module | 10 +- tests/includes/islandora_unit_test_case.inc | 2 +- tests/includes/islandora_web_test_case.inc | 2 +- tests/includes/test_utility_abstraction.inc | 153 ++++++++++++++++++++ tests/includes/utilities.inc | 144 ------------------ tests/scripts/travis_setup.sh | 1 + 7 files changed, 163 insertions(+), 150 deletions(-) create mode 100644 tests/includes/test_utility_abstraction.inc diff --git a/islandora.info b/islandora.info index e19f4937..55c50359 100644 --- a/islandora.info +++ b/islandora.info @@ -17,6 +17,7 @@ files[] = tests/includes/datastream_validators.inc files[] = tests/includes/islandora_web_test_case.inc files[] = tests/includes/islandora_unit_test_case.inc files[] = tests/includes/utilities.inc +files[] = tests/includes/test_utility_abstraction.inc files[] = tests/authtokens.test files[] = tests/hooks.test files[] = tests/ingest.test diff --git a/islandora.module b/islandora.module index dbca8844..aeb2c059 100644 --- a/islandora.module +++ b/islandora.module @@ -1813,13 +1813,15 @@ function islandora_form_simpletest_test_form_alter(array &$form) { * Submit handler for islandora_form_simpletest_test_form_alter(). */ function islandora_repair_drupal_filter() { - module_load_include('inc', 'islandora', 'tests/includes/utilities'); + + // Grab the config. + module_load_include('inc', 'islandora', 'tests/utilities/test_utility_abstraction'); try { - $configuration = islandora_get_test_configuration(); + IslandoraTestUtilityClass::getTestConfiguration(); } catch (Exception $e) { - drupal_set_message(t("Unable to get the test configuration: %e", array('%e' => $e)), 'error'); - return; + drupal_set_message(t("Error parsing test configuration: $e"), 'error'); + return FALSE; } // Xpath to the filter 'sql' elements. diff --git a/tests/includes/islandora_unit_test_case.inc b/tests/includes/islandora_unit_test_case.inc index 4cdf30bd..baffc62e 100644 --- a/tests/includes/islandora_unit_test_case.inc +++ b/tests/includes/islandora_unit_test_case.inc @@ -58,7 +58,7 @@ class IslandoraUnitTestCase extends DrupalUnitTestCase { module_load_include('inc', 'islandora', 'includes/tuque'); module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); - $this->configuration = islandora_get_test_configuration(); + $this->configuration = IslandoraTestUtilityClass::getTestConfiguration(); $this->connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); $api = new FedoraApi($this->connection); $this->repository = new FedoraRepository($api, new SimpleCache()); diff --git a/tests/includes/islandora_web_test_case.inc b/tests/includes/islandora_web_test_case.inc index 211f0df0..56645f83 100644 --- a/tests/includes/islandora_web_test_case.inc +++ b/tests/includes/islandora_web_test_case.inc @@ -74,7 +74,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); module_load_include('inc', 'islandora', 'tests/includes/utilities'); - $this->configuration = islandora_get_test_configuration(); + $this->configuration = IslandoraTestUtilityClass::getTestConfiguration(); if ($this->configuration['use_drupal_filter']) { $this->setUpDrupalFilter(); } diff --git a/tests/includes/test_utility_abstraction.inc b/tests/includes/test_utility_abstraction.inc new file mode 100644 index 00000000..aa09004b --- /dev/null +++ b/tests/includes/test_utility_abstraction.inc @@ -0,0 +1,153 @@ +message = $message; + $this->caller = $caller; + $this->type = $type; + } + + /** + * Get the message for this result. + * + * @return string + * The message for this result. + */ + public function getMessage() { + return $this->message; + } + + /** + * Get the caller for this result. + * + * @return string + * The caller for this result. + */ + public function getCaller() { + return $this->caller; + } + + /** + * Get the type of result. + * + * @return bool + * The type of pass (TRUE for pass, FALSE for fail). + */ + public function getType() { + return $this->type; + } +} + +/** + * Abstraction for test utility classes. + * + * This is to be implemented in any class that wants to have test utility + * functionality (i.e. that wants to pass back results to tests). Check out the + * datastream validator class for a good example of how this is implemented. + * + * Test utility classes should store per-installation configuration options in + * a test_config.ini file, contained in the islandora/tests folder. A function + * is included with the abstraction to parse the configuration file. + */ +abstract class IslandoraTestUtilityClass { + + /** + * An array of IslandoraTestUtilityResults. + * + * These should be generated using $this->addResult. + * + * @var IslandoraTestUtilityResult[] + */ + public $results = array(); + + /** + * Parses and returns the settings from the test configuration file. + * + * If no install specific test_config.ini file is found, it will use the + * assumed default configs found in default.test_config.ini. + * + * @return array + * The test configuration. + * + * @see parse_ini_file() + */ + public static function getTestConfiguration() { + $path = drupal_get_path('module', 'islandora'); + if (file_exists("$path/tests/test_config.ini")) { + return parse_ini_file("$path/tests/test_config.ini"); + } + elseif (file_exists("$path/tests/default.test_config.ini")) { + return parse_ini_file("$path/tests/default.test_config.ini"); + } + throw new Exception('Required default.test_config.ini/test_config.ini file not found'); + } + + /** + * Returns an array of IslandoraTestUtilityResults. + * + * The particular testing implementation you are using should use this to + * parse results from utilities and pass them through. + * + * @return IslandoraTestUtilityResult[] + * The results. + */ + abstract public function getResults(); + + /** + * Adds a result to $this->results. + * + * @param bool $type + * The type of result (TRUE for pass, FALSE for fail). + * @param string $message + * The message to put in the result. + */ + abstract public function addResult($type, $message); + + /** + * Gets the caller of the method that passed a result. + * + * @return array + * Array representing the true caller. + */ + abstract public function getAssertionCall(); + +} diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index 28179c05..81758880 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -13,150 +13,6 @@ * breaking existing implementations. */ -/** - * Parses and returns the settings from the test configuration file. - * - * If no install specific test_config.ini file is found, it will use the - * assumed default configs found in default.test_config.ini. - * - * @return array - * The test configuration. - * - * @see parse_ini_file() - */ -function islandora_get_test_configuration() { - $path = drupal_get_path('module', 'islandora'); - if (file_exists("$path/tests/test_config.ini")) { - return parse_ini_file("$path/tests/test_config.ini"); - } - elseif (file_exists("$path/tests/default.test_config.ini")) { - return parse_ini_file("$path/tests/default.test_config.ini"); - } - throw new Exception('Required default.test_config.ini/test_config.ini file not found'); -} - -/** - * A result from a utility method; $type defines TRUE/FALSE as pass/fail. - */ -class IslandoraTestUtilityResult { - - /** - * The message for this result. - * - * @var string - */ - protected $message; - - /** - * The caller for this result. - * - * @var array - */ - protected $caller; - - /** - * The type of result this is - TRUE for pass, FALSE for fail. - * - * @var bool - */ - protected $type; - - /** - * Constructs an IslandoraTestUtilityResult. - * - * @param bool $type - * Whether this result should indicate a pass (TRUE) or fail (FALSE). - * @param string $message - * The message that will be used by this result. - * @param array $caller - * The caller for this result. - */ - public function __construct($type, $message, array $caller) { - $this->message = $message; - $this->caller = $caller; - $this->type = $type; - } - - /** - * Get the message for this result. - * - * @return string - * The message for this result. - */ - public function getMessage() { - return $this->message; - } - - /** - * Get the caller for this result. - * - * @return string - * The caller for this result. - */ - public function getCaller() { - return $this->caller; - } - - /** - * Get the type of result. - * - * @return bool - * The type of pass (TRUE for pass, FALSE for fail). - */ - public function getType() { - return $this->type; - } -} - -/** - * Abstraction for test utility classes. - * - * This is to be implemented in any class that wants to have test utility - * functionality (i.e. that wants to pass back results to tests). Check out the - * datastream validator class for a good example of how this is implemented. - */ -abstract class IslandoraTestUtilityClass { - - /** - * An array of IslandoraTestUtilityResults. - * - * These should be generated using $this->addResult. - * - * @var IslandoraTestUtilityResult[] - */ - public $results = array(); - - /** - * Returns an array of IslandoraTestUtilityResults. - * - * The particular testing implementation you are using should use this to - * parse results from utilities and pass them through. - * - * @return IslandoraTestUtilityResult[] - * The results. - */ - abstract public function getResults(); - - /** - * Adds a result to $this->results. - * - * @param bool $type - * The type of result (TRUE for pass, FALSE for fail). - * @param string $message - * The message to put in the result. - */ - abstract public function addResult($type, $message); - - /** - * Gets the caller of the method that passed a result. - * - * @return array - * Array representing the true caller. - */ - abstract public function getAssertionCall(); - -} - class IslandoraTestUtilities extends IslandoraTestUtilityClass { protected $configuration; diff --git a/tests/scripts/travis_setup.sh b/tests/scripts/travis_setup.sh index 66061c72..56c651ab 100755 --- a/tests/scripts/travis_setup.sh +++ b/tests/scripts/travis_setup.sh @@ -8,6 +8,7 @@ cd $HOME git clone git://github.com/Islandora/tuque.git git clone -b $FEDORA_VERSION git://github.com/Islandora/islandora_tomcat.git cd islandora_tomcat +git fsck --full --verbose export CATALINA_HOME='.' export JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -Djavax.net.ssl.trustStore=$CATALINA_HOME/fedora/server/truststore -Djavax.net.ssl.trustStorePassword=tomcat" ./bin/startup.sh From 835d16739759a88aa56e99e3bd20091bf656941f Mon Sep 17 00:00:00 2001 From: qadan Date: Fri, 6 Jun 2014 18:02:22 +0000 Subject: [PATCH 11/15] let's not go nuts --- tests/scripts/travis_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scripts/travis_setup.sh b/tests/scripts/travis_setup.sh index 56c651ab..336087ed 100755 --- a/tests/scripts/travis_setup.sh +++ b/tests/scripts/travis_setup.sh @@ -8,7 +8,7 @@ cd $HOME git clone git://github.com/Islandora/tuque.git git clone -b $FEDORA_VERSION git://github.com/Islandora/islandora_tomcat.git cd islandora_tomcat -git fsck --full --verbose +git fsck export CATALINA_HOME='.' export JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -Djavax.net.ssl.trustStore=$CATALINA_HOME/fedora/server/truststore -Djavax.net.ssl.trustStorePassword=tomcat" ./bin/startup.sh From 1c5e2a1893615f9cd0bb680288d0930aac8d62d5 Mon Sep 17 00:00:00 2001 From: qadan Date: Fri, 6 Jun 2014 18:19:26 +0000 Subject: [PATCH 12/15] i did an incredibly stupid thing --- islandora.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islandora.module b/islandora.module index aeb2c059..635708bc 100644 --- a/islandora.module +++ b/islandora.module @@ -1817,7 +1817,7 @@ function islandora_repair_drupal_filter() { // Grab the config. module_load_include('inc', 'islandora', 'tests/utilities/test_utility_abstraction'); try { - IslandoraTestUtilityClass::getTestConfiguration(); + $configuration = IslandoraTestUtilityClass::getTestConfiguration(); } catch (Exception $e) { drupal_set_message(t("Error parsing test configuration: $e"), 'error'); From 3b797253add6c7f965d56e9a47600eb15af5604a Mon Sep 17 00:00:00 2001 From: qadan Date: Fri, 6 Jun 2014 18:47:18 +0000 Subject: [PATCH 13/15] the stupidity continues --- islandora.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islandora.module b/islandora.module index 635708bc..fd40790b 100644 --- a/islandora.module +++ b/islandora.module @@ -1820,7 +1820,7 @@ function islandora_repair_drupal_filter() { $configuration = IslandoraTestUtilityClass::getTestConfiguration(); } catch (Exception $e) { - drupal_set_message(t("Error parsing test configuration: $e"), 'error'); + drupal_set_message(t("Error parsing test configuration: %e", array('%e' => $e)), 'error'); return FALSE; } From f0a49fd44e6b003a3f7ad23b34a007965a39bbe0 Mon Sep 17 00:00:00 2001 From: qadan Date: Wed, 16 Jul 2014 12:43:29 -0300 Subject: [PATCH 14/15] code review changes --- tests/includes/islandora_unit_test_case.inc | 1 - tests/includes/islandora_web_test_case.inc | 8 +-- tests/includes/utilities.inc | 68 ++++++++------------- 3 files changed, 30 insertions(+), 47 deletions(-) diff --git a/tests/includes/islandora_unit_test_case.inc b/tests/includes/islandora_unit_test_case.inc index baffc62e..da0a8a71 100644 --- a/tests/includes/islandora_unit_test_case.inc +++ b/tests/includes/islandora_unit_test_case.inc @@ -56,7 +56,6 @@ class IslandoraUnitTestCase extends DrupalUnitTestCase { // It's possible test are running before class autoloading. module_load_include('inc', 'islandora', 'includes/tuque'); - module_load_include('inc', 'islandora', 'includes/tuque_wrapper'); $this->configuration = IslandoraTestUtilityClass::getTestConfiguration(); $this->connection = new RepositoryConnection($this->configuration['fedora_url'], $this->configuration['admin_user'], $this->configuration['admin_pass']); diff --git a/tests/includes/islandora_web_test_case.inc b/tests/includes/islandora_web_test_case.inc index 56645f83..55ee5824 100644 --- a/tests/includes/islandora_web_test_case.inc +++ b/tests/includes/islandora_web_test_case.inc @@ -222,7 +222,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { if (!$message) { $message = "Error found on current page when error was expected."; } - return $this->assertRaw("
", $message, $group); + return $this->assertFieldByXPath('//div[contains(@class, "message") and contains(@class, "error")]', NULL, $message, $group); } /** @@ -240,7 +240,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { if (!$message) { $message = "No error found on current page when no error was expected."; } - return $this->assertNoRaw("
", $message, $group); + return $this->assertNoFieldByXPath('//div[contains(@class, "message") and contains(@class, "error")]', NULL, $message, $group); } /** @@ -258,7 +258,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { if (!$message) { $message = "Warning found on current page when warning was expected."; } - return $this->assertRaw("
", $message, $group); + return $this->assertFieldByXPath('//div[contains(@class, "message") and contains(@class, "warning")]', NULL, $message, $group); } /** @@ -276,7 +276,7 @@ class IslandoraWebTestCase extends DrupalWebTestCase { if (!$message) { $message = "No warning found on current page when no warning was expected."; } - return $this->assertNoRaw("
", $message, $group); + return $this->assertNoFieldByXPath('//div[contains(@class, "message") and contains(@class, "warning")]', NULL, $message, $group); } /** diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index 81758880..719e76ca 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -40,29 +40,24 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { } /** - * Sets up a drupal filter that can read for the tests users table. + * Sets up a drupal filter that can read from the tests users table. */ public function setUpDrupalFilter() { $original_drupal_filter_content = file_get_contents($this->configuration['drupal_filter_file']); $connection_info = Database::getConnectionInfo('default'); $drupal_filter_dom = new DomDocument(); $drupal_filter_dom->loadXML($original_drupal_filter_content); - $server = $connection_info['default']['host']; - $dbname = $connection_info['default']['database']; - $user = $connection_info['default']['username']; - $password = $connection_info['default']['password']; - $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; $prefix = $connection_info['default']['prefix']['default']; $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); - $connection_node->setAttributeNode(new DOMAttr('server', $server)); - $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); - $connection_node->setAttributeNode(new DOMAttr('user', $user)); - $connection_node->setAttributeNode(new DOMAttr('password', $password)); - $connection_node->setAttributeNode(new DOMAttr('port', $port)); - $sql_node = $connection_node->appendChild(new DOMElement('sql')); - $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); + $connection_node->setAttribute('server', $connection_info['default']['host']); + $connection_node->setAttribute('dbname', $connection_info['default']['database']); + $connection_node->setAttribute('user', $connection_info['default']['username']); + $connection_node->setAttribute('password', $connection_info['default']['password']); + $connection_node->setAttribute('port', $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'); + $sql_node = $drupal_filter_dom->createElement('sql', "SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;"); + $connection_node->appendChild($sql_node); file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); } @@ -117,7 +112,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { * TRUE on success, FALSE on fail. */ public function assertDatastreams($object, array $datastreams) { - if (!is_object($object)) { + if (!$this->assertFedoraObject($object)) { $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); } else { @@ -142,7 +137,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { * An array of datastreams to confirm not present. */ public function assertNoDatastreams($object, array $datastreams) { - if (!is_object($object)) { + if (!$this->assertFedoraObject($object)) { $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); return; } @@ -175,7 +170,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { */ public function validateDatastreams($object, array $datastreams) { - if (!is_object($object)) { + if (!$this->assertFedoraObject($object)) { $this->addResult(FALSE, "Datastream validation failed; Object passed in is invalid.", 'Islandora'); return; } @@ -183,32 +178,8 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { module_load_include('inc', 'islandora', 'tests/includes/datastream_validators'); foreach ($datastreams as $datastream) { - // Let's give them conventional names. - $dsid = $datastream[0]; - $prefix = $datastream[1]; - $params = array(); - if (isset($datastream[2])) { - $params = $datastream[2]; - } - - // Legacy tests were created before the CamelCase conventions of the class - // system now in place. So, we need to automagically seek out prefixes - // that start with a lower-case letter and convert them to the proper - // format (rather than fixing every single legacy test). - if (ctype_lower(substr($prefix, 0, 1))) { - // Handle the case where the prefix is "image". - if ($prefix === 'image') { - $prefix = 'Image'; - } - // Handle the case where the prefix is "text". - elseif ($prefix === 'text') { - $prefix = 'Text'; - } - // All other cases involve just converting everything to caps. - else { - $prefix = strtoupper($prefix); - } - } + // XXX: The "+ array (2 => array())" is to allow the value to be optional. + list($dsid, $prefix, $params) = $datastream + array(2 => array()); // Instantiate the appropriate class, and grab the results. $class_name = "{$prefix}DatastreamValidator"; @@ -367,4 +338,17 @@ QUERY; } } + /** + * Asserts that an object is a FedoraObject or an IslandoraFedoraObject. + * + * @param object $object + * The object to assess. + * + * @return bool + * TRUE if it is either of those object types, or FALSE otherwise. + */ + public static function assertFedoraObject($object) { + return ($object instanceof FedoraObject); + } + } From 0a6e8300c1e6e6f78e6b2115d9dbd3d418c7a671 Mon Sep 17 00:00:00 2001 From: qadan Date: Wed, 16 Jul 2014 14:54:05 -0300 Subject: [PATCH 15/15] static context non-static statics contexts pfft --- tests/includes/utilities.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/includes/utilities.inc b/tests/includes/utilities.inc index 719e76ca..078e60d6 100644 --- a/tests/includes/utilities.inc +++ b/tests/includes/utilities.inc @@ -112,7 +112,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { * TRUE on success, FALSE on fail. */ public function assertDatastreams($object, array $datastreams) { - if (!$this->assertFedoraObject($object)) { + if (!self::assertFedoraObject($object)) { $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); } else { @@ -137,7 +137,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { * An array of datastreams to confirm not present. */ public function assertNoDatastreams($object, array $datastreams) { - if (!$this->assertFedoraObject($object)) { + if (!self::assertFedoraObject($object)) { $this->addResult(FALSE, "Failed. Object passed in is invalid.", 'Islandora'); return; } @@ -170,7 +170,7 @@ class IslandoraTestUtilities extends IslandoraTestUtilityClass { */ public function validateDatastreams($object, array $datastreams) { - if (!$this->assertFedoraObject($object)) { + if (!self::assertFedoraObject($object)) { $this->addResult(FALSE, "Datastream validation failed; Object passed in is invalid.", 'Islandora'); return; }