Skip to content

Commit

Permalink
Merge pull request #41 : Add trap()->stackTrace()
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Jan 28, 2024
2 parents a7c15c3 + fe1a61a commit 1377b13
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 34 deletions.
29 changes: 17 additions & 12 deletions src/Client/TrapHandle.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ public function if(bool|callable $condition): self
return $this;
}

/**
* Add stack trace to the dump.
*/
public function stackTrace(): self
{
$cwd = \getcwd();
$this->values['Stack trace'] = [
'cwd' => $cwd,
'trace' => new TraceStub($this->staticState->stackTrace),
];

return $this;
}

/**
* Set max depth for the dump.
*
Expand All @@ -61,7 +75,7 @@ public function depth(int $depth): self
* The counter isn't incremented if the dump is not sent (any other condition is not met).
* It might be useful for debugging in loops, recursive or just multiple function calls.
*
* @param positive-int $times
* @param positive-int $times Zero means no limit.
* @param bool $fullStack If true, the counter is incremented for each stack trace, not for the line.
*/
public function times(int $times, bool $fullStack = false): self
Expand Down Expand Up @@ -102,16 +116,6 @@ private function sendDump(): void
$_SERVER['VAR_DUMPER_SERVER'] = '127.0.0.1:9912';
}

// If there are no values - stack trace
if ($this->values === []) {
VarDumper::dump([
'cwd' => \getcwd(),
// todo StackTrace::stackTrace(\getcwd()) - add CWD
'trace' => new TraceStub($this->staticState->stackTrace),
], depth: $this->depth);
return;
}

