diff --git a/.github/.release-please-config.json b/.github/.release-please-config.json
index afcbefa3..e7ce35ac 100644
--- a/.github/.release-please-config.json
+++ b/.github/.release-please-config.json
@@ -1,4 +1,5 @@
{
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "php",
"packages": {
".": {
@@ -7,6 +8,8 @@
}
},
"include-component-in-tag": false,
+ "include-v-in-tag": false,
+ "changelog-type": "github",
"changelog-sections": [
{
"type": "feat",
diff --git a/.github/phar/keys.asc.gpg b/.github/phar/keys.asc.gpg
index fde9a21b..dbe42e96 100644
Binary files a/.github/phar/keys.asc.gpg and b/.github/phar/keys.asc.gpg differ
diff --git a/.github/workflows/build-phar-release.yml b/.github/workflows/build-phar-release.yml
index bc483416..93cd5873 100644
--- a/.github/workflows/build-phar-release.yml
+++ b/.github/workflows/build-phar-release.yml
@@ -91,37 +91,13 @@ jobs:
run: rm ${{ env.GPG_KEYS }}
- name: 📤 Upload release assets
- uses: actions/github-script@v7.0.1
+ uses: softprops/action-gh-release@v2.0.5
+ if: startsWith(github.ref, 'refs/tags/')
with:
- github-token: "${{ secrets.GITHUB_TOKEN }}"
- script: |
- const fs = require("fs");
-
- const files = [
- {
- name: "trap.phar",
- path: process.env.TRAP_PHAR,
- },
- {
- name: "trap.phar.asc",
- path: process.env.TRAP_PHAR_SIGNATURE,
- },
- ];
-
- for (const file of files) {
- try {
- await github.rest.repos.uploadReleaseAsset({
- data: fs.readFileSync(file.path),
- name: file.name,
- origin: process.env.RELEASE_UPLOAD_URL,
- owner: context.repo.owner,
- release_id: process.env.RELEASE_ID,
- repo: context.repo.repo,
- });
- } catch (error) {
- core.setFailed(error.message);
- }
- }
+ token: "${{ secrets.TRAP_RELEASE_TOKEN }}"
+ files: |
+ ${{ env.TRAP_PHAR }}
+ ${{ env.TRAP_PHAR_SIGNATURE }}
notify-discord:
runs-on: ubuntu-latest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4ad27e3..c7a3c3dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
# Changelog
+## 1.8.0 (2024-05-29)
+
+## What's Changed
+* Add tr() and td() functions by @roxblnfk in https://github.com/buggregator/trap/pull/102
+* Add `trap()->code()` sugar by @lee-to in https://github.com/buggregator/trap/pull/103
+* Change release generation type to Github API by @lotyp in https://github.com/buggregator/trap/pull/106
+
+## New Contributors
+* @lee-to made their first contribution in https://github.com/buggregator/trap/pull/103
+
+**Full Changelog**: https://github.com/buggregator/trap/compare/v1.7.5...v1.8.0
+
+## [1.7.5](https://github.com/buggregator/trap/compare/v1.7.4...v1.7.5) (2024-05-24)
+
+
+### Bug Fixes
+
+* fix environments in build-phar-release ([453e522](https://github.com/buggregator/trap/commit/453e522b6c49c6e9d54cd6256137c8f25925939f))
+
+## [1.7.4](https://github.com/buggregator/trap/compare/v1.7.3...v1.7.4) (2024-05-24)
+
+
+### Bug Fixes
+
+* release asset upload ([#99](https://github.com/buggregator/trap/issues/99)) ([b4616c5](https://github.com/buggregator/trap/commit/b4616c52056cd1803b2d3990178577537a694147))
+* suppress warnings if Closure::bind() is run with static callable ([3d72a7e](https://github.com/buggregator/trap/commit/3d72a7ef551bd2f21b0935826e8093a58da0b774))
+
## [1.7.3](https://github.com/buggregator/trap/compare/v1.7.2...v1.7.3) (2024-05-24)
diff --git a/README.md b/README.md
index b04f4254..1f293b09 100644
--- a/README.md
+++ b/README.md
@@ -131,10 +131,13 @@ Then just call the `trap()` function in your code:
```php
// dump the current stack trace
trap()->stackTrace();
+
// dump a variable with a depth limit
trap($var)->depth(4);
+
// dump a named variables sequence
trap($var, foo: $far, bar: $bar);
+
// dump a variable and return it
$responder->respond(trap($response)->return());
```
@@ -143,6 +146,27 @@ $responder->respond(trap($response)->return());
> The `trap()` function configures `$_SERVER['REMOTE_ADDR']` and `$_SERVER['REMOTE_PORT']` automatically,
> if they are not set.
+Also, there are a couple of shortcuts here:
+
+- `tr(...)` - equivalent to `trap(...)->return()`
+- `td(...)` - equivalent to `trap(...); die;`
+
+If called without arguments, they will display a short stack trace, used memory, and time between shortcut calls.
+
+```php
+function handle($input) {
+ tr(); // Trace #0 -.--- 3.42M
+
+ $data = $this->prepareData($input);
+
+ tr(); // Trace #1 0.015ms 6.58M
+
+ $this->processor->process(tr(data: $data));
+
+ td(); // exit with output: Trace #2 1.15ms 7.73M
+}
+```
+
### Default port
Trap automatically recognizes the type of traffic.
diff --git a/composer.json b/composer.json
index 6e55ed2d..6e3dd385 100644
--- a/composer.json
+++ b/composer.json
@@ -108,20 +108,35 @@
"sort-packages": true
},
"scripts": {
- "cs:diff": "php vendor/bin/php-cs-fixer fix --dry-run -v --diff",
- "cs:fix": "php vendor/bin/php-cs-fixer fix -v",
- "infect": "XDEBUG_MODE=coverage php vendor/bin/roave-infection-static-analysis-plugin --configuration=infection.json.dist",
- "infect:ci": "XDEBUG_MODE=coverage php vendor/bin/roave-infection-static-analysis-plugin --ansi --configuration=infection.json.dist --logger-github --ignore-msi-with-no-mutations --only-covered",
- "psalm": "php vendor/bin/psalm --show-info=true",
- "psalm:baseline": "php vendor/bin/psalm --set-baseline=psalm-baseline.xml",
- "psalm:ci": "php vendor/bin/psalm --output-format=github --shepherd --show-info=false --stats --threads=4",
- "refactor": "php vendor/bin/rector process --config=rector.php",
- "refactor:ci": "php vendor/bin/rector process --config=rector.php --dry-run --ansi",
- "stan": "php vendor/bin/phpstan analyse --memory-limit=2G",
- "stan:baseline": "php vendor/bin/phpstan analyse --generate-baseline --memory-limit=2G --allow-empty-baseline",
- "stan:ci": "php vendor/bin/phpstan analyse --memory-limit=2G --error-format=github",
- "test": "XDEBUG_MODE=coverage php vendor/bin/pest --exclude-group=phpunit-only --color=always",
- "test:cc": "XDEBUG_MODE=coverage php vendor/bin/pest --coverage --coverage-clover=.build/phpunit/logs/clover.xml --exclude-group=phpunit-only --color=always",
- "test:sep": "XDEBUG_MODE=coverage php vendor/bin/phpunit --group=phpunit-only --color=always --exclude-testsuite=Arch"
+ "cs:diff": "php-cs-fixer fix --dry-run -v --diff",
+ "cs:fix": "php-cs-fixer fix -v",
+ "infect": [
+ "@putenv XDEBUG_MODE=coverage",
+ "roave-infection-static-analysis-plugin --configuration=infection.json.dist"
+ ],
+ "infect:ci": [
+ "@putenv XDEBUG_MODE=coverage",
+ "roave-infection-static-analysis-plugin --ansi --configuration=infection.json.dist --logger-github --ignore-msi-with-no-mutations --only-covered"
+ ],
+ "psalm": "psalm --show-info=true",
+ "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml",
+ "psalm:ci": "psalm --output-format=github --shepherd --show-info=false --stats --threads=4",
+ "refactor": "rector process --config=rector.php",
+ "refactor:ci": "rector process --config=rector.php --dry-run --ansi",
+ "stan": "phpstan analyse --memory-limit=2G",
+ "stan:baseline": "phpstan analyse --generate-baseline --memory-limit=2G --allow-empty-baseline",
+ "stan:ci": "phpstan analyse --memory-limit=2G --error-format=github",
+ "test": [
+ "@putenv XDEBUG_MODE=coverage",
+ "pest --exclude-group=phpunit-only --color=always"
+ ],
+ "test:cc": [
+ "@putenv XDEBUG_MODE=coverage",
+ "pest --coverage --coverage-clover=.build/phpunit/logs/clover.xml --exclude-group=phpunit-only --color=always"
+ ],
+ "test:sep": [
+ "@putenv XDEBUG_MODE=coverage",
+ "phpunit --group=phpunit-only --color=always --exclude-testsuite=Arch"
+ ]
}
}
diff --git a/composer.lock b/composer.lock
index d2c44918..8bd01d12 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1592,16 +1592,16 @@
},
{
"name": "composer/pcre",
- "version": "3.1.3",
+ "version": "3.1.4",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
- "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8"
+ "reference": "04229f163664973f68f38f6f73d917799168ef24"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
- "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24",
+ "reference": "04229f163664973f68f38f6f73d917799168ef24",
"shasum": ""
},
"require": {
@@ -1643,7 +1643,7 @@
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
- "source": "https://github.com/composer/pcre/tree/3.1.3"
+ "source": "https://github.com/composer/pcre/tree/3.1.4"
},
"funding": [
{
@@ -1659,7 +1659,7 @@
"type": "tidelift"
}
],
- "time": "2024-03-19T10:26:25+00:00"
+ "time": "2024-05-27T13:40:54+00:00"
},
{
"name": "composer/semver",
@@ -3388,16 +3388,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.11.1",
+ "version": "1.11.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b"
+ "reference": "0d5d4294a70deb7547db655c47685d680e39cfec"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e524358f930e41a2b4cca1320e3b04fc26b39e0b",
- "reference": "e524358f930e41a2b4cca1320e3b04fc26b39e0b",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec",
+ "reference": "0d5d4294a70deb7547db655c47685d680e39cfec",
"shasum": ""
},
"require": {
@@ -3442,7 +3442,7 @@
"type": "github"
}
],
- "time": "2024-05-15T08:00:59+00:00"
+ "time": "2024-05-24T13:23:04+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -4415,16 +4415,16 @@
},
{
"name": "react/promise",
- "version": "v3.1.0",
+ "version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
- "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c"
+ "reference": "8a164643313c71354582dc850b42b33fa12a4b63"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c",
- "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63",
+ "reference": "8a164643313c71354582dc850b42b33fa12a4b63",
"shasum": ""
},
"require": {
@@ -4476,7 +4476,7 @@
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
- "source": "https://github.com/reactphp/promise/tree/v3.1.0"
+ "source": "https://github.com/reactphp/promise/tree/v3.2.0"
},
"funding": [
{
@@ -4484,7 +4484,7 @@
"type": "open_collective"
}
],
- "time": "2023-11-16T16:21:57+00:00"
+ "time": "2024-05-24T10:39:05+00:00"
},
{
"name": "react/socket",
@@ -6379,16 +6379,16 @@
},
{
"name": "wayofdev/cs-fixer-config",
- "version": "v1.4.4",
+ "version": "v1.4.5",
"source": {
"type": "git",
"url": "https://github.com/wayofdev/php-cs-fixer-config.git",
- "reference": "8d0578a412be010919a0109cf484f95742cf5291"
+ "reference": "d38222297a12344cb968b85213878534ffffbefc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/wayofdev/php-cs-fixer-config/zipball/8d0578a412be010919a0109cf484f95742cf5291",
- "reference": "8d0578a412be010919a0109cf484f95742cf5291",
+ "url": "https://api.github.com/repos/wayofdev/php-cs-fixer-config/zipball/d38222297a12344cb968b85213878534ffffbefc",
+ "reference": "d38222297a12344cb968b85213878534ffffbefc",
"shasum": ""
},
"require": {
@@ -6453,7 +6453,7 @@
"type": "github"
}
],
- "time": "2024-05-23T14:54:07+00:00"
+ "time": "2024-05-28T13:37:07+00:00"
},
{
"name": "webmozart/assert",
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 5e64aac1..c2612d42 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -204,7 +204,7 @@
-
+
getClass()]]>
diff --git a/resources/version.json b/resources/version.json
index 7d4ac4e4..3800c069 100644
--- a/resources/version.json
+++ b/resources/version.json
@@ -1,3 +1,3 @@
{
- ".": "1.7.3"
+ ".": "1.8.0"
}
diff --git a/src/Support/Caster/EnumValue.php b/src/Client/Caster/EnumValue.php
similarity index 84%
rename from src/Support/Caster/EnumValue.php
rename to src/Client/Caster/EnumValue.php
index ad6a6f89..c3c4a109 100644
--- a/src/Support/Caster/EnumValue.php
+++ b/src/Client/Caster/EnumValue.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Buggregator\Trap\Support\Caster;
+namespace Buggregator\Trap\Client\Caster;
/**
* @internal
diff --git a/src/Support/ProtobufCaster.php b/src/Client/Caster/ProtobufCaster.php
similarity index 98%
rename from src/Support/ProtobufCaster.php
rename to src/Client/Caster/ProtobufCaster.php
index 25789d4f..05dd8f4b 100644
--- a/src/Support/ProtobufCaster.php
+++ b/src/Client/Caster/ProtobufCaster.php
@@ -2,11 +2,10 @@
declare(strict_types=1);
-namespace Buggregator\Trap\Support;
+namespace Buggregator\Trap\Client\Caster;
-use Buggregator\Trap\Support\Caster\EnumValue;
-use Google\Protobuf\Internal\Descriptor as InternalDescriptor;
use Google\Protobuf\Descriptor as PublicDescriptor;
+use Google\Protobuf\Internal\Descriptor as InternalDescriptor;
use Google\Protobuf\Internal\DescriptorPool;
use Google\Protobuf\Internal\GPBType;
use Google\Protobuf\Internal\MapField;
diff --git a/src/Client/Caster/Trace.php b/src/Client/Caster/Trace.php
new file mode 100644
index 00000000..9be5c575
--- /dev/null
+++ b/src/Client/Caster/Trace.php
@@ -0,0 +1,56 @@
+ $number The tick number.
+ * @param float $delta The time delta between the current and previous tick.
+ * @param int<0, max> $memory The memory usage.
+ * @param list
+ * }> $stack The stack trace.
+ */
+ public function __construct(
+ public readonly int $number,
+ public readonly float $delta,
+ public readonly int $memory,
+ public readonly array $stack,
+ ) {}
+
+ public function __toString(): string
+ {
+ // Format delta
+ $delta = $this->delta;
+ $deltaStr = match (true) {
+ $delta === 0.0 => '-.---',
+ $delta < 0.001 => \sprintf('+%.3fms', $delta * 1000),
+ $delta < 0.01 => \sprintf('+%.2fms', $delta * 1000),
+ $delta < 1 => \sprintf('+%.1fms', ($delta * 1000)),
+ $delta < 10 => \sprintf('+%.2fs', $delta),
+ $delta < 60 => \sprintf('+%.1fs', $delta),
+ default => \sprintf('+%dm %ds', (int) $delta % 60, (int) $delta % 60),
+ };
+
+ return \sprintf(
+ "Trace #%d %s %sM",
+ $this->number,
+ $deltaStr,
+ \number_format($this->memory / 1024 / 1024, 2, '.', '_'),
+ );
+ }
+}
diff --git a/src/Client/Caster/TraceCaster.php b/src/Client/Caster/TraceCaster.php
new file mode 100644
index 00000000..377da934
--- /dev/null
+++ b/src/Client/Caster/TraceCaster.php
@@ -0,0 +1,60 @@
+class = $tick->__toString();
+ $stub->type = $stub::TYPE_OBJECT;
+ $stub->attr = $tick->stack;
+ $stub->handle = 0;
+
+ $result = [];
+ foreach ($tick->stack as $line) {
+ $result[Caster::PREFIX_VIRTUAL . self::renderMethod($line)] = new TraceFile($line);
+ }
+
+
+ return $result;
+ }
+
+ public static function castLine(TraceFile $line, array $a, Stub $stub, bool $isNested): array
+ {
+ $stub->type = $stub::TYPE_REF;
+ $stub->attr = $line->line;
+ $stub->handle = 0;
+ $stub->class = $line->__toString();
+ return [];
+ }
+
+ /**
+ * @param array{
+ * function: string,
+ * line?: int,
+ * file?: string,
+ * class?: class-string,
+ * type?: string,
+ * args?: list
+ * } $line The stack trace line.
+ */
+ private static function renderMethod(array $line): string
+ {
+ if (!isset($line['class'])) {
+ return $line['function'] . '()';
+ }
+
+ $line['type'] ??= "::";
+
+ return "{$line['class']}{$line['type']}{$line['function']}()";
+ }
+}
diff --git a/src/Client/Caster/TraceFile.php b/src/Client/Caster/TraceFile.php
new file mode 100644
index 00000000..0447750c
--- /dev/null
+++ b/src/Client/Caster/TraceFile.php
@@ -0,0 +1,32 @@
+
+ * } $line The stack trace line.
+ */
+ public function __construct(
+ public readonly array $line,
+ ) {}
+
+ public function __toString(): string
+ {
+ return isset($this->line['file'], $this->line['line'])
+ ? \basename($this->line['file']) . ':' . $this->line['line']
+ : '';
+ }
+}
diff --git a/src/Client/TrapHandle.php b/src/Client/TrapHandle.php
index 505f25fb..24041b36 100644
--- a/src/Client/TrapHandle.php
+++ b/src/Client/TrapHandle.php
@@ -4,6 +4,7 @@
namespace Buggregator\Trap\Client;
+use Buggregator\Trap\Client\Caster\Trace;
use Buggregator\Trap\Client\TrapHandle\Counter;
use Buggregator\Trap\Client\TrapHandle\Dumper as VarDumper;
use Buggregator\Trap\Client\TrapHandle\StaticState;
@@ -35,6 +36,22 @@ public static function fromArray(array $array): self
return new self($array);
}
+ /**
+ * Create a new instance with a single value.
+ *
+ * @param int<0, max> $number The tick number.
+ * @param float $delta The time delta between the current and previous tick.
+ *
+ * @internal
+ */
+ public static function fromTicker(int $number, float $delta, int $memory): self
+ {
+ $self = new self([]);
+ $self->values[] = new Trace($number, $delta, $memory, \array_slice($self->staticState->stackTrace, 0, 3));
+
+ return $self;
+ }
+
/**
* Dump only if the condition is true.
* The check is performed immediately upon declaration.
@@ -172,6 +189,28 @@ public function context(mixed ...$values): self
return $this;
}
+ /**
+ * Code syntax highlighting.
+ *
+ * Adds `language` data context to denote the passed data as source code.
+ * In this case, Buggregator will perform code highlighting.
+ *
+ * Note: it equals to `trap()->context(language: $syntax);`
+ *
+ * ```php
+ * trap(
+ * index: $indexCode,
+ * controller: $controllerCode,
+ * )->code('php');
+ * ```
+ *
+ * @param non-empty-string $syntax The name of the programming language
+ */
+ public function code(string $syntax): self
+ {
+ return $this->context(language: $syntax);
+ }
+
public function __destruct()
{
$this->haveToSend() and $this->sendDump();
diff --git a/src/Client/TrapHandle/StackTrace.php b/src/Client/TrapHandle/StackTrace.php
index 5d96006a..a8a9bb06 100644
--- a/src/Client/TrapHandle/StackTrace.php
+++ b/src/Client/TrapHandle/StackTrace.php
@@ -5,9 +5,8 @@
namespace Buggregator\Trap\Client\TrapHandle;
/**
- *
* @psalm-type StackTraceWithObjects = list
* }>
* @psalm-type SimpleStackTrace = list
+ */
+ public static array $facades = [];
+
/**
* Returns a backtrace as an array.
* Removes the internal frames and the first next frames after them.
@@ -46,16 +52,19 @@ public static function stackTrace(string $baseDir = '', bool $provideObjects = f
($provideObjects ? \DEBUG_BACKTRACE_PROVIDE_OBJECT : 0) | \DEBUG_BACKTRACE_IGNORE_ARGS,
) as $frame
) {
- if (\str_starts_with($frame['class'] ?? '', 'Buggregator\\Trap\\Client\\')) {
+ $class = $frame['class'] ?? '';
+ if (\str_starts_with($class, 'Buggregator\\Trap\\Client\\')) {
$internal = true;
$stack = [];
continue;
}
if ($internal) {
- // todo check the NoStackTrace attribute
-
- $internal = false;
+ if ($class === '' && \array_key_exists($frame['function'], self::$facades)) {
+ $stack = [];
+ } else {
+ $internal = false;
+ }
}
// Convert absolute paths to relative ones
diff --git a/src/Socket/Client.php b/src/Socket/Client.php
index ab9a06de..9b1a2692 100644
--- a/src/Socket/Client.php
+++ b/src/Socket/Client.php
@@ -112,7 +112,7 @@ public function process(): void
*/
public function setOnPayload(callable $callable): void
{
- $this->onPayload = \Closure::bind($callable(...), $this) ?? $callable(...);
+ $this->onPayload = @\Closure::bind($callable(...), $this) ?? $callable(...);
}
/**
@@ -121,7 +121,7 @@ public function setOnPayload(callable $callable): void
*/
public function setOnClose(callable $callable): void
{
- $this->onClose = \Closure::bind($callable(...), $this) ?? $callable(...);
+ $this->onClose = @\Closure::bind($callable(...), $this) ?? $callable(...);
}
public function send(string $payload): void
diff --git a/src/functions.php b/src/functions.php
index 622a505a..88f0f5fa 100644
--- a/src/functions.php
+++ b/src/functions.php
@@ -2,9 +2,13 @@
declare(strict_types=1);
+use Buggregator\Trap\Client\Caster\EnumValue;
+use Buggregator\Trap\Client\Caster\ProtobufCaster;
+use Buggregator\Trap\Client\Caster\Trace;
+use Buggregator\Trap\Client\Caster\TraceCaster;
+use Buggregator\Trap\Client\Caster\TraceFile;
use Buggregator\Trap\Client\TrapHandle;
-use Buggregator\Trap\Support\Caster\EnumValue;
-use Buggregator\Trap\Support\ProtobufCaster;
+use Buggregator\Trap\Client\TrapHandle\StackTrace;
use Google\Protobuf\Internal\MapField;
use Google\Protobuf\Internal\Message;
use Google\Protobuf\Internal\RepeatedField;
@@ -22,6 +26,67 @@ function trap(mixed ...$values): TrapHandle
/** @psalm-suppress InternalMethod */
return TrapHandle::fromArray($values);
}
+
+ StackTrace::$facades['trap'] = true;
+} catch (\Throwable $e) {
+ // do nothing
+}
+
+try {
+ /**
+ * Send values into Buggregator and return the first value.
+ *
+ * When arguments are passed it equals to {@see trap()} with `->return()` method call.
+ * When no arguments passed, it calculates ticks, time between the last `tr()` call and memory usage.
+ *
+ * @param mixed ...$values
+ */
+ function tr(mixed ...$values): mixed
+ {
+ /** @var int<-1, max> $counter */
+ static $counter = -1;
+ /** @var float $time */
+ static $time = 0.0;
+
+ ++$counter;
+
+ $previous = $time;
+ $mem = $time = \microtime(true);
+ try {
+ if ($values === []) {
+ /** @psalm-suppress InternalMethod */
+ return TrapHandle::fromTicker(
+ $counter,
+ $counter === 0 ? 0 : $mem - $previous,
+ \memory_get_usage(),
+ )->return();
+ }
+
+ /** @psalm-suppress InternalMethod */
+ return TrapHandle::fromArray($values)->return();
+ } finally {
+ $mem === $time and $time = \microtime(true);
+ }
+ }
+
+ StackTrace::$facades['tr'] = true;
+
+ /**
+ * Send values into Buggregator and die.
+ *
+ * When no arguments passed, it works like {@see tr()}.
+ *
+ * @param mixed ...$values
+ *
+ * @codeCoverageIgnore
+ */
+ function td(mixed ...$values): never
+ {
+ tr(...$values);
+ die;
+ }
+
+ StackTrace::$facades['td'] = true;
} catch (\Throwable $e) {
// do nothing
}
@@ -29,7 +94,7 @@ function trap(mixed ...$values): TrapHandle
/**
* Register the var-dump caster for protobuf messages
*/
-if (class_exists(AbstractCloner::class)) {
+if (\class_exists(AbstractCloner::class)) {
/** @psalm-suppress MixedAssignment */
AbstractCloner::$defaultCasters[Message::class] ??= [ProtobufCaster::class, 'cast'];
/** @psalm-suppress MixedAssignment */
@@ -38,4 +103,8 @@ function trap(mixed ...$values): TrapHandle
AbstractCloner::$defaultCasters[MapField::class] ??= [ProtobufCaster::class, 'castMap'];
/** @psalm-suppress MixedAssignment */
AbstractCloner::$defaultCasters[EnumValue::class] ??= [ProtobufCaster::class, 'castEnum'];
+ /** @psalm-suppress MixedAssignment */
+ AbstractCloner::$defaultCasters[Trace::class] = [TraceCaster::class, 'cast'];
+ /** @psalm-suppress MixedAssignment */
+ AbstractCloner::$defaultCasters[TraceFile::class] = [TraceCaster::class, 'castLine'];
}
diff --git a/tests/Unit/Client/Caster/TraceFileTest.php b/tests/Unit/Client/Caster/TraceFileTest.php
new file mode 100644
index 00000000..14012a0f
--- /dev/null
+++ b/tests/Unit/Client/Caster/TraceFileTest.php
@@ -0,0 +1,43 @@
+ 'foo',
+ 'line' => 42,
+ 'file' => '/path/to/file.php',
+ 'class' => 'Foo',
+ 'type' => '->',
+ 'args' => ['bar'],
+ ]);
+
+ self::assertSame('file.php:42', (string) $traceFile);
+ }
+
+ public function testToStringWithoutFile(): void
+ {
+ $traceFile = new \Buggregator\Trap\Client\Caster\TraceFile([
+ 'function' => 'foo',
+ ]);
+
+ self::assertSame('', (string) $traceFile);
+ }
+
+ public function testToStringWithoutLine(): void
+ {
+ $traceFile = new \Buggregator\Trap\Client\Caster\TraceFile([
+ 'function' => 'foo',
+ 'file' => '/path/to/file.php',
+ ]);
+
+ self::assertSame('', (string) $traceFile);
+ }
+}
diff --git a/tests/Unit/Client/Caster/TraceTest.php b/tests/Unit/Client/Caster/TraceTest.php
new file mode 100644
index 00000000..1b822684
--- /dev/null
+++ b/tests/Unit/Client/Caster/TraceTest.php
@@ -0,0 +1,58 @@
+ $number
+ * @param int<0, max> $memory
+ * @param non-empty-string $result
+ */
+ #[DataProvider('provideToString')]
+ public function testToString(int $number, float $delta, int $memory, string $result): void
+ {
+ $trace = new Trace(
+ number: $number,
+ delta: $delta,
+ memory: $memory,
+ stack: [
+ [
+ 'function' => 'foo',
+ 'line' => 42,
+ 'file' => '/path/to/file.php',
+ 'class' => 'Foo',
+ 'type' => '->',
+ 'args' => ['bar'],
+ ],
+ [
+ 'function' => 'bar',
+ 'line' => 23,
+ 'file' => '/path/to/file.php',
+ 'class' => 'Bar',
+ 'type' => '::',
+ 'args' => ['baz'],
+ ],
+ ],
+ );
+
+ self::assertSame($result, (string) $trace);
+ }
+}
diff --git a/tests/Unit/Client/FunctionTrapTest.php b/tests/Unit/Client/FunctionTrapTest.php
index 1a471215..e3cd7399 100644
--- a/tests/Unit/Client/FunctionTrapTest.php
+++ b/tests/Unit/Client/FunctionTrapTest.php
@@ -4,15 +4,14 @@
namespace Buggregator\Trap\Tests\Unit\Client;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunInSeparateProcess;
use PHPUnit\Framework\TestCase;
final class FunctionTrapTest extends TestCase
{
- /**
- * @runInSeparateProcess
- *
- * @group phpunit-only
- */
+ #[RunInSeparateProcess]
+ #[Group('phpunit-only')]
public function testLeak(): void
{
$object = new \stdClass();
diff --git a/tests/Unit/Client/TrTest.php b/tests/Unit/Client/TrTest.php
new file mode 100644
index 00000000..5bb22e03
--- /dev/null
+++ b/tests/Unit/Client/TrTest.php
@@ -0,0 +1,77 @@
+getContext()['label']);
+ }
+
+ /**
+ * Check the stacktrace contains three items and it begins with the right function name.
+ */
+ #[RunInSeparateProcess]
+ #[Group('phpunit-only')]
+ public function testTrAsStackTrace(): void
+ {
+ tr();
+
+ // 3 Stack Trace items
+ self::assertCount(3, self::$lastData->getValue());
+ self::assertStringMatchesFormat('Trace #0 -.--- %fM', self::$lastData->getType());
+ $firstLineKey = \array_key_first(self::$lastData->getValue());
+ self::assertStringContainsString('tr()', $firstLineKey);
+
+ tr();
+
+ self::assertCount(3, self::$lastData->getValue());
+ self::assertStringMatchesFormat('Trace #1 +%fms %fM', self::$lastData->getType());
+ $firstLineKey = \array_key_first(self::$lastData->getValue());
+ self::assertStringContainsString('tr()', $firstLineKey);
+ }
+
+ /**
+ * After calling {@see tr()} the dumped data isn't stored in the memory.
+ */
+ public function testLeak(): void
+ {
+ $object = new \stdClass();
+ $ref = \WeakReference::create($object);
+
+ tr($object, $object);
+ unset($object);
+
+ $this->assertNull($ref->get());
+ }
+
+ public function testReturn(): void
+ {
+ $this->assertSame(42, tr(42));
+ $this->assertSame(42, tr(named: 42));
+ $this->assertSame(42, tr(42, 43));
+ $this->assertSame('foo', tr(...['0' => 'foo', 42 => 90]));
+ $this->assertNull(tr(null));
+ }
+
+ public function testReturnSendsDumpOnce(): void
+ {
+ $dumper = $this->getMockBuilder(DataDumperInterface::class)
+ ->getMock();
+ $dumper->expects($this->once())
+ ->method('dump')
+ ->willReturnArgument(1);
+ Dumper::setDumper($dumper);
+
+ $this->assertSame(42, tr(42));
+ }
+}
diff --git a/tests/Unit/Client/TrapTest.php b/tests/Unit/Client/TrapTest.php
index 55cb241b..52ac0740 100644
--- a/tests/Unit/Client/TrapTest.php
+++ b/tests/Unit/Client/TrapTest.php
@@ -47,6 +47,13 @@ public function testContextMultiple(): void
self::assertSame(['foo' => 'new', 'bar' => 'bar-context'], self::$lastData->getContext());
}
+ public function testCodeHighlight(): void
+ {
+ trap('test-value')->code('php');
+
+ self::assertSame(['language' => 'php'], self::$lastData->getContext());
+ }
+
/**
* Check the first line of dumped stacktrace string contains right file and line.
*/