Skip to content

Commit

Permalink
Added verbose logging and timeout related options.
Browse files Browse the repository at this point in the history
  • Loading branch information
MattRink committed Aug 23, 2019
1 parent b3289ba commit f8e7c57
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# didibreakit - A basic smoke testing library designed for use in CI
# didibreakit
_A basic smoke testing library designed for use in CI_

## Installation
```shell
Expand All @@ -11,7 +12,8 @@ composer require mattrink/didibreakit
```

## Options
Currently the only optional argument is `--branch branchname`. This is to support branch based testing. If your CI process automatically creates environemnts for your branches this argument can be used to pass the name of the branch to the configuration. The branch can then be used in the `urlPattern` option via the `%branch%` replacement.
- `-b/--branch branchname` This is to support branch based testing. If your CI process automatically creates environemnts for your branches this argument can be used to pass the name of the branch to the configuration. The branch can then be used in the `urlPattern` option via the `%branch%` replacement.
- `-v/--verbose` Enables more in-depth logging and detail.

## Configuration
Configuration is done via a YAML file that is then passed as the only required argument to the command `./vendor/didibreakit tests:run example.yaml`. You can copy the `example.yaml` for a basic configuration to start from.
Expand All @@ -21,6 +23,8 @@ Within the `options` section of the config file you can provide some default con
- `urlPattern` The pattern to match for URLs. `%branch` is optional.
- `verifySSL` If set to false the SSL certificate for HTTPS will not be verified. Useful is using self-signed SSL certificates.
- `defaultURLs` Applies these URLs to the config for all hosts defined later.
- `timeout` The maximum request connection & read timeout before failing the request.
- `failOnTimeout` If set to false timeouts will not fail the whole execution.

You can specify the hosts and per-host URLs to check via the `hosts` option in the config. You can specify multiple hosts and URLs to check. The host key will be replaced into the `%host%` placeholder in the `urlPattern`. Along with each URL you also need to specify the expected HTTP status response, if a URL responds withs a status code that does not match the expected one the script will return a non-zero status code.

Expand All @@ -30,6 +34,8 @@ options:
defaultScheme: 'https'
urlPattern: '%branch%.%host%'
verifySSL: false
timeout: 5
failOnTimeout: false
defaultURLs:
/: 200

Expand Down
59 changes: 46 additions & 13 deletions src/Commands/RunTestsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

namespace MattRink\didibreakit\Commands;

use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use MattRink\didibreakit\ConfigLoader;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

Expand All @@ -21,14 +25,15 @@ protected function configure()
$this
->setDescription('Executes all tests defined in the specified yaml config.')
->addArgument('config-file', InputArgument::REQUIRED, 'The config file for this set of tests.')
->addOption('branch', 'b', InputArgument::OPTIONAL, 'The test branch to target.', '');
->addOption('branch', 'b', InputOption::VALUE_REQUIRED, 'The test branch to target.', '');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$returnCode = 0;
$configFile = $input->getArgument('config-file');
$branch = $input->getOption('branch');
$verbose = $input->getOption('verbose');
$hosts = ConfigLoader::load($configFile, $branch);

$handler = \GuzzleHttp\HandlerStack::create();
Expand All @@ -39,13 +44,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
foreach ($hosts as $host) {
$httpClient = new \GuzzleHttp\Client([
'verify' => $host->getOptions()->verifySSL(),
'timeout' => $host->getOptions()->getTimeout(),
'handler' => $handler,
]);

foreach ($host as $url => $expectedStatusCode) {
$uri = \GuzzleHttp\Psr7\Uri::composeComponents($host->getScheme(), $host->getHostName(), $url, '', '');
$request = new Request('GET', $uri);
$requests[] = [
'host' => $host,
'client' => $httpClient,
'request' => $request,
'uri' => $uri,
Expand All @@ -55,33 +62,59 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

// Set up a generator to send the requests
$promises = (function() use ($requests) {
foreach ($requests as $request) {
$promises = (function() use ($output, $requests, $verbose) {
foreach ($requests as $requestIndex => $request) {
$httpClient = $request['client'];
$uri = $request['uri'];
$expectedStatusCode = $request['expectedStatusCode'];

if ($verbose) {
$output->writeln("<info>[{$requestIndex}] Sending request to {$uri} expecting {$expectedStatusCode} response.</info>");
}

yield $httpClient->sendAsync($request['request']);
}
})();

// Function to handle responses
$handleResponse = function ($response, int $requestIndex) use ($output, $requests, &$returnCode) {
$handleResponse = function ($response, int $requestIndex) use ($output, $verbose, $requests, &$returnCode) {
$message = '';
$statusCode = 0;

if ($response instanceof RequestException) {
$statusCode = $response->getCode();
} else if ($response instanceof ResponseInterface) {
$statusCode = $response->getStatusCode();
}
$timeout = false;

$expectedStatusCode = $requests[$requestIndex]['expectedStatusCode'];
$uri = $requests[$requestIndex]['uri'];
$options = $requests[$requestIndex]['host']->getOptions();

$responseClass = get_class($response);

if ($verbose) {
$output->writeln("<info>[{$requestIndex}] Received {$responseClass} response for {$uri}.</info>");
}

switch ($responseClass) {
case ConnectException::class:
$timeout = $options->failOnTimeout();
case ClientException::class;
case RequestException::class:
$statusCode = $response->getCode();
$message = $response->getMessage();
break;
case Response::class:
$statusCode = $response->getStatusCode();
break;
}

if ($expectedStatusCode != $statusCode) {
$output->writeln("<error>{$uri} failed. Received {$statusCode} expected {$expectedStatusCode}.</error>");
if ($timeout || $expectedStatusCode != $statusCode) {
if ($verbose && !empty($message)) {
$output->writeln("<error>[{$requestIndex}] {$message}</error>");
}
$output->writeln("<error>[{$requestIndex}] {$uri} failed. Received status code {$statusCode} but expected {$expectedStatusCode}.</error>");
$returnCode = 1;
return;
}

$output->writeln("<info>{$uri}</info>");
$output->writeln("[{$requestIndex}] {$uri}");
};

// Create a pool/promise to handle the requests
Expand Down
14 changes: 14 additions & 0 deletions src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ final class Options
private $branch = '';
private $verifySSL = true;
private $defaultURLs = [];
private $timeout = 5;
private $failOnTimeout = true;

/**
* Left empty to prevent instantiating it directly.
Expand All @@ -33,6 +35,8 @@ public static function loadFromArray(array $configOptions, string $branch = '')
$options->branch = $branch;
$options->verifySSL = $configOptions['verifySSL'] ?? true;
$options->defaultURLs = $configOptions['defaultURLs'] ?? [];
$options->timeout = $configOptions['timeout'] ?? 5;
$options->failOnTimeout = $configOptions['failOnTimeout'] ?? true;

return $options;
}
Expand Down Expand Up @@ -66,4 +70,14 @@ public function getDefaultURLs() : array
{
return $this->defaultURLs;
}

public function getTimeout() : int
{
return $this->timeout;
}

public function failOnTimeout() : bool
{
return $this->failOnTimeout;
}
}

0 comments on commit f8e7c57

Please sign in to comment.