diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml index c7572e91ae502..c623cce3fb09f 100644 --- a/.github/workflows/phpunit-sqlite.yml +++ b/.github/workflows/phpunit-sqlite.yml @@ -125,6 +125,16 @@ jobs: - name: PHPUnit database tests run: composer run test:db -- --log-junit junit.xml + - name: Slowest tests + if: always() + continue-on-error: true + run: | + { + echo '```' + php tests/junit-analyzer.php junit.xml 30 + echo '```' + } | tee -a "${GITHUB_STEP_SUMMARY:-/dev/null}" + - name: Print logs if: always() run: | diff --git a/apps/settings/tests/UserMigration/AccountMigratorTest.php b/apps/settings/tests/UserMigration/AccountMigratorTest.php index f1df03343f0af..b8f35f65ac369 100644 --- a/apps/settings/tests/UserMigration/AccountMigratorTest.php +++ b/apps/settings/tests/UserMigration/AccountMigratorTest.php @@ -13,9 +13,7 @@ use OCP\Accounts\IAccountManager; use OCP\AppFramework\App; use OCP\IAvatarManager; -use OCP\IConfig; use OCP\IUserManager; -use OCP\Server; use OCP\UserMigration\IExportDestination; use OCP\UserMigration\IImportSource; use PHPUnit\Framework\Constraint\JsonMatches; @@ -46,7 +44,7 @@ protected function setUp(): void { $app = new App(Application::APP_ID); $container = $app->getContainer(); - $container->get(IConfig::class)->setSystemValue('has_internet_connection', false); + $this->overwriteSystemConfig('has_internet_connection', false); $this->userManager = $container->get(IUserManager::class); $this->avatarManager = $container->get(IAvatarManager::class); @@ -57,11 +55,6 @@ protected function setUp(): void { $this->output = $this->createMock(OutputInterface::class); } - protected function tearDown(): void { - Server::get(IConfig::class)->setSystemValue('has_internet_connection', true); - parent::tearDown(); - } - public static function dataImportExportAccount(): array { return array_map( static function (string $filename): array { diff --git a/apps/sharing/tests/Controller/ApiV1ControllerTest.php b/apps/sharing/tests/Controller/ApiV1ControllerTest.php index 1a7afe1ff216c..6b08ced66ec7e 100644 --- a/apps/sharing/tests/Controller/ApiV1ControllerTest.php +++ b/apps/sharing/tests/Controller/ApiV1ControllerTest.php @@ -39,23 +39,26 @@ public function testDefaultShareAccessContext(): void { $user = Server::get(IUserManager::class)->createUser('user', 'password'); $this->assertNotFalse($user); - self::loginAsUser($user->getUID()); - - $controller = new ApiV1Controller( - '', - Server::get(IRequest::class), - Server::get(IUserSession::class), - Server::get(ISharingManager::class), - $this->registry, - Server::get(IFactory::class), - Server::get(IURLGenerator::class), - Server::get(IUserManager::class), - Server::get(IDBConnection::class), - ); - - $this->assertEquals(new ShareAccessContext($user), $controller->accessContext); - - self::logout(); + try { + self::loginAsUser($user->getUID()); + + $controller = new ApiV1Controller( + '', + Server::get(IRequest::class), + Server::get(IUserSession::class), + Server::get(ISharingManager::class), + $this->registry, + Server::get(IFactory::class), + Server::get(IURLGenerator::class), + Server::get(IUserManager::class), + Server::get(IDBConnection::class), + ); + + $this->assertEquals(new ShareAccessContext($user), $controller->accessContext); + } finally { + self::logout(); + $user->delete(); + } } /** diff --git a/tests/Core/Command/Apps/AppsEnableTest.php b/tests/Core/Command/Apps/AppsEnableTest.php index 270d10664e029..67b9f30d6739b 100644 --- a/tests/Core/Command/Apps/AppsEnableTest.php +++ b/tests/Core/Command/Apps/AppsEnableTest.php @@ -36,6 +36,10 @@ protected function setUp(): void { $this->commandTester = new CommandTester($command); + // Not every setup disables the app store, and enabling an unknown app pulls + // the whole catalogue (~30 MB, cached for an hour) to find it is not there. + $this->overwriteSystemConfig('appstoreenabled', false); + Server::get(IAppManager::class)->disableApp('admin_audit'); Server::get(IAppManager::class)->disableApp('comments'); } diff --git a/tests/junit-analyzer.php b/tests/junit-analyzer.php new file mode 100644 index 0000000000000..0580d5e62e826 --- /dev/null +++ b/tests/junit-analyzer.php @@ -0,0 +1,139 @@ +open($file)) { + fwrite(STDERR, "cannot open $file\n"); + exit(1); +} + +/** @var list $tests in execution order */ +$tests = []; +while ($reader->read()) { + if ($reader->nodeType !== XMLReader::ELEMENT || $reader->name !== 'testcase') { + continue; + } + $tests[] = [ + 'class' => $reader->getAttribute('class') ?: '(none)', + 'name' => (string)$reader->getAttribute('name'), + 'duration' => (float)$reader->getAttribute('time'), + ]; +} +$reader->close(); + +$testCount = count($tests); +if ($testCount === 0) { + $error = libxml_get_errors()[0] ?? null; + fwrite(STDERR, "no testcase elements found in $file" + . ($error !== null ? ': ' . trim($error->message) : '') . "\n"); + exit(1); +} + +$totalDuration = array_sum(array_column($tests, 'duration')); +printf("%d tests, %.1fs total (%.1f min)\n\n", $testCount, $totalDuration, $totalDuration / 60); + +if ($totalDuration <= 0) { + fwrite(STDERR, "no timing data to rank\n"); + exit(0); +} + +printf("Execution order, %d buckets (are later tests slower?)\n", BUCKETS); +echo " bucket tests sum(s) mean(ms) median(ms) max(s) cum%\n"; + +$durationSoFar = 0.0; +for ($bucket = 0; $bucket < BUCKETS; $bucket++) { + $from = (int)floor($testCount * $bucket / BUCKETS); + $to = (int)floor($testCount * ($bucket + 1) / BUCKETS); + + $durations = array_column(array_slice($tests, $from, $to - $from), 'duration'); + sort($durations); + $inBucket = count($durations); + $bucketDuration = array_sum($durations); + $durationSoFar += $bucketDuration; + + printf( + " %3d-%3d%% %7d %9.1f %10.2f %12.2f %9.2f %5.1f%%\n", + $bucket * (100 / BUCKETS), + ($bucket + 1) * (100 / BUCKETS), + $inBucket, + $bucketDuration, + $inBucket ? $bucketDuration / $inBucket * 1000 : 0, + $inBucket ? $durations[intdiv($inBucket, 2)] * 1000 : 0, + $inBucket ? max($durations) : 0, + $durationSoFar / $totalDuration * 100, + ); +} + +$slowestFirst = $tests; +usort($slowestFirst, static fn (array $a, array $b): int => $b['duration'] <=> $a['duration']); + +printf("\nTop %d slowest tests\n", $topCount); +foreach (array_slice($slowestFirst, 0, $topCount) as $test) { + printf(" %8.2fs %s::%s\n", $test['duration'], $test['class'], $test['name']); +} + +/** @var array $classes */ +$classes = []; +foreach ($tests as $test) { + $classes[$test['class']] ??= ['duration' => 0.0, 'tests' => 0]; + $classes[$test['class']]['duration'] += $test['duration']; + $classes[$test['class']]['tests']++; +} +uasort($classes, static fn (array $a, array $b): int => $b['duration'] <=> $a['duration']); + +printf("\nTop %d slowest classes (sum of its tests)\n", $topCount); +printf(" %9s %7s %10s %s\n", 'sum', 'tests', 'mean(ms)', 'class'); +foreach (array_slice($classes, 0, $topCount, true) as $class => $stats) { + printf( + " %8.2fs %7d %10.2f %s\n", + $stats['duration'], + $stats['tests'], + $stats['duration'] / $stats['tests'] * 1000, + $class, + ); +} + +// How top-heavy is the run? A handful of tests dominating reads very differently +// from the cost being spread evenly. +$durationSoFar = 0.0; +$testsInHalfTheRuntime = 0; +foreach ($slowestFirst as $test) { + $durationSoFar += $test['duration']; + $testsInHalfTheRuntime++; + if ($durationSoFar >= $totalDuration / 2) { + break; + } +} +printf( + "\nThe slowest %d tests (%.1f%% of tests) account for 50%% of the runtime.\n", + $testsInHalfTheRuntime, + $testsInHalfTheRuntime / $testCount * 100, +); diff --git a/tests/lib/InstallerTest.php b/tests/lib/InstallerTest.php index 8bd9178514357..c7b8a533b0ece 100644 --- a/tests/lib/InstallerTest.php +++ b/tests/lib/InstallerTest.php @@ -30,7 +30,6 @@ #[\PHPUnit\Framework\Attributes\Group('DB')] class InstallerTest extends TestCase { private static $appid = 'testapp'; - private $appstore; private AppFetcher&MockObject $appFetcher; private IClientService&MockObject $clientService; private ITempManager&MockObject $tempManager; @@ -51,9 +50,6 @@ protected function setUp(): void { $this->appManager = $this->createMock(AppManager::class); $this->l10nFactory = $this->createMock(IFactory::class); - $config = Server::get(IConfig::class); - $this->appstore = $config->setSystemValue('appstoreenabled', true); - $config->setSystemValue('appstoreenabled', true); $installer = Server::get(Installer::class); $installer->removeApp(self::$appid); } @@ -75,7 +71,6 @@ protected function getInstaller() { protected function tearDown(): void { $installer = Server::get(Installer::class); $installer->removeApp(self::$appid); - Server::get(IConfig::class)->setSystemValue('appstoreenabled', $this->appstore); parent::tearDown(); } diff --git a/tests/lib/TestCase.php b/tests/lib/TestCase.php index 0c90b7fb91728..2f5b34fea6228 100644 --- a/tests/lib/TestCase.php +++ b/tests/lib/TestCase.php @@ -47,10 +47,13 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase { /** @psalm-suppress ImpureStaticProperty */ private static bool $wasDatabaseAllowed = false; protected array $services = []; + /** Original values keyed by config key; null means the key was unset. */ + private array $systemConfigValues = []; #[\Override] protected function onNotSuccessfulTest(\Throwable $t): never { $this->restoreAllServices(); + $this->restoreAllSystemConfig(); // restore database connection if (!$this->IsDatabaseAccessAllowed()) { @@ -114,6 +117,39 @@ public function restoreAllServices(): void { } } + /** + * Sets a system config value for the duration of the test, restoring the + * previous one in tearDown. System config is persisted to config.php, so a + * leaked value would outlive the whole run. + */ + protected function overwriteSystemConfig(string $key, mixed $value): void { + $config = Server::get(IConfig::class); + + if (!array_key_exists($key, $this->systemConfigValues)) { + $this->systemConfigValues[$key] = $config->getSystemValue($key, null); + } + + $config->setSystemValue($key, $value); + } + + public function restoreAllSystemConfig(): void { + if ($this->systemConfigValues === []) { + return; + } + + $config = Server::get(IConfig::class); + foreach ($this->systemConfigValues as $key => $value) { + // null reads back as the default, so remove the key instead. + if ($value === null) { + $config->deleteSystemValue($key); + } else { + $config->setSystemValue($key, $value); + } + } + + $this->systemConfigValues = []; + } + protected function getTestTraits(): array { $traits = []; $class = $this; @@ -161,6 +197,7 @@ protected function setUp(): void { #[\Override] protected function tearDown(): void { $this->restoreAllServices(); + $this->restoreAllSystemConfig(); // restore database connection if (!$this->IsDatabaseAccessAllowed()) { diff --git a/tests/lib/TestCaseTest.php b/tests/lib/TestCaseTest.php new file mode 100644 index 0000000000000..81d9c68ca67ef --- /dev/null +++ b/tests/lib/TestCaseTest.php @@ -0,0 +1,91 @@ +config = Server::get(IConfig::class); + $this->config->deleteSystemValue(self::KEY); + } + + #[\Override] + protected function tearDown(): void { + parent::tearDown(); + + $this->config->deleteSystemValue(self::KEY); + } + + public function testOverwriteSetsTheValue(): void { + $this->overwriteSystemConfig(self::KEY, 'overwritten'); + + $this->assertSame('overwritten', $this->config->getSystemValue(self::KEY)); + } + + public function testRestoreRemovesAPreviouslyUnsetKey(): void { + $this->overwriteSystemConfig(self::KEY, 'overwritten'); + $this->restoreAllSystemConfig(); + + $this->assertSame('fallback', $this->config->getSystemValue(self::KEY, 'fallback')); + } + + public function testRestoreReturnsThePreviousValue(): void { + $this->config->setSystemValue(self::KEY, 'original'); + + $this->overwriteSystemConfig(self::KEY, 'overwritten'); + $this->restoreAllSystemConfig(); + + $this->assertSame('original', $this->config->getSystemValue(self::KEY)); + } + + public function testRestoreReturnsThePreviousValueAfterRepeatedOverwrites(): void { + $this->config->setSystemValue(self::KEY, 'original'); + + $this->overwriteSystemConfig(self::KEY, 'first'); + $this->overwriteSystemConfig(self::KEY, 'second'); + $this->restoreAllSystemConfig(); + + $this->assertSame('original', $this->config->getSystemValue(self::KEY)); + } + + public function testRestoreIsIdempotent(): void { + $this->config->setSystemValue(self::KEY, 'original'); + + $this->overwriteSystemConfig(self::KEY, 'overwritten'); + $this->restoreAllSystemConfig(); + $this->config->setSystemValue(self::KEY, 'set afterwards'); + $this->restoreAllSystemConfig(); + + $this->assertSame('set afterwards', $this->config->getSystemValue(self::KEY)); + } + + /** false is falsy but set: an isset-based check would wrongly delete the key. */ + public function testRestoreReturnsAPreviousFalseValue(): void { + $this->config->setSystemValue(self::KEY, false); + + $this->overwriteSystemConfig(self::KEY, true); + $this->restoreAllSystemConfig(); + + $this->assertFalse($this->config->getSystemValue(self::KEY, 'fallback')); + } +} diff --git a/tests/phpunit-autotest.xml b/tests/phpunit-autotest.xml index 94f8e0a978770..55065f05eacec 100644 --- a/tests/phpunit-autotest.xml +++ b/tests/phpunit-autotest.xml @@ -12,7 +12,7 @@ timeoutForMediumTests="300" timeoutForLargeTests="600" cacheDirectory=".phpunit.cache" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"> + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"> lib/ Core/ @@ -21,7 +21,9 @@ ../apps/files_external - + + .. diff --git a/tests/preseed-config.php b/tests/preseed-config.php index 0853b220a653e..e3e07eb7accb7 100644 --- a/tests/preseed-config.php +++ b/tests/preseed-config.php @@ -9,6 +9,15 @@ */ $CONFIG = [ 'appstoreenabled' => false, + // Argon2 at its minimum cost. The suite creates users constantly and the + // default parameters make every password hash take ~100ms. + 'hashingMemoryCost' => 8, + 'hashingTimeCost' => 1, + 'hashingThreads' => 1, + // Only used when argon2 is unavailable and bcrypt is the fallback. + 'hashingCost' => 4, + // Smaller keys are faster to generate and the tests do not need 4096 bit. + 'openssl' => ['private_key_bits' => 2048], 'apps_paths' => [ [ 'path' => OC::$SERVERROOT . '/apps', diff --git a/vendor-bin/behat/composer.lock b/vendor-bin/behat/composer.lock index c537836ce4039..22a67b22ef5b1 100644 --- a/vendor-bin/behat/composer.lock +++ b/vendor-bin/behat/composer.lock @@ -338,20 +338,20 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -386,32 +386,31 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -450,9 +449,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "phar-io/manifest", @@ -664,28 +663,28 @@ }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -713,15 +712,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -909,31 +920,31 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.50", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -945,6 +956,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -990,31 +1002,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "psr/container", diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index f951b885db01f..e24aaa161af5c 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -8,20 +8,20 @@ "packages": [ { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -56,32 +56,31 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -120,9 +119,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "phar-io/manifest", @@ -334,28 +333,28 @@ }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -383,15 +382,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -579,31 +590,31 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.50", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -615,6 +626,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -660,31 +672,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "sebastian/cli-parser", @@ -1786,5 +1782,5 @@ "platform-overrides": { "php": "8.2" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" }