Logo Deutsch Contact
Who wound the clock?

Who wound the clock?

PHPUnit prints information about resource consumption when it finished running a test suite. But how does PHPUnit measure the elapsed time?

In the past, PHPUnit, more precisely the phpunit/php-timer component it depends on, used to call microtime(true) at the point in time the output shown below is printed.

Time: 00:02.204, Memory: 26.00 MB

microtime(true) returns the number of seconds since the Unix epoch with microsecond granularity. Since PHP sets the superglobal variable $_SERVER['REQUEST_TIME_FLOAT'] to the number of seconds since the Unix epoch with microsecond granularity at the beginning of a script's execution, you can use microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'] to get the time since the script execution started.

Over the years, there have been repeated bug reports that PHPUnit either does not show or shows a wrong elapsed time at the end. Often PHP would print the following error message:

PHP Notice: Undefined index: REQUEST_TIME_FLOAT

The code of phpunit/php-timer tries to access the variable $_SERVER['REQUEST_TIME_FLOAT'] . However, since that is not set the error shown above will occur.

Just because $_SERVER['REQUEST_TIME_FLOAT'] is set by PHP at the beginning of script execution does not mean that it is not variable. Nothing prevents the test code or the production code from using statements such as unset($_SERVER['REQUEST_TIME_FLOAT']); to delete the variable.

If a statement like unset($_SERVER['REQUEST_TIME_FLOAT']); is really necessary, you can annotate this test with @backupGlobals enabled . This annotation instructs PHPUnit to create a copy of all global and superglobal variables before the test is run and to use this backup to restore the original values afterwards.

Some time ago I changed the code of phpunit/php-timer so that an exception is raised when $_SERVER['REQUEST_TIME_FLOAT'] is not set. Of course this does not solve the actual problem, but it is at least cleaner than the PHP notice that was triggered before. Of course, now I started receiving bug reports that PHPUnit throws this exception at the end.

Recently there have been bug reports which are caused by $_SERVER['REQUEST_TIME_FLOAT'] not containing a value of type float . This happens when either the test code or the production code set $_SERVER['REQUEST_TIME_FLOAT'] to an invalid value. I have therefore extended the code of phpunit/php-timer to throw an exception if $_SERVER['REQUEST_TIME_FLOAT'] contains a value that is not of type float .

But what happens if test code or production code set $_SERVER['REQUEST_TIME_FLOAT'] to a value that has the correct type ( float ), but does not represent the actual current time? phpunit/php-timer cannot detect this and will calculate the elapsed time incorrectly. The same applies if test code or production code change the actual system time.

It was time to realize that "looking at the clock", especially through a superglobal variable such as $_SERVER['REQUEST_TIME_FLOAT'] is not a good idea. By the way, this does not only apply to showing the elapsed time at the end of the test suite's execution, but also to measuring how long individual tests take. This information is, for instance, reported in the TestDox output or in the XML logfiles.

So if we cannot trust $_SERVER['REQUEST_TIME_FLOAT'] and microtime() then how are we supposed to measure time? The answer is hrtime() .

hrtime() was introduced in PHP 7.3. The current version of PHPUnit requires PHP 7.3: perfect! I am allowed to use hrtime() .

PHPUnit 9.2, which was released today, now uses phpunit/php-timer in version 4.0 which has been rewritten from scratch. PHPUnit no longer uses $_SERVER['REQUEST_TIME_FLOAT'] and microtime() .

Instead, hrtime() is called once when the CLI test runner starts. hrtime() is called again at the end of the test run. The difference between the two return values of hrtime() is the elapsed time that is printed. Before and after each test, hrtime() is called. The difference between these two return values of hrtime() is the duration of the respective test.