Browse Source

First set of tests

master
J. King 4 years ago
parent
commit
286cfe5b30
  1. 2
      .gitignore
  2. 146
      RoboFile.php
  3. 5
      composer.json
  4. 10
      lib/Exception.php
  5. 16
      lib/Parser/Entry.php
  6. 12
      lib/Parser/Exception.php
  7. 10
      lib/Parser/ExceptionSemantics.php
  8. 10
      lib/Parser/ExceptionSyntax.php
  9. 18
      lib/Parser/Feed.php
  10. 14
      robo
  11. 21
      robo.bat
  12. 20
      tests/bootstrap.php
  13. 43
      tests/cases/JSON/TestJSONFeed.php
  14. 42
      tests/cases/JSON/failures.json
  15. 26
      tests/phpunit.dist.xml
  16. 5
      vendor-bin/robo/composer.json
  17. 1402
      vendor-bin/robo/composer.lock

2
.gitignore

@ -1,2 +1,4 @@
/vendor
/vendor-bin/*/vendor
/tests/coverage
/tests/.phpunit.result.cache

146
RoboFile.php

@ -0,0 +1,146 @@
<?php
use Robo\Result;
const BASE = __DIR__.\DIRECTORY_SEPARATOR;
const BASE_TEST = BASE."tests".\DIRECTORY_SEPARATOR;
define("IS_WIN", defined("PHP_WINDOWS_VERSION_MAJOR"));
define("IS_MAC", php_uname("s") === "Darwin");
function norm(string $path): string {
$out = realpath($path);
if (!$out) {
$out = str_replace(["/", "\\"], \DIRECTORY_SEPARATOR, $path);
}
return $out;
}
class RoboFile extends \Robo\Tasks {
/** Runs the typical test suite
*
* Arguments passed to the task are passed on to PHPUnit. Thus one may, for
* example, run the following command and get the expected results:
*
* ./robo test --testsuite TTRSS --exclude-group slow --testdox
*
* Please see the PHPUnit documentation for available options.
*/
public function test(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
}
/** Runs the full test suite
*
* This includes pedantic tests which may help to identify problems.
* See help for the "test" task for more details.
*/
public function testFull(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
}
/**
* Runs a quick subset of the test suite
*
* See help for the "test" task for more details.
*/
public function testQuick(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
}
/** Produces a code coverage report
*
* By default this task produces an HTML-format coverage report in
* tests/coverage/. Additional reports may be produced by passing
* arguments to this task as one would to PHPUnit.
*
* Robo first tries to use pcov and will fall back first to xdebug then
* phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
* only need to be present in the extension load path to be used.
*/
public function coverage(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "coverage", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Produces a code coverage report, with redundant tests
*
* Depending on the environment, some tests that normally provide
* coverage may be skipped, while working alternatives are normally
* suppressed for reasons of time. This coverage report will try to
* run all tests which may cover code.
*
* See also help for the "coverage" task for more details.
*/
public function coverageFull(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Runs the coding standards fixer */
public function clean($opts = ['demo|d' => false]): Result {
$t = $this->taskExec(norm(BASE."vendor/bin/php-cs-fixer"));
$t->arg("fix");
if ($opts['demo']) {
$t->args("--dry-run", "--diff")->option("--diff-format", "udiff");
}
return $t->run();
}
protected function findCoverageEngine(): string {
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
$ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(BASE."lib");
if (extension_loaded("pcov")) {
return "$php -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (extension_loaded("xdebug")) {
return $php;
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d zend_extension=xdebug.$ext";
} else {
if (IS_WIN) {
$dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
$dbg = file_exists($dbg) ? $dbg : "";
} else {
$dbg = trim(`which phpdbg 2>/dev/null`);
}
if ($dbg) {
return escapeshellarg($dbg)." -qrr";
} else {
return $php;
}
}
}
protected function blackhole(bool $all = false): string {
$hole = IS_WIN ? "nul" : "/dev/null";
return $all ? ">$hole 2>&1" : "2>$hole";
}
protected function runTests(string $executor, string $set, array $args) : Result {
switch ($set) {
case "typical":
$set = ["--exclude-group", "optional"];
break;
case "quick":
$set = ["--exclude-group", "optional,slow"];
break;
case "coverage":
$set = ["--exclude-group", "optional,coverageOptional"];
break;
case "full":
$set = [];
break;
default:
throw new \Exception;
}
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
//$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
}
}

5
composer.json

@ -26,5 +26,10 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.3"
},
"autoload-dev": {
"psr-4": {
"JKingWeb\\Lax\\TestCase\\": "tests/cases/"
}
}
}

