Skip to content

Commit d98de4d

Browse files
author
Thomas Kerin
committed
Integration tests with bitcoin in regtest mode
1 parent 69821cd commit d98de4d

File tree

7 files changed

+555
-1
lines changed

7 files changed

+555
-1
lines changed

.travis.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ php:
66
- 5.5
77
- 5.4
88

9+
matrix:
10+
include:
11+
- php: 5.4
12+
env: RPC_TEST=true BITCOIN_VERSION=0.15.0
13+
914
branches:
1015
only:
1116
- 1.x
@@ -17,11 +22,30 @@ notifications:
1722
irc: "chat.freenode.net#dspacelabs"
1823

1924
install:
25+
- |
26+
if [ "$BITCOIN_VERSION" != "" ] && [ ! -e "${HOME}/bitcoin" ]; then
27+
mkdir ${HOME}/bitcoin
28+
fi
29+
- |
30+
if [ "$BITCOIN_VERSION" != "" ] && [ ! -e "${HOME}/bitcoin/bitcoin-$BITCOIN_VERSION" ]; then
31+
cd ${HOME}/bitcoin &&
32+
rm bitcoin-* -rf &&
33+
wget https://bitcoin.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz &&
34+
mv bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz bitcoin.tar.gz &&
35+
tar xvf bitcoin.tar.gz &&
36+
cd ${TRAVIS_BUILD_DIR}
37+
else
38+
echo "Had bitcoind"
39+
fi
2040
- composer require "codeclimate/php-test-reporter:*" -n
2141
- composer install
2242

