[BridgeImplementationTest] Refactor unit test to check bridges (#980)
This commit is contained in:
parent
245af35a60
commit
600f2290b6
1 changed files with 160 additions and 144 deletions
|
@ -1,188 +1,204 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use PHPUnit\Framework\TestResult;
|
|
||||||
use PHPUnit\Framework\AssertionFailedError;
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../lib/rssbridge.php';
|
require_once __DIR__ . '/../lib/rssbridge.php';
|
||||||
|
|
||||||
/**
|
use PHPUnit\Framework\TestCase;
|
||||||
* This class checks bridges for implementation details:
|
|
||||||
*
|
|
||||||
* - A bridge must not implement public functions other than the ones specified
|
|
||||||
* by the bridge interfaces. Custom functions must be defined in private or
|
|
||||||
* protected scope.
|
|
||||||
* - getName() must return a valid string (non-empty)
|
|
||||||
* - getURI() must return a valid URI
|
|
||||||
* - A bridge must define constants for NAME, URI, DESCRIPTION and MAINTAINER,
|
|
||||||
* CACHE_TIMEOUT and PARAMETERS are optional
|
|
||||||
*/
|
|
||||||
final class BridgeImplementationTest extends TestCase {
|
|
||||||
private function CheckBridgePublicFunctions($bridgeName){
|
|
||||||
|
|
||||||
$parent_methods = array();
|
class BridgeImplementationTest extends TestCase {
|
||||||
|
private $class;
|
||||||
if(in_array('BridgeInterface', class_parents($bridgeName))) {
|
private $obj;
|
||||||
$parent_methods = array_merge($parent_methods, get_class_methods('BridgeInterface'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(in_array('BridgeAbstract', class_parents($bridgeName))) {
|
|
||||||
$parent_methods = array_merge($parent_methods, get_class_methods('BridgeAbstract'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(in_array('FeedExpander', class_parents($bridgeName))) {
|
|
||||||
$parent_methods = array_merge($parent_methods, get_class_methods('FeedExpander'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive all non abstract methods
|
|
||||||
$methods = array_diff(get_class_methods($bridgeName), $parent_methods);
|
|
||||||
$method_names = implode(', ', $methods);
|
|
||||||
|
|
||||||
$errmsg = $bridgeName
|
|
||||||
. ' implements additional public method(s): '
|
|
||||||
. $method_names
|
|
||||||
. '! Custom functions must be defined in private or protected scope!';
|
|
||||||
|
|
||||||
$this->assertEmpty($method_names, $errmsg);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testClassName($path) {
|
||||||
|
$this->setBridge($path);
|
||||||
|
$this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character');
|
||||||
|
$this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces');
|
||||||
|
$this->assertStringEndsWith('Bridge', $this->class, 'class name must end with "Bridge"');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function CheckBridgeGetNameDefaultValue($bridgeName){
|
/**
|
||||||
|
* @dataProvider dataBridgesProvider
|
||||||
if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge
|
*/
|
||||||
|
public function testClassType($path) {
|
||||||
if(!$this->isFunctionMemberOf($bridgeName, 'getName'))
|
$this->setBridge($path);
|
||||||
return;
|
$this->assertInstanceOf(BridgeInterface::class, $this->obj);
|
||||||
|
|
||||||
$bridge = new $bridgeName();
|
|
||||||
$abstract = new BridgeAbstractTest();
|
|
||||||
|
|
||||||
$message = $bridgeName . ': \'getName\' must return a valid name!';
|
|
||||||
|
|
||||||
$this->assertNotEmpty(trim($bridge->getName()), $message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether the getURI function returns empty or default values
|
/**
|
||||||
private function CheckBridgeGetURIDefaultValue($bridgeName){
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testConstants($path) {
|
||||||
|
$this->setBridge($path);
|
||||||
|
|
||||||
if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge
|
$this->assertInternalType('string', $this->obj::NAME, 'class::NAME');
|
||||||
|
$this->assertNotEmpty($this->obj::NAME, 'class::NAME');
|
||||||
if(!$this->isFunctionMemberOf($bridgeName, 'getURI'))
|
$this->assertInternalType('string', $this->obj::URI, 'class::URI');
|
||||||
return;
|
$this->assertNotEmpty($this->obj::URI, 'class::URI');
|
||||||
|
$this->assertInternalType('string', $this->obj::DESCRIPTION, 'class::DESCRIPTION');
|
||||||
$bridge = new $bridgeName();
|
$this->assertNotEmpty($this->obj::DESCRIPTION, 'class::DESCRIPTION');
|
||||||
$abstract = new BridgeAbstractTest();
|
$this->assertInternalType('string', $this->obj::MAINTAINER, 'class::MAINTAINER');
|
||||||
|
$this->assertNotEmpty($this->obj::MAINTAINER, 'class::MAINTAINER');
|
||||||
$message = $bridgeName . ': \'getURI\' must return a valid URI!';
|
|
||||||
|
|
||||||
$this->assertNotEmpty(trim($bridge->getURI()), $message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$this->assertInternalType('array', $this->obj::PARAMETERS, 'class::PARAMETERS');
|
||||||
|
$this->assertInternalType('int', $this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
|
||||||
|
$this->assertGreaterThanOrEqual(0, $this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function CheckBridgePublicConstants($bridgeName){
|
/**
|
||||||
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testParameters($path) {
|
||||||
|
$this->setBridge($path);
|
||||||
|
|
||||||
// Assertion only works for BridgeAbstract!
|
$multiMinimum = 2;
|
||||||
if(in_array('BridgeAbstract', class_parents($bridgeName))) {
|
if (isset($this->obj::PARAMETERS['global'])) ++$multiMinimum;
|
||||||
|
$multiContexts = (count($this->obj::PARAMETERS) >= $multiMinimum);
|
||||||
|
$paramsSeen = array();
|
||||||
|
|
||||||
$ref = new ReflectionClass($bridgeName);
|
$allowedTypes = array(
|
||||||
$constants = $ref->getConstants();
|
'text',
|
||||||
|
'number',
|
||||||
|
'list',
|
||||||
|
'checkbox'
|
||||||
|
);
|
||||||
|
|
||||||
$ref = new ReflectionClass('BridgeAbstract');
|
foreach($this->obj::PARAMETERS as $context => $params) {
|
||||||
$parent_constants = $ref->getConstants();
|
if ($multiContexts) {
|
||||||
|
$this->assertInternalType('string', $context, 'invalid context name');
|
||||||
|
$this->assertNotEmpty($context, 'empty context name');
|
||||||
|
}
|
||||||
|
|
||||||
foreach($parent_constants as $key => $value) {
|
foreach ($paramsSeen as $seen) {
|
||||||
|
$this->assertNotEquals($seen, $params, 'same set of parameters not allowed');
|
||||||
|
}
|
||||||
|
$paramsSeen[] = $params;
|
||||||
|
|
||||||
$this->assertArrayHasKey($key, $constants, 'Constant ' . $key . ' missing in ' . $bridgeName);
|
foreach ($params as $field => $options) {
|
||||||
|
$this->assertInternalType('string', $field, $field . ': invalid id');
|
||||||
|
$this->assertNotEmpty($field, $field . ':empty id');
|
||||||
|
|
||||||
// Skip optional constants
|
$this->assertInternalType('string', $options['name'], $field . ': invalid name');
|
||||||
if($key !== 'PARAMETERS' && $key !== 'CACHE_TIMEOUT') {
|
$this->assertNotEmpty($options['name'], $field . ': empty name');
|
||||||
$this->assertNotEquals($value, $constants[$key], 'Constant ' . $key . ' missing in ' . $bridgeName);
|
|
||||||
|
if (isset($options['type'])) {
|
||||||
|
$this->assertInternalType('string', $options['type'], $field . ': invalid type');
|
||||||
|
$this->assertContains($options['type'], $allowedTypes, $field . ': unknown type');
|
||||||
|
|
||||||
|
if ($options['type'] == 'list') {
|
||||||
|
$this->assertArrayHasKey('values', $options, $field . ': missing list values');
|
||||||
|
$this->assertInternalType('array', $options['values'], $field . ': invalid list values');
|
||||||
|
$this->assertNotEmpty($options['values'], $field . ': empty list values');
|
||||||
|
|
||||||
|
foreach ($options['values'] as $valueName => $value) {
|
||||||
|
$this->assertInternalType('string', $valueName, $field . ': invalid value name');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if (isset($options['required'])) {
|
||||||
|
$this->assertInternalType('bool', $options['required'], $field . ': invalid required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options['title'])) {
|
||||||
|
$this->assertInternalType('string', $options['title'], $field . ': invalid title');
|
||||||
|
$this->assertNotEmpty($options['title'], $field . ': empty title');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options['pattern'])) {
|
||||||
|
$this->assertInternalType('string', $options['pattern'], $field . ': invalid pattern');
|
||||||
|
$this->assertNotEquals('', $options['pattern'], $field . ': empty pattern');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options['exampleValue'])) {
|
||||||
|
if (is_string($options['exampleValue']))
|
||||||
|
$this->assertNotEquals('', $options['exampleValue'], $field . ': empty exampleValue');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options['defaultValue'])) {
|
||||||
|
if (is_string($options['defaultValue']))
|
||||||
|
$this->assertNotEquals('', $options['defaultValue'], $field . ': empty defaultValue');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isFunctionMemberOf($bridgeName, $functionName){
|
/**
|
||||||
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testVisibleMethods($path) {
|
||||||
|
$allowedBridgeAbstract = get_class_methods(BridgeAbstract::class);
|
||||||
|
sort($allowedBridgeAbstract);
|
||||||
|
$allowedFeedExpander = get_class_methods(FeedExpander::class);
|
||||||
|
sort($allowedFeedExpander);
|
||||||
|
|
||||||
$bridgeReflector = new ReflectionClass($bridgeName);
|
$this->setBridge($path);
|
||||||
$bridgeMethods = $bridgeReflector->GetMethods();
|
|
||||||
$bridgeHasMethod = false;
|
|
||||||
|
|
||||||
foreach($bridgeMethods as $method) {
|
|
||||||
|
|
||||||
if($method->name === $functionName && $method->class === $bridgeReflector->name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$methods = get_class_methods($this->obj);
|
||||||
|
sort($methods);
|
||||||
|
if ($this->obj instanceof FeedExpander) {
|
||||||
|
$this->assertEquals($allowedFeedExpander, $methods);
|
||||||
|
} else {
|
||||||
|
$this->assertEquals($allowedBridgeAbstract, $methods);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBridgeImplementation($bridgeName){
|
/**
|
||||||
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testMethodValues($path) {
|
||||||
|
$this->setBridge($path);
|
||||||
|
|
||||||
require_once('bridges/' . $bridgeName . '.php');
|
$value = $this->obj->getDescription();
|
||||||
|
$this->assertInternalType('string', $value, '$class->getDescription()');
|
||||||
|
$this->assertNotEmpty($value, '$class->getDescription()');
|
||||||
|
|
||||||
$this->CheckBridgePublicFunctions($bridgeName);
|
$value = $this->obj->getMaintainer();
|
||||||
$this->CheckBridgePublicConstants($bridgeName);
|
$this->assertInternalType('string', $value, '$class->getMaintainer()');
|
||||||
$this->CheckBridgeGetNameDefaultValue($bridgeName);
|
$this->assertNotEmpty($value, '$class->getMaintainer()');
|
||||||
$this->CheckBridgeGetURIDefaultValue($bridgeName);
|
|
||||||
|
|
||||||
|
$value = $this->obj->getName();
|
||||||
|
$this->assertInternalType('string', $value, '$class->getName()');
|
||||||
|
$this->assertNotEmpty($value, '$class->getName()');
|
||||||
|
|
||||||
|
$value = $this->obj->getURI();
|
||||||
|
$this->assertInternalType('string', $value, '$class->getURI()');
|
||||||
|
$this->assertNotEmpty($value, '$class->getURI()');
|
||||||
|
|
||||||
|
$value = $this->obj->getIcon();
|
||||||
|
$this->assertInternalType('string', $value, '$class->getIcon()');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function count() {
|
/**
|
||||||
return count(Bridge::getBridgeNames());
|
* @dataProvider dataBridgesProvider
|
||||||
|
*/
|
||||||
|
public function testUri($path) {
|
||||||
|
$this->setBridge($path);
|
||||||
|
|
||||||
|
$this->checkUrl($this->obj::URI);
|
||||||
|
$this->checkUrl($this->obj->getURI());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(TestResult $result = null) {
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if ($result === null) {
|
public function dataBridgesProvider() {
|
||||||
$result = new TestResult;
|
$bridges = array();
|
||||||
|
foreach (glob(PATH_LIB_BRIDGES . '*.php') as $path) {
|
||||||
|
$bridges[basename($path, '.php')] = array($path);
|
||||||
}
|
}
|
||||||
|
return $bridges;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Bridge::getBridgeNames() as $bridge) {
|
private function setBridge($path) {
|
||||||
|
require_once $path;
|
||||||
|
$this->class = basename($path, '.php');
|
||||||
|
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
|
||||||
|
$this->obj = new $this->class();
|
||||||
|
}
|
||||||
|
|
||||||
$bridge .= 'Bridge';
|
private function checkUrl($url) {
|
||||||
|
$this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL), 'no valid URL: ' . $url);
|
||||||
$result->startTest($this);
|
|
||||||
PHP_Timer::start();
|
|
||||||
$stopTime = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->testBridgeImplementation($bridge);
|
|
||||||
} catch (AssertionFailedError $e) {
|
|
||||||
|
|
||||||
$stopTime = PHP_Timer::stop();
|
|
||||||
$result->addFailure($this, $e, $stopTime);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
|
|
||||||
$stopTime = PHP_Timer::stop();
|
|
||||||
$result->addError($this, $e, $stopTime);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stopTime === null) {
|
|
||||||
$stopTime = PHP_Timer::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
$result->endTest($this, $stopTime);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BridgeAbstractTest extends BridgeAbstract {
|
|
||||||
public function collectData(){}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue