Skip to content

Commit

Permalink
Add functional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
johnstevenson committed Mar 26, 2024
1 parent da886fc commit 5b92607
Show file tree
Hide file tree
Showing 26 changed files with 2,105 additions and 0 deletions.
69 changes: 69 additions & 0 deletions .github/workflows/functional.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Functional Tests

on:
push:
paths-ignore: ["**.md"]
pull_request:
paths-ignore: ["**.md"]

env:
COMPOSER_FLAGS: --ansi --no-interaction --no-progress --prefer-dist

jobs:
tests:
name: Functional Tests

runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}

strategy:
matrix:
php-version:
- "7.2"
- "8.3"
os: [ubuntu-latest]
experimental: [false]
include:
- php-version: "7.2"
os: windows-latest
experimental: false
- php-version: "8.3"
os: windows-latest
experimental: false
- php-version: "8.4"
os: ubuntu-latest
experimental: true

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: xdebug
php-version: ${{ matrix.php-version }}

- name: Get composer cache directory
id: composercache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Restore cached dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composercache.outputs.dir }}
key: php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: php-${{ matrix.php-version }}-composer-

- name: Install latest dependencies
run: composer update ${{ env.COMPOSER_FLAGS }}

- name: Run tests
if: ${{ !matrix.experimental }}
run: vendor/bin/phpunit --group functional

# Show deprecations on PHP 8.4
- name: Run tests (experimental)
if: ${{ matrix.experimental }}
run: vendor/bin/phpunit --group functional --display-deprecations
6 changes: 6 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@
<directory>tests</directory>
</testsuite>
</testsuites>

<groups>
<exclude>
<group>functional</group>
</exclude>
</groups>
</phpunit>
185 changes: 185 additions & 0 deletions tests/App/Framework/AppHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

declare(strict_types=1);

/*
* This file is part of composer/xdebug-handler.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Composer\XdebugHandler\Tests\App\Framework;

use Composer\XdebugHandler\Process;
use Composer\XdebugHandler\XdebugHandler;

class AppHelper extends AppRunner
{
public const ENV_IS_DISPLAY = 'XDEBUG_HANDLER_TEST_DISPLAY';
public const ENV_SHOW_INIS = 'XDEBUG_HANDLER_TEST_INIS';

/** @var string */
private $appName;
/** @var string */
private $envPrefix;
/** @var non-empty-list<string> */
private $serverArgv;
/** @var class-string|null */
private $className;
/** @var bool */
private $display;
/** @var bool */
private $showInis;

/** @var Logger */
private $logger;

/** @var Output */
private $output;

/** @var Status */
private $status = null;

public function __construct(string $script)
{
parent::__construct(dirname($script));
$this->appName = $this->getAppName($script);
$this->envPrefix = $this->getEnvPrefix($script);

$this->display = $this->getDisplayFromServer();
$this->showInis = $this->getInisFromServer();
$this->setServerArgv();

$this->output = new Output($this->display);
$this->logger = new Logger($this->output);
$this->status = new Status($this->display, $this->showInis);
}

/**
*
* @return non-empty-list<string>
*/
public function getServerArgv(): array
{
return $this->serverArgv;
}

public function getServerArgv0(): string
{
return $this->getServerArgv()[0];
}

/**
*
* @param class-string $class
* @param array<string, string> $settings
*/
public function getXdebugHandler(?string $class = null, ?array $settings = null): XdebugHandler
{
if ($class === null) {
$class = XdebugHandler::class;
} elseif (!is_subclass_of($class, XdebugHandler::class)) {
throw new \RuntimeException($class.' must extend XdebugHandler');
}

$this->className = $class;

/** @var XdebugHandler $xdebug */
$xdebug = new $class($this->envPrefix);
$xdebug->setLogger($this->logger);

if (isset($settings['mainScript'])) {
$xdebug->setMainScript($settings['mainScript']);
}

if (isset($settings['persistent'])) {
$xdebug->setPersistent();
}

return $xdebug;
}

public function runScript(string $script, ?PhpOptions $options = null): void
{
parent::runScript($script, $options);
}

public function write(string $message): void
{
$this->logger->write($message, $this->appName);
}

public function writeXdebugStatus(): void
{
$className = $this->className ?? XdebugHandler::class;
$items = $this->status->getWorkingsStatus($className);

foreach($items as $item) {
$this->write('working '.$item);
}
}

private function setServerArgv(): void
{
$args = [];
$errors = false;

if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
foreach ($_SERVER['argv'] as $value) {
if (!is_string($value)) {
$errors = true;
break;
}

$args[] = $value;
}
}

if ($errors || count($args) === 0) {
throw new \RuntimeException('$_SERVER[argv] is not as expected');
}

$this->serverArgv = $args;
}

private function getDisplayFromServer(): bool
{
$result = false;

if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
$key = array_search('--display', $_SERVER['argv'], true);

if ($key !== false) {
$result = true;
Process::setEnv(self::ENV_IS_DISPLAY, '1');
unset($_SERVER['argv'][$key]);
} else {
$result = false !== getenv(self::ENV_IS_DISPLAY);
}
}

return $result;
}

private function getInisFromServer(): bool
{
$result = false;

if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
$key = array_search('--inis', $_SERVER['argv'], true);

if ($key !== false) {
$result = true;
Process::setEnv(self::ENV_SHOW_INIS, '1');
unset($_SERVER['argv'][$key]);
} else {
$result = false !== getenv(self::ENV_SHOW_INIS);
}
}

return $result;
}
}
101 changes: 101 additions & 0 deletions tests/App/Framework/AppRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

/*
* This file is part of composer/xdebug-handler.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Composer\XdebugHandler\Tests\App\Framework;

class AppRunner
{
/** @var string */
private $scriptDir;
/** @var PhpExecutor */
private $phpExecutor;

public function __construct(?string $scriptDir = null)
{
$this->scriptDir = (string) realpath($scriptDir ?? __DIR__);

if (!is_dir($this->scriptDir)) {
throw new \RuntimeException('Directory does not exist: '.$this->scriptDir);
}

$this->phpExecutor = new PhpExecutor();
}

public function run(string $script, ?PhpOptions $options = null, bool $allow = false): Logs
{
$script = $this->checkScript($script);

if ($options === null) {
$options = new PhpOptions();
}

// enforce output
$options->setPassthru(false);

if ($allow) {
$options->addEnv($this->getEnvAllow($script), '1');
}

$output = $this->phpExecutor->run($script, $options, $this->scriptDir);

$lines = preg_split("/\r\n|\n\r|\r|\n/", trim($output));
$outputLines = $lines !== false ? $lines : [];

return new Logs($outputLines);
}

public function runScript(string $script, ?PhpOptions $options = null): void
{
$script = $this->checkScript($script);

if ($options === null) {
$options = new PhpOptions();
}

// set passthru in child proccesses so output gets collected
$options->setPassthru(true);
$this->phpExecutor->run($script, $options, $this->scriptDir);
}

public function getAppName(string $script): string
{
return basename($script, '.php');
}

public function getEnvAllow(string $script): string
{
return sprintf('%s_ALLOW_XDEBUG', $this->getEnvPrefix($script));
}

public function getEnvPrefix(string $script): string
{
$name = $this->getAppName($script);

return strtoupper(str_replace(array('-', ' '), '_', $name));
}

private function checkScript(string $script): string
{
if (file_exists($script)) {
return $script;
}

$path = $this->scriptDir.'/'.$script;

if (file_exists($path)) {
return $path;
}

throw new \RuntimeException('File does not exist: '.$script);
}
}

0 comments on commit 5b92607

Please sign in to comment.