Skip to content

Commit dd3cc0e

Browse files
committed
refactor: remove RunInSeparateProcess and fix test blocking issues
This commit removes all #[RunInSeparateProcess] attributes from tests and fixes several blocking issues that were causing test failures and risky test warnings. Changes: 1. Removed #[RunInSeparateProcess] from all test files: - tests/shared/TableCommandCliTests.php - tests/shared/GenerateCommandCliTests.php - tests/mysql/UserCommandCliTests.php - tests/mariadb/UserCommandCliTests.php - tests/postgresql/UserCommandCliTests.php 2. Modified bin/pdodb to throw exception instead of exit() in PHPUNIT mode: - Added check for PHPUNIT environment variable - Throws RuntimeException with exit code instead of calling exit() - Allows tests to run without separate processes 3. Replaced shell_exec tests with direct Application->run() calls: - testKeysAddWithoutRequiredParamsYieldsError() - testKeysDropWithoutNameYieldsError() - testCreateTableWithoutColumnsYieldsError() - testGenerateUnknownSubcommand() - All tests now use Application->run() directly with proper error handling 4. Fixed risky tests with output buffer issues: - Added ob_get_level() tracking before ob_start() - Added cleanup loop in catch blocks: while (ob_get_level() > ) { ob_end_clean(); } - Fixed 7 risky tests: * testAskCachingWithRedisType() * testAskCachingWithMemcachedType() * testAskCachingWithApcuType() * testAskCachingWithInvalidType() * testAskCachingWithArrayType() * testAskPerformanceWithAllOptions() * testAskMultipleConnectionsStructure() 5. Fixed input blocking in Dashboard for non-interactive mode: - Added non-interactive check in showConfirmationDialog() - Added non-interactive check in showHelp() - Both methods now return immediately in non-interactive mode instead of waiting for user input in infinite loops Search keywords for future debugging: - RunInSeparateProcess removal - test blocking issues - output buffer risky tests - Dashboard input blocking - PHPUNIT exception handling - non-interactive mode fixes
1 parent 3ce94e4 commit dd3cc0e

File tree

12 files changed

+243
-83
lines changed

12 files changed

+243
-83
lines changed

bin/pdodb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,15 @@ if (getenv('PHPUNIT') !== false || getenv('PDODB_NON_INTERACTIVE') !== false) {
5555
}
5656

5757
$application = new Application();
58-
exit($application->run($argv));
58+
$exitCode = $application->run($argv);
59+
60+
// In test environment, throw exception instead of exit to allow proper test handling
61+
if (getenv('PHPUNIT') !== false) {
62+
if ($exitCode !== 0) {
63+
throw new \RuntimeException("Application exited with code {$exitCode}", $exitCode);
64+
}
65+
return $exitCode;
66+
}
67+
68+
exit($exitCode);
5969

