drap5081683 2011-12-26 16:03
浏览 73
已采纳

PHPUnit和SplFileObject在只读对象上返回true isWritable

I have a Logger interface that accepts a SplFileObject in the constructor to use as the file for that particular log. There is also a log($timestamp, $message) method available to actually do the logging. In my first implementation when instantiating a new object and passing a read-only SplFileObject an exception should be thrown. I wrote up an appropriate unit test:

<?php
class FileLoggerTest extends PHPUnit_Framework_TestCase {

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testReadOnlyFileObjectFailure() {
        $file = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
        $LogFile = new \SplFileObject($file);
        $Logger = new \libs\sprayfire\logger\FileLogger($LogFile);
        $Logger->log('test', 'something');
    }

}
?>

Normally I would have a method producing the directory name but when I started encountering problems I changed it to an absolute path to rule out that as the cause.

And here's the implementation:

namespace libs\sprayfire\logger;
use \SplFileObject as SplFileObject;
use \InvalidArgumentException as InvalidArgumentException;
use libs\sprayfire\logger\Logger as Logger;

    /**
     * @brief A framework implemented class that adds a timestamp log message to
     * the end of an injected file.
     */
    class FileLogger implements Logger  {

        /**
         * @brief A SplFileObject that should be used to write log messages to.
         *
         * @property $LogFile
         */
        protected $LogFile;

        /**
         * @param $LogFile SplFileObject that should have log messages written to
         */
        public function __construct(SplFileObject $LogFile) {
            $this->LogFile = $LogFile;
            $this->throwExceptionIfFileNotWritable();
        }

        /**
         * @throws InvalidArgumentException
         */
        protected function throwExceptionIfFileNotWritable() {
            $isWritable = $this->LogFile->isWritable();
            if (!$isWritable) {
                throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
            }
        }

        /**
         * @param $timestamp A formatted timestamp string
         * @param $message The message string to log
         * @return boolean true if the message was logged, false if it wasn't
         */
        public function log($timestamp, $message) {
            if (!isset($timestamp) || empty($timestamp)) {
                $timestamp = 'No timestamp given';
            }

            if (!isset($message) || empty($message)) {
                $message = 'Attempting to log an empty message';
            }

            $separator = ' := ';
            $message = $timestamp . $separator . $message;
            $wasWritten = $this->LogFile->fwrite($message);
            if (!isset($wasWritten)) {
                return false;
            }
            return true;
        }

    }

    // End FileLogger

The problem is that the test passes and I can tell by the code coverage generated by the test that isWritable() returns true and SplFileObject::fwrite() on a readonly object returns a non-null value as well.

The really, really weird part of this is that the very same code ran in a non-unit test example fails, just as it should.

$logFile = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
$SplFile = new \SplFileObject($logFile);
$Logger = new \libs\sprayfire\logger\FileLogger($SplFile);

Running this from index.php results in xdebug showing an uncaught InvalidArgumentException from FileLogger with the expected message that the file passed is not writable. This is completely baffling, the same exact code is being ran in both situations, yet the code inside the unit test is "failing" and the non-unit tested code is performing as expected.


  1. Yes, the file exists. SplFileObject would throw an exception if it didn't.
  2. The very exact same code is being ran in both situations, other code that is being ran includes setting up 2 constants, a file directory and a shortcut to DIRECTORY_SEPARATOR, and setting up class autoloading. But, again, this is happening exactly the same in both situations and would result in a failure long before this unit test is actually ran.
  3. Help!

Looking at it now the problem seems relatively simple. PHP is running under the _www user and phpunit is running as the user that installed it. These users have different permissions, which makes perfect sense. If you somehow are encountering this problem I suggest you look at edorian's answer and re-evaluate how you are writing your unit tests.

  • 写回答

1条回答 默认 最新

  • douyi6818 2011-12-26 20:34
    关注

    First off:

    For unit testing there is SplTempFileObject extends SplFileObject.

    You usually don't need to create real files on the disk for that as it's slow anyways ;)

    For an isReadable / isWriteable check in your phpunit tests you generally create ether create non readable / writeable files on the disk or use vfsStreamWrapper where applicable. It also works with SplFileObject.

    Our issue:

    In the test the last line should be removed. You except the exception during construct so let's make away with that ;)

    What I found strange is that you have an absolute path there. Does your root folder structure really start with '/Library/WebServer/Documents/ ? Mainly I'm confused because that would imply that your tests are in a "WebServer" directory. Anyways.. moving on:

    "Works for me"

    Attached is a standalone version of the test that works and throws the exception like expected.

    Apart from telling you that the problem seem to be somewhere else in your setup I don't see much I can do here. Maybe try the InvalidArgumentException without the / or try the test in isolation / with a newly created file.

    PHPUnit doesn't interfere with the file handling functions so I'm out of idea apart from that. Does the sample code below work for you? :)

    phpunit mep.php 
    PHPUnit 3.6.5 by Sebastian Bergmann.
    
    .
    
    Time: 1 second, Memory: 5.00Mb
    
    OK (1 test, 1 assertion)
    
    <?php
    
    class FileLoggerTest extends PHPUnit_Framework_TestCase {
    
        /**
         * @expectedException InvalidArgumentException
         */
        public function testReadOnlyFileObjectFailure() {
            $file = __DIR__."/_files/test-log.txt";
            touch($file);
            chmod($file, 0444);
            $LogFile = new \SplFileObject($file);
            $Logger = new FileLogger($LogFile);
        }
    
    }
    
    
    class FileLogger {
    
        protected $LogFile;
        public function __construct(SplFileObject $LogFile) {
            $this->LogFile = $LogFile;
            $this->throwExceptionIfFileNotWritable();
        }
    
        /**
         * @throws InvalidArgumentException
         */
        protected function throwExceptionIfFileNotWritable() {
            $isWritable = $this->LogFile->isWritable();
            if (!$isWritable) {
                throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
            }
        }
    
        /**
         * @param $timestamp A formatted timestamp string
         * @param $message The message string to log
         * @return boolean true if the message was logged, false if it wasn't
         */
        public function log($timestamp, $message) {
            if (!isset($timestamp) || empty($timestamp)) {
                $timestamp = 'No timestamp given';
            }
    
            if (!isset($message) || empty($message)) {
                $message = 'Attempting to log an empty message';
            }
    
            $separator = ' := ';
            $message = $timestamp . $separator . $message;
            $wasWritten = $this->LogFile->fwrite($message);
            if (!isset($wasWritten)) {
                return false;
            }
            return true;
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 请教:如何用postman调用本地虚拟机区块链接上的合约?
  • ¥15 为什么使用javacv转封装rtsp为rtmp时出现如下问题:[h264 @ 000000004faf7500]no frame?
  • ¥15 乘性高斯噪声在深度学习网络中的应用
  • ¥15 运筹学排序问题中的在线排序
  • ¥15 关于docker部署flink集成hadoop的yarn,请教个问题 flink启动yarn-session.sh连不上hadoop,这个整了好几天一直不行,求帮忙看一下怎么解决
  • ¥15 深度学习根据CNN网络模型,搭建BP模型并训练MNIST数据集
  • ¥15 C++ 头文件/宏冲突问题解决
  • ¥15 用comsol模拟大气湍流通过底部加热(温度不同)的腔体
  • ¥50 安卓adb backup备份子用户应用数据失败
  • ¥20 有人能用聚类分析帮我分析一下文本内容嘛