Skip to content

Commit

Permalink
Update tests and start testing code paths
Browse files Browse the repository at this point in the history
  • Loading branch information
oddevan committed Jan 20, 2024
1 parent cffcb1c commit 4fd2aa8
Show file tree
Hide file tree
Showing 29 changed files with 318 additions and 259 deletions.
53 changes: 0 additions & 53 deletions .github/workflows/documentation.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ jobs:
- name: Run PHPCS
run: composer run-script lint

- name: Run unit test suite
run: composer run-script test
- name: Run unit test suite with code coverage
run: composer run-script test-coverage
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
coverage.txt
test-coverage
.DS_Store
coverage.php
11 changes: 10 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
"Smolblog\\Framework\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Smolblog\\Test\\": "test-utils/"
}
},
"authors": [
{
"name": "Smolblog",
Expand All @@ -25,7 +30,11 @@
},
"scripts": {
"test": "phpunit --testsuite framework",
"test-coverage": "export XDEBUG_MODE=coverage; phpunit --testsuite framework",
"test-coverage": [
"@putenv XDEBUG_MODE=coverage",
"phpunit --testsuite framework",
"Smolblog\\Test\\CoverageReport::report"
],
"lint": "./vendor/squizlabs/php_codesniffer/bin/phpcs",
"lintfix": "./vendor/squizlabs/php_codesniffer/bin/phpcbf"
},
Expand Down
6 changes: 4 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<phpunit
bootstrap="tests/_bootstrap.php"
bootstrap="vendor/autoload.php"
colors="true"
testdox="true"
>
Expand All @@ -11,17 +11,19 @@
<coverage
cacheDirectory=".phpunit-cache"
ignoreDeprecatedCodeUnits="true"
pathCoverage="true"
>
<include>
<directory>src</directory>
</include>
<report>
<text showOnlySummary="true" />
<text outputFile="coverage.txt" showOnlySummary="true" />
<html outputDirectory="test-coverage" />
<php outputFile="coverage.php" />
</report>
</coverage>
<php>
<ini name="xdebug.mode" value="coverage"/>
<ini name="memory_limit" value="1024M"/>
</php>
</phpunit>
55 changes: 55 additions & 0 deletions test-utils/Constraints/EventIsEquivalent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Smolblog\Test\Constraints;

use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\InvalidArgumentException;
use SebastianBergmann\Comparator\ComparisonFailure;
use Smolblog\Framework\Messages\Event;

class EventIsEquivalent extends Constraint {
public function __construct(private Event $expected) {}

public function toString(): string { return 'two Events are equivalent'; }
protected function failureDescription($other): string { return $this->toString(); }

protected function matches($other): bool {
if (!is_a($other, Event::class)) {
throw new InvalidArgumentException('Object is not an Event.');
}

$expectedData = $this->expected->toArray();
unset($expectedData['id']);
unset($expectedData['timestamp']);

$actualData = $other->toArray();
unset($actualData['id']);
unset($actualData['timestamp']);

return $expectedData == $actualData;
}

protected function fail($other, $description, ?ComparisonFailure $comparisonFailure = null): void
{
if ($comparisonFailure === null) {
$expectedData = $this->expected->toArray();
unset($expectedData['id']);
unset($expectedData['timestamp']);

$actualData = $other->toArray();
unset($actualData['id']);
unset($actualData['timestamp']);

$comparisonFailure = new ComparisonFailure(
$this->expected,
$other,
$this->exporter()->export($expectedData),
$this->exporter()->export($actualData),
false,
'Failed asserting that two Events are equivalent.'
);
}

parent::fail($other, $description, $comparisonFailure);
}
}
72 changes: 72 additions & 0 deletions test-utils/Constraints/HttpMessageIsEquivalent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Smolblog\Test\Constraints;

use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\InvalidArgumentException;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use SebastianBergmann\Comparator\ComparisonFailure;

class HttpMessageIsEquivalent extends Constraint {
public function __construct(private RequestInterface|ResponseInterface $expected) {}

public function toString(): string { return 'two HTTP messages are equivalent'; }
protected function failureDescription($other): string { return $this->toString(); }

private function makeArray(RequestInterface|ResponseInterface $message): array {
$data = ['type' => ''];

foreach($message->getHeaders() as $key => $value) {
$lowerKey = strtolower($key);
$data[$lowerKey] = $value;
}

$bodyString = $message->getBody()->__toString();
$data['body'] = empty($bodyString) ? null : $bodyString;

if (is_a($message, RequestInterface::class)) {
$data['type'] .= 'Request';
$data['url'] = $message->getUri()->__toString();
$data['method'] = $message->getMethod();
}

if (is_a($message, ResponseInterface::class)) {
$data['type'] .= 'Response';
$data['code'] = $message->getStatusCode();
}

return $data;
}

protected function matches($other): bool {
if (!is_a($other, MessageInterface::class)) {
throw new InvalidArgumentException('Object is not an HTTP Message.');
}

$expectedData = $this->makeArray($this->expected);
$actualData = $this->makeArray($other);

return $expectedData == $actualData;
}

protected function fail($other, $description, ?ComparisonFailure $comparisonFailure = null): void
{
if ($comparisonFailure === null) {
$expectedData = $this->makeArray($this->expected);
$actualData = $this->makeArray($other);

$comparisonFailure = new ComparisonFailure(
$this->expected,
$other,
$this->exporter()->export($expectedData),
$this->exporter()->export($actualData),
false,
'Failed asserting that two HTTP messages are equivalent.'
);
}

parent::fail($other, $description, $comparisonFailure);
}
}
68 changes: 68 additions & 0 deletions test-utils/CoverageReport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Smolblog\Test;

use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeCoverage\Node\File;

class CoverageReport {
public static function report() {
$report = require __DIR__ . '/../coverage.php';
$baseDirCharCount = strlen(dirname(__DIR__) . '/src/');

$score = $report->getReport()->numberOfExecutedPaths() / $report->getReport()->numberOfExecutablePaths();

echo 'Total coverage: ' . floor($score * 100) . "%\n";

if ($score >= 1) {
echo "PASS\n\n";
return;
}

echo "FAIL\n\nThe following files have incomplete code coverage:\n";
foreach(self::getProblemFiles($report->getReport()) as $file) {
echo ' ' . substr($file->pathAsString(), $baseDirCharCount) . "\n";
}

echo "\nRun `composer test-coverage` and view the HTML report in the test-coverage folder.\n\n";

exit(1);
}

private static function getProblemFiles(Directory $dir): array {
return array_reduce(
$dir->directories(),
fn($all, $subDir) => array_merge($all, self::getProblemFiles($subDir)),
array_filter(
$dir->files(),
fn($file) => ($file->numberOfExecutablePaths() - $file->numberOfExecutedPaths()) > 0
),
);
}

private static function getFileMessage(File $file): string {
$output = $file->pathAsString() . ":\n";

foreach (array_filter($file->functions(), fn($fn) => $fn['coverage'] < 100) as $func) {
$output .= ' Function ' . $func['functionName'] . ': ' . ($func['executablePaths'] - $func['executedPaths']) . "\n";
}
foreach (array_filter($file->traits(), fn($fn) => $fn['coverage'] < 100) as $trait) {
$output .= ' Trait ' . $trait['traitName'] . ":\n" . self::getMethodMessages($trait['methods']);
}
foreach (array_filter($file->classes(), fn($fn) => $fn['coverage'] < 100) as $class) {
$output .= ' Class ' . $class['className'] . ":\n" . self::getMethodMessages($class['methods']);
}

$output .= "\n";
return $output;
}

private static function getMethodMessages(array $methods): string {
$output = '';
foreach (array_filter($methods, fn($mt) => $mt['coverage'] < 100) as $method) {
$output .= ' ' . $method['methodName'] . ': ' . ($method['executablePaths'] - $method['executedPaths']) . "\n";
}

return $output;
}
}
11 changes: 11 additions & 0 deletions test-utils/Kits/ActivityPubActivityTestKit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Smolblog\Test\Kits;

trait ActivityPubActivityTestKit {
use SerializableTestKit;

public function testItGivesTheCorrectType() {
$this->assertEquals($this->subject->type(), static::EXPECTED_TYPE);
}
}
18 changes: 18 additions & 0 deletions test-utils/Kits/DateIdentifierTestKit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Smolblog\Test\Kits;

use Smolblog\Framework\Objects\Identifier;

trait DateIdentifierTestKit {
/**
* Asserts that two identifiers are created from the same date. A v7 UUID hashes the date, then adds random bytes.
* This function trims the random bytes and compares the remaining data.
*/
private function assertIdentifiersHaveSameDate(Identifier $expected, Identifier $actual) {
$expectedTrim = substr(strval($expected), offset: 0, length: -8);
$actualTrim = substr(strval($actual), offset: 0, length: -8);

$this->assertEquals($expectedTrim, $actualTrim);
}
}
13 changes: 13 additions & 0 deletions test-utils/Kits/EventComparisonTestKit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Smolblog\Test\Kits;

use PHPUnit\Framework\Constraint\Constraint;
use Smolblog\Framework\Messages\Event;
use Smolblog\Test\Constraints\EventIsEquivalent;

trait EventComparisonTestKit {
private function eventEquivalentTo(Event $expected): Constraint {
return new EventIsEquivalent($expected);
}
}
14 changes: 14 additions & 0 deletions test-utils/Kits/HttpMessageComparisonTestKit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Smolblog\Test\Kits;

use PHPUnit\Framework\Constraint\Constraint;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Smolblog\Test\Constraints\HttpMessageIsEquivalent;

trait HttpMessageComparisonTestKit {
private function httpMessageEqualTo(RequestInterface|ResponseInterface $expected): Constraint {
return new HttpMessageIsEquivalent($expected);
}
}
15 changes: 15 additions & 0 deletions test-utils/Kits/SerializableTestKit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Smolblog\Test\Kits;

trait SerializableTestKit {
public function testItWillSerializeToArrayAndDeserializeToItself() {
$class = get_class($this->subject);
$this->assertEquals($this->subject, $class::fromArray($this->subject->toArray()));
}

public function testItWillSerializeToJsonAndDeserializeToItself() {
$class = get_class($this->subject);
$this->assertEquals($this->subject, $class::jsonDeserialize(json_encode($this->subject)));
}
}
Loading

0 comments on commit 4fd2aa8

Please sign in to comment.