Skip to content

Commit

Permalink
[tests] Rewrite the whole test suite to allow more granular testing.
Browse files Browse the repository at this point in the history
Fix also a few bugs found while rewriting the test suite.

In order to be able to run integration tests, the test suite requires
a version of Redis >= 2.4.0.

The units have been splitted into several different groups using
PHPUnit @group annotation to allow developers to enable, disable
and combine certain types of tests. The available groups are:

  - disconnected: can run without a Redis server online
  - connected: active connection to a Redis server is required
  - commands: test dedicated to a specific Redis command
  - slow: performs operations that can slow down execution;

A list of the available groups can be obtained by running

  phpunit --list-groups

Groups of tests can be disabled or enabled via the XML configuration
file or the standard command-line test runner. Please note that due
to a bug in PHPUnit, older versions ignore the --group option when
the group is excluded in the XML configuration file. Please refer to
https://github.com/sebastianbergmann/phpunit/issues/320 for details

Integration tests in the @connected group check if the command being
tested is defined in the selected server profile (see the value of
the TEST_SERVER_VERSION constant in phpunit.xml). If the command is
not defined in the target server profile, the integration test is
automatically marked as skipped.

We also provide an helper script in the bin directory that can be
used to automatically generate a file with the scheleton of a test
case for a Redis command by specifying the name of the class in the
Predis\Commands namespace. For example, to generate a test case for
SET (represented by the Predis\Commands\StringSet class):

  ./bin/generate-command-test.php --class=StringSet

The realm of a command is automatically inferred from the name of the
class, but it can be set using the --realm option.
  • Loading branch information
