Question

I have a File class, with a ->deleteOnExit function. If this function is called, the file will delete itself on shutdown. The File class uses the register_shutdown_function function to achieve this, deleting the file when the shutdown handler is ran. Question is, how can i test this in PHPUnit. I tried this

$file = new File(__DIR__ . "/delete_on_exit.txt");
$file->createFile();
$file->deleteOnExit();
register_shutdown_function(function() use ($file) {
    $this->assertFalse($file->exists());
});

But that did not work. I guess that is because PHPUnit is already done with its testing by the time the function registered with register_shutdown_function is called.

Is there a way to do this?

Was it helpful?

Solution

You should probably use tmpfile() (documentation) internally instead of deleting the file with a shutdown function.

Also, the way PHPUnit works, you cannot test proper shutdown deletion. PHPUnit tests run in a single huge "application" - any shutdown function will only ever be called when the last test finishes and any other tasks like generating code coverage have been done. At this time, it is way too late for your test to report that the file indeed has been deleted.

Additionally, because of this I'd have my doubts whether or not your code will reliably delete the file, because any other registered shutdown function that simply calls exit() would prevent your shutdown function to delete the file. All these problems seem to not exist when using tmpfile(), because that is a OS supported call that will delete the file if the process opening it dies for whatever reason.

OTHER TIPS

For the general purpose of unit testing php_register_callback_function, this is my hack :

<?php

namespace PAG\Testing;


class BlackboxedScriptRunner
{
    public static $php = "/usr/bin/php";

    public static function fetchScriptStdout($script): string
    {
        return self::executeFile($script, ' 2>/dev/null');
    }

    private static function executeFile($script, $option): string
    {
        if (php_sapi_name() !== "cli")
            throw new RuntimeException("Cannot use this function in this setting for security reasons");
        return shell_exec(self::$php ." -f $script -- $option");
    }

    public static function fetchScriptStderr($script): string
    {
        return self::executeFile($script, ' 2>&1 > /dev/null');
    }
}

and the test :

<?php

use PAG\Testing\BlackboxedScriptRunner;
use PHPUnit\Framework\TestCase;

class ShutdownEventHandlerTest extends TestCase
{
    public function testRegisterShutdownHandler()
    {
        $output =
            BlackboxedScriptRunner::fetchScriptStdout(__DIR__ . "/../ManualTesting/testRegisterShutdownHandler.php");

        $this->assertEquals(
            file_get_contents(__DIR__ . "/../ManualTesting/testRegisterShutdownHandler.txt"),
            $output);
    }
}

Now I'm not saying this is awesome, I am saying this is awesome, I am saying it allows you to check the output of a whole script.

Warning : this is worse than eval, do not use for other purposes than testing.

And I am in a joyful mood, so here is my so called RegisterShutdownHandler :

<?php

namespace PAG\Shutdown;


class ShutdownEventHandler
{
    private static $shutdown;
    private static $shutdown_error;
    private static $initialized = false;
    private static $context     = [];

    public static function registerShutdownHandler($identifier, $function)
    {
        self::ensureInitialization();
        self::$shutdown[$identifier] = $function;
    }

    private static function ensureInitialization(): void
    {
        if (!self::$initialized) {
            self::$initialized = true;
            self::registerShutdown();
        }
    }

    private static function registerShutdown(): void
    {
        register_shutdown_function(function () {
            self::shutdown();
        });
    }

    private static function shutdown()
    {
        if (!is_null($error = error_get_last())) {
            self::runCallbacks(self::$shutdown_error, $error);
        }
        self::runCallbacks(self::$shutdown);
    }

    private static function runCallbacks($array, $arguments = []): void
    {
        foreach ($array as $function) {
            call_user_func($function, self::$context, $arguments);
        }
    }

    public static function registerErrorShutdownHandler($identifier, $function)
    {
        self::ensureInitialization();
        self::$shutdown_error[$identifier] = $function;
    }

    public static function deleteShutdownHandler($identifier)
    {
        unset(self::$shutdown[$identifier]);
    }

    public static function deleteErrorShutdownHandler($identifier)
    {
        unset(self::$shutdown_error[$identifier]);
    }

    public static function setContext(array $context): void
    {
        self::$context = $context;
    }

}

You can find more of that on github. Must be frank though, it still has poor coverage.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top