// Dump single value
if (\array_keys($this->values) === [0]) {
VarDumper::dump($this->values[0], depth: $this->depth);
Expand Down Expand Up @@ -140,11 +144,12 @@ private function __construct(

private function haveToSend(): bool
{
if (!$this->haveToSend) {
if (!$this->haveToSend || $this->values === []) {
return false;
}

if ($this->times > 0) {
\assert($this->timesCounterKey !== '');
return Counter::checkAndIncrement($this->timesCounterKey, $this->times);
}

Expand Down
14 changes: 13 additions & 1 deletion src/Client/TrapHandle/Counter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@

namespace Buggregator\Trap\Client\TrapHandle;

/**
* Static counter for {@see TrapHandle::once()} and {@see TrapHandle::times()} methods.
*
* @internal
* @psalm-internal Buggregator\Trap\Client
*/
final class Counter
{
/** @var array<string, int<0, max>> */
/** @var array<non-empty-string, int<0, max>> */
private static array $counters = [];

/**
* Returns true if the counter of related stack trace is less than $times. In this case, the counter is incremented.
*
* @param non-empty-string $key
* @param int<0, max> $times
*/
public static function checkAndIncrement(string $key, int $times): bool
Expand All @@ -25,4 +32,9 @@ public static function checkAndIncrement(string $key, int $times): bool

return false;
}

public static function clear(): void
{
self::$counters = [];
}
}
27 changes: 15 additions & 12 deletions src/Sender/Frontend/Http/StaticFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ public function handle(ServerRequestInterface $request, callable $next): Respons
}

if (\preg_match('#^/((?:[a-zA-Z0-9\\-_]+/)*[a-zA-Z0-9.\\-\\[\\]() _]+?\\.([a-zA-Z0-4]++))$#', $path, $matches)) {
$file = \sprintf("%s/resources/frontend/%s", Info::TRAP_ROOT, $matches[1]);
$file = \sprintf('%s/resources/frontend/%s', Info::TRAP_ROOT, $matches[1]);

/** @var array<non-empty-string, string> $cache */
static $cache = [];
/** @var array<non-empty-string, string> $cacheContent */
static $cacheContent = [];
/** @var array<non-empty-string, non-empty-string> $cacheHash */
static $cacheHash = [];
/** @var array<non-empty-string, int<0, max>> $cacheSize */
static $cacheSize = [];

if (!\array_key_exists($file, $cache) && !\is_file($file)) {
if (!\array_key_exists($file, $cacheContent) && !\is_file($file)) {
return new Response(404);
}

$content = null;
$headers = [];

$type = match($matches[2]) {
Expand All @@ -54,11 +57,11 @@ public function handle(ServerRequestInterface $request, callable $next): Respons

if ($path === '/index.html') {
if (empty($this->earlyResponse)) {
$cache[$file] ??= \file_get_contents($file);
$cacheContent[$file] ??= \file_get_contents($file);
// Find all CSS files
\preg_match_all(
'#\\bhref="([^"]+?\\.css)"#i',
$cache[$file],
$cacheContent[$file],
$matches,
);
$this->earlyResponse = \array_unique($matches[1]);
Expand All @@ -74,18 +77,18 @@ public function handle(ServerRequestInterface $request, callable $next): Respons
// (new \Buggregator\Trap\Support\Timer(2))->wait(); // to test early hints
}

$cache[$file] ??= \file_get_contents($file);

$cacheContent[$file] ??= \file_get_contents($file);
return new Response(
200,
[
'Content-Type' => [$type],
'Content-Length' => [\filesize($file)],
'Content-Length' => [$cacheSize[$file] ??= \filesize($file)],
'Date' => [\gmdate('D, d M Y H:i:s T')],
'Cache-Control' => ['max-age=604801'],
'ETag' => [\sha1($cache[$file])],
'Accept-Ranges' => ['none'],
'ETag' => [$cacheHash[$file] ??= \sha1($cacheContent[$file])],
] + $headers,
$cache[$file] ??= \file_get_contents($file),
$cacheContent[$file],
);
}

Expand Down
15 changes: 7 additions & 8 deletions tests/Unit/Client/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@

namespace Buggregator\Trap\Tests\Unit\Client;

use Buggregator\Trap\Client\TrapHandle\Counter;
use Buggregator\Trap\Client\TrapHandle\Dumper;
use Closure;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\DataDumperInterface;

class Base extends TestCase
{
protected static Data $lastData;
protected static ?Closure $handler = null;
protected static ?Data $lastData = null;

protected function setUp(): void
{
$cloner = new VarCloner();
/** @psalm-suppress InvalidArgument */
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
self::$lastData = null;
Counter::clear();
$dumper = $this->getMockBuilder(DataDumperInterface::class)
->getMock();
$dumper->expects($this->once())
$dumper->expects($this->any())
->method('dump')
->with(
$this->callback(static function (Data $data): bool {
Expand All @@ -41,6 +38,8 @@ protected function setUp(): void

protected function tearDown(): void
{
self::$lastData = null;
Counter::clear();
Dumper::setDumper(null);
parent::tearDown();
}
Expand Down
24 changes: 24 additions & 0 deletions tests/Unit/Client/FunctionTrapTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Tests\Unit\Client;

use PHPUnit\Framework\TestCase;

final class FunctionTrapTest extends TestCase
{
/**
* @runInSeparateProcess
*/
public function testLeak(): void
{
$object = new \stdClass();
$ref = \WeakReference::create($object);

\trap($object, $object);
unset($object);

$this->assertNull($ref->get());
}
}
58 changes: 57 additions & 1 deletion tests/Unit/Client/TrapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,70 @@ public function testLabel(): void
$this->assertSame('FooName', static::$lastData->getContext()['label']);
}

/**
* Check the first line of dumped stacktrace string contains right file and line.
*/
public function testStackTrace(): void
{
$line = __FILE__ . ':' . __LINE__ and trap();
$line = __FILE__ . ':' . __LINE__ and trap()->stackTrace();

$this->assertArrayHasKey('trace', static::$lastData->getValue());

$neededLine = \array_key_first(static::$lastData->getValue()['trace']->getValue());

$this->assertStringContainsString($line, $neededLine);
}

/**
* Nothing is dumped if no arguments are passed to {@see trap()}.
*/
public function testEmptyTrapCall(): void
{
trap();

self::assertNull(self::$lastData);
}

/**
* After calling {@see trap()} the dumped data isn't stored in the memory.
*/
public function testLeak(): void
{
$object = new \stdClass();
$ref = \WeakReference::create($object);

\trap($object, $object);
unset($object);

$this->assertNull($ref->get());
}

public function testTrapOnce(): void
{
foreach ([false, true, true, true, true] as $isNull) {
\trap(42)->once();
self::assertSame($isNull, static::$lastData === null);
static::$lastData = null;
}
}

public static function provideTrapTimes(): iterable
{
yield 'no limit' => [0, [false, false, false, false, false]];
yield 'once' => [1, [false, true, true, true, true, true]];
yield 'twice' => [2, [false, false, true, true, true]];
yield 'x' => [10, [false, false, false, false, false, false, false, false, false, false, true, true, true]];
}

/**
* @dataProvider provideTrapTimes
*/
public function testTrapTimes(int $times, array $sequence): void
{
foreach ($sequence as $isNull) {
\trap(42)->times($times);
self::assertSame($isNull, static::$lastData === null);
static::$lastData = null;
}
}
}

0 comments on commit 1377b13

Please sign in to comment.