Skip to content

Commit

Permalink
(REF) FileTest - Prevent test-interactions from testIsDirWithOpenBasedir
Browse files Browse the repository at this point in the history
  • Loading branch information
totten committed Sep 7, 2022
1 parent 50cd6ab commit 7d3ebfe
Showing 1 changed file with 88 additions and 16 deletions.
104 changes: 88 additions & 16 deletions tests/phpunit/CRM/Utils/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,9 @@ public function testIsDirLinks() {
* @param bool $expected
*/
public function testIsDirWithOpenBasedir(?string $input, bool $expected) {
$originalOpenBasedir = ini_get('open_basedir');

// This might not always be under cms root, but let's see how it goes.
$a_dir = \Civi::paths()->getPath('[civicrm.compile]/');

if (file_exists("{$a_dir}/isDirTest/ok/ok.txt")) {
unlink("{$a_dir}/isDirTest/ok/ok.txt");
}
Expand All @@ -336,36 +335,109 @@ public function testIsDirWithOpenBasedir(?string $input, bool $expected) {
// For now let's try this, assuming a drupal 7 structure where we know
// where this file is:
$cms_root = realpath(__DIR__ . '/../../../../../../../..');

// This test requires tightening `open_basedir` settings, which is a one-way change.
// Therefore, we have to do that part of the test in a sub-process.
// If you run directly in the same-process, then (eg) `phpunit` will have trouble writing error-data to JUnit XML files.
[$exitCode, $stdout, $stderr] = $this->runStaticMethodAsScript(__CLASS__, 'doIsDirWithOpenBasedir', [$a_dir, $cms_root, $input, $expected]);
$this->assertEquals('"OK"', trim($stdout), 'doIsDirWithOpenBasedir() should return OK');
$this->assertEquals('', trim($stderr), 'doIsDirWithOpenBasedir() should not generate warnings');
$this->assertEquals(0, $exitCode, 'doIsDirWithOpenBasedir() should exit normally');

unlink("{$a_dir}/isDirTest/ok/ok.txt");
rmdir("{$a_dir}/isDirTest/ok");
rmdir("{$a_dir}/isDirTest");
}

/**
* @param string $a_dir
* @param string $cms_root
* @param string|null $input
* @param bool $expected
* @return string
* @throws \ErrorException
*/
public static function doIsDirWithOpenBasedir(string $a_dir, string $cms_root, ?string $input, bool $expected): string {
// We also need temp dir because phpunit creates files in there as it does stuff before we can reset basedir.
ini_set('open_basedir', $cms_root . PATH_SEPARATOR . sys_get_temp_dir());

$this->assertTrue(mkdir("{$a_dir}/isDirTest"));
$this->assertTrue(mkdir("{$a_dir}/isDirTest/ok"));
if (!mkdir("{$a_dir}/isDirTest")) {
return 'Failed to make isDirTest';
}

if (!mkdir("{$a_dir}/isDirTest/ok")) {
return 'Failed to make isDirTest/ok';
}

file_put_contents("{$a_dir}/isDirTest/ok/ok.txt", 'Hello World!');
// hmm the "bad" isn't going to work the same way php's own tests work. We
// need to find a directory outside both cms_root and the sys temp dir.
// Let's just use some known unix files that always exist instead.
// mkdir("{$a_dir}/isDirTest/bad");

$old_cwd = getcwd();
$this->assertTrue(chdir("{$a_dir}/isDirTest/ok"));
if (!chdir("{$a_dir}/isDirTest/ok")) {
return 'Failed to chdir to isDirTest/ok';
}

clearstatcache();
if ($expected) {
$this->assertTrue(CRM_Utils_File::isDir($input));
}
else {
$actual = CRM_Utils_File::isDir($input); /* Should pass */
// $actual = is_dir($input); /* Should fail */
if ($expected !== $actual) {
return sprintf("isDir returned incorrect result. Expected=%s Actual=%s", json_encode($expected), json_encode($actual));
// Note that except for 'ok.txt', the real is_dir() would give an
// error for these. For 'ok.txt' it would return false, but no error.
// So this is what we are changing about the real function.
$this->assertFalse(CRM_Utils_File::isDir($input));
}

ini_set('open_basedir', $originalOpenBasedir);
$this->assertTrue(chdir($old_cwd));
unlink("{$a_dir}/isDirTest/ok/ok.txt");
rmdir("{$a_dir}/isDirTest/ok");
rmdir("{$a_dir}/isDirTest");
return 'OK';
}

protected function runStaticMethodAsScript(string $class, string $method, $args = []) {
$func = new ReflectionMethod($class, $method);
$outClass = 'Wrapper' . time();
$ignoreStdErr = ';^Xdebug:;';

$inFile = $func->getFileName();
$inFileLines = explode("\n", file_get_contents($inFile));

$lines = array_merge(
[
'<' . '?php',
"class $outClass {",
],
array_slice($inFileLines, $func->getStartLine() - 1, $func->getEndLine() - $func->getStartLine() + 1),
[
'}',
sprintf('$args = %s;', var_export($args, 1)),
sprintf('return %s::%s(...$args);', $outClass, $method),
]
);
$tmpSrc = implode("\n", $lines);

$outFile = tempnam(sys_get_temp_dir(), 'test-script-') . '.php';
file_put_contents($outFile, $tmpSrc);

try {
$cmd = 'cv ev -v ' . escapeshellarg("return require \"$outFile\";");
$descriptorSpec = array(0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']);
$oldOutput = getenv('CV_OUTPUT');
putenv("CV_OUTPUT=json");
$process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__);
putenv("CV_OUTPUT=$oldOutput");
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);

$stderr = implode("\n", preg_grep($ignoreStdErr, explode("\n", $stderr), PREG_GREP_INVERT));
$exitCode = proc_close($process);

return [$exitCode, $stdout, $stderr];
}
finally {
unlink($outFile);
}
}

/**
Expand Down

0 comments on commit 7d3ebfe

Please sign in to comment.