[BridgeImplementationTest] Refactor unit test to check bridges (#980)

This commit is contained in:
fulmeek 2019-01-08 20:02:51 +01:00 committed by LogMANOriginal
parent 245af35a60
commit 600f2290b6

View file

@ -1,188 +1,204 @@
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\AssertionFailedError;
require_once __DIR__ . '/../lib/rssbridge.php';
/**
* 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){
use PHPUnit\Framework\TestCase;
$parent_methods = array();
if(in_array('BridgeInterface', class_parents($bridgeName))) {
$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);
class BridgeImplementationTest extends TestCase {
private $class;
private $obj;
/**
* @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){
if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge
if(!$this->isFunctionMemberOf($bridgeName, 'getName'))
return;
$bridge = new $bridgeName();
$abstract = new BridgeAbstractTest();
$message = $bridgeName . ': \'getName\' must return a valid name!';
$this->assertNotEmpty(trim($bridge->getName()), $message);
}
/**
* @dataProvider dataBridgesProvider
*/
public function testClassType($path) {
$this->setBridge($path);
$this->assertInstanceOf(BridgeInterface::class, $this->obj);
}
// 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
if(!$this->isFunctionMemberOf($bridgeName, 'getURI'))
return;
$bridge = new $bridgeName();
$abstract = new BridgeAbstractTest();
$message = $bridgeName . ': \'getURI\' must return a valid URI!';
$this->assertNotEmpty(trim($bridge->getURI()), $message);
}
$this->assertInternalType('string', $this->obj::NAME, 'class::NAME');
$this->assertNotEmpty($this->obj::NAME, 'class::NAME');
$this->assertInternalType('string', $this->obj::URI, 'class::URI');
$this->assertNotEmpty($this->obj::URI, 'class::URI');
$this->assertInternalType('string', $this->obj::DESCRIPTION, 'class::DESCRIPTION');
$this->assertNotEmpty($this->obj::DESCRIPTION, 'class::DESCRIPTION');
$this->assertInternalType('string', $this->obj::MAINTAINER, 'class::MAINTAINER');
$this->assertNotEmpty($this->obj::MAINTAINER, 'class::MAINTAINER');
$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!
if(in_array('BridgeAbstract', class_parents($bridgeName))) {
$multiMinimum = 2;
if (isset($this->obj::PARAMETERS['global'])) ++$multiMinimum;
$multiContexts = (count($this->obj::PARAMETERS) >= $multiMinimum);
$paramsSeen = array();
$ref = new ReflectionClass($bridgeName);
$constants = $ref->getConstants();
$allowedTypes = array(
'text',
'number',
'list',
'checkbox'
);
$ref = new ReflectionClass('BridgeAbstract');
$parent_constants = $ref->getConstants();
foreach($this->obj::PARAMETERS as $context => $params) {
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
if($key !== 'PARAMETERS' && $key !== 'CACHE_TIMEOUT') {
$this->assertNotEquals($value, $constants[$key], 'Constant ' . $key . ' missing in ' . $bridgeName);
$this->assertInternalType('string', $options['name'], $field . ': invalid name');
$this->assertNotEmpty($options['name'], $field . ': empty name');
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);
$bridgeMethods = $bridgeReflector->GetMethods();
$bridgeHasMethod = false;
foreach($bridgeMethods as $method) {
if($method->name === $functionName && $method->class === $bridgeReflector->name) {
return true;
}
$this->setBridge($path);
$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);
$this->CheckBridgePublicConstants($bridgeName);
$this->CheckBridgeGetNameDefaultValue($bridgeName);
$this->CheckBridgeGetURIDefaultValue($bridgeName);
$value = $this->obj->getMaintainer();
$this->assertInternalType('string', $value, '$class->getMaintainer()');
$this->assertNotEmpty($value, '$class->getMaintainer()');
$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) {
$result = new TestResult;
public function dataBridgesProvider() {
$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';
$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;
private function checkUrl($url) {
$this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL), 'no valid URL: ' . $url);
}
}
class BridgeAbstractTest extends BridgeAbstract {
public function collectData(){}
}