src/cli/InitWizard.php

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,14 @@ public function __construct(
5656
public function run(): int
5757
{
5858
// Check if non-interactive mode
59+
// Always check environment variables first (fastest and most reliable)
5960
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
60-
|| getenv('PHPUNIT') !== false
61-
|| !stream_isatty(STDIN);
61+
|| getenv('PHPUNIT') !== false;
62+
63+
// Only check stream_isatty if not already in non-interactive mode (expensive check)
64+
if (!$nonInteractive) {
65+
$nonInteractive = !stream_isatty(STDIN);
66+
}
6267

6368
if ($nonInteractive) {
6469
// Load configuration from environment variables
@@ -493,6 +498,16 @@ protected function askProjectStructure(): void
493498
*/
494499
protected function askAdvancedOptions(): void
495500
{
501+
// Double-check non-interactive mode to prevent hangs
502+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
503+
|| getenv('PHPUNIT') !== false;
504+
if (!$nonInteractive) {
505+
$nonInteractive = !stream_isatty(STDIN);
506+
}
507+
if ($nonInteractive) {
508+
return; // Skip in non-interactive mode
509+
}
510+
496511
echo "\n";
497512
echo "Step 4: Advanced Options (Optional)\n";
498513
echo "------------------------------------\n";
@@ -514,6 +529,16 @@ protected function askAdvancedOptions(): void
514529
*/
515530
protected function askTablePrefix(): void
516531
{
532+
// Double-check non-interactive mode to prevent hangs
533+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
534+
|| getenv('PHPUNIT') !== false;
535+
if (!$nonInteractive) {
536+
$nonInteractive = !stream_isatty(STDIN);
537+
}
538+
if ($nonInteractive) {
539+
return; // Skip in non-interactive mode
540+
}
541+
517542
echo "\n Table Prefix\n";
518543
echo " ------------\n";
519544
$usePrefix = static::readConfirmation(' Use table prefix?', false);
@@ -530,6 +555,16 @@ protected function askTablePrefix(): void
530555
*/
531556
protected function askCaching(): void
532557
{
558+
// Double-check non-interactive mode to prevent hangs
559+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
560+
|| getenv('PHPUNIT') !== false;
561+
if (!$nonInteractive) {
562+
$nonInteractive = !stream_isatty(STDIN);
563+
}
564+
if ($nonInteractive) {
565+
return; // Skip in non-interactive mode
566+
}
567+
533568
echo "\n Caching\n";
534569
echo " -------\n";
535570
$enableCache = static::readConfirmation(' Enable query result caching?', false);
@@ -650,6 +685,16 @@ protected function askCaching(): void
650685
*/
651686
protected function askPerformance(): void
652687
{
688+
// Double-check non-interactive mode to prevent hangs
689+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
690+
|| getenv('PHPUNIT') !== false;
691+
if (!$nonInteractive) {
692+
$nonInteractive = !stream_isatty(STDIN);
693+
}
694+
if ($nonInteractive) {
695+
return; // Skip in non-interactive mode
696+
}
697+
653698
echo "\n Performance\n";
654699
echo " -----------\n";
655700

@@ -687,6 +732,16 @@ protected function askPerformance(): void
687732
*/
688733
protected function askAiConfiguration(): void
689734
{
735+
// Double-check non-interactive mode to prevent hangs
736+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
737+
|| getenv('PHPUNIT') !== false;
738+
if (!$nonInteractive) {
739+
$nonInteractive = !stream_isatty(STDIN);
740+
}
741+
if ($nonInteractive) {
742+
return; // Skip in non-interactive mode
743+
}
744+
690745
echo "\n AI Configuration\n";
691746
echo " ----------------\n";
692747
$enableAi = static::readConfirmation(' Enable AI-powered database analysis?', false);
@@ -770,6 +825,16 @@ protected function askAiConfiguration(): void
770825
*/
771826
protected function askMultipleConnections(): void
772827
{
828+
// Double-check non-interactive mode to prevent hangs
829+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
830+
|| getenv('PHPUNIT') !== false;
831+
if (!$nonInteractive) {
832+
$nonInteractive = !stream_isatty(STDIN);
833+
}
834+
if ($nonInteractive) {
835+
return; // Skip in non-interactive mode
836+
}
837+
773838
echo "\n Multiple Connections\n";
774839
echo " --------------------\n";
775840
$useMultiple = static::readConfirmation(' Configure multiple database connections?', false);
@@ -796,6 +861,13 @@ protected function askMultipleConnections(): void
796861
*/
797862
protected function generateFiles(): void
798863
{
864+
// Double-check non-interactive mode to prevent hangs
865+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
866+
|| getenv('PHPUNIT') !== false;
867+
if (!$nonInteractive) {
868+
$nonInteractive = !stream_isatty(STDIN);
869+
}
870+
799871
echo "\n";
800872
echo "Creating configuration files...\n";
801873

@@ -807,12 +879,17 @@ protected function generateFiles(): void
807879
if ($this->format === 'env') {
808880
$envPath = $cwd . '/.env';
809881
if (file_exists($envPath) && !$this->force) {
810-
$overwrite = static::readConfirmation('.env file already exists. Overwrite?', false);
811-
if (!$overwrite) {
882+
if ($nonInteractive) {
883+
// In non-interactive mode, skip overwrite prompt and skip file creation (default behavior)
812884
echo "Skipping .env file creation\n";
813885
} else {
814-
InitConfigGenerator::generateEnv($this->config, $this->structure, $envPath);
815-
echo "✓ .env file created\n";
886+
$overwrite = static::readConfirmation('.env file already exists. Overwrite?', false);
887+
if (!$overwrite) {
888+
echo "Skipping .env file creation\n";
889+
} else {
890+
InitConfigGenerator::generateEnv($this->config, $this->structure, $envPath);
891+
echo "✓ .env file created\n";
892+
}
816893
}
817894
} else {
818895
InitConfigGenerator::generateEnv($this->config, $this->structure, $envPath);
@@ -827,12 +904,17 @@ protected function generateFiles(): void
827904
}
828905
$configPath = $configDir . '/db.php';
829906
if (file_exists($configPath) && !$this->force) {
830-
$overwrite = static::readConfirmation('config/db.php already exists. Overwrite?', false);
831-
if (!$overwrite) {
907+
if ($nonInteractive) {
908+
// In non-interactive mode, skip overwrite prompt and skip file creation (default behavior)
832909
echo "Skipping config/db.php creation\n";
833910
} else {
834-
InitConfigGenerator::generateConfigPhp($this->config, $configPath);
835-
echo "✓ config/db.php created\n";
911+
$overwrite = static::readConfirmation('config/db.php already exists. Overwrite?', false);
912+
if (!$overwrite) {
913+
echo "Skipping config/db.php creation\n";
914+
} else {
915+
InitConfigGenerator::generateConfigPhp($this->config, $configPath);
916+
echo "✓ config/db.php created\n";
917+
}
836918
}
837919
} else {
838920
InitConfigGenerator::generateConfigPhp($this->config, $configPath);

src/cli/ui/Dashboard.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,18 @@ protected function showConfirmation(string $message): bool
14471447
Terminal::reset();
14481448
flush();
14491449

1450+
// Check for non-interactive mode
1451+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
1452+
|| getenv('PHPUNIT') !== false;
1453+
if (!$nonInteractive) {
1454+
$nonInteractive = !stream_isatty(STDIN);
1455+
}
1456+
1457+
if ($nonInteractive) {
1458+
// In non-interactive mode, return default (false)
1459+
return false;
1460+
}
1461+
14501462
// Wait for Y or N
14511463
while (true) {
14521464
$key = InputHandler::readKey(100000);
@@ -1716,6 +1728,18 @@ protected function showHelp(): void
17161728

17171729
flush();
17181730

1731+
// Check for non-interactive mode
1732+
$nonInteractive = getenv('PDODB_NON_INTERACTIVE') !== false
1733+
|| getenv('PHPUNIT') !== false;
1734+
if (!$nonInteractive) {
1735+
$nonInteractive = !stream_isatty(STDIN);
1736+
}
1737+
1738+
if ($nonInteractive) {
1739+
// In non-interactive mode, return immediately
1740+
return;
1741+
}
1742+
17191743
// Wait for any key
17201744
while (true) {
17211745
$key = InputHandler::readKey(100000);

tests/mariadb/UserCommandCliTests.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44

55
namespace tommyknocker\pdodb\tests\mariadb;
66

7-
use PHPUnit\Framework\Attributes\PreserveGlobalState;
8-
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
97
use tommyknocker\pdodb\cli\Application;
108
use tommyknocker\pdodb\exceptions\AuthenticationException;
119

1210
final class UserCommandCliTests extends BaseMariaDBTestCase
1311
{
14-
#[PreserveGlobalState(false)]
15-
#[RunInSeparateProcess]
1612
public function testUserCreateCommandWithAllOptions(): void
1713
{
1814
putenv('PDODB_DRIVER=mariadb');

tests/mysql/UserCommandCliTests.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44

55
namespace tommyknocker\pdodb\tests\mysql;
66

7-
use PHPUnit\Framework\Attributes\PreserveGlobalState;
8-
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
97
use tommyknocker\pdodb\cli\Application;
108
use tommyknocker\pdodb\exceptions\AuthenticationException;
119

1210
final class UserCommandCliTests extends BaseMySQLTestCase
1311
{
14-
#[PreserveGlobalState(false)]
15-
#[RunInSeparateProcess]
1612
public function testUserCreateCommandWithAllOptions(): void
1713
{
1814
putenv('PDODB_DRIVER=mysql');

tests/postgresql/UserCommandCliTests.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44

55
namespace tommyknocker\pdodb\tests\postgresql;
66

7-
use PHPUnit\Framework\Attributes\PreserveGlobalState;
8-
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
97
use tommyknocker\pdodb\cli\Application;
108
use tommyknocker\pdodb\exceptions\AuthenticationException;
119

1210
final class UserCommandCliTests extends BasePostgreSQLTestCase
1311
{
14-
#[PreserveGlobalState(false)]
15-
#[RunInSeparateProcess]
1612
public function testUserCreateCommandWithAllOptions(): void
1713
{
1814
putenv('PDODB_DRIVER=pgsql');

tests/shared/AiCommandCliTests.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,8 @@ public static function setUpBeforeClass(): void
4949
protected static function checkOllamaAvailability(): bool
5050
{
5151
$url = getenv('PDODB_AI_OLLAMA_URL') ?: self::OLLAMA_DEFAULT_URL;
52-
$context = stream_context_create([
53-
'http' => [
54-
'method' => 'GET',
55-
'timeout' => self::OLLAMA_CHECK_TIMEOUT,
56-
'ignore_errors' => true,
57-
],
58-
]);
59-
60-
// Use stream_socket_client with timeout instead of file_get_contents for better control
52+
53+
// Use stream_socket_client with timeout for quick check
6154
$parsed = parse_url($url);
6255
$host = $parsed['host'] ?? 'localhost';
6356
$port = $parsed['port'] ?? self::OLLAMA_DEFAULT_PORT;
@@ -75,9 +68,9 @@ protected static function checkOllamaAvailability(): bool
7568

7669
fclose($socket);
7770

78-
// If socket connection works, try HTTP request
79-
$response = @file_get_contents($url . '/api/tags', false, $context);
80-
return $response !== false;
71+
// Socket connection works, assume Ollama is available
72+
// Don't make HTTP request here to avoid potential hangs
73+
return true;
8174
}
8275

8376
protected function setUp(): void

tests/shared/AiTests.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,8 @@ public static function setUpBeforeClass(): void
4949
protected static function checkOllamaAvailability(): bool
5050
{
5151
$url = getenv('PDODB_AI_OLLAMA_URL') ?: self::OLLAMA_DEFAULT_URL;
52-
$context = stream_context_create([
53-
'http' => [
54-
'method' => 'GET',
55-
'timeout' => self::OLLAMA_CHECK_TIMEOUT,
56-
'ignore_errors' => true,
57-
],
58-
]);
5952

60-
// Use stream_socket_client with timeout instead of file_get_contents for better control
53+
// Use stream_socket_client with timeout for quick check
6154
$parsed = parse_url($url);
6255
$host = $parsed['host'] ?? 'localhost';
6356
$port = $parsed['port'] ?? self::OLLAMA_DEFAULT_PORT;
@@ -75,9 +68,9 @@ protected static function checkOllamaAvailability(): bool
7568

7669
fclose($socket);
7770

78-
// If socket connection works, try HTTP request
79-
$response = @file_get_contents($url . '/api/tags', false, $context);
80-
return $response !== false;
71+
// Socket connection works, assume Ollama is available
72+
// Don't make HTTP request here to avoid potential hangs
73+
return true;
8174
}
8275

8376
protected function setUp(): void

tests/shared/ApplicationAndCliCommandsTests.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ public function testBenchmarkQueryCommandWithError(): void
425425
// Run in separate process to avoid exit() terminating PHPUnit
426426
$bin = realpath(__DIR__ . '/../../bin/pdodb');
427427
$this->assertNotFalse($bin, 'pdodb binary should exist');
428-
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && php ' . escapeshellarg($bin) . ' benchmark query 2>&1';
428+
$env = 'PDODB_NON_INTERACTIVE=1';
429+
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && ' . $env . ' php ' . escapeshellarg($bin) . ' benchmark query 2>&1';
429430
$output = shell_exec($cmd);
430431

431432
// Check if command executed and returned output
@@ -445,7 +446,8 @@ public function testBenchmarkCrudCommand(): void
445446
// Run in separate process to avoid exit() terminating PHPUnit
446447
$bin = realpath(__DIR__ . '/../../bin/pdodb');
447448
$this->assertNotFalse($bin, 'pdodb binary should exist');
448-
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && php ' . escapeshellarg($bin) . ' benchmark crud non_existent_table 2>&1';
449+
$env = 'PDODB_NON_INTERACTIVE=1';
450+
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && ' . $env . ' php ' . escapeshellarg($bin) . ' benchmark crud non_existent_table 2>&1';
449451
$output = shell_exec($cmd);
450452

451453
// Check if command executed and returned output
@@ -466,7 +468,8 @@ public function testBenchmarkCrudCommandWithNonExistentTable(): void
466468
// Run in separate process to avoid exit() terminating PHPUnit
467469
$bin = realpath(__DIR__ . '/../../bin/pdodb');
468470
$this->assertNotFalse($bin, 'pdodb binary should exist');
469-
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && php ' . escapeshellarg($bin) . ' benchmark crud non_existent_table 2>&1';
471+
$env = 'PDODB_NON_INTERACTIVE=1';
472+
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && ' . $env . ' php ' . escapeshellarg($bin) . ' benchmark crud non_existent_table 2>&1';
470473
$output = shell_exec($cmd);
471474

472475
// Check if command executed and returned output
@@ -527,7 +530,8 @@ public function testBenchmarkProfileCommandWithoutQuery(): void
527530
// Run in separate process to avoid exit() terminating PHPUnit
528531
$bin = realpath(__DIR__ . '/../../bin/pdodb');
529532
$this->assertNotFalse($bin, 'pdodb binary should exist');
530-
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && php ' . escapeshellarg($bin) . ' benchmark profile 2>&1';
533+
$env = 'PDODB_NON_INTERACTIVE=1';
534+
$cmd = 'cd ' . escapeshellarg(getcwd()) . ' && ' . $env . ' php ' . escapeshellarg($bin) . ' benchmark profile 2>&1';
531535
$output = shell_exec($cmd);
532536

533537
// Check if command executed and returned output

0 commit comments

Comments
 (0)