-
-
Notifications
You must be signed in to change notification settings - Fork 28
fix(sentry): prevent duplicate metric timer ticks #1082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
| /** | ||
| * This file is part of friendsofhyperf/components. | ||
| * | ||
| * @link https://github.com/friendsofhyperf/components | ||
| * @document https://github.com/friendsofhyperf/components/blob/main/README.md | ||
| * @contact huangdijia@gmail.com | ||
| */ | ||
|
|
||
| namespace FriendsOfHyperf\Tests\Sentry\Metrics\Listener; | ||
|
|
||
| use Hyperf\Coordinator\Constants; | ||
| use Hyperf\Coordinator\Timer; | ||
|
|
||
| /** | ||
| * A Timer that records tick() calls without spawning coroutines. | ||
| * | ||
| * @internal | ||
| */ | ||
| class FakeTimer extends Timer | ||
| { | ||
| /** | ||
| * @var array<int, array{0: float, 1: callable, 2: string}> | ||
| */ | ||
| public array $ticks = []; | ||
|
|
||
| public function tick(float $timeout, callable $closure, string $identifier = Constants::WORKER_EXIT): int | ||
| { | ||
| $this->ticks[] = [$timeout, $closure, $identifier]; | ||
|
|
||
| return count($this->ticks); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
| /** | ||
| * This file is part of friendsofhyperf/components. | ||
| * | ||
| * @link https://github.com/friendsofhyperf/components | ||
| * @document https://github.com/friendsofhyperf/components/blob/main/README.md | ||
| * @contact huangdijia@gmail.com | ||
| */ | ||
|
|
||
| namespace FriendsOfHyperf\Tests\Sentry\Metrics\Listener; | ||
|
|
||
| use FriendsOfHyperf\Sentry\Constants; | ||
| use FriendsOfHyperf\Sentry\Feature; | ||
| use FriendsOfHyperf\Sentry\Metrics\Listener\OnBeforeHandle; | ||
| use Hyperf\Command\Command; | ||
| use Hyperf\Command\Event\BeforeHandle; | ||
| use Psr\Container\ContainerInterface; | ||
| use Symfony\Component\Console\Application; | ||
|
|
||
| beforeEach(function () { | ||
| Constants::$runningInCommand = false; | ||
|
|
||
| $this->timer = new FakeTimer(); | ||
| $this->container = $this->createMock(ContainerInterface::class); | ||
| $this->feature = $this->createMock(Feature::class); | ||
| $this->application = $this->createMock(Application::class); | ||
| $this->command = $this->createMock(Command::class); | ||
|
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
fd -a 'phpstan.neon.dist' . -x sed -n '1,220p' {}
fd -a 'InteractsWithContainer.php' tests -x sed -n '1,260p' {}
rg -n -C 3 --glob='*.php' 'createMock\(|Mockery::mock\(|->mock\(' tests/Sentry tests/ConcernsRepository: friendsofhyperf/components Length of output: 8024 🏁 Script executed: #!/bin/bash
set -euo pipefail
for f in \
tests/Sentry/Metrics/Listener/OnBeforeHandleTest.php \
tests/Sentry/Metrics/Listener/OnMetricFactoryReadyTest.php \
tests/Sentry/Metrics/Listener/QueueWatcherTest.php \
tests/Concerns/InteractsWithContainer.php \
phpstan.neon.dist
do
echo "===== $f ====="
sed -n '1,220p' "$f"
done
echo "===== test bootstrap and Pest setup ====="
rg -n -C 4 --glob='*.php' \
'uses\(|InteractsWithContainer|beforeEach\(|createMock\(|mock\(' \
tests/Sentry tests/Pest.php tests/bootstrap.php tests 2>/dev/null | head -n 500
echo "===== static-analysis configuration ====="
rg -n -C 3 \
'phpstan|level|max|baseline|pest|phpunit' \
composer.json composer.lock phpstan.neon.dist phpstan-baseline.neon 2>/dev/null | head -n 300Repository: friendsofhyperf/components Length of output: 47280 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "===== test base case ====="
fd -a -t f 'TestCase.php' tests -x sed -n '1,220p' {}
echo "===== baseline entries related to createMock and protected access ====="
rg -n -C 3 \
'createMock|protected method|protected|Access to protected|Call to protected' \
phpstan-baseline.neon 2>/dev/null || true
echo "===== package and analysis versions ====="
python3 - <<'PY'
import json
for name in ("composer.json", "composer.lock"):
try:
data = json.load(open(name))
except FileNotFoundError:
continue
print(f"===== {name} =====")
if name == "composer.json":
for section in ("require", "require-dev", "scripts"):
print(section, data.get(section, {}))
else:
for package in data.get("packages", []) + data.get("packages-dev", []):
n = package.get("name", "")
if any(x in n for x in ("phpstan", "phpunit", "pest", "mockery")):
print(n, package.get("version"))
PY
echo "===== all Sentry test setup declarations ====="
rg -n -C 2 \
'uses\(|->group\(|beforeEach\(' \
tests/Sentry tests/Pest.php | head -n 300Repository: friendsofhyperf/components Length of output: 12674 🌐 Web query:
💡 Result: To resolve issues with Pest, PHPStan, and protected method access (including mocks), you should ensure your PHPStan environment is correctly configured to understand Pest's runtime Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "===== every direct createMock use in tests ====="
rg -n --glob='*.php' '->createMock\(' tests | head -n 300
echo "===== Pest and PHPStan integration references ====="
rg -n --hidden -g '!vendor/**' -g '!composer.lock' \
'pest.*phpstan|phpstan.*pest|Pest\\|TestCase::class|createMock' \
.github tests phpstan* composer.json 2>/dev/null | head -n 400
echo "===== relevant PHPStan baseline structure ====="
sed -n '1,120p' phpstan-baseline.neon 2>/dev/null || trueRepository: friendsofhyperf/components Length of output: 238 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "===== every direct createMock use in tests ====="
rg -n --glob='*.php' -- '->createMock\(' tests | head -n 400
echo "===== PHPStan extensions and test paths ====="
rg -n -C 3 --hidden -g '!composer.lock' -g '!vendor/**' \
'extension|pest|tests|scanDirectories|paths|method\.protected|createMock' \
phpstan*.neon* composer.json .github 2>/dev/null | head -n 400
echo "===== available analysis tooling (without running repository code) ====="
command -v php || true
command -v phpstan || true
test -x vendor/bin/phpstan && echo vendor/bin/phpstan-present || true
test -x vendor/bin/pest && echo vendor/bin/pest-present || trueRepository: friendsofhyperf/components Length of output: 22323 🏁 Script executed: #!/bin/bash
set -u
files=(
tests/Sentry/Metrics/Listener/OnBeforeHandleTest.php
tests/Sentry/Metrics/Listener/OnMetricFactoryReadyTest.php
tests/Sentry/Metrics/Listener/QueueWatcherTest.php
)
for config in phpstan.neon.dist phpstan.neon; do
echo "===== PHPStan: $config ====="
vendor/bin/phpstan analyse "${files[@]}" \
--configuration="$config" \
--no-progress \
--error-format=table
status=$?
echo "exit status: $status"
doneRepository: friendsofhyperf/components Length of output: 2699 🏁 Script executed: #!/bin/bash
set -u
files=(
tests/Sentry/Metrics/Listener/OnBeforeHandleTest.php
tests/Sentry/Metrics/Listener/OnMetricFactoryReadyTest.php
tests/Sentry/Metrics/Listener/QueueWatcherTest.php
)
for config in phpstan.neon.dist phpstan.neon; do
echo "===== PHPStan debug: $config ====="
vendor/bin/phpstan analyse "${files[@]}" \
--configuration="$config" \
--debug \
--no-progress \
--error-format=table
echo "exit status: $?"
doneRepository: friendsofhyperf/components Length of output: 24437 🏁 Script executed: #!/bin/bash
set -u
echo "===== active PHPStan configuration ====="
cat phpstan.neon
echo "===== existing repository usage of the configured mock helper ====="
vendor/bin/phpstan analyse \
tests/Lock/FunctionsTest.php \
tests/Telescope/TelescopeConfigTest.php \
--configuration=phpstan.neon \
--debug \
--no-progress \
--error-format=table
echo "exit status: $?"
echo "===== definitions and signatures used by the helper ====="
sed -n '1,95p' tests/Concerns/InteractsWithContainer.phpRepository: friendsofhyperf/components Length of output: 5119 替换三个测试文件中的 当前 PHPStan 配置会报告 8 个
🧰 Tools🪛 PHPStan (2.2.7)[error] 26-26: Call to protected method createMock() of class PHPUnit\Framework\TestCase. (method.protected) [error] 27-27: Call to protected method createMock() of class PHPUnit\Framework\TestCase. (method.protected) [error] 28-28: Call to protected method createMock() of class PHPUnit\Framework\TestCase. (method.protected) [error] 29-29: Call to protected method createMock() of class PHPUnit\Framework\TestCase. (method.protected) 📍 Affects 3 files
🤖 Prompt for AI AgentsSources: Coding guidelines, Linters/SAST tools |
||
| $this->command->method('getApplication')->willReturn($this->application); | ||
|
|
||
| $this->event = new BeforeHandle($this->command); | ||
| }); | ||
|
|
||
| test('ticks only once when process is called twice', function () { | ||
| $this->feature->method('isCommandMetricsEnabled')->willReturn(true); | ||
| $this->feature->method('isDefaultMetricsEnabled')->willReturn(true); | ||
| $this->feature->method('getMetricsInterval')->willReturn(10); | ||
| $this->container->method('has')->willReturn(false); | ||
| $this->application->method('isAutoExitEnabled')->willReturn(true); | ||
|
|
||
| $listener = new OnBeforeHandle($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(1); | ||
| }); | ||
|
|
||
| test('does not tick when metrics are disabled', function () { | ||
| $this->feature->method('isCommandMetricsEnabled')->willReturn(false); | ||
| $this->feature->method('isDefaultMetricsEnabled')->willReturn(false); | ||
| $this->application->method('isAutoExitEnabled')->willReturn(true); | ||
|
|
||
| $listener = new OnBeforeHandle($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(0); | ||
| }); | ||
|
|
||
| test('does not tick when auto exit is disabled', function () { | ||
| $this->application->method('isAutoExitEnabled')->willReturn(false); | ||
|
|
||
| $listener = new OnBeforeHandle($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(0); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
| /** | ||
| * This file is part of friendsofhyperf/components. | ||
| * | ||
| * @link https://github.com/friendsofhyperf/components | ||
| * @document https://github.com/friendsofhyperf/components/blob/main/README.md | ||
| * @contact huangdijia@gmail.com | ||
| */ | ||
|
|
||
| namespace FriendsOfHyperf\Tests\Sentry\Metrics\Listener; | ||
|
|
||
| use FriendsOfHyperf\Sentry\Constants; | ||
| use FriendsOfHyperf\Sentry\Feature; | ||
| use FriendsOfHyperf\Sentry\Metrics\Event\MetricFactoryReady; | ||
| use FriendsOfHyperf\Sentry\Metrics\Listener\OnMetricFactoryReady; | ||
| use Psr\Container\ContainerInterface; | ||
|
|
||
| beforeEach(function () { | ||
| Constants::$runningInCommand = false; | ||
|
|
||
| $this->timer = new FakeTimer(); | ||
| $this->container = $this->createMock(ContainerInterface::class); | ||
| $this->feature = $this->createMock(Feature::class); | ||
|
|
||
| $this->event = new MetricFactoryReady(); | ||
| }); | ||
|
|
||
| test('ticks only once when process is called twice', function () { | ||
| $this->feature->method('isDefaultMetricsEnabled')->willReturn(true); | ||
| $this->feature->method('getMetricsInterval')->willReturn(10); | ||
| $this->container->method('has')->willReturn(false); | ||
|
|
||
| $listener = new OnMetricFactoryReady($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(1); | ||
| }); | ||
|
|
||
| test('does not tick when default metrics are disabled', function () { | ||
| $this->feature->method('isDefaultMetricsEnabled')->willReturn(false); | ||
|
|
||
| $listener = new OnMetricFactoryReady($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(0); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
| /** | ||
| * This file is part of friendsofhyperf/components. | ||
| * | ||
| * @link https://github.com/friendsofhyperf/components | ||
| * @document https://github.com/friendsofhyperf/components/blob/main/README.md | ||
| * @contact huangdijia@gmail.com | ||
| */ | ||
|
|
||
| namespace FriendsOfHyperf\Tests\Sentry\Metrics\Listener; | ||
|
|
||
| use FriendsOfHyperf\Sentry\Feature; | ||
| use FriendsOfHyperf\Sentry\Metrics\Event\MetricFactoryReady; | ||
| use FriendsOfHyperf\Sentry\Metrics\Listener\QueueWatcher; | ||
| use Psr\Container\ContainerInterface; | ||
|
|
||
| beforeEach(function () { | ||
| $this->timer = new FakeTimer(); | ||
| $this->container = $this->createMock(ContainerInterface::class); | ||
| $this->feature = $this->createMock(Feature::class); | ||
|
|
||
| $this->event = new MetricFactoryReady(); | ||
| }); | ||
|
|
||
| test('ticks only once when process is called twice', function () { | ||
| $this->feature->method('isQueueMetricsEnabled')->willReturn(true); | ||
| $this->feature->method('getMetricsInterval')->willReturn(10); | ||
|
|
||
| $listener = new QueueWatcher($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(1); | ||
| }); | ||
|
|
||
| test('does not tick when queue metrics are disabled', function () { | ||
| $this->feature->method('isQueueMetricsEnabled')->willReturn(false); | ||
|
|
||
| $listener = new QueueWatcher($this->container, $this->feature, $this->timer); | ||
| $listener->process($this->event); | ||
|
|
||
| expect($this->timer->ticks)->toHaveCount(0); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
在新文件头中添加许可证信息。
这些新文件的头部只包含项目信息,没有许可证标识或许可证文本。请使用项目适用的许可证信息更新所有新文件头。
tests/Sentry/Metrics/Listener/FakeTimer.php#L4-L10: 添加许可证信息。tests/Sentry/Metrics/Listener/OnBeforeHandleTest.php#L4-L10: 添加许可证信息。tests/Sentry/Metrics/Listener/OnMetricFactoryReadyTest.php#L4-L10: 添加许可证信息。tests/Sentry/Metrics/Listener/QueueWatcherTest.php#L4-L10: 添加许可证信息。As per coding guidelines: “File headers must include license information”.
📍 Affects 4 files
tests/Sentry/Metrics/Listener/FakeTimer.php#L4-L10(this comment)tests/Sentry/Metrics/Listener/OnBeforeHandleTest.php#L4-L10tests/Sentry/Metrics/Listener/OnMetricFactoryReadyTest.php#L4-L10tests/Sentry/Metrics/Listener/QueueWatcherTest.php#L4-L10🤖 Prompt for AI Agents
Source: Coding guidelines