Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ composer.phar
.phpunit.cache
/tests/.results/
.DS_Store
*.local
*.local

.claude
.cursor
12 changes: 6 additions & 6 deletions Kununu/CsFixer/Command/CsFixerGitHookCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,29 @@ private function installHook(string $gitPath): void
$sourceHook = __DIR__ . '/../Hooks/git-pre-commit';
$destHook = $hooksDir . '/pre-commit';

if (!is_dir($hooksDir) && !mkdir($hooksDir, 0777, true) && !is_dir($hooksDir)) {
if (!is_dir($hooksDir) && !@mkdir($hooksDir, 0777, true) && !is_dir($hooksDir)) {
throw new RuntimeException(sprintf(
'Could not create hooks directory: "%s".',
$hooksDir
));
}

if (file_exists($destHook) && !unlink($destHook)) {
if (file_exists($destHook) && !@unlink($destHook)) {
throw new RuntimeException(sprintf(
'Could not remove existing hook at "%s".',
$destHook
));
}

if (!copy($sourceHook, $destHook)) {
if (!@copy($sourceHook, $destHook)) {
throw new RuntimeException(sprintf(
'Failed to copy hook from "%s" to "%s".',
$sourceHook,
$destHook
));
}

if (!chmod($destHook, 0755)) {
if (!@chmod($destHook, 0755)) {
throw new RuntimeException(sprintf(
'Failed to make hook executable at "%s".',
$destHook
Expand Down Expand Up @@ -151,7 +151,7 @@ private function ensureSymlinkRelative(string $target, string $linkPath): void
{
$linkDir = dirname($linkPath);

if (!is_dir($linkDir) && !mkdir($linkDir, 0777, true) && !is_dir($linkDir)) {
if (!is_dir($linkDir) && !@mkdir($linkDir, 0777, true) && !is_dir($linkDir)) {
throw new RuntimeException(sprintf(
'Could not create directory for symlink: "%s".',
$linkDir
Expand All @@ -164,7 +164,7 @@ private function ensureSymlinkRelative(string $target, string $linkPath): void

$relativeTarget = $this->makeRelativePath($linkDir, $target);

if (!symlink($relativeTarget, $linkPath)) {
if (!@symlink($relativeTarget, $linkPath)) {
throw new RuntimeException(sprintf(
'Failed to create symlink from "%s" to "%s".',
$linkPath,
Expand Down
22 changes: 22 additions & 0 deletions tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,28 @@ public function testCsFixerCommandReturnsFailureWhenConfigNotFound(): void
self::assertSame(CsFixerCommand::FAILURE, $exitCode);
}

public function testCsFixerCommandReturnsFailureWhenProcessFails(): void
{
$this->tempFile = sys_get_temp_dir() . '/csfixer_' . uniqid('', true) . '.php';
file_put_contents($this->tempFile, "<?php\ndeclare(strict_types=1);\n");

$application = new Application();
$command = new CsFixerCommand();
method_exists($application, 'addCommand')
? $application->addCommand($command)
: $application->add($command);

$command = $application->find('kununu:cs-fixer');
$tester = new CommandTester($command);

$exitCode = $tester->execute([
'files' => [$this->tempFile],
'--extra-args' => ['--invalid-flag-xyz'],
]);

self::assertSame(CsFixerCommand::FAILURE, $exitCode);
}

protected function tearDown(): void
{
if ($this->tempFile !== null && is_file($this->tempFile)) {
Expand Down
132 changes: 132 additions & 0 deletions tests/Unit/Kununu/CsFixer/Command/CsFixerGitHookCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,126 @@ public function testInstallsHookSuccessfully(): void
self::assertTrue(is_link($symlinkBin), 'php-cs-fixer symlink should exist');
}

public function testReinstallRemovesExistingHookAndSymlinks(): void
{
chdir($this->repoDir);
exec('git init 2>/dev/null');

$vendorBase = $this->baseDir . '/vendor';
$codeToolsDir = $vendorBase . '/kununu/code-tools';
$binDir = $vendorBase . '/bin';
mkdir($codeToolsDir, 0777, true);
mkdir($binDir, 0777, true);
file_put_contents($codeToolsDir . '/php-cs-fixer.php', "<?php\n// stub\n");
file_put_contents($binDir . '/php-cs-fixer', "#!/usr/bin/env php\n<?php\n");
@chmod($binDir . '/php-cs-fixer', 0755);

$app = new Application();
$command = new CsFixerGitHookCommand();
method_exists($app, 'addCommand')
? $app->addCommand($command)
: $app->add($command);

$command = $app->find('kununu:cs-fixer-git-hook');
$tester = new CommandTester($command);

$tester->execute([]);

$exitCode = $tester->execute([]);

self::assertSame(CsFixerGitHookCommand::SUCCESS, $exitCode);
self::assertFileExists($this->repoDir . '/.git/hooks/pre-commit');
self::assertTrue(is_link($this->repoDir . '/.git/kununu/.php-cs-fixer.php'));
self::assertTrue(is_link($this->repoDir . '/.git/kununu/php-cs-fixer'));
}

public function testFailsWhenGitPathIsNotDirectory(): void
{
$mainRepo = $this->baseDir . '/main';
mkdir($mainRepo, 0777, true);
chdir($mainRepo);
exec('git init 2>/dev/null');

file_put_contents(
$this->repoDir . '/.git',
'gitdir: ' . $mainRepo . '/.git' . "\n"
);

chdir($this->repoDir);

$tester = $this->createCommandTester();

$exitCode = $tester->execute([]);

self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode);
self::assertStringContainsString('.git directory not found', $tester->getDisplay());
}

public function testFailsWhenHooksDirCannotBeCreated(): void
{
chdir($this->repoDir);
exec('git init 2>/dev/null');
exec('rm -rf ' . escapeshellarg($this->repoDir . '/.git/hooks'));
chmod($this->repoDir . '/.git', 0555);

$tester = $this->createCommandTester();

$exitCode = $tester->execute([]);

chmod($this->repoDir . '/.git', 0755);

self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode);
self::assertStringContainsString('Could not create hooks directory', $tester->getDisplay());
}

public function testFailsWhenHookCopyFails(): void
{
chdir($this->repoDir);
exec('git init 2>/dev/null');
chmod($this->repoDir . '/.git/hooks', 0555);

$tester = $this->createCommandTester();

$exitCode = $tester->execute([]);

chmod($this->repoDir . '/.git/hooks', 0755);

self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode);
self::assertStringContainsString('Failed to copy hook', $tester->getDisplay());
}

public function testFailsWhenExistingHookCannotBeRemoved(): void
{
chdir($this->repoDir);
exec('git init 2>/dev/null');

$hooksDir = $this->repoDir . '/.git/hooks';
file_put_contents($hooksDir . '/pre-commit', '#!/bin/sh');
chmod($hooksDir, 0555);

$tester = $this->createCommandTester();

$exitCode = $tester->execute([]);

chmod($hooksDir, 0755);

self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode);
self::assertStringContainsString('Could not remove existing hook', $tester->getDisplay());
}

public function testFailsWhenVendorDirNotFound(): void
{
chdir($this->repoDir);
exec('git init 2>/dev/null');

$tester = $this->createCommandTester();

$exitCode = $tester->execute([]);

self::assertSame(CsFixerGitHookCommand::FAILURE, $exitCode);
self::assertStringContainsString('Could not find vendor directory', $tester->getDisplay());
}

protected function setUp(): void
{
parent::setUp();
Expand All @@ -95,8 +215,20 @@ protected function tearDown(): void
{
chdir($this->oldCwd);
if (is_dir($this->baseDir)) {
exec('chmod -R 755 ' . escapeshellarg($this->baseDir) . ' 2>/dev/null');
exec('rm -rf ' . escapeshellarg($this->baseDir));
}
parent::tearDown();
}

private function createCommandTester(): CommandTester
{
$app = new Application();
$command = new CsFixerGitHookCommand();
method_exists($app, 'addCommand')
? $app->addCommand($command)
: $app->add($command);

return new CommandTester($app->find('kununu:cs-fixer-git-hook'));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class EmptyLineAfterClassElementsSniffTest extends SniffTestCase
{
public function testEmptyLineAfterClassElementsSniffer(): void
{
$this->assertSnifferFindsFixableErrors(new EmptyLineAfterClassElementsSniff(), 2, 2);
$this->assertSnifferFindsFixableErrors(new EmptyLineAfterClassElementsSniff(), 4, 4);
}

public function testEmptyLineAfterClassElementsFixer(): void
Expand Down
10 changes: 9 additions & 1 deletion tests/Unit/Kununu/Sniffs/Files/LineLengthSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ class LineLengthSniffTest extends SniffTestCase
{
public function testMethodSignatureArgumentsSniffer(): void
{
$this->assertSnifferFindsErrors(new LineLengthSniff(), 3);
$this->assertSnifferFindsErrors(new LineLengthSniff(), 4);
}

public function testIgnoreUseStatementsCoversUseBranch(): void
{
$sniff = new LineLengthSniff();
$sniff->ignoreUseStatements = true;

$this->assertSnifferFindsErrors($sniff, 4);
}
}
37 changes: 37 additions & 0 deletions tests/_data/EmptyLineAfterClassElements/after/Fixme.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,40 @@ public function someMethod(): void
$this->propertyOne = 'value';
}
}

class ExtraBlankAfterConst
{
public const string A = 'a';


public function doSomething(): void
{
}
}

class ExtraBlankAfterProp
{
private readonly string $prop;


public function doSomething(): void
{
}
}

class PropertyThenClose
{
protected string $onlyProperty;
}

class ConstantThenClose
{
public const string ONLY = 'only';
}

class NoPropertiesOrConstants
{
public function firstMethod(): void
{
}
}
39 changes: 39 additions & 0 deletions tests/_data/EmptyLineAfterClassElements/before/Fixme.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,42 @@ public function someMethod(): void
$this->propertyOne = 'value';
}
}

class ExtraBlankAfterConst
{
public const string A = 'a';



public function doSomething(): void
{
}
}

class ExtraBlankAfterProp
{
private readonly string $prop;



public function doSomething(): void
{
}
}

class PropertyThenClose
{
protected string $onlyProperty;
}

class ConstantThenClose
{
public const string ONLY = 'only';
}

class NoPropertiesOrConstants
{
public function firstMethod(): void
{
}
}
2 changes: 2 additions & 0 deletions tests/_data/LineLength/before/Fixme.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

namespace Kununu;

use Some\Very\Long\Namespace\Path\That\Definitely\Exceeds\The\Maximum\Line\Length\Of\One\Hundred\And\Twenty\Characters\In\Total;

class Fixme
{
public const VERY_BIG_STRING = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec nisl nec nunc tincidunt tincidunt. Nullam nec nisl nec nunc tincidunt tincidunt.';
Expand Down
16 changes: 16 additions & 0 deletions tests/_data/MethodSignatureArguments/after/Fixme.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,20 @@ public function signatureArgumentsShouldBeMultiLine(
string $thirdArgument
): void {
}

public function aReallyLongMethodNameWithNoParametersThatIsDefinitelyOverOneHundredAndTwentyCharactersForCoverage(): void
{
}

public function multilineWithFirstLineParamsAndLongEnough(string $firstParamWithLongName,
string $secondParamWithAlsoALongName
): void {
}

public function multiThree(
int $a,
int $b,
int $c
): void {
}
}
16 changes: 16 additions & 0 deletions tests/_data/MethodSignatureArguments/before/Fixme.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,20 @@ public function signatureArgumentsShouldBeSingleLine(
public function signatureArgumentsShouldBeMultiLine(string $firstArgument, string $secondArgument, string $thirdArgument): void
{
}

public function aReallyLongMethodNameWithNoParametersThatIsDefinitelyOverOneHundredAndTwentyCharactersForCoverage(): void
{
}

public function multilineWithFirstLineParamsAndLongEnough(string $firstParamWithLongName,
string $secondParamWithAlsoALongName
): void {
}

public function multiThree(
int $a,
int $b,
int $c
): void {
}
}
Loading