2343
script:
2444
- php bin/phpunit
45+
- |
46+
if [ "$RPC_TEST" != "" ]; then
47+
BITCOIND_PATH="$HOME/bitcoin/bitcoin-$BITCOIN_VERSION/bin/bitcoind" php bin/phpunit -c rpc.phpunit.xml
48+
fi
2549
2650
after_script:
2751
- bin/test-reporter --stdout > codeclimate.json

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
"psr-4": { "Nbobtc\\": "src/" }
1919
},
2020
"autoload-dev": {
21-
"psr-4": { "Tests\\Nbobtc\\": "tests/" }
21+
"psr-4": {
22+
"Tests\\Nbobtc\\": "tests/",
23+
"RpcTests\\Nbobtc\\": "tests-rpc/"
24+
}
2225
},
2326
"extra": {
2427
"branch-alias": {

rpc.phpunit.xml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit
3+
backupGlobals="false"
4+
backupStaticAttributes="false"
5+
bootstrap="vendor/autoload.php"
6+
colors="true"
7+
convertErrorsToExceptions="true"
8+
convertNoticesToExceptions="true"
9+
convertWarningsToExceptions="true"
10+
processIsolation="false"
11+
stopOnError="false"
12+
stopOnFailure="false"
13+
stopOnIncomplete="false"
14+
stopOnSkipped="false"
15+
verbose="false">
16+
17+
<testsuites>
18+
<testsuite name="Test Suite">
19+
<directory>tests-rpc/</directory>
20+
</testsuite>
21+
</testsuites>
22+
23+
<filter>
24+
<whitelist>
25+
<directory>src/</directory>
26+
</whitelist>
27+
</filter>
28+
29+
<listeners>
30+
<listener class="\Mockery\Adapter\Phpunit\TestListener"></listener>
31+
</listeners>
32+
33+
<logging>
34+
<log type="coverage-html" target="docs/code-coverage" charset="UTF-8" />
35+
<log type="coverage-clover" target="build/logs/clover-rpc.xml"/>
36+
</logging>
37+
</phpunit>

tests-rpc/Bitcoind.php

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
3+
namespace RpcTests\Nbobtc;
4+
5+
use Nbobtc\Command\Command;
6+
use Nbobtc\Http\Client;
7+
8+
class Bitcoind
9+
{
10+
const ERROR_STARTUP = -28;
11+
const ERROR_TX_MEMPOOL_CONFLICT = -26;
12+
const ERROR_UNKNOWN_COMMAND = -32601;
13+
14+
/**
15+
* @var string
16+
*/
17+
private $dataDir;
18+
19+
/**
20+
* @var string
21+
*/
22+
private $bitcoind;
23+
24+
/**
25+
* @var Credential
26+
*/
27+
private $credential;
28+
29+
/**
30+
* @var Client
31+
*/
32+
private $client;
33+
34+
private $defaultOptions = [
35+
"daemon" => 1,
36+
"server" => 1,
37+
"regtest" => 1,
38+
];
39+
40+
private $options = [];
41+
42+
/**
43+
* RpcServer constructor.
44+
* @param $bitcoind
45+
* @param string $dataDir
46+
* @param Credential $credential
47+
* @param array $options
48+
*/
49+
public function __construct($bitcoind, $dataDir, Credential $credential, array $options = [])
50+
{
51+
$this->bitcoind = $bitcoind;
52+
$this->dataDir = $dataDir;
53+
$this->credential = $credential;
54+
$this->options = array_merge($options, $this->defaultOptions);
55+
}
56+
57+
/**
58+
* @return string
59+
*/
60+
private function getPidFile()
61+
{
62+
return "{$this->dataDir}/regtest/bitcoind.pid";
63+
}
64+
65+
/**
66+
* @return string
67+
*/
68+
private function getConfigFile()
69+
{
70+
return "{$this->dataDir}/bitcoin.conf";
71+
}
72+
73+
/**
74+
* @param Credential $rpcCredential
75+
*/
76+
private function writeConfigToFile(Credential $rpcCredential)
77+
{
78+
$fd = fopen($this->getConfigFile(), "w");
79+
if (!$fd) {
80+
throw new \RuntimeException("Failed to open bitcoin.conf for writing");
81+
}
82+
83+
$config = array_merge(
84+
$this->options,
85+
$rpcCredential->getConfigArray()
86+
);
87+
88+
$iniConfig = implode("\n", array_map(function ($value, $key) {
89+
return "{$key}={$value}";
90+
}, $config, array_keys($config)));
91+
92+
if (!fwrite($fd, $iniConfig)) {
93+
throw new \RuntimeException("Failed to write to bitcoin.conf");
94+
}
95+
96+
fclose($fd);
97+
}
98+
99+
/**
100+
* Start bitcoind and
101+
* @return void
102+
*/
103+
public function start()
104+
{
105+
if ($this->isRunning()) {
106+
return;
107+
}
108+
109+
$this->writeConfigToFile($this->credential);
110+
$res = 0;
111+
$out = '';
112+
exec(sprintf("%s -datadir=%s", $this->bitcoind, $this->dataDir), $out, $res);
113+
114+
if ($res !== 0) {
115+
throw new \RuntimeException("Failed to start bitcoind: {$this->dataDir}\n");
116+
}
117+
118+
$start = microtime(true);
119+
$limit = 10;
120+
$connected = false;
121+
122+
$conn = new Client($this->credential->getDsn());
123+
124+
do {
125+
try {
126+
$result = json_decode($conn->sendCommand(new Command("getchaintips"))->getBody()->getContents(), true);
127+
if ($result['error'] === null) {
128+
$connected = true;
129+
} else {
130+
if ($result['error']['code'] !== self::ERROR_STARTUP && $result['error']['code'] !== self::ERROR_UNKNOWN_COMMAND) {
131+
throw new \RuntimeException("Unexpected error code during startup: {$result['error']['code']}");
132+
}
133+
134+
sleep(0.2);
135+
}
136+
137+
} catch (\Exception $e) {
138+
sleep(0.2);
139+
}
140+
141+
if (microtime(true) > $start + $limit) {
142+
throw new \RuntimeException("Timeout elapsed, never made connection to bitcoind");
143+
}
144+
} while (!$connected);
145+
}
146+
147+
/**
148+
* Recursive delete of datadir.
149+
* @param string $src
150+
*/
151+
private function recursiveDelete($src)
152+
{
153+
$dir = opendir($src);
154+
while(false !== ( $file = readdir($dir)) ) {
155+
if (( $file != '.' ) && ( $file != '..' )) {
156+
$full = $src . '/' . $file;
157+
if ( is_dir($full) ) {
158+
$this->recursiveDelete($full);
159+
}
160+
else {
161+
unlink($full);
162+
}
163+
}
164+
}
165+
closedir($dir);
166+
rmdir($src);
167+
}
168+
169+
/**
170+
* @return void
171+
*/
172+
public function destroy()
173+
{
174+
if ($this->isRunning()) {
175+
$this->makeClient()->sendCommand(new Command("stop"));
176+
177+
do {
178+
sleep(0.2);
179+
} while($this->isRunning());
180+
181+
$this->recursiveDelete($this->dataDir);
182+
}
183+
}
184+
185+
/**
186+
* @return bool
187+
*/
188+
public function isRunning()
189+
{
190+
return file_exists($this->getPidFile());
191+
}
192+
193+
/**
194+
* @return Client
195+
*/
196+
public function makeClient()
197+
{
198+
if (!$this->isRunning()) {
199+
throw new \RuntimeException("No client, server not running");
200+
}
201+
202+
if (null === $this->client) {
203+
$this->client = new Client($this->credential->getDsn());
204+
}
205+
206+
return $this->client;
207+
}
208+
}

0 commit comments

Comments
 (0)