From f003704ee9090faaf781efad7616511ed885bc09 Mon Sep 17 00:00:00 2001 From: brettmc Date: Thu, 10 Sep 2026 10:09:37 +1000 Subject: [PATCH 1/3] bugfix: fix finding commits since release instead of using the date the most recent release was published, use the date its tag was created, since there can be a gap between a release being drafted and published --- .../Release/AbstractReleaseCommand.php | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Console/Command/Release/AbstractReleaseCommand.php b/src/Console/Command/Release/AbstractReleaseCommand.php index 7447296..5c2197b 100644 --- a/src/Console/Command/Release/AbstractReleaseCommand.php +++ b/src/Console/Command/Release/AbstractReleaseCommand.php @@ -92,7 +92,8 @@ protected function get_latest_release(Repository $repository): ?Release $data = json_decode($response->getBody()->getContents()); $release = new Release(); - $release->timestamp = $data->published_at; + //created_at is when the tag was created, which may be well before the release was published + $release->timestamp = $data->created_at; $release->version = $data->tag_name; $this->output->isVerbose() && $this->output->writeln("[INFO] Latest release of {$repository->downstream} is {$release}"); @@ -103,12 +104,25 @@ protected function get_downstream_unreleased_commits(Repository $repository): ar { $commits_url = "https://api.github.com/repos/{$repository->downstream}/commits"; if ($repository->latestRelease !== null) { - $commits_url .= "?since={$repository->latestRelease->timestamp}"; + $commits_url .= '?since=' . $this->since($repository->latestRelease); } return $this->get_commits($commits_url, $repository); } + /** + * Github's `since` filter is inclusive, so advance one second past the tag to avoid listing the + * already-released commit. This happens with lightweight tags, where the tag's timestamp is the + * timestamp of the commit it points at. + */ + private function since(Release $release): string + { + return (new \DateTimeImmutable($release->timestamp)) + ->setTimezone(new \DateTimeZone('UTC')) + ->modify('+1 second') + ->format('Y-m-d\TH:i:s\Z'); + } + /** * @param Repository $repository * @return array @@ -117,7 +131,7 @@ protected function get_upstream_unreleased_commits(Repository $repository): arra { $commits_url = "https://api.github.com/repos/{$repository->upstream}/commits?path={$repository->upstream->path}"; if ($repository->latestRelease !== null) { - $commits_url .= "&since={$repository->latestRelease->timestamp}"; + $commits_url .= '&since=' . $this->since($repository->latestRelease); } return $this->get_commits($commits_url, $repository); From 08f3efdeafe8d8fe07bb8ca04d3d7c43b08c9bf8 Mon Sep 17 00:00:00 2001 From: brettmc Date: Thu, 10 Sep 2026 12:07:22 +1000 Subject: [PATCH 2/3] fix nullable latest release --- src/Console/Command/Release/AbstractReleaseCommand.php | 10 ++++++---- src/Console/Release/Repository.php | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Console/Command/Release/AbstractReleaseCommand.php b/src/Console/Command/Release/AbstractReleaseCommand.php index 5c2197b..793ca5a 100644 --- a/src/Console/Command/Release/AbstractReleaseCommand.php +++ b/src/Console/Command/Release/AbstractReleaseCommand.php @@ -103,8 +103,9 @@ protected function get_latest_release(Repository $repository): ?Release protected function get_downstream_unreleased_commits(Repository $repository): array { $commits_url = "https://api.github.com/repos/{$repository->downstream}/commits"; - if ($repository->latestRelease !== null) { - $commits_url .= '?since=' . $this->since($repository->latestRelease); + $latestRelease = $repository->latestRelease; + if ($latestRelease !== null) { + $commits_url .= '?since=' . $this->since($latestRelease); } return $this->get_commits($commits_url, $repository); @@ -130,8 +131,9 @@ private function since(Release $release): string protected function get_upstream_unreleased_commits(Repository $repository): array { $commits_url = "https://api.github.com/repos/{$repository->upstream}/commits?path={$repository->upstream->path}"; - if ($repository->latestRelease !== null) { - $commits_url .= '&since=' . $this->since($repository->latestRelease); + $latestRelease = $repository->latestRelease; + if ($latestRelease !== null) { + $commits_url .= '&since=' . $this->since($latestRelease); } return $this->get_commits($commits_url, $repository); diff --git a/src/Console/Release/Repository.php b/src/Console/Release/Repository.php index abe4601..7b28a8c 100644 --- a/src/Console/Release/Repository.php +++ b/src/Console/Release/Repository.php @@ -8,7 +8,7 @@ class Repository { public Project $upstream; //the monorepo that this repo is split from public Project $downstream; //the read-only downstream repo (split target) - public ?Release $latestRelease; //latest release + public ?Release $latestRelease = null; //latest release /** * @var array */ From b1ddb81af031bdc2c128b424c97c5170ec1b4481 Mon Sep 17 00:00:00 2001 From: brettmc Date: Thu, 10 Sep 2026 14:12:23 +1000 Subject: [PATCH 3/3] fix tests on 8.4 --- composer.json | 7 +++--- .../Composer/TestInstallationFactoryTest.php | 11 ++------ .../Package/Composer/TestInstallationTest.php | 25 +++---------------- .../Package/Composer/TestInstallerTest.php | 15 ++--------- .../ValueObject/AbstractCollectionTest.php | 2 +- 5 files changed, 13 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index afada49..31a341f 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "kriswallsmith/buzz": "^1.2", "nyholm/psr7": "^1.4", "php-http/discovery": "^1.19", + "symfony/console": "^6.0|^7.0|^8.0", "symfony/polyfill-php82": "^1.26", "symfony/polyfill-php83": "^1.32", "symfony/polyfill-php84": "^1.32", @@ -41,9 +42,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8", "mikey179/vfsstream": "^1.6", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-mockery": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.3" }, "scripts": { diff --git a/tests/Unit/Package/Composer/TestInstallationFactoryTest.php b/tests/Unit/Package/Composer/TestInstallationFactoryTest.php index 7c41676..b58c115 100644 --- a/tests/Unit/Package/Composer/TestInstallationFactoryTest.php +++ b/tests/Unit/Package/Composer/TestInstallationFactoryTest.php @@ -10,6 +10,7 @@ use OpenTelemetry\DevTools\Package\Composer\TestInstallationFactory; use OpenTelemetry\DevTools\Package\Composer\ValueObject\RepositoryCollection; use OpenTelemetry\DevTools\Package\Composer\ValueObject\SingleRepositoryInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -21,7 +22,7 @@ class TestInstallationFactoryTest extends TestCase private const TESTED_BRANCH_VERSION = 'dev-' . self::TESTED_BRANCH; private TestInstallationFactory $instance; - private TestConfigFactory $testConfigFactory; + private TestConfigFactory&MockObject $testConfigFactory; #[\Override] protected function setUp(): void @@ -33,13 +34,9 @@ protected function setUp(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_build(): void { $config = $this->createMock(TestConfig::class); - /** @phpstan-ignore-next-line */ $this->testConfigFactory ->method('build') ->willReturn($config); @@ -85,12 +82,8 @@ public function test_get_config_factory(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_add_default_dependency(): void { - /** @phpstan-ignore-next-line */ $this->testConfigFactory ->expects($this->once()) ->method('addDefaultDependency'); diff --git a/tests/Unit/Package/Composer/TestInstallationTest.php b/tests/Unit/Package/Composer/TestInstallationTest.php index 12f0fc9..cdfe0a0 100644 --- a/tests/Unit/Package/Composer/TestInstallationTest.php +++ b/tests/Unit/Package/Composer/TestInstallationTest.php @@ -14,6 +14,7 @@ use OpenTelemetry\DevTools\Package\Composer\ValueObject\SingleRepositoryInterface; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -35,9 +36,9 @@ class TestInstallationTest extends TestCase ]; private TestInstallation $instance; - private SingleRepositoryInterface $repository; - private TestConfig $config; - private RepositoryCollection $dependencies; + private SingleRepositoryInterface&MockObject $repository; + private TestConfig&MockObject $config; + private RepositoryCollection&MockObject $dependencies; private vfsStreamDirectory $root; private string $testDirectory; @@ -106,11 +107,9 @@ public function test_get_tested_branch(): void /** * @throws JsonException - * @psalm-suppress UndefinedMethod */ public function test_to_json(): void { - /** @phpstan-ignore-next-line */ $this->config ->method('toArray') ->willReturn(self::TEST_CONFIG); @@ -121,12 +120,8 @@ public function test_to_json(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_to_json_throws_exception_on_invalid_json(): void { - /** @phpstan-ignore-next-line */ $this->config ->method('toArray') ->willThrowException( @@ -140,11 +135,9 @@ public function test_to_json_throws_exception_on_invalid_json(): void /** * @throws JsonException - * @psalm-suppress UndefinedMethod */ public function test_to_string(): void { - /** @phpstan-ignore-next-line */ $this->config ->method('toArray') ->willReturn(self::TEST_CONFIG); @@ -155,18 +148,13 @@ public function test_to_string(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_write_composer_file(): void { $composerPath = $this->testDirectory . DIRECTORY_SEPARATOR . TestInstallation::COMPOSER_FILE_NAME; - /** @phpstan-ignore-next-line */ $this->repository ->method('getComposerFilePath') ->willReturn($composerPath); - /** @phpstan-ignore-next-line */ $this->config ->method('toArray') ->willReturn(self::TEST_CONFIG); @@ -181,19 +169,14 @@ public function test_write_composer_file(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_write_composer_file_throws_exception_on_file_write_error(): void { $composerPath = $this->testDirectory . DIRECTORY_SEPARATOR . TestInstallation::COMPOSER_FILE_NAME; - /** @phpstan-ignore-next-line */ $this->repository->method('getComposerFilePath') ->willReturn('foo://bar.baz'); $this->expectException(RuntimeException::class); - /** @phpstan-ignore-next-line */ $this->config ->method('toArray') ->willReturn(self::TEST_CONFIG); diff --git a/tests/Unit/Package/Composer/TestInstallerTest.php b/tests/Unit/Package/Composer/TestInstallerTest.php index 4b94701..969e586 100644 --- a/tests/Unit/Package/Composer/TestInstallerTest.php +++ b/tests/Unit/Package/Composer/TestInstallerTest.php @@ -11,6 +11,7 @@ use OpenTelemetry\DevTools\Util\RecursiveDirectoryRemover; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -24,7 +25,7 @@ class TestInstallerTest extends TestCase public const COMPOSER_FILE_NAME = 'composer.json'; private TestInstaller $instance; - private RecursiveDirectoryRemover $directoryRemover; + private RecursiveDirectoryRemover&MockObject $directoryRemover; private vfsStreamDirectory $root; #[\Override] @@ -46,9 +47,6 @@ protected function tearDown(): void TestInstaller::setDirectoryRemover(null); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_install(): void { $composerDirectory = self::TEST_DIR; @@ -56,7 +54,6 @@ public function test_install(): void ->at($this->root) ->url(); - /** @phpstan-ignore-next-line */ $this->directoryRemover->method('remove') ->willReturnCallback(function () use ($testDirectory) { rmdir($testDirectory); @@ -92,9 +89,6 @@ public function test_install_throws_exception_on_error(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_install_throws_exception_when_not_able_to_create_install_directory(): void { $this->expectException(InvalidArgumentException::class); @@ -104,7 +98,6 @@ public function test_install_throws_exception_when_not_able_to_create_install_di $this->root->chown(12345); $this->root->chmod(0700); - /** @phpstan-ignore-next-line */ $this->directoryRemover->method('remove') ->willReturn(true); @@ -136,9 +129,6 @@ public function test_remove(): void ); } - /** - * @psalm-suppress UndefinedMethod - */ public function test_remove_throws_exception_on_error(): void { $exception = new RuntimeException(); @@ -147,7 +137,6 @@ public function test_remove_throws_exception_on_error(): void $installation = $this->createMock(TestInstallation::class); - /** @phpstan-ignore-next-line */ $this->directoryRemover->method('remove') ->willThrowException($exception); diff --git a/tests/Unit/Package/Composer/ValueObject/AbstractCollectionTest.php b/tests/Unit/Package/Composer/ValueObject/AbstractCollectionTest.php index fb7692a..fef704d 100644 --- a/tests/Unit/Package/Composer/ValueObject/AbstractCollectionTest.php +++ b/tests/Unit/Package/Composer/ValueObject/AbstractCollectionTest.php @@ -43,7 +43,7 @@ public function test_offset_exists(): void $this->instance[self::OFFSET_KEY] = $this->createMock(ValueObjectInterface::class); $this->assertTrue( - isset($this->instance[self::OFFSET_KEY]) + $this->instance->offsetExists(self::OFFSET_KEY) ); }