mustRun(); } public static function tearDownAfterClass(): void { self::recursiveRemoveDirectory(self::$tmpDir); parent::tearDownAfterClass(); } public function setUp(): void { @unlink(self::$tmpDir . '/psalm.xml'); copy(__DIR__ . '/../fixtures/DummyProjectWithErrors/src/FileWithErrors.php', self::$tmpDir . '/src/FileWithErrors.php'); parent::setUp(); } public function testHelpReturnsMessage(): void { $this->assertStringContainsString('Usage:', $this->runPsalm(['--help'], self::$tmpDir)['STDOUT']); } public function testVersion(): void { $this->assertStringStartsWith('Psalm 3', $this->runPsalm(['--version'], self::$tmpDir, false, false)['STDOUT']); } public function testInit(): void { $this->assertStringStartsWith('Calculating best config level based on project files', $this->runPsalmInit()['STDOUT']); $this->assertFileExists(self::$tmpDir . '/psalm.xml'); } public function testAlter(): void { $this->runPsalmInit(); $this->assertStringContainsString( 'No errors found!', $this->runPsalm(['--alter', '--issues=all'], self::$tmpDir, false, true)['STDOUT'] ); $this->assertSame(0, $this->runPsalm([], self::$tmpDir)['CODE']); } public function testPsalter(): void { $this->runPsalmInit(); (new Process(['php', $this->psalter, '--alter', '--issues=InvalidReturnType'], self::$tmpDir))->mustRun(); $this->assertSame(0, $this->runPsalm([], self::$tmpDir)['CODE']); } public function testPsalm(): void { $this->runPsalmInit(1); $result = $this->runPsalm([], self::$tmpDir, true); $this->assertStringContainsString('InvalidReturnType', $result['STDOUT']); $this->assertStringContainsString('InvalidReturnStatement', $result['STDOUT']); $this->assertStringContainsString('2 errors', $result['STDOUT']); $this->assertSame(1, $result['CODE']); } public function testLegacyConfigWithoutresolveFromConfigFile(): void { $this->runPsalmInit(1); $psalmXmlContent = file_get_contents(self::$tmpDir . '/psalm.xml'); $count = 0; $psalmXmlContent = preg_replace('/resolveFromConfigFile="true"/', '', $psalmXmlContent, -1, $count); $this->assertEquals(1, $count); file_put_contents(self::$tmpDir . '/src/psalm.xml', $psalmXmlContent); $process = new Process(['php', $this->psalm, '--config=src/psalm.xml'], self::$tmpDir); $process->run(); $this->assertSame(1, $process->getExitCode()); $this->assertStringContainsString('InvalidReturnType', $process->getOutput()); } /** * @return array{STDOUT: string, STDERR: string, CODE: int|null} */ private function runPsalmInit(?int $level = null): array { $args = ['--init']; if ($level) { $args[] = 'src'; $args[] = (string) $level; } return $this->runPsalm($args, self::$tmpDir, false, false); } /** from comment by itay at itgoldman dot com at * https://www.php.net/manual/en/function.rmdir.php#117354 */ private static function recursiveRemoveDirectory(string $src): void { $dir = opendir($src); while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { $full = $src . '/' . $file; if (is_dir($full)) { self::recursiveRemoveDirectory($full); } else { unlink($full); } } } closedir($dir); rmdir($src); } }