nrk committed Dec 10, 2011
1 parent dd20e85 commit 371ac91
Show file tree
Hide file tree
Showing 234 changed files with 24,387 additions and 3,911 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
phpunit.xml
experiments/
test/enable.tests
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ language: php
php:
- 5.3
- 5.4
before_script: touch test/enable.tests
branches:
only:
- master
- version-0.6
- v0.7
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,27 +148,40 @@ $redis->newcmd();
```


## Development ##
## Test suite ##

Predis is fully backed up by a test suite which tries to cover all the aspects of the
client library and the interaction of every single command with a Redis server. If you
want to work on Predis, it is highly recommended that you first run the test suite to
be sure that everything is OK, and report strange behaviours or bugs.
__ATTENTION__: Do not run the test suite shipped with Predis against instances of
Redis running in production environments or containing data you are interested in!

When modifying Predis please be sure that no warnings or notices are emitted by PHP
Predis has a comprehensive test suite covering every aspect of the library. The suite
performs integration tests against a running instance of Redis (>= 2.4.0 is required)
to verify the correct behaviour of the implementation of each command and automatically
skips commands that are not defined in the selected version of Redis. If you do not have
Redis up and running, integration tests can be disabled. By default, the test suite is
configured to execute integration tests using the server profile for Redis v2.4 (which
is the current stable version of Redis). You can optionally run the suite against a
Redis instance built from the `unstable` branch with the development profile by changing
the `TEST_SERVER_VERSION` to `dev` in the `phpunit.xml` file. More details about testing
Predis are available in `tests/README.md`.

## Contributing ##

If you want to work on Predis, it is highly recommended that you first run the test
suite in order to check that everything is OK, and report strange behaviours or bugs.

When modifying Predis please make sure that no warnings or notices are emitted by PHP
by running the interpreter in your development environment with the `error_reporting`
variable set to `E_ALL | E_STRICT`.

The recommended way to contribute to Predis is to fork the project on GitHub, create
new topic branches on your newly created repository to fix or add features and then
open a new pull request with a description of the applied changes. Obviously, you can
use any other Git hosting provider of your preference. Diff patches will be accepted
too, even though they are not the preferred way to contribute to Predis.
open a new pull request with a description of the applied changes. Obviously, you
can use any other Git hosting provider of your preference.


## Dependencies ##

- PHP >= 5.3.0
- PHP >= 5.3.2
- PHPUnit >= 3.5.0 (needed to run the test suite)

## Links ##
Expand Down
8 changes: 0 additions & 8 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,5 @@

* Implement smart and transparent support for redis-cluster.

* Switch to smaller test units and add more granular tests that try to cover
every single class.

* Documentation! The README is obviously not enought to show how to use Predis
as it does not cover all of its features.

* Missing tests for commands:
PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, DEBUG, OBJECT,
CLIENT, CONFIG GET, CONFIG SET, CONFIG RESETSTAT, SLOWLOG, SCRIPT, PTTL,
PSETEX, PEXPIRE, PEXPIREAT, INCRBYFLOAT, HINCRBYFLOAT
237 changes: 237 additions & 0 deletions bin/generate-command-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#!/usr/bin/env php
<?php

/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Predis\Commands\ICommand;
use Predis\Commands\IPrefixable;

class CommandTestCaseGenerator
{
private $options;

public function __construct(Array $options)
{
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}
$this->options = $options;
}

public static function fromCommandLine()
{
$parameters = array(
'c:' => 'class:',
'r::' => 'realm::',
'o::' => 'output::',
'x::' => 'overwrite::'
);

$getops = getopt(implode(array_keys($parameters)), $parameters);

$options = array(
'overwrite' => false,
'tests' => __DIR__.'/../tests',
);

foreach ($getops as $option => $value) {
switch ($option) {
case 'c':
case 'class':
$options['class'] = $value;
break;

case 'r':
case 'realm':
$options['realm'] = $value;
break;

case 'o':
case 'output':
$options['output'] = $value;
break;

case 'x':
case 'overwrite':
$options['overwrite'] = true;
break;
}
}

if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
}

$options['fqn'] = "Predis\\Commands\\{$options['class']}";
$options['path'] = "Predis/Commands/{$options['class']}.php";

$source = __DIR__.'/../lib/'.$options['path'];
if (!file_exists($source)) {
throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
}

if (!isset($options['output'])) {
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
}

return new self($options);
}

protected function getTestRealm()
{
if (isset($this->options['realm'])) {
if (!$this->options['realm']) {
throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
}
return $this->options['realm'];
}

$class = array_pop(explode('\\', $this->options['fqn']));
list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

return strtolower($realm);
}

public function generate()
{
$reflection = new ReflectionClass($class = $this->options['fqn']);

if (!$reflection->isInstantiable()) {
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
}
if (!$reflection->implementsInterface('Predis\Commands\ICommand')) {
throw new RuntimeException("Class $class must implement the Predis\Commands\ICommand interface.");
}

$instance = $reflection->newInstance();
$buffer = $this->getTestCaseBuffer($instance);

return $buffer;
}

public function save()
{
$options = $this->options;
if (file_exists($options['output']) && !$options['overwrite']) {
throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
}
file_put_contents($options['output'], $this->generate());
}

protected function getTestCaseBuffer(ICommand $instance)
{
$id = $instance->getId();
$fqn = get_class($instance);
$class = array_pop(explode('\\', $fqn)) . "Test";
$realm = $this->getTestRealm();

$buffer =<<<PHP
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use \PHPUnit_Framework_TestCase as StandardTestCase;
/**
* @group commands
* @group realm-$realm
*/
class $class extends CommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return '$fqn';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return '$id';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$arguments = array(/* add arguments */);
\$expected = array(/* add arguments */);
\$command = \$this->getCommand();
\$command->setArguments(\$arguments);
\$this->assertSame(\$expected, \$command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$raw = null;
\$expected = null;
\$command = \$this->getCommand();
\$this->assertSame(\$expected, \$command->parseResponse(\$raw));
}
PHP;

if ($instance instanceof IPrefixable) {
$buffer .=<<<PHP
/**
* @group disconnected
*/
public function testPrefixKeys()
{
\$this->markTestIncomplete('This test has not been implemented yet.');
\$arguments = array(/* add arguments */);
\$expected = array(/* add arguments */);
\$command = \$this->getCommandWithArgumentsArray(\$arguments);
\$command->prefixKeys('prefix:');
\$this->assertSame(\$expected, \$command->getArguments());
}
PHP;
}

return "$buffer}\n";
}
}

// ------------------------------------------------------------------------- //

require __DIR__.'/../autoload.php';

$generator = CommandTestCaseGenerator::fromCommandLine();
$generator->save();
2 changes: 1 addition & 1 deletion examples/ServerSideScripting.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

class IncrementExistingKey extends ScriptedCommand
{
protected function keysCount()
public function getKeysCount()
{
return 1;
}
Expand Down
7 changes: 1 addition & 6 deletions lib/Predis/Autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ public function autoload($className)
$relativeClassName = substr($className, strlen($this->prefix));
$classNameParts = explode('\\', $relativeClassName);

$path = $this->baseDir .
DIRECTORY_SEPARATOR .
implode(DIRECTORY_SEPARATOR, $classNameParts) .
'.php';

require_once $path;
require_once $this->baseDir.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $classNameParts).'.php';
}
}
6 changes: 2 additions & 4 deletions lib/Predis/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,9 @@ public function getConnection($id = null)
{
if (isset($id)) {
if (!Helpers::isCluster($this->connection)) {
throw new ClientException(
'Retrieving connections by alias is supported only with clustered connections'
);
$message = 'Retrieving connections by alias is supported only with clustered connections';
throw new NotSupportedException($message);
}

return $this->connection->getConnectionById($id);
}

Expand Down
8 changes: 0 additions & 8 deletions lib/Predis/Commands/HashDelete.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,4 @@ protected function filterArguments(Array $arguments)
{
return Helpers::filterVariadicValues($arguments);
}

/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
1 change: 1 addition & 0 deletions lib/Predis/Commands/KeyKeysV12x.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
/**
* @link https://redis.io/commands/keys
* @author Daniele Alessandri <[email protected]>
* @deprecated
*/
class KeyKeysV12x extends KeyKeys
{
Expand Down
2 changes: 1 addition & 1 deletion lib/Predis/Commands/KeyRandom.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @link https://redis.io/commands/randomkey
* @author Daniele Alessandri <[email protected]>
*/
class KeyRandom extends PrefixableCommand
class KeyRandom extends Command
{
/**
* {@inheritdoc}
Expand Down
Loading

0 comments on commit 371ac91

Please sign in to comment.