From 7c0753c8e9f8bff2e65912bae408b2a7e59b3bcc Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 13 Jul 2026 21:50:12 +0200 Subject: [PATCH 1/6] test(Database): fix random-order test execution issues and state leakage under PostgreSQL, MySQL, and OCI8 --- .github/scripts/random-tests-config.txt | 2 +- system/Database/OCI8/Connection.php | 13 +++- tests/_support/Config/Registrar.php | 62 +++++++++++++++++- tests/system/Database/Live/ConnectTest.php | 15 ++++- .../Live/ExecuteLogMessageFormatTest.php | 15 +++-- tests/system/Database/Live/ForgeTest.php | 63 +++++++++++++++++-- tests/system/Database/Live/GetVersionTest.php | 3 +- tests/system/Database/Live/MetadataTest.php | 4 +- .../Database/Live/MySQLi/FoundRowsTest.php | 16 ++--- .../Database/Live/MySQLi/NumberNativeTest.php | 8 +-- .../Database/Live/Postgre/ConnectTest.php | 2 +- tests/system/Database/Live/UpsertTest.php | 23 ++++--- tests/system/Database/Live/WorkerModeTest.php | 1 - .../Migrations/MigrationRunnerTest.php | 4 +- 14 files changed, 181 insertions(+), 50 deletions(-) diff --git a/.github/scripts/random-tests-config.txt b/.github/scripts/random-tests-config.txt index 5bf0fce66733..0c667b4efb46 100644 --- a/.github/scripts/random-tests-config.txt +++ b/.github/scripts/random-tests-config.txt @@ -18,7 +18,7 @@ Config Cookie # DataCaster # DataConverter -# Database +Database # Debug Email # Encryption diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index dc884588a251..44f2f48a2a59 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -150,6 +150,17 @@ public function connect(bool $persistent = false) : $func($this->username, $this->password, $this->DSN, $this->charset); } + public function initialize() + { + parent::initialize(); + + if ($this->connID) { + $this->simpleQuery("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + $this->simpleQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + $this->simpleQuery("ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + } + } + /** * Close the database connection. * @@ -422,7 +433,7 @@ protected function _indexData(string $table): array $retVal[$row->INDEX_NAME] = new stdClass(); $retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME; $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME]; - $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX'; + $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE ?? ''] ?? 'INDEX'; } return $retVal; diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index 058fec440b55..caefaac80479 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -13,6 +13,10 @@ namespace Tests\Support\Config; +use mysqli; +use PDO; +use Throwable; + /** * Class Registrar * @@ -137,7 +141,63 @@ public static function Database(): array // so that we can test against multiple databases. $group = env('DB', 'SQLite3'); - $config['tests'] = self::$dbConfig[$group] ?? []; + $dbParams = self::$dbConfig[$group] ?? []; + + if (! empty($dbParams) && $group !== 'SQLite3') { + $componentName = ''; + + foreach ($_SERVER['argv'] ?? [] as $arg) { + if (str_contains($arg, 'tests/system/')) { + $parts = explode('tests/system/', $arg); + if (isset($parts[1])) { + $componentName = explode('/', $parts[1])[0]; + break; + } + } + } + + if ($componentName !== '') { + $dbParams['database'] = 'test_' . strtolower($componentName); + + try { + if ($group === 'MySQLi') { + $conn = new mysqli( + $dbParams['hostname'], + $dbParams['username'], + $dbParams['password'], + '', + (int) $dbParams['port'], + ); + if (! $conn->connect_error) { + $conn->query('CREATE DATABASE IF NOT EXISTS ' . $conn->real_escape_string($dbParams['database'])); + $conn->close(); + } + } elseif ($group === 'Postgre') { + $dsn = 'pgsql:host=' . $dbParams['hostname'] . ';port=' . $dbParams['port'] . ';user=' . $dbParams['username'] . ';password=' . $dbParams['password']; + $pdo = new PDO($dsn); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $pdo->prepare('SELECT 1 FROM pg_database WHERE datname = ?'); + $stmt->execute([$dbParams['database']]); + if (! $stmt->fetchColumn()) { + $pdo->exec('CREATE DATABASE ' . $pdo->quote($dbParams['database'])); + } + } elseif ($group === 'SQLSRV') { + $dsn = 'sqlsrv:Server=' . $dbParams['hostname'] . ',' . $dbParams['port'] . ';Encrypt=False;TrustServerCertificate=True'; + $pdo = new PDO($dsn, $dbParams['username'], $dbParams['password']); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $pdo->prepare('SELECT 1 FROM sys.databases WHERE name = ?'); + $stmt->execute([$dbParams['database']]); + if (! $stmt->fetchColumn()) { + $pdo->exec('CREATE DATABASE [' . str_replace(']', ']]', $dbParams['database']) . '] COLLATE Latin1_General_100_CS_AS_SC_UTF8'); + } + } + } catch (Throwable) { + // Ignore any error and let the connection fail naturally + } + } + } + + $config['tests'] = $dbParams; return $config; } diff --git a/tests/system/Database/Live/ConnectTest.php b/tests/system/Database/Live/ConnectTest.php index e41fdbdfc114..9b2f261e3bc0 100644 --- a/tests/system/Database/Live/ConnectTest.php +++ b/tests/system/Database/Live/ConnectTest.php @@ -46,11 +46,20 @@ protected function setUp(): void $this->group2['DBDriver'] = 'Postgre'; } + protected function tearDown(): void + { + parent::tearDown(); + $this->setPrivateProperty(Database::class, 'instances', []); + } + public function testConnectWithMultipleCustomGroups(): void { + $this->group1['DBPrefix'] = uniqid('g1_', true); + $this->group2['DBPrefix'] = uniqid('g2_', true); + // We should have our test database connection already. - $instances = $this->getPrivateProperty(Database::class, 'instances'); - $this->assertCount(1, $instances); + $instances = $this->getPrivateProperty(Database::class, 'instances'); + $initialCount = count($instances); $db1 = Database::connect($this->group1); $db2 = Database::connect($this->group2); @@ -58,7 +67,7 @@ public function testConnectWithMultipleCustomGroups(): void $this->assertNotSame($db1, $db2); $instances = $this->getPrivateProperty(Database::class, 'instances'); - $this->assertCount(3, $instances); + $this->assertCount($initialCount + 2, $instances); } public function testConnectReturnsProvidedConnection(): void diff --git a/tests/system/Database/Live/ExecuteLogMessageFormatTest.php b/tests/system/Database/Live/ExecuteLogMessageFormatTest.php index 9913a2da05c0..1884b76d3633 100644 --- a/tests/system/Database/Live/ExecuteLogMessageFormatTest.php +++ b/tests/system/Database/Live/ExecuteLogMessageFormatTest.php @@ -47,7 +47,7 @@ public function testLogMessageWhenExecuteFailsShowFullStructuredBacktrace(): voi $db->query($sql, [3, 'live', 'Rick']); $pattern = match ($db->DBDriver) { - 'MySQLi' => '/Table \'test\.some_table\' doesn\'t exist/', + 'MySQLi' => '/Table \'' . preg_quote($db->database, '/') . '\.some_table\' doesn\'t exist/', 'Postgre' => '/pg_query\(\): Query failed: ERROR: relation "some_table" does not exist/', 'SQLite3' => '/Unable to prepare statement:\s(\d+,\s)?no such table: some_table/', 'OCI8' => '/oci_execute\(\): ORA-00942: table or view "ORACLE"\."SOME_TABLE" does not exist/', @@ -60,11 +60,18 @@ public function testLogMessageWhenExecuteFailsShowFullStructuredBacktrace(): voi if ($db->DBDriver === 'Postgre') { $messageFromLogs = array_slice($messageFromLogs, 2); - } elseif ($db->DBDriver === 'OCI8') { - $messageFromLogs = array_slice($messageFromLogs, 1); } - $this->assertMatchesRegularExpression('/^in \S+ on line \d+\.$/', array_shift($messageFromLogs)); + $inLine = null; + + while (($line = array_shift($messageFromLogs)) !== null) { + if (preg_match('/^in \S+ on line \d+\.$/', $line)) { + $inLine = $line; + break; + } + } + + $this->assertNotNull($inLine, 'Could not find "in ... on line ..." in log message'); foreach ($messageFromLogs as $line) { $this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $line); diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 39433abde857..1f18d02b625d 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -36,25 +36,64 @@ final class ForgeTest extends CIUnitTestCase protected $seed = CITestSeeder::class; private Forge $forge; + private function dropAllMockTables(): void + { + $tablesToDrop = [ + 'forge_test_invoices', + 'forge_test_inv', + 'forge_test_users', + 'actions', + 'forge_test_table', + 'test_exists', + 'forge_test_attributes', + 'forge_array_constraint', + 'forge_nullable_table', + 'forge_test_1', + 'forge_test_two', + 'forge_test_three', + 'forge_test_four', + 'forge_test_modify', + 'droptest', + 'key_test_users', + 'test_stores', + 'user2', + 'forge_test_table_dummy', + ]; + + foreach ($tablesToDrop as $table) { + $this->forge->dropTable($table, true); + } + } + protected function setUp(): void { $this->forge = Database::forge($this->DBGroup); - // when running locally if one of these tables isn't dropped it may cause error - $this->forge->dropTable('forge_test_invoices', true); - $this->forge->dropTable('forge_test_inv', true); - $this->forge->dropTable('forge_test_users', true); - $this->forge->dropTable('actions', true); + $this->dropAllMockTables(); + + db_connect($this->DBGroup)->resetDataCache(); parent::setUp(); } + protected function tearDown(): void + { + parent::tearDown(); + $this->dropAllMockTables(); + } + public function testCreateDatabase(): void { if ($this->db->DBDriver === 'OCI8') { $this->markTestSkipped('OCI8 does not support create database.'); } + try { + $this->forge->dropDatabase('test_forge_database'); + } catch (DatabaseException) { + // Ignore if doesn't exist + } + $databaseCreated = $this->forge->createDatabase('test_forge_database'); $this->assertTrue($databaseCreated); @@ -68,6 +107,12 @@ public function testCreateDatabaseWithDots(): void $dbName = 'test_com.sitedb.web'; + try { + $this->forge->dropDatabase($dbName); + } catch (DatabaseException) { + // Ignore if doesn't exist + } + $databaseCreated = $this->forge->createDatabase($dbName); $this->assertTrue($databaseCreated); @@ -75,7 +120,7 @@ public function testCreateDatabaseWithDots(): void // Checks if tableExists() works. $config = config(Database::class)->{$this->DBGroup}; $config['database'] = $dbName; - $db = db_connect($config); + $db = db_connect($config, false); $result = $db->tableExists('not_exist'); $this->assertFalse($result); @@ -151,6 +196,12 @@ public function testDropDatabase(): void $this->markTestSkipped('SQLite3 requires file path to drop database'); } + try { + $this->forge->createDatabase('test_forge_database'); + } catch (DatabaseException) { + // Ignore if exists + } + $databaseDropped = $this->forge->dropDatabase('test_forge_database'); $this->assertTrue($databaseDropped); diff --git a/tests/system/Database/Live/GetVersionTest.php b/tests/system/Database/Live/GetVersionTest.php index 93678e3b8356..ad94134ff659 100644 --- a/tests/system/Database/Live/GetVersionTest.php +++ b/tests/system/Database/Live/GetVersionTest.php @@ -36,7 +36,6 @@ public function testGetVersion(): void $this->db->connID = false; $version = $this->db->getVersion(); - - $this->assertMatchesRegularExpression('/\A\d+(\.\d+)*\z/', $version); + $this->assertMatchesRegularExpression('/\A\d+(\.\d+)*/', $version); } } diff --git a/tests/system/Database/Live/MetadataTest.php b/tests/system/Database/Live/MetadataTest.php index 5030a6544231..b17a53930900 100644 --- a/tests/system/Database/Live/MetadataTest.php +++ b/tests/system/Database/Live/MetadataTest.php @@ -120,12 +120,10 @@ public function testListTablesConstrainedByPrefixReturnsOnlyTablesWithMatchingPr public function testListTablesConstrainedByExtraneousPrefixReturnsOnlyTheExtraneousTable(): void { - $oldPrefix = ''; + $oldPrefix = $this->db->getPrefix(); try { $this->createExtraneousTable(); - - $oldPrefix = $this->db->getPrefix(); $this->db->setPrefix('tmp_'); $tables = $this->db->listTables(true); diff --git a/tests/system/Database/Live/MySQLi/FoundRowsTest.php b/tests/system/Database/Live/MySQLi/FoundRowsTest.php index b39f8999085d..f5a42b3e5a48 100644 --- a/tests/system/Database/Live/MySQLi/FoundRowsTest.php +++ b/tests/system/Database/Live/MySQLi/FoundRowsTest.php @@ -54,7 +54,7 @@ public function testEnableFoundRows(): void { $this->tests['foundRows'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $this->assertTrue($db1->foundRows); } @@ -63,7 +63,7 @@ public function testDisableFoundRows(): void { $this->tests['foundRows'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $this->assertFalse($db1->foundRows); } @@ -72,7 +72,7 @@ public function testAffectedRowsAfterEnableFoundRowsWithNoChange(): void { $this->tests['foundRows'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('country', 'US') @@ -88,7 +88,7 @@ public function testAffectedRowsAfterDisableFoundRowsWithNoChange(): void { $this->tests['foundRows'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('country', 'US') @@ -104,7 +104,7 @@ public function testAffectedRowsAfterEnableFoundRowsWithChange(): void { $this->tests['foundRows'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('country', 'NZ') @@ -120,7 +120,7 @@ public function testAffectedRowsAfterDisableFoundRowsWithChange(): void { $this->tests['foundRows'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('country', 'NZ') @@ -136,7 +136,7 @@ public function testAffectedRowsAfterEnableFoundRowsWithPartialChange(): void { $this->tests['foundRows'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('name', 'Derek Jones') @@ -152,7 +152,7 @@ public function testAffectedRowsAfterDisableFoundRowsWithPartialChange(): void { $this->tests['foundRows'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); $db1->table('db_user') ->set('name', 'Derek Jones') diff --git a/tests/system/Database/Live/MySQLi/NumberNativeTest.php b/tests/system/Database/Live/MySQLi/NumberNativeTest.php index 4469e4c3659a..b9186257b6c8 100644 --- a/tests/system/Database/Live/MySQLi/NumberNativeTest.php +++ b/tests/system/Database/Live/MySQLi/NumberNativeTest.php @@ -44,7 +44,7 @@ public function testEnableNumberNative(): void { $this->tests['numberNative'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); if ($db1->DBDriver !== 'MySQLi') { $this->markTestSkipped('Only MySQLi can complete this test.'); @@ -57,7 +57,7 @@ public function testDisableNumberNative(): void { $this->tests['numberNative'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); if ($db1->DBDriver !== 'MySQLi') { $this->markTestSkipped('Only MySQLi can complete this test.'); @@ -70,7 +70,7 @@ public function testQueryDataAfterEnableNumberNative(): void { $this->tests['numberNative'] = true; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); if ($db1->DBDriver !== 'MySQLi') { $this->markTestSkipped('Only MySQLi can complete this test.'); @@ -88,7 +88,7 @@ public function testQueryDataAfterDisableNumberNative(): void { $this->tests['numberNative'] = false; - $db1 = Database::connect($this->tests); + $db1 = Database::connect($this->tests, false); if ($db1->DBDriver !== 'MySQLi') { $this->markTestSkipped('Only MySQLi can complete this test.'); diff --git a/tests/system/Database/Live/Postgre/ConnectTest.php b/tests/system/Database/Live/Postgre/ConnectTest.php index d616a60b968c..001fa222df20 100644 --- a/tests/system/Database/Live/Postgre/ConnectTest.php +++ b/tests/system/Database/Live/Postgre/ConnectTest.php @@ -47,7 +47,7 @@ public function testShowErrorMessageWhenSettingInvalidCharset(): void $group = $config->tests; // Sets invalid charset. $group['charset'] = 'utf8mb4'; - $db = Database::connect($group); + $db = Database::connect($group, false); // Actually connect to DB. $db->initialize(); diff --git a/tests/system/Database/Live/UpsertTest.php b/tests/system/Database/Live/UpsertTest.php index 000fa6fec7cb..99bf86ea72ac 100644 --- a/tests/system/Database/Live/UpsertTest.php +++ b/tests/system/Database/Live/UpsertTest.php @@ -253,18 +253,17 @@ public function testGetCompiledUpsert(): void break; case 'SQLSRV': - $expected = <<<'SQL' - MERGE INTO "test"."dbo"."db_user" - USING ( - VALUES ('Iran','ahmadinejad@world.com','Ahmadinejad') - ) "_upsert" ("country", "email", "name") - ON ("test"."dbo"."db_user"."email" = "_upsert"."email") - WHEN MATCHED THEN UPDATE SET - "country" = "_upsert"."country", - "name" = "_upsert"."name" - WHEN NOT MATCHED THEN INSERT ("country", "email", "name") - VALUES ("_upsert"."country", "_upsert"."email", "_upsert"."name"); - SQL; + $qualified = '"' . $this->db->getDatabase() . '"."dbo"."db_user"'; + $expected = 'MERGE INTO ' . $qualified . "\n" + . "USING (\n" + . "VALUES ('Iran','ahmadinejad@world.com','Ahmadinejad')\n" + . ') "_upsert" ("country", "email", "name")' . "\n" + . 'ON (' . $qualified . '."email" = "_upsert"."email")' . "\n" + . "WHEN MATCHED THEN UPDATE SET\n" + . "\"country\" = \"_upsert\".\"country\",\n" + . "\"name\" = \"_upsert\".\"name\"\n" + . 'WHEN NOT MATCHED THEN INSERT ("country", "email", "name")' . "\n" + . 'VALUES ("_upsert"."country", "_upsert"."email", "_upsert"."name");'; break; case 'OCI8': diff --git a/tests/system/Database/Live/WorkerModeTest.php b/tests/system/Database/Live/WorkerModeTest.php index a8c77d756da7..f614f8d68df1 100644 --- a/tests/system/Database/Live/WorkerModeTest.php +++ b/tests/system/Database/Live/WorkerModeTest.php @@ -30,7 +30,6 @@ final class WorkerModeTest extends CIUnitTestCase protected function tearDown(): void { parent::tearDown(); - $this->setPrivateProperty(Config::class, 'instances', []); } diff --git a/tests/system/Database/Migrations/MigrationRunnerTest.php b/tests/system/Database/Migrations/MigrationRunnerTest.php index 510c8169fa34..706d4f86fc6e 100644 --- a/tests/system/Database/Migrations/MigrationRunnerTest.php +++ b/tests/system/Database/Migrations/MigrationRunnerTest.php @@ -69,9 +69,7 @@ protected function tearDown(): void { parent::tearDown(); - // To delete data with `$this->regressDatabase()`, set it true. - $this->migrate = true; - $this->regressDatabase(); + $this->resetTables(); } public function testLoadsDefaultDatabaseWhenNoneSpecified(): void From d0910dec77d79a7909946ab4e2c99d9ddd508c9b Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 3 Aug 2026 23:50:37 +0200 Subject: [PATCH 2/6] test(Database): fix Oracle alias and Postgre CREATE DATABASE quoting in Registrar --- tests/_support/Config/Registrar.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index caefaac80479..4869617f9ad3 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -141,9 +141,13 @@ public static function Database(): array // so that we can test against multiple databases. $group = env('DB', 'SQLite3'); + if ($group === 'Oracle') { + $group = 'OCI8'; + } + $dbParams = self::$dbConfig[$group] ?? []; - if (! empty($dbParams) && $group !== 'SQLite3') { + if (! empty($dbParams) && ! in_array($group, ['SQLite3', 'OCI8'], true)) { $componentName = ''; foreach ($_SERVER['argv'] ?? [] as $arg) { @@ -179,7 +183,8 @@ public static function Database(): array $stmt = $pdo->prepare('SELECT 1 FROM pg_database WHERE datname = ?'); $stmt->execute([$dbParams['database']]); if (! $stmt->fetchColumn()) { - $pdo->exec('CREATE DATABASE ' . $pdo->quote($dbParams['database'])); + $dbName = str_replace('"', '""', $dbParams['database']); + $pdo->exec('CREATE DATABASE "' . $dbName . '"'); } } elseif ($group === 'SQLSRV') { $dsn = 'sqlsrv:Server=' . $dbParams['hostname'] . ',' . $dbParams['port'] . ';Encrypt=False;TrustServerCertificate=True'; From 43b0bbf2de1a94b72296db1b8bc60e73d86ce892 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 3 Aug 2026 23:56:10 +0200 Subject: [PATCH 3/6] ci(random-tests): install DB PHP extensions (incl. oci8) for Oracle platform --- .github/workflows/test-random-execution.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index e90de6bb031d..d8730ef2846f 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -177,7 +177,7 @@ jobs: uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php-version }} - extensions: gd, curl, iconv, json, mbstring, openssl, sodium + extensions: gd, curl, iconv, json, mbstring, openssl, sodium, mysqli, oci8, pgsql, sqlsrv, sqlite3 ini-values: opcache.enable_cli=0 coverage: none From d964c2c38acc16595125c2a57ceaeb2ee287fa61 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 00:26:02 +0200 Subject: [PATCH 4/6] ci(random-tests): run Oracle components sequentially OCI8 connects to a single shared schema (FREEPDB1) via DSN, so components cannot be isolated with per-component databases like MySQLi/Postgre/SQLSRV. Running Database and Commands in parallel makes Commands' migrate:rollback drop tables that Database tests rely on (ORA-00942/04043/08103). --- .github/workflows/test-random-execution.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index d8730ef2846f..773b5c6d0225 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -212,8 +212,15 @@ jobs: args+=("--component" "${{ inputs.component }}") fi - # Add --max-jobs flag if specified (empty means auto-detect) - if [[ -n "${{ inputs.max-jobs }}" ]]; then + # Add --max-jobs flag if specified (empty means auto-detect). + # OCI8 connects to a single shared schema (FREEPDB1) via DSN, so + # components cannot be isolated with per-component databases like + # MySQLi/Postgre/SQLSRV. Running components in parallel would make + # e.g. Commands' migrate:rollback drop tables that Database tests + # rely on. Run Oracle sequentially instead. + if [[ "${{ matrix.db-platform }}" == "Oracle" ]]; then + args+=("--max-jobs" "1") + elif [[ -n "${{ inputs.max-jobs }}" ]]; then args+=("--max-jobs" "${{ inputs.max-jobs }}") fi From a7c321045a59b2eebf55e62ce5dc539d91a039e0 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 01:29:57 +0200 Subject: [PATCH 5/6] ci: retrigger random-tests workflow From 4a20dc2cf245e94610c79645e210cfd46386fa93 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 01:49:28 +0200 Subject: [PATCH 6/6] test(Database): apply PR review suggestions for MigrationRunnerTest --- tests/system/Database/Migrations/MigrationRunnerTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/system/Database/Migrations/MigrationRunnerTest.php b/tests/system/Database/Migrations/MigrationRunnerTest.php index 706d4f86fc6e..510c8169fa34 100644 --- a/tests/system/Database/Migrations/MigrationRunnerTest.php +++ b/tests/system/Database/Migrations/MigrationRunnerTest.php @@ -69,7 +69,9 @@ protected function tearDown(): void { parent::tearDown(); - $this->resetTables(); + // To delete data with `$this->regressDatabase()`, set it true. + $this->migrate = true; + $this->regressDatabase(); } public function testLoadsDefaultDatabaseWhenNoneSpecified(): void