Skip to content

Commit

Permalink
FileMock: fixed opening modes r, w, x
Browse files Browse the repository at this point in the history
- 'r' fails when mock does not exist
- 'w' truncates mock contents
- 'x' fails when mock exist
  • Loading branch information
milo committed Mar 19, 2016
1 parent 0b765ba commit 27e063f
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 14 deletions.
47 changes: 42 additions & 5 deletions src/Framework/FileMock.php
Expand Up @@ -30,20 +30,46 @@ class FileMock
*/
public static function create($content, $extension = NULL)
{
if (!self::$files) {
stream_wrapper_register(self::PROTOCOL, __CLASS__);
}
self::register();

static $id;
$name = self::PROTOCOL . '://' . (++$id) . '.' . $extension;
self::$files[$name] = $content;
return $name;
}


public static function register()
{
if (!in_array(self::PROTOCOL, stream_get_wrappers(), TRUE)) {
stream_wrapper_register(self::PROTOCOL, __CLASS__);
}
}


public function stream_open($path, $mode)
{
if (!preg_match('#^([rwaxc])#', $mode, $m)) {
// Windows: failed to open stream: Bad file descriptor
// Linux: failed to open stream: Illegal seek
$this->warning("failed to open stream: Invalid mode '$mode'");
return FALSE;

} elseif ($m[1] === 'x' && isset(self::$files[$path])) {
$this->warning('failed to open stream: File exists');
return FALSE;

} elseif ($m[1] === 'r' && !isset(self::$files[$path])) {
$this->warning('failed to open stream: No such file or directory');
return FALSE;

} elseif ($m[1] === 'w' || $m[1] === 'x') {
self::$files[$path] = '';
}

$this->content = & self::$files[$path];
$this->pos = strpos($mode, 'a') === FALSE ? 0 : strlen($this->content);
$this->pos = $m[1] === 'a' ? strlen($this->content) : 0;

return TRUE;
}

Expand Down Expand Up @@ -130,8 +156,19 @@ public function unlink($path)
return TRUE;
}

trigger_error("unlink($path): No such file", E_USER_WARNING);
$this->warning('No such file');
return FALSE;
}


private function warning($message)
{
$bt = PHP_VERSION_ID < 50400 ? debug_backtrace(FALSE) : debug_backtrace(0, 3);
if (isset($bt[2]['function'])) {
$message = $bt[2]['function'] . '(' . @$bt[2]['args'][0] . '): ' . $message;
}

trigger_error($message, E_USER_WARNING);
}

}
9 changes: 8 additions & 1 deletion tests/CodeCoverage/CloverXMLGenerator.phpt
Expand Up @@ -24,7 +24,14 @@ $generator->render($output = Tester\FileMock::create('', 'xml'));

$dom = new DOMDocument;
$dom->load($output);
$files = $sorted = iterator_to_array($dom->getElementsByTagName('file'), FALSE); // TRUE crashes on Travis & PHP 5.3.3

//$files = $sorted = iterator_to_array($dom->getElementsByTagName('file')); // iterator_to_array() crashes on Travis & PHP 5.3.3
$files = array();
foreach ($dom->getElementsByTagName('file') as $node) {
$files[] = $node;
}

$sorted = $files;

This comment has been minimized.

Copy link
@JanTvrdik

JanTvrdik Mar 20, 2016

Contributor

Why is the foreach better than iterator_to_array?

This comment has been minimized.

Copy link
@milo

milo Mar 20, 2016

Author Member

Temporary fix for PHP 5.3, see #292

This comment has been minimized.

Copy link
@dg

dg Mar 20, 2016

Member

iterator_to_array(..., FALSE) crashes too?

This comment has been minimized.

Copy link
@milo

milo Mar 20, 2016

Author Member

