openstack-sdk-php/test/Tests/StreamWrapperTest.php
2013-08-28 15:51:45 -04:00

607 lines
15 KiB
PHP

<?php
/* ============================================================================
(c) Copyright 2012 Hewlett-Packard Development Company, L.P.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
============================================================================ */
/**
* @file
*
* Unit tests for the stream wrapper.
*/
namespace HPCloud\Tests\Storage\ObjectStorage;
require_once 'src/HPCloud/Bootstrap.php';
require_once 'test/TestCase.php';
use \HPCloud\Storage\ObjectStorage\StreamWrapper;
use \HPCloud\Storage\ObjectStorage\Container;
use \HPCloud\Storage\ObjectStorage\Object;
use \HPCloud\Storage\ObjectStorage\ACL;
/**
* @group streamWrapper
*/
class StreamWrapperTest extends \HPCloud\Tests\TestCase {
const FNAME = 'streamTest.txt';
const FTYPE = 'application/x-tuna-fish; charset=iso-8859-13';
/**
* Cleaning up the test container so we can reuse it for other tests.
*/
public static function tearDownAfterClass() {
// First we get an identity
$user = self::conf('hpcloud.identity.username');
$pass = self::conf('hpcloud.identity.password');
$tenantId = self::conf('hpcloud.identity.tenantId');
$url = self::conf('hpcloud.identity.url');
$ident = new \HPCloud\Services\IdentityServices($url);
$token = $ident->authenticateAsUser($user, $pass, $tenantId);
// Then we need to get an instance of storage
$store = \HPCloud\Storage\ObjectStorage::newFromIdentity($ident);
// Delete the container and all the contents.
$cname = self::$settings['hpcloud.swift.container'];
try {
$container = $store->container($cname);
}
// The container was never created.
catch (\HPCloud\Transport\FileNotFoundException $e) {
return;
}
foreach ($container as $object) {
try {
$container->delete($object->name());
}
catch (\Exception $e) {}
}
$store->deleteContainer($cname);
}
protected function newUrl($objectName) {
$scheme = StreamWrapper::DEFAULT_SCHEME;
$cname = self::$settings['hpcloud.swift.container'];
$cname = urlencode($cname);
$objectParts = explode('/', $objectName);
for ($i = 0; $i < count($objectParts); ++$i) {
$objectParts[$i] = urlencode($objectParts[$i]);
}
$objectName = implode('/', $objectParts);
$url = $scheme . '://' . $cname . '/' . $objectName;
return $url;
}
/**
* This assumes auth has already been done.
*/
protected function basicSwiftContext($add = array(), $scheme = NULL) {
$cname = self::$settings['hpcloud.swift.container'];
if (empty($scheme)) {
$scheme = StreamWrapper::DEFAULT_SCHEME;
}
if (empty(self::$ostore)) {
throw new \Exception('OStore is gone.');
}
$params = $add + array(
'token' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
'content_type' => self::FTYPE,
);
$cxt = array($scheme => $params);
return stream_context_create($cxt);
}
/**
* This performs authentication via context.
*
* UPDATE: This now users IdentityServices instead of deprecated
* swauth.
*/
protected function authSwiftContext($add = array(), $scheme = NULL) {
$cname = self::$settings['hpcloud.swift.container'];
$account = self::$settings['hpcloud.identity.account'];
$key = self::$settings['hpcloud.identity.secret'];
$tenant = self::$settings['hpcloud.identity.tenantId'];
$baseURL = self::$settings['hpcloud.identity.url'];
if (empty($scheme)) {
$scheme = StreamWrapper::DEFAULT_SCHEME;
}
$params = $add + array(
'account' => $account,
'key' => $key,
'endpoint' => $baseURL,
'tenantid' => $tenant,
'content_type' => self::FTYPE,
);
$cxt = array($scheme => $params);
return stream_context_create($cxt);
}
/**
* Add additional params to the config.
*
* This allows us to insert credentials into the
* bootstrap config, which in turn allows us to run
* high-level context-less functions like
* file_get_contents(), stat(), and is_file().
*/
protected function addBootstrapConfig() {
$opts = array(
'account' => self::$settings['hpcloud.identity.account'],
'key' => self::$settings['hpcloud.identity.secret'],
'endpoint' => self::$settings['hpcloud.identity.url'],
'tenantid' => self::$settings['hpcloud.identity.tenantId'],
'token' => $this->objectStore()->token(),
'swift_endpoint' => $this->objectStore()->url(),
);
\HPCloud\Bootstrap::setConfiguration($opts);
}
// Canary. There are UTF-8 encoding issues in stream wrappers.
public function testStreamContext() {
// Reset this in case something else left its
// auth token lying around.
\HPCloud\Bootstrap::setConfiguration(array(
'token' => NULL,
));
$cxt = $this->authSwiftContext();
$array = stream_context_get_options($cxt);
$opts = $array['swift'];
$endpoint = self::conf('hpcloud.identity.url');
$this->assertEquals($endpoint, $opts['endpoint'], 'A UTF-8 encoding issue.');
}
/**
* @depends testStreamContext
*/
public function testRegister() {
// Canary
$this->assertNotEmpty(StreamWrapper::DEFAULT_SCHEME);
$klass = '\HPCloud\Storage\ObjectStorage\StreamWrapper';
stream_wrapper_register(StreamWrapper::DEFAULT_SCHEME, $klass);
$wrappers = stream_get_wrappers();
$this->assertContains(StreamWrapper::DEFAULT_SCHEME, $wrappers);
}
/**
* @depends testRegister
*/
public function testOpenFailureWithoutContext() {
$cname = self::$settings['hpcloud.swift.container'];
// Create a fresh container.
$this->eradicateContainer($cname);
$this->containerFixture();
$url = $this->newUrl('foo→/bar.txt');
$ret = @fopen($url, 'r');
$this->assertFalse($ret);
}
/**
* @depends testRegister
*/
public function testOpen() {
$cname = self::$settings['hpcloud.swift.container'];
// Create a fresh container.
$this->eradicateContainer($cname);
$this->containerFixture();
// Simple write test.
$oUrl = $this->newUrl('foo→/test.csv');
$res = fopen($oUrl, 'nope', FALSE, $this->authSwiftContext());
$this->assertTrue(is_resource($res));
$md = stream_get_meta_data($res);
$wrapper = $md['wrapper_data'];
fclose($res);
// Now we test the same, but re-using the auth token:
$cxt = $this->basicSwiftContext(array('token' => $wrapper->token()));
$res = fopen($oUrl, 'nope', FALSE, $cxt);
$this->assertTrue(is_resource($res));
fclose($res);
}
/**
* @depends testOpen
*/
public function testOpenFailureWithRead() {
$url = $this->newUrl(__FUNCTION__);
$res = @fopen($url, 'r', FALSE, $this->basicSwiftContext());
$this->assertFalse($res);
}
// DO we need to test other modes?
/**
* @depends testOpen
*/
public function testOpenCreateMode() {
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'c+', FALSE, $this->basicSwiftContext());
$this->assertTrue(is_resource($res));
//fclose($res);
return $res;
}
/**
* @depends testOpenCreateMode
*/
public function testTell($res) {
// Sould be at the beginning of the buffer.
$this->assertEquals(0, ftell($res));
return $res;
}
/**
* @depends testTell
*/
public function testWrite($res) {
$str = 'To be is to be the value of a bound variable. -- Quine';
fwrite($res, $str);
$this->assertGreaterThan(0, ftell($res));
return $res;
}
/**
* @depends testWrite
*/
public function testStat($res) {
$stat = fstat($res);
$this->assertGreaterThan(0, $stat['size']);
return $res;
}
/**
* @depends testStat
*/
public function testSeek($res) {
$then = ftell($res);
rewind($res);
$now = ftell($res);
// $now should be 0
$this->assertLessThan($then, $now);
$this->assertEquals(0, $now);
fseek($res, 0, SEEK_END);
$final = ftell($res);
$this->assertEquals($then, $final);
return $res;
}
/**
* @depends testSeek
*/
public function testEof($res) {
rewind($res);
$this->assertEquals(0, ftell($res));
$this->assertFalse(feof($res));
fseek($res, 0, SEEK_END);
$this->assertGreaterThan(0, ftell($res));
$read = fread($res, 8192);
$this->assertEmpty($read);
$this->assertTrue(feof($res));
return $res;
}
/**
* @depends testEof
*/
public function testFlush($res) {
$stat1 = fstat($res);
fflush($res);
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', FALSE, $this->basicSwiftContext());
$stat2 = fstat($newObj);
$this->assertEquals($stat1['size'], $stat2['size']);
return $res;
}
/**
* @depends testFlush
*/
public function testStreamGetMetadata($res) {
// Grab a copy of the object.
$url = $this->newUrl(self::FNAME);
$newObj = fopen($url, 'r', FALSE, $this->basicSwiftContext());
$md = stream_get_meta_data($newObj);
//throw new \Exception(print_r($md, true));
$obj = $md['wrapper_data']->object();
$this->assertInstanceOf('\HPCloud\Storage\ObjectStorage\RemoteObject', $obj);
$this->assertEquals(self::FTYPE, $obj->contentType());
}
/**
* @depends testFlush
*/
public function testClose($res) {
$this->assertTrue(is_resource($res));
fwrite($res, '~~~~');
//throw new \Exception(stream_get_contents($res));
fflush($res);
// This is occasionally generating seemingly
// spurious PHP errors about Bootstrap::$config.
fclose($res);
$url = $this->newUrl(self::FNAME);
$res2 = fopen($url, 'r', FALSE, $this->basicSwiftContext());
$this->assertTrue(is_resource($res2));
$contents = stream_get_contents($res2);
fclose($res2);
$this->assertRegExp('/~{4}$/', $contents);
}
/**
* @depends testClose
*/
public function testCast() {
$url = $this->newUrl(self::FNAME);
$res = fopen($url, 'r', FALSE, $this->basicSwiftContext());
$read = array($res);
$write = array();
$except = array();
$num_changed = stream_select($read, $write, $except, 0);
$this->assertGreaterThan(0, $num_changed);
}
/**
* @depends testClose
*/
public function testUrlStat(){
// Add context to the bootstrap config.
$this->addBootstrapConfig();
$url = $this->newUrl(self::FNAME);
$ret = stat($url);
// Check that the array looks right.
$this->assertEquals(26, count($ret));
$this->assertEquals(0, $ret[3]);
$this->assertEquals($ret[2], $ret['mode']);
$this->assertTrue(file_exists($url));
$this->assertTrue(is_readable($url));
$this->assertTrue(is_writeable($url));
$this->assertFalse(is_link($url));
$this->assertGreaterThan(0, filemtime($url));
$this->assertGreaterThan(5, filesize($url));
$perm = fileperms($url);
// Assert that this is a file. Objects are
// *always* marked as files.
$this->assertEquals(0x8000, $perm & 0x8000);
// Assert writeable by owner.
$this->assertEquals(0x0080, $perm & 0x0080);
// Assert not world writable.
$this->assertEquals(0, $perm & 0x0002);
$contents = file_get_contents($url);
$this->assertGreaterThan(5, strlen($contents));
$fsCopy = '/tmp/hpcloud-copy-test.txt';
copy($url, $fsCopy, $this->basicSwiftContext());
$this->assertTrue(file_exists($fsCopy));
unlink($fsCopy);
}
/**
* @depends testFlush
*/
public function testUnlink(){
$url = $this->newUrl(self::FNAME);
$cxt = $this->basicSwiftContext();
$ret = unlink($url, $cxt);
$this->assertTrue($ret);
$ret2 = unlink($url, $cxt);
$this->assertFalse($ret2);
}
public function testSetOption() {
$url = $this->newUrl('fake.foo');
$fake = fopen($url, 'nope', FALSE, $this->basicSwiftContext());
$this->assertTrue(stream_set_blocking($fake, 1));
// Returns 0 on success.
$this->assertEquals(0, stream_set_write_buffer($fake, 8192));
// Cant set a timeout on a tmp storage:
$this->assertFalse(stream_set_timeout($fake, 10));
fclose($fake);
}
/**
* @depends testUnlink
*/
public function testRename(){
$url = $this->newUrl('rename.foo');
$fake = fopen($url, 'w+', FALSE, $this->basicSwiftContext());
fwrite($fake, 'test');
fclose($fake);
$this->assertTrue(file_exists($url));
$url2 = $this->newUrl('rename.txt');
rename($url, $url2, $this->basicSwiftContext());
$this->assertTrue(file_exists($url2));
$this->assertFalse(file_exists($url));
unlink($url2, $this->basicSwiftContext());
}
/**
* @depends testUnlink
*/
public function testOpenDir() {
$urls = array('test1.txt', 'foo/test2.txt', 'foo/test3.txt', 'bar/test4.txt');
foreach ($urls as $base) {
$url = $this->newUrl($base);
$f = fopen($url, 'c+', FALSE, $this->basicSwiftContext());
fwrite($f, 'Test.');
fclose($f);
}
$dirUrl = $this->newUrl('');
$dir = opendir($dirUrl, $this->basicSwiftContext());
$this->assertTrue(is_resource($dir));
return $dir;
}
/**
* @depends testOpenDir
*/
public function testReaddir($dir){
// Order should be newest to oldest.
$expects = array('bar/', 'foo/', 'test1.txt');
$buffer = array();
while (($entry = readdir($dir)) !== FALSE) {
$should_be = array_shift($expects);
$this->assertEquals($should_be, $entry);
}
$this->assertFalse(readdir($dir));
return $dir;
}
/**
* @depends testReaddir
*/
public function testRewindDir($dir){
$this->assertFalse(readdir($dir));
rewinddir($dir);
$this->assertEquals('bar/', readdir($dir));
return $dir;
}
/**
* @depends testRewindDir
*/
public function testCloseDir($dir) {
$this->assertTrue(is_resource($dir));
closedir($dir);
// There is a bug in PHP where a
// resource buffer is not getting cleared.
// So this might return a value even though
// the underlying stream is cleared.
//$this->assertFalse(readdir($dir));
}
/**
* @depends testCloseDir
*/
public function testOpenSubdir() {
// Opening foo we should find test2.txt and test3.txt.
$url = $this->newUrl('foo/');
$dir = opendir($url, $this->basicSwiftContext());
// I don't know why, but these are always returned in
// lexical order.
$this->assertEquals('test2.txt', readdir($dir));
$this->assertEquals('test3.txt', readdir($dir));
$array = scandir($url, -1, $this->basicSwiftContext());
$this->assertEquals(2, count($array));
$this->assertEquals('test3.txt', $array[0]);
}
}