10
lib/Exception.php

@ -0,0 +1,10 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax;
abstract class Exception extends \Exception {
}

16
lib/Parser/Entry.php

@ -11,26 +11,26 @@ use JKingWeb\Lax\Category\Collection as CategoryCollection;
interface Entry {
/** General function to fetch the entry title */
abstract public function getTitle(): string;
public function getTitle(): string;
/** General function to fetch the categories of an entry */
abstract public function getCategories(): CategoryCollection;
public function getCategories(): CategoryCollection;
/** General function to fetch the entry identifier */
abstract public function getId(): string;
public function getId(): string;
/** General function to fetch a collection of people associated with an entry */
abstract public function getPeople(): PersonCollection;
public function getPeople(): PersonCollection;
/** General function to fetch the entry's modification date */
abstract public function getDateModified();
public function getDateModified();
/** General function to fetch the entry's creation date */
abstract public function getDateCreated();
public function getDateCreated();
/** General function to fetch the Web URL of the entry */
abstract public function getLink(): string;
public function getLink(): string;
/** General function to fetch the URL of a article related to the entry */
abstract public function getRelatedLink(): string;
public function getRelatedLink(): string;
}

12
lib/Parser/Exception.php

@ -0,0 +1,12 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\Parser;
use JKingWeb\Lax\Exception as BaseException;
abstract class Exception extends BaseException {
}

10
lib/Parser/ExceptionSemantics.php

@ -0,0 +1,10 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\Parser;
class ExceptionSemantics extends Exception {
}

10
lib/Parser/ExceptionSyntax.php

@ -0,0 +1,10 @@
<?php
/** @license MIT
* Copyright 2018 J. King et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\Parser;
class ExceptionSyntax extends Exception {
}

18
lib/Parser/Feed.php

@ -11,29 +11,29 @@ use JKingWeb\Lax\Category\Collection as CategoryCollection;
interface Feed {
/** General function to fetch the canonical feed URL */
abstract public function getUrl(): string;
public function getUrl(): string;
/** General function to fetch the feed title */
abstract public function getTitle(): string;
public function getTitle(): string;
/** General function to fetch the feed's Web-representation URL */
abstract public function getLink(): string;
public function getLink(): string;
/** General function to fetch the description of a feed */
abstract public function getSummary(): string;
public function getSummary(): string;
/** General function to fetch the categories of a feed */
abstract public function getCategories(): CategoryCollection;
public function getCategories(): CategoryCollection;
/** General function to fetch the feed identifier */
abstract public function getId(): string;
public function getId(): string;
/** General function to fetch a collection of people associated with a feed */
abstract public function getPeople(): PersonCollection;
public function getPeople(): PersonCollection;
/** General function to fetch the feed's modification date */
abstract public function getDateModified();
public function getDateModified();
/** General function to fetch the feed's modification date */
abstract public function getEntries() : array;
public function getEntries() : array;
}

14
robo

