-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add workflows for unit tests and static code analysis
- Loading branch information
1 parent
9a23176
commit f41c72a
Showing
5 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
name: Static Code Analysis | ||
|
||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- 1.x | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
tests: | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
php-version: | ||
- 8.2 | ||
- 8.3 | ||
|
||
name: Static Code Analysis | ||
runs-on: ubuntu-24.04 | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup PHP | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
php-version: ${{ matrix.php-version }} | ||
coverage: none | ||
|
||
- name: Get composer cache directory | ||
id: cache-dir | ||
run: | | ||
echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_OUTPUT | ||
- name: Cache Composer dependencies | ||
uses: actions/cache@v4 | ||
with: | ||
path: ${{ steps.cache-dir.outputs.COMPOSER_CACHE_DIR }} | ||
key: ${{ github.workflow }}-PHP_${{ matrix.php-version }}-${{ hashFiles('**/composer.json') }}-${{ github.run_id }} | ||
restore-keys: | | ||
${{ github.workflow }}-PHP_${{ matrix.php-version }}-${{ hashFiles('**/composer.json') }}- | ||
${{ github.workflow }}-PHP_${{ matrix.php-version }}- | ||
- name: Install dependencies | ||
run: composer update --ansi | ||
|
||
- name: Check - PHP-CS-Fixer | ||
run: composer cs:check | ||
|
||
- name: Check - PHPStan | ||
run: composer phpstan:check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
name: Unit Tests | ||
|
||
on: | ||
pull_request: | ||
push: | ||
branches: | ||
- 1.x | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
tests: | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
php-version: | ||
- 8.2 | ||
- 8.3 | ||
os: # always use the latest runners | ||
- ubuntu-24.04 | ||
- windows-2022 | ||
|
||
name: Unit Tests | ||
runs-on: ${{ matrix.os }} | ||
|
||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup PHP | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
php-version: ${{ matrix.php-version }} | ||
|
||
- name: Setup Git for Windows | ||
if: matrix.os == 'windows-2022' | ||
run: | | ||
git config --global core.autocrlf false | ||
git config --global core.eol lf | ||
- name: Get composer cache directory | ||
id: cache-dir | ||
shell: bash | ||
run: | | ||
echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_OUTPUT | ||
- name: Cache Composer dependencies | ||
uses: actions/cache@v4 | ||
with: | ||
path: ${{ steps.cache-dir.outputs.COMPOSER_CACHE_DIR }} | ||
key: ${{ github.workflow }}-PHP_${{ matrix.php-version }}-${{ matrix.os }}-${{ hashFiles('**/composer.json') }}-${{ github.run_id }} | ||
restore-keys: | | ||
${{ github.workflow }}-PHP_${{ matrix.php-version }}-${{ matrix.os }}-${{ hashFiles('**/composer.json') }}- | ||
${{ github.workflow }}-PHP_${{ matrix.php-version }}-${{ matrix.os }}- | ||
${{ github.workflow }}-PHP_${{ matrix.php-version }}- | ||
- name: Install dependencies | ||
run: composer update --ansi --no-scripts | ||
|
||
- name: Run Unit Tests | ||
run: | | ||
bin/parallel-phpunit ${{ env.COVERAGE_OPTION }} | ||
env: | ||
COVERAGE_OPTION: ${{ matrix.os == 'windows-2022' && '' || '--coverage' }} | ||
|
||
- name: Display structure of coverage files | ||
if: matrix.os != 'windows-2022' | ||
run: ls -la | ||
working-directory: build/cov | ||
|
||
- name: Merge coverage files into Clover | ||
if: matrix.os != 'windows-2022' | ||
run: | | ||
composer global require phpunit/phpcov --ansi | ||
phpcov merge --clover build/phpunit/clover.xml build/cov | ||
- name: Upload coverage to Coveralls | ||
if: matrix.os != 'windows-2022' | ||
run: | | ||
composer global require php-coveralls/php-coveralls --ansi | ||
php-coveralls --verbose --exclude-no-stmt --ansi --coverage_clover build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json | ||
env: | ||
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
COVERALLS_PARALLEL: true | ||
COVERALLS_FLAG_NAME: ${{ format('PHP {0}-{1}', matrix.php-version, matrix.os) }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ | |
]) | ||
->append([ | ||
__FILE__, | ||
'bin/parallel-phpunit', | ||
'bin/prune-cache', | ||
]) | ||
; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/usr/bin/env php | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* This file is part of the Nexus framework. | ||
* | ||
* (c) John Paul E. Balandan, CPA <[email protected]> | ||
* | ||
* For the full copyright and license information, please view | ||
* the LICENSE file that was distributed with this source code. | ||
*/ | ||
|
||
require __DIR__.'/parallel-phpunit.php'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* This file is part of the Nexus framework. | ||
* | ||
* (c) John Paul E. Balandan, CPA <[email protected]> | ||
* | ||
* For the full copyright and license information, please view | ||
* the LICENSE file that was distributed with this source code. | ||
*/ | ||
|
||
if (PHP_SAPI !== 'cli') { | ||
echo "\033[31mFAIL\033[0m This script must be run from the command line.\n"; | ||
|
||
exit(1); | ||
} | ||
|
||
$exit = 0; | ||
$args = $argv; | ||
$coverage = false; | ||
$runsOnGithubActions = (bool) getenv('GITHUB_ACTIONS'); | ||
|
||
if (in_array('--coverage', $args, true)) { | ||
$coverage = true; | ||
$args = array_flip($args); | ||
unset($args['--coverage']); | ||
$args = array_values(array_flip($args)); | ||
} | ||
|
||
$directory = $args[1] ?? 'src/Nexus'; | ||
$components = []; | ||
|
||
if (! is_dir($directory)) { | ||
echo sprintf("\033[31mFAIL\033[0m The \"%s\" directory does not exist.\n", $directory); | ||
|
||
exit(1); | ||
} | ||
|
||
$finder = new RecursiveDirectoryIterator($directory, FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::UNIX_PATHS); | ||
$finder = new RecursiveIteratorIterator($finder); | ||
$finder->setMaxDepth(3); | ||
|
||
/** @var SplFileInfo $splFileInfo */ | ||
foreach ($finder as $file => $splFileInfo) { | ||
if ('composer.json' === $file) { | ||
$components[] = dirname($splFileInfo->getPathname()); | ||
} | ||
} | ||
|
||
$githubActionsGroup = static function (string $component, string $message, int $exitCode) use ($runsOnGithubActions): string { | ||
if (! $runsOnGithubActions) { | ||
if ($exitCode > 0) { | ||
return sprintf("%2\$s\n\033[31mFAIL\033[0m %1\$s\n", $component, $message); | ||
} | ||
|
||
return sprintf("\n\033[32mOK\033[0m %1\$s\n", $component); | ||
} | ||
|
||
if ($exitCode > 0) { | ||
return sprintf("%1\$s\n%2\$s\n\033[31mFAIL\033[0m %1\$s\n", $component, $message); | ||
} | ||
|
||
return sprintf( | ||
<<<EOF | ||
::group::%1\$s | ||
%2\$s | ||
\033[32mOK\033[0m %1\$s | ||
::endgroup | ||
EOF, | ||
$component, | ||
$message, | ||
); | ||
}; | ||
$phpunitCommand = static function (string $component) use ($directory, $coverage): string { | ||
$coverageFile = sprintf('build/cov/%s-PHP_%s-%s.cov', basename($component), PHP_VERSION, PHP_OS_FAMILY); | ||
$cmd = implode(' ', [ | ||
'vendor/bin/phpunit', | ||
sprintf('--colors=%s', getenv('NO_COLOR') !== false ? 'never' : 'always'), | ||
'--group=unit', | ||
$coverage ? sprintf('--coverage-php %s', $coverageFile) : '--no-coverage', | ||
'tests/'.substr($component, strlen($directory) + 1), | ||
]); | ||
$redirects = sprintf(' > %1$s/phpunit.stdout 2> %1$s/phpunit.stderr', $component); | ||
|
||
if (DIRECTORY_SEPARATOR === '\\') { | ||
$cmd = sprintf('cmd /v:on /d /c "(%s)%s"', $cmd, $redirects); | ||
} else { | ||
$cmd .= $redirects; | ||
} | ||
|
||
return $cmd; | ||
}; | ||
$runningProcesses = []; | ||
|
||
foreach ($components as $component) { | ||
$process = proc_open($phpunitCommand($component), [], $pipes); | ||
|
||
if (false !== $process) { | ||
$runningProcesses[$component] = $process; | ||
} else { | ||
$exit = 1; | ||
echo "\n\033[31mFAIL\033[0m {$component}\n"; | ||
} | ||
} | ||
|
||
$lastOutput = null; | ||
$lastOutputTime = 0; | ||
|
||
while ([] !== $runningProcesses) { | ||
usleep(300_000); | ||
$terminatedProcesses = []; | ||
|
||
foreach ($runningProcesses as $component => $process) { | ||
$processStatus = proc_get_status($process); | ||
|
||
if (! $processStatus['running']) { | ||
$terminatedProcesses[$component] = [$processStatus['command'], $processStatus['exitcode']]; | ||
unset($runningProcesses[$component]); | ||
proc_close($process); | ||
} | ||
} | ||
|
||
if ([] === $terminatedProcesses && count($runningProcesses) === 1) { | ||
$component = key($runningProcesses); | ||
$process = $runningProcesses[$component]; | ||
|
||
$output = file_get_contents(sprintf('%s/phpunit.stdout', $component)); | ||
$output .= file_get_contents(sprintf('%s/phpunit.stderr', $component)); | ||
|
||
if ($lastOutput !== $output) { | ||
$lastOutput = $output; | ||
$lastOutputTime = microtime(true); | ||
} elseif (microtime(true) - $lastOutputTime > 60) { | ||
echo "\n\033[31mFAIL\033[0m Timeout {$component}\n"; | ||
|
||
if (DIRECTORY_SEPARATOR === '\\') { | ||
exec(sprintf('taskkill /F /T /PID %d 2>&1', proc_get_status($process)['pid'])); | ||
} else { | ||
proc_terminate($process); | ||
} | ||
} | ||
} | ||
|
||
foreach ($terminatedProcesses as $component => [$command, $status]) { | ||
$output = $command."\n\n"; | ||
|
||
foreach (['out', 'err'] as $file) { | ||
$file = sprintf('%s/phpunit.std%s', $component, $file); | ||
$output .= file_get_contents($file); | ||
unlink($file); | ||
} | ||
|
||
echo $githubActionsGroup($component, $output, $status); | ||
} | ||
} | ||
|
||
exit($exit); |