Yes. Actually, the PHP 5.3.3 only. Exhaused memory. On local Windows (and Linux too) the test consumed less than 10MB of memory. There are my attempts (https://travis-ci.org/milo/tester/builds).

It will be reverted: c5c6038

usort($sorted, function($a, $b) {
return strcmp($a->getAttribute('name'), $b->getAttribute('name'));
});
Expand Down
159 changes: 151 additions & 8 deletions tests/Framework/FileMock.phpt
Expand Up @@ -6,6 +6,153 @@ use Tester\FileMock;
require __DIR__ . '/../bootstrap.php';


Assert::notContains('mock', stream_get_wrappers());
FileMock::create('');
Assert::contains('mock', stream_get_wrappers());


// Opening non-existing
test(function () {
$cases = array(
'r' => $tmp = array(
array(E_USER_WARNING, 'fopen(mock://none): failed to open stream: No such file or directory'),
array(E_WARNING, 'fopen(mock://none): failed to open stream: "Tester\FileMock::stream_open" call failed'),
),
'r+' => $tmp,
'w' => array(),
'w+' => array(),
'a' => array(),
'a+' => array(),
'x' => array(),
'x+' => array(),
'c' => array(),
'c+' => array(),
);

foreach ($cases as $mode => $errors) {
FileMock::$files = array();

Assert::error(function () use ($mode) {
fopen('mock://none', $mode);
}, $errors);
Assert::count(count($errors) ? 0 : 1, FileMock::$files);
}
});


// Opening existing
test(function () {
FileMock::$files = array();

$cases = array(
'r' => array(),
'r+' => array(),
'w' => array(),
'w+' => array(),
'a' => array(),
'a+' => array(),
'x' => $tmp = array(
array(E_USER_WARNING, 'fopen(mock://%i%.): failed to open stream: File exists'),
array(E_WARNING, 'fopen(mock://%i%.): failed to open stream: "Tester\FileMock::stream_open" call failed'),
),
'x+' => $tmp,
'c' => array(),
'c+' => array(),
);

foreach ($cases as $mode => $errors) {
Assert::error(function () use ($mode) {
fopen(FileMock::create(''), $mode);
}, $errors);
}
});


// Initial cursor position
test(function () {
FileMock::$files = array();

$cases = array(
'r' => 0,
'r+' => 0,
'w' => 0,
'w+' => 0,
'a' => 3,
'a+' => 3,
'x' => 0,
'x+' => 0,
'c' => 0,
'c+' => 0,
);

foreach ($cases as $mode => $position) {
$file = $mode[0] === 'x' ? "mock://none-$mode" : FileMock::create('ABC');
Assert::same($position, ftell(fopen($file, $mode)), "Mode $mode");
}
});


// Truncation on open
test(function () {
FileMock::$files = array();

$cases = array(
'r' => array('ABC', 'ABC'),
'r+' => array('ABC', 'ABC'),
'w' => array('', ''),
'w+' => array('', ''),
'a' => array('ABC', 'ABC'),
'a+' => array('ABC', 'ABC'),
'x' => array('', ''),
'x+' => array('', ''),
'c' => array('ABC', 'ABC'),
'c+' => array('ABC', 'ABC'),
);

foreach ($cases as $mode => $case) {
list($contents, $readOut) = $case;
$file = $mode[0] === 'x' ? "mock://none-$mode" : FileMock::create('ABC');

$f = fopen($file, $mode);
fseek($f, 0);

Assert::same($contents, FileMock::$files[$file], "Mode $mode");
Assert::same($readOut, fread($f, 512), "Mode $mode");
}
});

// Writing position after open
test(function () {
FileMock::$files = array();

$cases = array(
'r' => array('_BC', '_BC'),
'r+' => array('_BC', '_BC'),
'w' => array('_', '_'),
'w+' => array('_', '_'),
'a' => array('ABC_', 'ABC_'),
'a+' => array('ABC_', 'ABC_'),
'x' => array('_', '_'),
'x+' => array('_', '_'),
'c' => array('_BC', '_BC'),
'c+' => array('_BC', '_BC'),
);

foreach ($cases as $mode => $case) {
list($contents, $readOut) = $case;
$file = $mode[0] === 'x' ? "mock://none-$mode" : FileMock::create('ABC');

$f = fopen($file, $mode);
fwrite($f, '_');
fseek($f, 0);

Assert::same($contents, FileMock::$files[$file], "Mode $mode");
Assert::same($readOut, fread($f, 512), "Mode $mode");
}
});


// Filesystem functions
test(function () {
$f = fopen($name = FileMock::create('', 'txt'), 'w+');

Expand Down Expand Up @@ -55,13 +202,7 @@ test(function () {
});


test(function () {
$f = fopen($name = Tester\FileMock::create('A'), 'a');
fwrite($f, 'B');
Assert::same('AB', file_get_contents($name));
});


// Unlink
test(function () {
fopen($name = Tester\FileMock::create('foo'), 'r');
Assert::true(unlink($name));
Expand All @@ -72,11 +213,13 @@ test(function () {
});


// Runtime include
test(function () {
Assert::same(123, require FileMock::create('<?php return 123;'));
});


// Locking
test(function () {
Assert::false(flock(fopen(\Tester\FileMock::create(''), 'x'), LOCK_EX));
Assert::false(flock(fopen(FileMock::create(''), 'w'), LOCK_EX));
});

0 comments on commit 27e063f

Please sign in to comment.