, * Michael Stilkerich * * This file is part of RCMCardDAV. * * RCMCardDAV is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * RCMCardDAV is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RCMCardDAV. If not, see . */ declare(strict_types=1); namespace MStilkerich\Tests\RCMCardDAV; use PHPUnit\Framework\TestCase; use Sabre\VObject; use Sabre\VObject\Component\VCard; use MStilkerich\RCMCardDAV\Db\AbstractDatabase; use MStilkerich\RCMCardDAV\Frontend\AdminSettings; final class TestInfrastructure { /** @var Config */ public static $infra; /** @var ?TestLogger */ private static $logger; /** * Initializes the RCMCardDAV infrastructure Config by a test-specific implementation. * * This includes the creation of test-specific logger and roundcube adapter interface. */ public static function init(AbstractDatabase $db, ?string $admSettingsPath = null): void { if (!isset($admSettingsPath)) { $admSettingsPath = __DIR__ . "/../config.inc.php.dist"; } $logger = self::logger(); $admPrefs = new AdminSettings($admSettingsPath, $logger, $logger); self::$infra = new Config($db, $logger, $admPrefs); \MStilkerich\RCMCardDAV\Config::$inst = self::$infra; } public static function logger(): TestLogger { if (!isset(self::$logger)) { self::$logger = new TestLogger(); } return self::$logger; } public static function readJsonArray(string $jsonFile): array { TestCase::assertFileIsReadable($jsonFile); $json = file_get_contents($jsonFile); TestCase::assertNotFalse($json, "File read error on $jsonFile"); $phpArray = json_decode($json, true); TestCase::assertTrue(is_array($phpArray), "JSON parse error on $jsonFile"); return $phpArray; } public static function readPhpPrefsArray(string $phpIncFile): array { $prefs = []; if (file_exists($phpIncFile)) { include($phpIncFile); } return $prefs; } public static function readVCard(string $vcfFile): VCard { TestCase::assertFileIsReadable($vcfFile); $vcard = VObject\Reader::read(fopen($vcfFile, 'r')); TestCase::assertInstanceOf(VCard::class, $vcard); return $vcard; } /** * Reads a file at a path relative to a given base file and returns its content. * * @param string $path Relative path of the file to read. * @param string $baseFile File relative to whose path $path is to be interpreted to. */ public static function readFileRelative(string $path, string $baseFile): string { $comp = pathinfo($baseFile); $file = "{$comp["dirname"]}/$path"; TestCase::assertFileIsReadable($file); $content = file_get_contents($file); TestCase::assertIsString($content, "$file could not be read"); return $content; } /** * Transforms a list of rows in associative array form to a list form. * * In other words, transforms the rows as returned by AbstractDatabase::get() to the format used internally. * * The function asserts that all fields listed in $cols actually have an entry in the provided associative rows. * * The order of rows is preserved. * * @param list $cols The list of columns that defines fields and their order in the result rows. * @param list> $assocRows The associative row set where column names are keys. * @param bool $extraIsError If true, an assertion is done on that the associative rows contain no extra fields * that are not listed in $cols. If false, such fields will be discarded. * @return list> */ public static function xformDatabaseResultToRowList(array $cols, array $assocRows, bool $extraIsError): array { $result = []; foreach ($assocRows as $aRow) { $lRow = []; foreach ($cols as $col) { TestCase::assertArrayHasKey($col, $aRow, "cols requires field not existing in associative row"); $lRow[] = $aRow[$col]; unset($aRow[$col]); } $result[] = $lRow; if ($extraIsError) { TestCase::assertEmpty($aRow, "Associative row has fields not listed in cols"); } } return $result; } /** * Extracts specific columns from a set of data rows. * * @param list $cols Gives the columns of each row in $rows by name. Indexes correspond to row indexes. * @param list $neededCols Gives the columns names to extract. Resulting rows will have the columns at * matching indexes. * @param list> $rows The data rows * @return list> The data rows with only the extracted columns */ public static function arrayColumns(array $cols, array $neededCols, array $rows): array { $col2Index = array_flip($cols); $result = []; foreach ($rows as $row) { TestCase::assertCount(count($cols), $row, "Input data row has mismatching columns to input columns"); $resultRow = []; foreach ($neededCols as $col) { TestCase::assertArrayHasKey($col, $col2Index); $resultRow[] = $row[$col2Index[$col]]; } $result[] = $resultRow; } return $result; } /** * Sorts a list of rows, where each row is a list of nullable strings. * * Fields are sorted alphabetically, in order of columns. * * @param list> $rowlist * @return list> */ public static function sortRowList(array $rowlist): array { TestCase::assertTrue(usort($rowlist, [self::class, 'compareRows'])); return $rowlist; } /** * Compare functions for two rows to use for sorting. * * @param list $r1 * @param list $r2 * @return int smaller/equal/greater 0 if $r1 is less/equal/greater than $r2 * */ public static function compareRows(array $r1, array $r2): int { $res = 0; TestCase::assertCount(count($r1), $r2, "compareRows requires rows of equal length"); for ($i = 0; $res == 0 && $i < count($r1); ++$i) { $v1 = $r1[$i]; $v2 = $r2[$i]; if (isset($v1) && isset($v2)) { $res = strcmp($v1, $v2); } elseif (isset($v2)) { $res = -1; } elseif (isset($v1)) { $res = 1; } } return $res; } /** * @param object $obj * @param mixed $value */ public static function setPrivateProperty($obj, string $propName, $value): void { $class = get_class($obj); $prop = new \ReflectionProperty($class, $propName); $prop->setAccessible(true); $prop->setValue($obj, $value); } /** * @param object $obj * @return mixed $value */ public static function getPrivateProperty($obj, string $propName) { $class = get_class($obj); $prop = new \ReflectionProperty($class, $propName); $prop->setAccessible(true); return $prop->getValue($obj); } /** * Recursively copies the given directory to the given destination. * * The destination must not exist yet. */ public static function copyDir(string $src, string $dst): void { TestCase::assertDirectoryIsReadable($src, "Source directory $src cannot be read"); TestCase::assertDirectoryDoesNotExist($dst, "Destination directory $dst already exists"); // create destination TestCase::assertTrue(mkdir($dst, 0755, true), "Destination directory $dst could not be created"); // process all directory entries in source $dirh = opendir($src); TestCase::assertIsResource($dirh, "Source directory could not be opened"); while (false !== ($entry = readdir($dirh))) { if ($entry != "." && $entry != "..") { $entryp = "$src/$entry"; $targetp = "$dst/$entry"; if (is_dir($entryp)) { self::copyDir($entryp, $targetp); } elseif (is_file($entryp)) { TestCase::assertTrue(copy($entryp, $targetp), "Copy failed: $entryp -> $targetp"); } else { TestCase::assertFalse(true, "$entryp: copyDir only supports files/directories"); } } } closedir($dirh); } /** * Recursively deletes the given directory. */ public static function rmDirRecursive(string $dir): void { TestCase::assertDirectoryIsWritable($dir, "Target directory $dir not writeable"); // 1: purge directory contents $dirh = opendir($dir); TestCase::assertIsResource($dirh, "Target directory could not be opened"); while (false !== ($entry = readdir($dirh))) { if ($entry != "." && $entry != "..") { $entryp = "$dir/$entry"; if (is_dir($entryp)) { self::rmDirRecursive($entryp); } else { TestCase::assertTrue(unlink($entryp), "Unlink failed: $entryp"); } } } closedir($dirh); // 2: delete directory TestCase::assertTrue(rmdir($dir), "rmdir failed: $dir"); } } // vim: ts=4:sw=4:expandtab:fenc=utf8:ff=unix:tw=120:ft=php