Skip to content

Commit f3fd611

Browse files
committed
test: unit test the debug timer
1 parent 441e23f commit f3fd611

File tree

3 files changed

+58
-87
lines changed

3 files changed

+58
-87
lines changed

src/Debug/Timer.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
<?php
22
namespace Gt\WebEngine\Debug;
33

4+
use Closure;
5+
6+
/**
7+
* Debug timer for measuring elapsed time within the request lifecycle.
8+
*
9+
* Behavior:
10+
* - On construction, records the start time.
11+
* - Call stop() to record the end time.
12+
* - Call getDelta() to retrieve the elapsed time in seconds (float).
13+
*
14+
* Testability:
15+
* - Accepts an optional Closure $timeGetter to supply the current time.
16+
* - By default, this calls microtime(true).
17+
*/
418
class Timer {
519
private float $startTime;
620
private float $endTime;
21+
private Closure $timeGetter;
722

8-
public function __construct() {
9-
$this->startTime = microtime(true);
23+
public function __construct(?Closure $timeGetter = null) {
24+
$this->timeGetter = $timeGetter ?? fn() => microtime(true);
25+
$this->startTime = ($this->timeGetter)();
1026
}
1127

1228
public function stop():void {
13-
$this->endTime = microtime(true);
29+
$this->endTime = ($this->timeGetter)();
1430
}
1531

1632
public function getDelta():float {

src/Redirection/Redirect.php

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -10,95 +10,12 @@
1010
* project root, applied before the page lifecycle begins. It is designed to be
1111
* simple to maintain, friendly for editors, and safe by default.
1212
*
13-
* Contents
14-
* - Overview
15-
* - Quick start
16-
* - Redirect file formats (INI, CSV, TSV)
17-
* - Regex rules (nginx-style)
18-
* - Status codes and validation
19-
* - Exceptions and error messages
20-
* - Programmatic API (Redirect, RedirectUri)
21-
* - How loading and matching works
22-
*
23-
* Overview
2413
* Place a file named one of the following in your project root:
2514
* - redirect.ini
2615
* - redirect.csv
2716
* - redirect.tsv
2817
* The Redirect class automatically discovers and loads the single redirect file
29-
* using the glob pattern `redirect.{csv,tsv,ini}`. If more than one file
30-
* matches, a RedirectException is thrown, as multiple sources would be
31-
* ambiguous.
32-
*
33-
* Quick start
34-
* 1) Create redirect.ini in your project root:
35-
* /old-path=/new-path
36-
* 2) Start your application (Application calls Redirect->execute()). Requests to
37-
* /old-path will be redirected to /new-path with HTTP 307 by default.
38-
*
39-
* Redirect file formats
40-
* - INI
41-
* - Flat (no sections): each line `oldURI=newURI` uses the default status 307.
42-
* - Sectioned: use numeric status code sections, e.g.:
43-
* [301]
44-
* /moved=/new-location
45-
* [303]
46-
* /see-other=/somewhere
47-
* - Section names must be numeric between 301 and 308 inclusive; any
48-
* non-numeric section causes a RedirectException.
49-
*
50-
* - CSV
51-
* - Columns: oldURI,newURI[,status]
52-
* - If the third column is present it must be numeric (301–308); otherwise a
53-
* RedirectException is thrown. If omitted, 307 is used.
54-
*
55-
* - TSV
56-
* - Same as CSV but tab-delimited: oldURI \t newURI [\t status]
57-
* - Same validation rules as CSV apply.
58-
*
59-
* Regex rules (nginx-style)
60-
* - To define a regex match for the old URI, prefix the pattern with `~`.
61-
* Example that maps both /shop/cat/item and /shop/dog/thing:
62-
* ~^/shop/([^/]+)/(.+)$=/newShop/$1/$2 (INI)
63-
* ~^/shop/([^/]+)/(.+)$,/newShop/$1/$2,302 (CSV)
64-
* The replacement may reference capture groups using $1, $2, etc.
65-
* - All URIs begin with `/`, so patterns should be anchored appropriately.
66-
* - Invalid regex patterns result in a RedirectException.
67-
*
68-
* Status codes and validation
69-
* - Default status code: 307.
70-
* - Allowed range: 301–308 (inclusive). Values are validated and normalised.
71-
* - For INI with sections, the section number is the status code; for CSV/TSV,
72-
* the optional third column is the status.
73-
* - Non-numeric INI sections and non-numeric third columns in CSV/TSV throw a
74-
* RedirectException with a message describing the invalid value.
75-
*
76-
* Exceptions and error messages
77-
* - Multiple redirect files: "Multiple redirect files in project root".
78-
* - Invalid HTTP status code read from a file: "Invalid HTTP status code in
79-
* redirect file: <value>".
80-
* - Invalid regex pattern: message explains which pattern failed.
81-
*
82-
* Programmatic API
83-
* - new Redirect(string $glob = "redirect.{csv,tsv,ini}", ?Closure $handler = null)
84-
* - $glob allows overriding the discovery pattern/location.
85-
* - $handler receives (string $uri, int $code). If omitted, the default
86-
* implementation sends a Location header and code.
87-
* - execute(string $uri = "/"): void
88-
* - Looks up the given $uri and, if a redirect exists, invokes the handler.
89-
* - getRedirectUri(string $oldUri): ?RedirectUri
90-
* - Returns RedirectUri with target uri and status code, or null if no match.
91-
* - RedirectUri
92-
* - Value object with public string $uri and public int $code.
93-
*
94-
* How loading and matching works
95-
* - Redirect determines the loader from the file extension (INI, CSV, TSV) and
96-
* populates an internal RedirectMap.
97-
* - Literal rules and regex rules are both supported. Matching prefers literal
98-
* rules first; if no literal match is found, regex rules are evaluated in a
99-
* deterministic order.
100-
* - Only a single redirect file may exist; if none is found, getRedirectUri
101-
* returns null and execute does nothing.
18+
* using the glob pattern `redirect.{csv,tsv,ini}`.
10219
*/
10320
class Redirect {
10421
private ?string $redirectFile = null;

test/phpunit/Debug/TimerTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
namespace Gt\WebEngine\Test\Debug;
3+
4+
use Gt\WebEngine\Debug\Timer;
5+
use PHPUnit\Framework\TestCase;
6+
7+
class TimerTest extends TestCase {
8+
public function testDeltaWithInjectedTimeGetter():void {
9+
$times = [100.0, 102.5];
10+
$index = 0;
11+
$timeGetter = function() use (&$times, &$index) {
12+
$value = $times[$index] ?? end($times);
13+
$index++;
14+
return $value;
15+
};
16+
17+
$sut = new Timer($timeGetter);
18+
$sut->stop();
19+
self::assertSame(2.5, $sut->getDelta());
20+
}
21+
22+
public function testStopMultipleTimesUsesLatestEndTime():void {
23+
$times = [10.0, 15.0, 20.0];
24+
$index = 0;
25+
$timeGetter = function() use (&$times, &$index) {
26+
$value = $times[$index] ?? end($times);
27+
$index++;
28+
return $value;
29+
};
30+
31+
$sut = new Timer($timeGetter);
32+
$sut->stop();
33+
self::assertSame(5.0, $sut->getDelta());
34+
35+
$sut->stop();
36+
self::assertSame(10.0, $sut->getDelta());
37+
}
38+
}

0 commit comments

Comments
 (0)