UnitTestNodeService   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 160
rs 10
c 0
b 0
f 0
ccs 0
cts 78
cp 0
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 21 3
A __destruct() 0 3 1
B cleanup() 0 18 6
A findAvailablePort() 0 13 4
A createRandomConfig() 0 13 1
A __construct() 0 8 2
A setCleanup() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoind;
6
7
use BitWasp\Bitcoind\Config;
8
use BitWasp\Bitcoind\Exception\UnitTestException;
9
use BitWasp\Bitcoind\Node\NodeOptions;
10
use BitWasp\Bitcoind\Node\Server;
11
use BitWasp\Bitcoind\Utils\File;
12
use Matomo\Ini\IniReader;
13
14
class UnitTestNodeService
15
{
16
    /**
17
     * @var string
18
     */
19
    private $tmpDir;
20
21
    /**
22
     * @var string
23
     */
24
    private $bitcoindPath;
25
26
    /**
27
     * @var NodeService
28
     */
29
    private $service;
30
31
    /**
32
     * @var Config\FilesystemWriter
33
     */
34
    private $writer;
35
36
    /**
37
     * @var Config\FilesystemLoader
38
     */
39
    private $reader;
40
41
    /**
42
     * @var array
43
     */
44
    private $nodeDirs = [];
45
46
    /**
47
     * @var Server[]
48
     */
49
    private $servers = [];
50
51
    /**
52
     * @var DataDirGeneratorInterface
53
     */
54
    private $dataDirGenerator;
55
56
    private $cleanupWhenFinished = true;
57
58
    public function __construct(string $bitcoindPath, NodeService $nodeService, DataDirGeneratorInterface $dataDirGenerator = null)
59
    {
60
        $this->tmpDir = sys_get_temp_dir();
61
        $this->bitcoindPath = $bitcoindPath;
62
        $this->dataDirGenerator = $dataDirGenerator ?: new IncrementalDataDirGenerator($this->tmpDir);
63
        $this->service = $nodeService;
64
        $this->writer = new Config\FilesystemWriter();
65
        $this->reader = new Config\FilesystemLoader(new IniReader());
66
    }
67
68
    public function setCleanup(bool $doCleanup)
69
    {
70
        $this->cleanupWhenFinished = $doCleanup;
71
        return $this;
72
    }
73
74
    /**
75
     * @param int $testId
76
     * @return int
77
     * @throws UnitTestException
78
     */
79
    protected function findAvailablePort(int $testId): int
80
    {
81
        $port = $testId;
82
        do {
83
            if ($port > 65535) {
84
                throw new UnitTestException("Invalid port for node");
85
            }
86
            $connection = @fsockopen('localhost', $port);
87
            $isUsed = is_resource($connection);
88
            if (!$isUsed) {
89
                return $port;
90
            }
91
            $port++;
92
        } while ($isUsed);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return integer. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
93
    }
94
95
    /**
96
     * @param int $testId
97
     * @return Config\Config
98
     * @throws UnitTestException
99
     * @throws \Exception
100
     */
101
    protected function createRandomConfig(int $testId): Config\Config
102
    {
103
        $this->findAvailablePort($testId);
104
        $password = preg_replace("/[^A-Za-z0-9 ]/", '', base64_encode(random_bytes(16)));
105
        $nonce = preg_replace("/[^A-Za-z0-9 ]/", '', base64_encode(random_bytes(4)));
106
        return new Config\Config([
107
            'rpcuser' => "user-{$testId}-{$nonce}",
108
            'rpcpassword' => $password,
109
            'rpcallowip' => '127.0.0.1',
110
            'regtest' => '1',
111
            'rpcport' => '18443',
112
            'server' => '1',
113
            'daemon' => '1',
114
        ]);
115
    }
116
117
    /**
118
     * @return Server
119
     * @throws Exception\ServerException
120
     * @throws UnitTestException
121
     * @throws \Exception
122
     */
123
    public function create(bool $wait = null): Server
124
    {
125
        if ($wait === null) {
126
            $wait = true;
127
        }
128
129
        $testId = count($this->nodeDirs);
130
        $dataDir = $this->dataDirGenerator->createNextDir();
131
        $config = $this->createRandomConfig($testId);
132
        $options = new NodeOptions($this->bitcoindPath, $dataDir);
133
134
        $node = $this->service->createNewNode($options, $config, $this->writer);
135
        $node->start($this->reader);
136
        if ($wait) {
137
            $node->waitForStartup();
138
            $node->waitForRpc();
139
        }
140
141
        $this->servers[] = $node;
142
143
        return $node;
144
    }
145
146
    /**
147
     *
148
     */
149
    protected function cleanup()
150
    {
151
        $servers = 0;
152
        foreach ($this->servers as $server) {
153
            $servers++;
154
            if ($server->isRunning()) {
155
                $server->shutdown();
156
157
                $dataDir = $server->getNodeOptions()->getDataDir();
158
                if (file_exists($dataDir)) {
159
                    File::recursiveDelete($dataDir);
160
                }
161
            }
162
        }
163
164
        foreach ($this->dataDirGenerator->getDirs() as $dir) {
165
            if (is_dir($dir)) {
166
                File::recursiveDelete($dataDir);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $dataDir does not seem to be defined for all execution paths leading up to this point.
Loading history...
167
            }
168
        }
169
    }
170
171
    public function __destruct()
172
    {
173
        $this->cleanup();
174
    }
175
}
176