Skip to content

Commit

Permalink
Add workflows for unit tests and static code analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbalandan committed Aug 3, 2024
1 parent 9a23176 commit e459561
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 0 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/static-code-analysis.yml
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
87 changes: 87 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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
run: ls -la
working-directory: build/cov

- name: Merge coverage files into Clover
run: |
composer global require phpunit/phpcov --ansi
phpcov merge --clover build/phpunit/clover.xml build/cov
- name: Upload coverage to Coveralls
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) }}
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
])
->append([
__FILE__,
'bin/parallel-phpunit',
'bin/prune-cache',
])
;
Expand Down
15 changes: 15 additions & 0 deletions bin/parallel-phpunit
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';
161 changes: 161 additions & 0 deletions bin/parallel-phpunit.php
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);

0 comments on commit e459561

Please sign in to comment.