Search code examples
cakephpphpunitcakephp-3.0unlink

CakePHP 3 - Unit Test tearDown() failing to remove file: 'resource temporarily unavailable'


In my application users can upload files and other users can download them. As a part of my controller integration test I move a couple of test files into the upload directory so I can test the download action.

The moving of files in setUp() and removing them in teardown() works, as long as I don't touch the files in a test. When the files were returned by a test, the tearDown() is failing to remove them, I get the error:

Warning Error: unlink(C:\xampp\htdocs\2deal\keys_test\1_open.key): Resource temporarily unavailable

How can I fix this?

  • I tried opening and closing the file inside tearDown(), but to no effect. Interestingly the fopen() function succeeds, so the file isn't actually locked.
  • I also found this similar question, where the solution was removing the object using the file. So I added unset($this->_response); to the top of tearDown(), but again to no avail.
  • And I tried adding sleep(10); add the start of tearDown(), which also made no difference. So it's not a timing issue.

The entire test file looks like this:

    /**
     * setUp method before each test
     */
    public function setUp()
    {
        Configure::write('Keys.path', 'keys_test');

        // Config writing must happen before parent::setUp()
        parent::setUp();

        $files = [
            '1_open.key',
            '1_close.key',
            '2_open.key',
            '2_close.key'
        ];
        foreach ($files as $file)
        {
            copy(self::SOURCE_DIR . $file, ROOT . DS . Configure::read('Keys.path') . DS . $file);
        }
    }

    /**
     * tearDown method after each test
     */
    public function tearDown()
    {
        parent::tearDown();

        // Clear test upload directory
        $files = glob(ROOT . DS . Configure::read('Keys.path') . DS . '*');

        foreach ($files as $file)
        {

            if (is_file($file))
            {
                \unlink($file); // Delete file
            }
        }
    }

    /**
     * Test getFile method
     * 
     * @return void
     */
    public function testGetFile()
    {
        $this->get('/carkeys/get-file/1/open.json');

        $file = $this->_response->getFile();

        $real_file = self::SOURCE_DIR . '1_open.key';

        $this->assertResponseOk();
        $this->assertFileEquals($file->path, $real_file);
    }

Solution

  • The file isn't locked, but there's an open handle for it.

    As of CakePHP 3.4, the response uses streams for the response body. It creates the streams when attaching body contents or files, and the stream is being opened immediately for writing or reading.

    Long story short, close the stream before trying to delete the file:

    $this->_response->getBody()->close();