diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf02210 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + ci: + name: CI + uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.github/workflows/dispatch-ci.yml b/.github/workflows/dispatch-ci.yml new file mode 100644 index 0000000..5632551 --- /dev/null +++ b/.github/workflows/dispatch-ci.yml @@ -0,0 +1,16 @@ +name: Dispatch CI + +on: + # At 6:30 PM UTC, only on Sunday and Monday + schedule: + - cron: '30 18 * * 0,1' + +jobs: + dispatch-ci: + name: Dispatch CI + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Dispatch CI + uses: silverstripe/gha-dispatch-ci@v1 diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index 41f3410..d343ab7 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -1,9 +1,9 @@ name: Merge-up on: - # At 6:30 PM UTC, only on Sunday + # At 6:30 PM UTC, only on Thursday schedule: - - cron: '30 18 * * 0' + - cron: '30 18 * * 4' workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index 4fbb073..525642d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ /composer.lock +.phpunit.result.cache diff --git a/composer.json b/composer.json index f449e52..3da10e0 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,14 @@ }, "autoload": { "psr-4": { - "SilverStripe\\MD_PHP_CodeSniffer\\": "src/" + "SilverStripe\\MD_PHP_CodeSniffer\\": "src/", + "SilverStripe\\MD_PHP_CodeSniffer\\Tests\\": "tests/" } }, "bin": [ "bin/mdphpcs" - ] + ], + "require-dev": { + "phpunit/phpunit": "^9.6" + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d50172b --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + tests + tests/SnifferFixTest.php + + + tests/SnifferFixTest.php + + + diff --git a/src/Sniffer.php b/src/Sniffer.php index 12ea34c..2d569cd 100644 --- a/src/Sniffer.php +++ b/src/Sniffer.php @@ -119,6 +119,10 @@ private function prepareConfig(bool $usingExplicitStandard, string $lintLanguage // Creating the Config object populates it with all required settings based on the phpcs/phpcbf CLI arguments provided. $config = new Config(); + if (defined('PHP_CODESNIFFER_IN_TESTS') && PHP_CODESNIFFER_IN_TESTS) { + $config->files = [str_replace('/src', '/tests/fixtures', __DIR__ )]; + } + // We don't support STDIN for passing markdown in if ($config->stdin === true) { // 3 is the exit code phpcs uses for errors like this diff --git a/tests/CodeBlockTest.php b/tests/CodeBlockTest.php new file mode 100644 index 0000000..edb18ca --- /dev/null +++ b/tests/CodeBlockTest.php @@ -0,0 +1,50 @@ +assertSame('This is the content', $block->getContent()); + + $block->setContent('New content now'); + + $this->assertSame('New content now', $block->getContent()); + } + + public function testCleanup() + { + $config = new Config(); + $block = new CodeBlock('This is the content', new Ruleset($config), $config); + $block->cleanUp(); + + $this->assertSame('This is the content', $block->getContent()); + + $reflectionContent = new ReflectionProperty($block, 'content'); + $reflectionContent->setAccessible(true); + $reflectionFinalContent = new ReflectionProperty($block, 'finalContent'); + $reflectionFinalContent->setAccessible(true); + + $this->assertNull($reflectionContent->getValue($block)); + $this->assertSame('This is the content', $reflectionFinalContent->getValue($block)); + } +} diff --git a/tests/SnifferFixTest.php b/tests/SnifferFixTest.php new file mode 100644 index 0000000..211ec4a --- /dev/null +++ b/tests/SnifferFixTest.php @@ -0,0 +1,66 @@ + $v) { + $orig[$path] = file_get_contents($path); + } + + try { + ob_start(); + $exitCode = $sniffer->run('PHP', true, true); + $output = ob_get_clean(); + + // Validate that the files which should change did, and which shouldn't change didn't + foreach ($orig as $path => $content) { + $this->assertFileExists($path); + + if (str_contains($path, 'lint-with-problems')) { + $this->assertFileEquals(str_replace('/fixtures/', '/expected-after-fixing/', $path), $path); + } else { + $this->assertSame($content, file_get_contents($path)); + } + } + + // There are no remaining auto-fixable problems + $this->assertSame(1, $exitCode); + } finally { + // Put the original content back + foreach ($orig as $path => $content) { + file_put_contents($path, $content); + } + } + } + + private static function getFilesList(Sniffer $sniffer): FileList + { + $prepareConfig = new ReflectionMethod($sniffer, 'prepareConfig'); + $prepareConfig->setAccessible(true); + $config = $prepareConfig->invoke($sniffer, false, 'PHP', true); + + return new FileList($config, new Ruleset($config)); + } +} diff --git a/tests/SnifferTest.php b/tests/SnifferTest.php new file mode 100644 index 0000000..addfc2b --- /dev/null +++ b/tests/SnifferTest.php @@ -0,0 +1,141 @@ +setAccessible(true); + $blocks = $findFencedCodeblocks->invoke($sniffer, $files, 'PHP'); + + $blockKey = __DIR__ . $path; + + if ($exists) { + $this->assertArrayHasKey($blockKey, $blocks, 'block must be found'); + + $block = $blocks[$blockKey]; + $this->assertSame($blockKey, $block['path'], 'block path must be correct'); + $this->assertSame(__DIR__ . $realPath, $block['realpath'], 'block realpath must be correct'); + $this->assertSame($num, $block['num'], 'block must be numbered correctly'); + $this->assertSame($content, ltrim($block['content']), 'block content must be correct'); + } else { + $this->assertArrayNotHasKey($blockKey, $blocks, 'block must not be found'); + } + } + + public function provideFindFencedBlocks() + { + return [ + 'nothing to lint 1' => [ + 'path' => '/fixtures/nothing-to-lint.md', + 'exists' => false, + ], + 'nothing to lint 2' => [ + 'path' => '/fixtures/nothing-to-lint_1.md', + 'exists' => false, + ], + 'file paths all include block numbers' => [ + 'path' => '/fixtures/lint-but-no-problems.md', + 'exists' => false, + ], + [ + 'path' => '/fixtures/lint-but-no-problems_1.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-but-no-problems.md', + 'num' => 1, + 'content' => <<<'MD' + [ + 'path' => '/fixtures/lint-but-no-problems_2.md', + 'exists' => false, + ], + // No need to check lint-with-problems_1 and lint-with-problems_2 - they're functionality + // identical to lint-but-no-problems_1 for the purposes of this test. + 'language identifier not case sensitive' => [ + 'path' => '/fixtures/lint-with-problems_3.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-with-problems.md', + 'num' => 3, + 'content' => <<<'MD' + [ + 'path' => '/fixtures/lint-with-problems_4.md', + 'exists' => true, + 'realpath' => '/fixtures/lint-with-problems.md', + 'num' => 4, + 'content' => <<<'MD' + run('PHP', false, true); + $output = ob_get_clean(); + + // There are auto-fixable problems + $this->assertSame(2, $exitCode); + + // Check we didn't find problems where there aren't any + $this->assertStringNotContainsString('nothing-to-lint', $output, 'nothing to lint, so nothing found'); + $this->assertStringNotContainsString('lint-but-no-problems', $output, 'linted but no problems found'); + $this->assertStringNotContainsString('lint-with-problems_2', $output, 'that code block has no linting problems'); + + // Check we did find problems where there are plenty + $this->assertStringContainsString('lint-with-problems_1', $output); + $this->assertStringContainsString('lint-with-problems_3', $output); + $this->assertStringContainsString('lint-with-problems_4', $output); + } + + private static function getFilesList(Sniffer $sniffer): FileList + { + $prepareConfig = new ReflectionMethod($sniffer, 'prepareConfig'); + $prepareConfig->setAccessible(true); + $config = $prepareConfig->invoke($sniffer, false, 'PHP', true); + + return new FileList($config, new Ruleset($config)); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..7713bfa --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,28 @@ +