Skip to content

Commit

Permalink
Integration tests with bitcoin in regtest mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Kerin committed Dec 29, 2017
1 parent 69821cd commit beeec07
Show file tree
Hide file tree
Showing 7 changed files with 555 additions and 1 deletion.
24 changes: 24 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ php:
- 5.5
- 5.4

matrix:
include:
- php: 5.4
env: RPC_TEST=true BITCOIN_VERSION=0.15.0

branches:
only:
- 1.x
Expand All @@ -17,11 +22,30 @@ notifications:
irc: "chat.freenode.net#dspacelabs"

install:
- |
if [ "$BITCOIN_VERSION" != "" ] && [ ! -e "${HOME}/bitcoin" ]; then
mkdir ${HOME}/bitcoin
fi
- |
if [ "$BITCOIN_VERSION" != "" ] && [ ! -e "${HOME}/bitcoin/bitcoin-$BITCOIN_VERSION" ]; then
cd ${HOME}/bitcoin &&
rm bitcoin-* -rf &&
wget https://bitcoin.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz &&
mv bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz bitcoin.tar.gz &&
tar xvf bitcoin.tar.gz &&
cd ${TRAVIS_BUILD_DIR}
else
echo "Had bitcoind"
fi
- composer require "codeclimate/php-test-reporter:*" -n
- composer install

script:
- php bin/phpunit
- |
if [ "$RPC_TEST" != "" ]; then
BITCOIND_PATH="$HOME/bitcoin/bitcoin-$BITCOIN_VERSION/bin/bitcoind" php bin/phpunit -c rpc.phpunit.xml
fi
after_script:
- bin/test-reporter --stdout > codeclimate.json
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"psr-4": { "Nbobtc\\": "src/" }
},
"autoload-dev": {
"psr-4": { "Tests\\Nbobtc\\": "tests/" }
"psr-4": {
"Tests\\Nbobtc\\": "tests/",
"RpcTests\\Nbobtc\\": "tests-rpc/"
}
},
"extra": {
"branch-alias": {
Expand Down
37 changes: 37 additions & 0 deletions rpc.phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
verbose="false">

<testsuites>
<testsuite name="Test Suite">
<directory>tests-rpc/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>src/</directory>
</whitelist>
</filter>

<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener"></listener>
</listeners>

<logging>
<log type="coverage-html" target="docs/code-coverage" charset="UTF-8" />
<log type="coverage-clover" target="build/logs/clover-rpc.xml"/>
</logging>
</phpunit>
208 changes: 208 additions & 0 deletions tests-rpc/Bitcoind.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php

namespace RpcTests\Nbobtc;

use Nbobtc\Command\Command;
use Nbobtc\Http\Client;

class Bitcoind
{
const ERROR_STARTUP = -28;
const ERROR_TX_MEMPOOL_CONFLICT = -26;
const ERROR_UNKNOWN_COMMAND = -32601;

/**
* @var string
*/
private $dataDir;

/**
* @var string
*/
private $bitcoind;

/**
* @var Credential
*/
private $credential;

/**
* @var Client
*/
private $client;

private $defaultOptions = [
"daemon" => 1,
"server" => 1,
"regtest" => 1,
];

private $options = [];

/**
* RpcServer constructor.
* @param $bitcoind
* @param string $dataDir
* @param Credential $credential
* @param array $options
*/
public function __construct($bitcoind, $dataDir, Credential $credential, array $options = [])
{
$this->bitcoind = $bitcoind;
$this->dataDir = $dataDir;
$this->credential = $credential;
$this->options = array_merge($options, $this->defaultOptions);
}

/**
* @return string
*/
private function getPidFile()
{
return "{$this->dataDir}/regtest/bitcoind.pid";
}

/**
* @return string
*/
private function getConfigFile()
{
return "{$this->dataDir}/bitcoin.conf";
}

/**
* @param Credential $rpcCredential
*/
private function writeConfigToFile(Credential $rpcCredential)
{
$fd = fopen($this->getConfigFile(), "w");
if (!$fd) {
throw new \RuntimeException("Failed to open bitcoin.conf for writing");
}

$config = array_merge(
$this->options,
$rpcCredential->getConfigArray()
);

$iniConfig = implode("\n", array_map(function ($value, $key) {
return "{$key}={$value}";
}, $config, array_keys($config)));

if (!fwrite($fd, $iniConfig)) {
throw new \RuntimeException("Failed to write to bitcoin.conf");
}

fclose($fd);
}

/**
* Start bitcoind and
* @return void
*/
public function start()
{
if ($this->isRunning()) {
return;
}

$this->writeConfigToFile($this->credential);
$res = 0;
$out = '';
exec(sprintf("%s -datadir=%s", $this->bitcoind, $this->dataDir), $out, $res);

if ($res !== 0) {
throw new \RuntimeException("Failed to start bitcoind: {$this->dataDir}\n");
}

$start = microtime(true);
$limit = 10;
$connected = false;

$conn = new Client($this->credential->getDsn());

do {
try {
$result = json_decode($conn->sendCommand(new Command("getchaintips"))->getBody()->getContents(), true);
if ($result['error'] === null) {
$connected = true;
} else {
if ($result['error']['code'] !== self::ERROR_STARTUP && $result['error']['code'] !== self::ERROR_UNKNOWN_COMMAND) {
throw new \RuntimeException("Unexpected error code during startup: {$result['error']['code']}");
}

sleep(0.2);
}

} catch (\Exception $e) {
sleep(0.2);
}

if (microtime(true) > $start + $limit) {
throw new \RuntimeException("Timeout elapsed, never made connection to bitcoind");
}
} while (!$connected);
}

/**
* Recursive delete of datadir.
* @param string $src
*/
private function recursiveDelete($src)
{
$dir = opendir($src);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
$full = $src . '/' . $file;
if ( is_dir($full) ) {
$this->recursiveDelete($full);
}
else {
unlink($full);
}
}
}
closedir($dir);
rmdir($src);
}

/**
* @return void
*/
public function destroy()
{
if ($this->isRunning()) {
$this->makeClient()->sendCommand(new Command("stop"));

do {
sleep(0.2);
} while($this->isRunning());

$this->recursiveDelete($this->dataDir);
}
}

/**
* @return bool
*/
public function isRunning()
{
return file_exists($this->getPidFile());
}

/**
* @return Client
*/
public function makeClient()
{
if (!$this->isRunning()) {
throw new \RuntimeException("No client, server not running");
}

if (null === $this->client) {
$this->client = new Client($this->credential->getDsn());
}

return $this->client;
}
}
Loading

0 comments on commit beeec07

Please sign in to comment.