@ -0,0 +1,14 @@
#! /bin/sh
base=`dirname "$0"`
roboCommand="$1"
if [ $# -eq 0 ]; then
"$base/vendor/bin/robo"
else
shift
ulimit -n 2048
if [ "$1" = "clean" ]; then
"$base/vendor/bin/robo" "$roboCommand" "$@"
else
"$base/vendor/bin/robo" "$roboCommand" -- "$@"
fi
fi

21
robo.bat

@ -0,0 +1,21 @@
@echo off
setlocal
set base=%~dp0
set roboCommand=%1
rem get all arguments except the first
shift
set "args="
:parse
if "%~1" neq "" (
set args=%args% %1
shift
goto :parse
)
if defined args set args=%args:~1%
if "%1"=="clean" (
call "%base%vendor\bin\robo" "%roboCommand%" %args%
) else (
call "%base%vendor\bin\robo" "%roboCommand%" -- %args%
)

20
tests/bootstrap.php

@ -0,0 +1,20 @@
<?php
/** @license MIT
* Copyright 2018 J. King
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax;
const NS_BASE = __NAMESPACE__."\\";
define(NS_BASE."BASE", dirname(__DIR__).DIRECTORY_SEPARATOR);
const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR;
ini_set("memory_limit", "-1");
ini_set("zend.assertions", "1");
ini_set("assert.exception", "true");
error_reporting(\E_ALL);
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
if (function_exists("xdebug_set_filter")) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [BASE."lib/"]);
}

43
tests/cases/JSON/TestJSONFeed.php

@ -0,0 +1,43 @@
<?php
/** @license MIT
* Copyright 2018 J. King
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\TestCase\JSON;
use JKingWeb\Lax\Parser\JSON\Feed;
/** @covers JKingWeb\Lax\Parser\JSON\Feed<extended> */
class TestJSON extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideJSONFeedVersion1 */
public function testJSONFeedVersion1($input, string $type, $output): void {
if (is_array($input)) {
$input = json_encode($input);
} elseif (!is_string($input)) {
throw new \Exception("Test input is invalid");
}
if ($output instanceof \Exception) {
$this->expectExceptionObject($output);
new Feed($input, $type);
} else {
$this->assertTrue(false);
}
}
public function provideJSONFeedVersion1(): iterable {
foreach (new \GlobIterator(__DIR__."/*.json", \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::KEY_AS_FILENAME) as $file => $path) {
foreach (json_decode(file_get_contents($path), true) as $index => $test) {
if (isset($test['exception'])) {
$class = "\JKingWeb\Lax\Parser\Exception" . $test['exception'][0];
$test['output'] = new $class($test['exception'][1] ?? "");
}
yield "$file #$index: {$test['description']}" => [
$test['input'],
$test['type'] ?? "",
$test['output'],
];
}
}
}
}

42
tests/cases/JSON/failures.json

@ -0,0 +1,42 @@
[
{
"description": "Not valid JSON 1",
"input": "{",
"exception": ["Syntax", "notJSON"]
},
{
"description": "Not valid JSON 2",
"input": "<rss><channel/></rss>",
"exception": ["Syntax", "notJSON"]
},
{
"description": "Not a JSON feed 1",
"input": {},
"exception": ["Semantics", "notAFeed"]
},
{
"description": "Not a JSON feed 2",
"input": {"version": "https://example.com/"},
"exception": ["Semantics", "notAFeed"]
},
{
"description": "Unknown version 1",
"input": {"version": "https://jsonfeed.org/version/"},
"exception": ["Semantics", "unknownVersion"]
},
{
"description": "Unknown version 2",
"input": {"version": "https://jsonfeed.org/version/2"},
"exception": ["Semantics", "unknownVersion"]
},
{
"description": "Unknown version 3",
"input": {"version": "https://jsonfeed.org/version/11"},
"exception": ["Semantics", "unknownVersion"]
},
{
"description": "Unknown version 4",
"input": {"version": "https://jsonfeed.org/version/1.1"},
"exception": ["Semantics", "unknownVersion"]
}
]

26
tests/phpunit.dist.xml

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<phpunit
colors="true"
bootstrap="bootstrap.php"
cacheTokens="true"
convertErrorsToExceptions="false"
convertNoticesToExceptions="false"
convertWarningsToExceptions="false"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
forceCoversAnnotation="true"
executionOrder="defects"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../lib</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="JSON">
<directory suffix=".php">cases/JSON</directory>
</testsuite>
</testsuites>
</phpunit>

5
vendor-bin/robo/composer.json

@ -0,0 +1,5 @@
{
"require-dev": {
"consolidation/robo": "^2.0"
}
}

1402
vendor-bin/robo/composer.lock

File diff suppressed because it is too large
Loading…
Cancel
Save