From 7c0753c8e9f8bff2e65912bae408b2a7e59b3bcc Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 13 Jul 2026 21:50:12 +0200 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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 From 02f955429008056e4653da3fbbe63f54d9942986 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 20:58:59 +0200 Subject: [PATCH 07/16] ci: retrigger workflows From 2e954c23d1ff09c0bdaa315aee7363ce059b1e7b Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 22:11:24 +0200 Subject: [PATCH 08/16] ci(random-tests): remove max-jobs 1 restriction for Oracle --- .github/workflows/test-random-execution.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index 773b5c6d0225..d8730ef2846f 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -212,15 +212,8 @@ jobs: args+=("--component" "${{ inputs.component }}") fi - # 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 + # Add --max-jobs flag if specified (empty means auto-detect) + if [[ -n "${{ inputs.max-jobs }}" ]]; then args+=("--max-jobs" "${{ inputs.max-jobs }}") fi From c05d2474fa52498023d2455037ebd6d0aa2e94aa Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 22:22:19 +0200 Subject: [PATCH 09/16] test(Database): isolate OCI8 component tables with DBPrefix in Registrar --- tests/_support/Config/Registrar.php | 76 +++++++++++++++-------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index 4869617f9ad3..b2ace691fc88 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -147,7 +147,7 @@ public static function Database(): array $dbParams = self::$dbConfig[$group] ?? []; - if (! empty($dbParams) && ! in_array($group, ['SQLite3', 'OCI8'], true)) { + if (! empty($dbParams) && $group !== 'SQLite3') { $componentName = ''; foreach ($_SERVER['argv'] ?? [] as $arg) { @@ -161,43 +161,47 @@ public static function Database(): array } 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()) { - $dbName = str_replace('"', '""', $dbParams['database']); - $pdo->exec('CREATE DATABASE "' . $dbName . '"'); - } - } 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'); + if ($group === 'OCI8') { + $dbParams['DBPrefix'] = 'c_' . strtolower(substr($componentName, 0, 10)) . '_'; + } else { + $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()) { + $dbName = str_replace('"', '""', $dbParams['database']); + $pdo->exec('CREATE DATABASE "' . $dbName . '"'); + } + } 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 } - } catch (Throwable) { - // Ignore any error and let the connection fail naturally } } } From f0c534d978e428823290c9c7be6fe2944bb53853 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 22:32:16 +0200 Subject: [PATCH 10/16] test(Database): safely drop Oracle procedures and packages in migration down --- .../20160428212500_Create_test_tables.php | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 74fb2aa072f3..0eee609874a5 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -196,9 +196,25 @@ public function down(): void } if ($this->db->DBDriver === 'OCI8') { - $this->db->query('DROP PROCEDURE one'); - $this->db->query('DROP PROCEDURE plus'); - $this->db->query('DROP PACKAGE BODY calculator'); + try { + $this->db->query('DROP PROCEDURE one'); + } catch (Throwable) { + } + + try { + $this->db->query('DROP PROCEDURE plus'); + } catch (Throwable) { + } + + try { + $this->db->query('DROP PACKAGE BODY calculator'); + } catch (Throwable) { + } + + try { + $this->db->query('DROP PACKAGE calculator'); + } catch (Throwable) { + } } } } From 370a178774af6adbd56da6e3f7e6ed492e58ce51 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 22:38:14 +0200 Subject: [PATCH 11/16] test(Database): clean up migrations_lock table to prevent test state leakage --- tests/system/Database/Live/MetadataTest.php | 2 ++ tests/system/Database/Migrations/MigrationRunnerTest.php | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/system/Database/Live/MetadataTest.php b/tests/system/Database/Live/MetadataTest.php index b17a53930900..34c50cd571be 100644 --- a/tests/system/Database/Live/MetadataTest.php +++ b/tests/system/Database/Live/MetadataTest.php @@ -34,6 +34,8 @@ protected function setUp(): void { parent::setUp(); + Database::forge($this->DBGroup)->dropTable('migrations_lock', true); + $prefix = $this->db->getPrefix(); $tables = [ diff --git a/tests/system/Database/Migrations/MigrationRunnerTest.php b/tests/system/Database/Migrations/MigrationRunnerTest.php index 510c8169fa34..76a5a64a02d7 100644 --- a/tests/system/Database/Migrations/MigrationRunnerTest.php +++ b/tests/system/Database/Migrations/MigrationRunnerTest.php @@ -72,6 +72,7 @@ protected function tearDown(): void // To delete data with `$this->regressDatabase()`, set it true. $this->migrate = true; $this->regressDatabase(); + Database::forge($this->DBGroup)->dropTable('migrations_lock', true); } public function testLoadsDefaultDatabaseWhenNoneSpecified(): void From 72a35f59862b0f412e7e89fe46daef18f379e889 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 22:51:11 +0200 Subject: [PATCH 12/16] fix(Database): handle closed PgSql connection and import Throwable in Create_test_tables migration --- system/Database/Postgre/Connection.php | 16 ++++++++++++++-- .../20160428212500_Create_test_tables.php | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 4c3358a4b470..1badc6194313 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -22,6 +22,7 @@ use PgSql\Result as PgSqlResult; use stdClass; use Stringable; +use Throwable; /** * Connection for Postgre @@ -149,7 +150,10 @@ private function convertDSN() */ protected function _close() { - pg_close($this->connID); + if ($this->connID !== false) { + @pg_close($this->connID); + $this->connID = false; + } } /** @@ -157,7 +161,15 @@ protected function _close() */ protected function _ping(): bool { - return pg_ping($this->connID); + if ($this->connID === false) { + return false; + } + + try { + return pg_ping($this->connID); + } catch (Throwable) { + return false; + } } /** diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 0eee609874a5..4dc887df1efb 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -14,6 +14,7 @@ namespace Tests\Support\Database\Migrations; use CodeIgniter\Database\Migration; +use Throwable; class Migration_Create_test_tables extends Migration { From 83f1c97ce6a0ee384b8f4d379937fc92bd610688 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 23:01:44 +0200 Subject: [PATCH 13/16] ci(random-tests): run Oracle sequentially and increase per-component timeout to 600s --- .github/workflows/test-random-execution.yml | 26 ++++--- system/Database/Postgre/Connection.php | 8 ++- tests/_support/Config/Registrar.php | 76 ++++++++++----------- 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index d8730ef2846f..3149849e217e 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -51,7 +51,7 @@ on: description: Per-component timeout in seconds (0 disables) type: string required: false - default: '300' + default: '600' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -212,16 +212,24 @@ jobs: args+=("--component" "${{ inputs.component }}") fi - # Add --max-jobs flag if specified (empty means auto-detect) - if [[ -n "${{ inputs.max-jobs }}" ]]; then - args+=("--max-jobs" "${{ inputs.max-jobs }}") + # 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 makes + # e.g. Commands' migrate:rollback drop tables that Database tests + # rely on (ORA-00942/04043/08103). Run Oracle sequentially with + # default repeat 2 to avoid schema collisions and timeouts. + if [[ "${{ matrix.db-platform }}" == "Oracle" ]]; then + args+=("--max-jobs" "1") + args+=("--repeat" "${{ inputs.repeat || '2' }}") + else + if [[ -n "${{ inputs.max-jobs }}" ]]; then + args+=("--max-jobs" "${{ inputs.max-jobs }}") + fi + args+=("--repeat" "${{ inputs.repeat || '10' }}") fi - # Add --repeat flag (always, default is 10) - args+=("--repeat" "${{ inputs.repeat || '10' }}") - - # Add --timeout flag (always, default is 300) - args+=("--timeout" "${{ inputs.timeout || '300' }}") + # Add --timeout flag (always, default is 600) + args+=("--timeout" "${{ inputs.timeout || '600' }}") .github/scripts/run-random-tests.sh "${args[@]}" env: diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 1badc6194313..1aa5c62e25af 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -151,8 +151,12 @@ private function convertDSN() protected function _close() { if ($this->connID !== false) { - @pg_close($this->connID); - $this->connID = false; + try { + @pg_close($this->connID); + } catch (Throwable) { + } finally { + $this->connID = false; + } } } diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index b2ace691fc88..4869617f9ad3 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -147,7 +147,7 @@ public static function Database(): array $dbParams = self::$dbConfig[$group] ?? []; - if (! empty($dbParams) && $group !== 'SQLite3') { + if (! empty($dbParams) && ! in_array($group, ['SQLite3', 'OCI8'], true)) { $componentName = ''; foreach ($_SERVER['argv'] ?? [] as $arg) { @@ -161,47 +161,43 @@ public static function Database(): array } if ($componentName !== '') { - if ($group === 'OCI8') { - $dbParams['DBPrefix'] = 'c_' . strtolower(substr($componentName, 0, 10)) . '_'; - } else { - $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()) { - $dbName = str_replace('"', '""', $dbParams['database']); - $pdo->exec('CREATE DATABASE "' . $dbName . '"'); - } - } 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'); - } + $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()) { + $dbName = str_replace('"', '""', $dbParams['database']); + $pdo->exec('CREATE DATABASE "' . $dbName . '"'); + } + } 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 } + } catch (Throwable) { + // Ignore any error and let the connection fail naturally } } } From 7d64fd68b9514fabe48e77d7409b03447026d810 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 4 Aug 2026 23:41:13 +0200 Subject: [PATCH 14/16] ci(random-tests): set max-jobs to 2 for Oracle platform --- .github/workflows/test-random-execution.yml | 4 ++-- system/Database/Postgre/Connection.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index 3149849e217e..8446ea79fff9 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -219,8 +219,8 @@ jobs: # rely on (ORA-00942/04043/08103). Run Oracle sequentially with # default repeat 2 to avoid schema collisions and timeouts. if [[ "${{ matrix.db-platform }}" == "Oracle" ]]; then - args+=("--max-jobs" "1") - args+=("--repeat" "${{ inputs.repeat || '2' }}") + args+=("--max-jobs" "2") + args+=("--repeat" "${{ inputs.repeat || '10' }}") else if [[ -n "${{ inputs.max-jobs }}" ]]; then args+=("--max-jobs" "${{ inputs.max-jobs }}") diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 1aa5c62e25af..c1c693551fb3 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -152,7 +152,7 @@ protected function _close() { if ($this->connID !== false) { try { - @pg_close($this->connID); + pg_close($this->connID); } catch (Throwable) { } finally { $this->connID = false; From caf4aed8ae256a57c0155ac535f1267340cc86a9 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 5 Aug 2026 00:04:30 +0200 Subject: [PATCH 15/16] revert: restore original .github/workflows/test-random-execution.yml from develop --- .github/workflows/test-random-execution.yml | 28 ++++++++------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-random-execution.yml b/.github/workflows/test-random-execution.yml index 8446ea79fff9..e90de6bb031d 100644 --- a/.github/workflows/test-random-execution.yml +++ b/.github/workflows/test-random-execution.yml @@ -51,7 +51,7 @@ on: description: Per-component timeout in seconds (0 disables) type: string required: false - default: '600' + default: '300' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -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, mysqli, oci8, pgsql, sqlsrv, sqlite3 + extensions: gd, curl, iconv, json, mbstring, openssl, sodium ini-values: opcache.enable_cli=0 coverage: none @@ -212,24 +212,16 @@ jobs: args+=("--component" "${{ inputs.component }}") fi - # 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 makes - # e.g. Commands' migrate:rollback drop tables that Database tests - # rely on (ORA-00942/04043/08103). Run Oracle sequentially with - # default repeat 2 to avoid schema collisions and timeouts. - if [[ "${{ matrix.db-platform }}" == "Oracle" ]]; then - args+=("--max-jobs" "2") - args+=("--repeat" "${{ inputs.repeat || '10' }}") - else - if [[ -n "${{ inputs.max-jobs }}" ]]; then - args+=("--max-jobs" "${{ inputs.max-jobs }}") - fi - args+=("--repeat" "${{ inputs.repeat || '10' }}") + # Add --max-jobs flag if specified (empty means auto-detect) + if [[ -n "${{ inputs.max-jobs }}" ]]; then + args+=("--max-jobs" "${{ inputs.max-jobs }}") fi - # Add --timeout flag (always, default is 600) - args+=("--timeout" "${{ inputs.timeout || '600' }}") + # Add --repeat flag (always, default is 10) + args+=("--repeat" "${{ inputs.repeat || '10' }}") + + # Add --timeout flag (always, default is 300) + args+=("--timeout" "${{ inputs.timeout || '300' }}") .github/scripts/run-random-tests.sh "${args[@]}" env: From 8c848f1ffa0d9bc1acbe409de061f3930022237d Mon Sep 17 00:00:00 2001 From: Bogdan Date: Wed, 5 Aug 2026 00:11:43 +0200 Subject: [PATCH 16/16] fix(OCI8): define fallback OCI_COMMIT_ON_SUCCESS constant when oci8 extension is not loaded --- system/Database/OCI8/Connection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 44f2f48a2a59..a9339f31fb5b 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -20,6 +20,8 @@ use ErrorException; use stdClass; +defined('OCI_COMMIT_ON_SUCCESS') || define('OCI_COMMIT_ON_SUCCESS', 32); + /** * Connection for OCI8 *