├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── composer.json ├── phpcs.xml ├── phpunit-printer-context.png ├── phpunit-printer-logs.png ├── phpunit.xml.dist └── src ├── Functions └── helpers.php ├── Printer.php ├── Printer6.php ├── Printer7.php ├── Printer8.php └── Printer9.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | test/ export-ignore 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | jobs: 10 | run: 11 | env: 12 | ACTIONS_STEP_DEBUG: true 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macos-latest] 18 | php-versions: ["7.3", "7.4"] 19 | phpunit-version: ["6", "7", "8", "9"] 20 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.os }} (PHPUnit ${{ matrix.phpunit-version }}) 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-versions }} 29 | extensions: mbstring 30 | coverage: xdebug2 31 | 32 | - name: Set PHPUnit Version 33 | run: | 34 | $content = Get-Content -Path 'composer.json' | ConvertFrom-Json 35 | $content.{require-dev}.{phpunit/phpunit} = "^${{ matrix.phpunit-version }}" 36 | $content | ConvertTo-Json | Set-Content -Path 'composer.json' 37 | shell: pwsh 38 | 39 | - name: Composer dependencies 40 | run: composer install --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist 41 | 42 | - name: Set correct phpt file name 43 | id: phpt-filename 44 | run: | 45 | If (${{matrix.phpunit-version}} -eq 6) { 46 | '##[set-output name=version;]-phpunit6' 47 | } Else { 48 | '##[set-output name=version;]' 49 | } 50 | shell: pwsh 51 | 52 | - name: Configure tests for the current PHPUnit version 53 | run: | 54 | $V = (.\vendor\bin\phpunit --version | Out-String).trim() 55 | $content = Get-Content -Path 'test/states-test${{ steps.phpt-filename.outputs.version}}.phpt' 56 | $newContent = $content -replace '%%VERSION%%', $V 57 | $newContent | Set-Content -Path 'test/states-test${{ steps.phpt-filename.outputs.version}}.phpt' 58 | shell: pwsh 59 | 60 | - name: Run phpunit 61 | run: ./vendor/bin/phpunit 62 | 63 | - name: Run phpcs 64 | run: ./vendor/bin/phpcs 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## phpunit-github-actions-printer 2 | 3 | > There's a zero-config way to achieve this at [mheap/phpunit-matcher-action](https://github.com/mheap/phpunit-matcher-action) 4 | 5 | This is a PHPUnit printer that uses the `::error` and `::warning` functionality of GitHub Actions to add annotiations for failing test runs. It's main differentiator to the above is that it supports adding warnings in addition to errors. 6 | 7 | ![PHPUnit Action Matcher Logs Example](https://github.com/mheap/phpunit-github-actions-printer/blob/master/phpunit-printer-logs.png?raw=true) 8 | 9 | ![PHPUnit Action Matcher Context Example](https://github.com/mheap/phpunit-github-actions-printer/blob/master/phpunit-printer-context.png?raw=true) 10 | 11 | > If you're interested in learning more about GitHub Actions, [sign up here](https://michaelheap.com/building-github-actions/) 12 | 13 | ## Usage 14 | 15 | Add this printer to your project 16 | 17 | ```bash 18 | composer require --dev mheap/phpunit-github-actions-printer 19 | ``` 20 | 21 | When you run your tests, specify `mheap\GithubActionsReporter\Printer` as the printer to use 22 | 23 | ```bash 24 | ./vendor/bin/phpunit --printer mheap\\GithubActionsReporter\\Printer /path/to/tests 25 | ``` 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mheap/phpunit-github-actions-printer", 3 | "description": "PHPUnit Printer for adding test failures as annotations on GitHub Actions", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Michael Heap", 9 | "email": "m@michaelheap.com" 10 | } 11 | ], 12 | "autoload": { 13 | "files": ["src/Functions/helpers.php"], 14 | "psr-4": { 15 | "mheap\\GithubActionsReporter\\": "src" 16 | } 17 | }, 18 | "require": {}, 19 | "require-dev": { 20 | "phpunit/phpunit": "^9", 21 | "squizlabs/php_codesniffer": "3.*" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PSR12 4 | src 5 | 6 | 7 | -------------------------------------------------------------------------------- /phpunit-printer-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mheap/phpunit-github-actions-printer/19b34d79a6abfaa362debcd25dd7bd11e82576b8/phpunit-printer-context.png -------------------------------------------------------------------------------- /phpunit-printer-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mheap/phpunit-github-actions-printer/19b34d79a6abfaa362debcd25dd7bd11e82576b8/phpunit-printer-logs.png -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | ./test/Unit/ 19 | 20 | 21 | 22 | 23 | ./src 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Functions/helpers.php: -------------------------------------------------------------------------------- 1 | =') == true && 32 | ($upperVersion === true || version_compare($version, $upperVersion, '<=') == true) 33 | ) { 34 | return $class; 35 | } 36 | } 37 | 38 | return null; 39 | } 40 | 41 | /** 42 | * @param TestFailure $defect 43 | * @param string $defectType 44 | * 45 | * @return string 46 | * @throws \ReflectionException 47 | * @internal 48 | */ 49 | function printDefectTrace($defect, $defectType) 50 | { 51 | $e = $defect->thrownException(); 52 | 53 | $errorLines = array_filter( 54 | explode("\n", (string)$e), 55 | static function ($l) { 56 | return $l; 57 | } 58 | ); 59 | 60 | $error = end($errorLines); 61 | $lineIndex = strrpos($error, ":"); 62 | $path = substr($error, 0, $lineIndex); 63 | $line = substr($error, $lineIndex + 1); 64 | 65 | list($reflectedPath, $reflectedLine) = getReflectionFromTest( 66 | $defect->getTestName() 67 | ); 68 | 69 | if ($path !== $reflectedPath) { 70 | $path = $reflectedPath; 71 | $line = $reflectedLine; 72 | } 73 | 74 | $message = explode("\n", $defect->getExceptionAsString()); 75 | $message = implode('%0A', $message); 76 | 77 | // Some messages might contain paths. Let's convert thost to relative paths too 78 | $message = relativePath($message); 79 | $message = preg_replace('/%0A$/', '', $message); 80 | 81 | $path = relativePath($path); 82 | $file = "file={$path}"; 83 | $line = "line={$line}"; 84 | 85 | return "::{$defectType} $file,$line::{$message}\n"; 86 | } 87 | 88 | /** 89 | * @param string $path 90 | * 91 | * @return mixed 92 | * @internal 93 | */ 94 | function relativePath($path) 95 | { 96 | $relative = str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $path); 97 | 98 | // Translate \ in to / for Windows 99 | return str_replace('\\', '/', $relative); 100 | } 101 | 102 | /** 103 | * @param string $name 104 | * 105 | * @return array 106 | * @throws \ReflectionException 107 | * @internal 108 | */ 109 | function getReflectionFromTest($name) 110 | { 111 | list($klass, $method) = explode('::', $name); 112 | 113 | // Handle data providers 114 | $parts = explode(" ", $method, 2); 115 | if (count($parts) > 1) { 116 | $method = $parts[0]; 117 | } 118 | 119 | $c = new ReflectionClass($klass); 120 | $m = $c->getMethod($method); 121 | 122 | return [$m->getFileName(), $m->getStartLine()]; 123 | } 124 | -------------------------------------------------------------------------------- /src/Printer.php: -------------------------------------------------------------------------------- 1 | currentType = (in_array($type, ['error', 'failure']) === true) ? 'error' : 'warning'; 33 | 34 | foreach ($defects as $i => $defect) { 35 | $this->printDefect($defect, $i); 36 | } 37 | } 38 | 39 | protected function printDefectHeader(TestFailure $defect, $count): void 40 | { 41 | } 42 | 43 | /** 44 | * @throws \ReflectionException 45 | */ 46 | protected function printDefectTrace(TestFailure $defect): void 47 | { 48 | $this->write(printDefectTrace($defect, $this->currentType)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Printer7.php: -------------------------------------------------------------------------------- 1 | currentType = (in_array($type, ['error', 'failure']) === true) ? 'error' : 'warning'; 33 | 34 | foreach ($defects as $i => $defect) { 35 | $this->printDefect($defect, $i); 36 | } 37 | } 38 | 39 | protected function printDefectHeader(TestFailure $defect, int $count): void 40 | { 41 | } 42 | 43 | protected function printDefectTrace(TestFailure $defect): void 44 | { 45 | $this->write(printDefectTrace($defect, $this->currentType)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Printer8.php: -------------------------------------------------------------------------------- 1 | currentType = (in_array($type, ['error', 'failure']) === true) ? 'error' : 'warning'; 35 | 36 | foreach ($defects as $i => $defect) { 37 | $this->printDefect($defect, $i); 38 | } 39 | } 40 | 41 | protected function printDefectHeader(TestFailure $defect, int $count): void 42 | { 43 | } 44 | 45 | protected function printDefectTrace(TestFailure $defect): void 46 | { 47 | $this->write(printDefectTrace($defect, $this->currentType)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Printer9.php: -------------------------------------------------------------------------------- 1 | currentType = (in_array($type, ['error', 'failure']) === true) ? 'error' : 'warning'; 33 | 34 | foreach ($defects as $i => $defect) { 35 | $this->printDefect($defect, $i); 36 | } 37 | } 38 | 39 | protected function printDefectHeader(TestFailure $defect, int $count): void 40 | { 41 | } 42 | 43 | protected function printDefectTrace(TestFailure $defect): void 44 | { 45 | $this->write(printDefectTrace($defect, $this->currentType)); 46 | } 47 | } 48 | --------------------------------------------------------------------------------