Skip to content

Commit 7c79c33

Browse files
committed
Merge branch 'tests'
2 parents f905ec2 + f15683a commit 7c79c33

File tree

6 files changed

+295
-52
lines changed

6 files changed

+295
-52
lines changed

.travis.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,43 @@ php:
1010

1111
sudo: false
1212

13+
env:
14+
- TEST_SECURE=6001 TEST_PLAIN=6000
15+
16+
# install required system packages, see 'install' below for details
17+
# Travis' containers require this, otherwise use this:
18+
# sudo apt-get install openssl build-essential libev-dev libssl-dev
19+
addons:
20+
apt:
21+
packages:
22+
- openssl
23+
- build-essential
24+
- libev-dev
25+
- libssl-dev
26+
1327
install:
28+
# install this library plus its dependencies
1429
- composer install --prefer-source --no-interaction
1530

31+
# we need openssl and either stunnel or stud
32+
# unfortunately these are not available in Travis' containers
33+
# sudo apt-get install -y openssl stud
34+
# sudo apt-get install -y openssl stunnel4
35+
36+
# instead, let's install stud from source
37+
# build dependencies are already installed, see 'addons.apt.packages' above
38+
# sudo apt-get install openssl build-essential libev-dev libssl-dev
39+
- git clone https://github.com/bumptech/stud.git
40+
- (cd stud && make)
41+
42+
# create self-signed certificate
43+
- openssl genrsa 1024 > stunnel.key
44+
- openssl req -batch -subj '/CN=127.0.0.1' -new -x509 -nodes -sha1 -days 3650 -key stunnel.key > stunnel.cert
45+
- cat stunnel.cert stunnel.key > stunnel.pem
46+
47+
# start TLS/SSL terminating proxy
48+
# stunnel -f -p stunnel.pem -d $TEST_SECURE -r $TEST_PLAIN 2>/dev/null &
49+
- ./stud/stud --daemon -f 127.0.0.1,$TEST_SECURE -b 127.0.0.1,$TEST_PLAIN stunnel.pem
50+
1651
script:
1752
- phpunit --coverage-text

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,25 @@ $connector->create('/tmp/demo.sock')->then(function (React\Stream\Stream $stream
137137

138138
$loop->run();
139139
```
140+
141+
## Tests
142+
143+
To run the test suite, you need PHPUnit. Go to the project root and run:
144+
145+
```bash
146+
$ phpunit
147+
```
148+
149+
The test suite also contains some optional integration tests which operate on a
150+
TCP/IP socket server and an optional TLS/SSL terminating proxy in front of it.
151+
The underlying TCP/IP socket server will be started automatically, whereas the
152+
TLS/SSL terminating proxy has to be started and enabled like this:
153+
154+
```bash
155+
$ stunnel -f -p stunnel.pem -d 6001 -r 6000 &
156+
$ TEST_SECURE=6001 TEST_PLAIN=6000 phpunit
157+
```
158+
159+
See also the [Travis configuration](.travis.yml) for details on how to set up
160+
the TLS/SSL terminating proxy and the required certificate file (`stunnel.pem`)
161+
if you're unsure.

tests/IntegrationTest.php

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,12 @@ public function gettingStuffFromGoogleShouldWork()
2121
$dns = $factory->create('8.8.8.8', $loop);
2222
$connector = new Connector($loop, $dns);
2323

24-
$connected = false;
25-
$response = null;
26-
27-
$connector->create('google.com', 80)
28-
->then(function ($conn) use (&$connected) {
29-
$connected = true;
30-
$conn->write("GET / HTTP/1.0\r\n\r\n");
31-
return BufferedSink::createPromise($conn);
32-
})
33-
->then(function ($data) use (&$response) {
34-
$response = $data;
35-
});
36-
37-
$loop->run();
38-
39-
$this->assertTrue($connected);
24+
$conn = Block\await($connector->create('google.com', 80), $loop);
25+
26+
$conn->write("GET / HTTP/1.0\r\n\r\n");
27+
28+
$response = Block\await(BufferedSink::createPromise($conn), $loop);
29+
4030
$this->assertRegExp('#^HTTP/1\.0#', $response);
4131
}
4232

@@ -52,26 +42,17 @@ public function gettingEncryptedStuffFromGoogleShouldWork()
5242
$factory = new Factory();
5343
$dns = $factory->create('8.8.8.8', $loop);
5444

55-
$connected = false;
56-
$response = null;
57-
5845
$secureConnector = new SecureConnector(
5946
new Connector($loop, $dns),
6047
$loop
6148
);
62-
$secureConnector->create('google.com', 443)
63-
->then(function ($conn) use (&$connected) {
64-
$connected = true;
65-
$conn->write("GET / HTTP/1.0\r\n\r\n");
66-
return BufferedSink::createPromise($conn);
67-
})
68-
->then(function ($data) use (&$response) {
69-
$response = $data;
70-
});
71-
72-
$loop->run();
73-
74-
$this->assertTrue($connected);
49+
50+
$conn = Block\await($secureConnector->create('google.com', 443), $loop);
51+
52+
$conn->write("GET / HTTP/1.0\r\n\r\n");
53+
54+
$response = Block\await(BufferedSink::createPromise($conn), $loop);
55+
7556
$this->assertRegExp('#^HTTP/1\.0#', $response);
7657
}
7758

@@ -87,7 +68,6 @@ public function testSelfSignedRejectsIfVerificationIsEnabled()
8768
$factory = new Factory();
8869
$dns = $factory->create('8.8.8.8', $loop);
8970

90-
9171
$secureConnector = new SecureConnector(
9272
new Connector($loop, $dns),
9373
$loop,

tests/SecureIntegrationTest.php

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?php
2+
3+
namespace React\Tests\SocketClient;
4+
5+
use React\EventLoop\Factory as LoopFactory;
6+
use React\Socket\Server;
7+
use React\SocketClient\TcpConnector;
8+
use React\SocketClient\SecureConnector;
9+
use React\Stream\Stream;
10+
use Clue\React\Block;
11+
use React\Promise\Promise;
12+
use Evenement\EventEmitterInterface;
13+
use React\Promise\Deferred;
14+
use React\Stream\BufferedSink;
15+
16+
class SecureIntegrationTest extends TestCase
17+
{
18+
private $portSecure;
19+
private $portPlain;
20+
21+
private $loop;
22+
private $server;
23+
private $connector;
24+
25+
public function setUp()
26+
{
27+
if (defined('HHVM_VERSION')) {
28+
$this->markTestSkipped('Not supported on HHVM');
29+
}
30+
31+
$this->portSecure = getenv('TEST_SECURE');
32+
$this->portPlain = getenv('TEST_PLAIN');
33+
34+
if ($this->portSecure === false || $this->portPlain === false) {
35+
$this->markTestSkipped('Needs TEST_SECURE=X and TEST_PLAIN=Y environment variables to run, see README.md');
36+
}
37+
38+
$this->loop = LoopFactory::create();
39+
$this->server = new Server($this->loop);
40+
$this->server->listen($this->portPlain);
41+
$this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false));
42+
}
43+
44+
public function tearDown()
45+
{
46+
if ($this->server !== null) {
47+
$this->server->shutdown();
48+
$this->server = null;
49+
}
50+
}
51+
52+
public function testConnectToServer()
53+
{
54+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
55+
/* @var $client Stream */
56+
57+
$client->close();
58+
}
59+
60+
public function testConnectToServerEmitsConnection()
61+
{
62+
$promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
63+
64+
$promiseClient = $this->connector->create('127.0.0.1', $this->portSecure);
65+
66+
list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop);
67+
/* @var $client Stream */
68+
69+
$client->close();
70+
}
71+
72+
public function testSendSmallDataToServerReceivesOneChunk()
73+
{
74+
// server expects one connection which emits one data event
75+
$received = new Deferred();
76+
$this->server->on('connection', function (Stream $peer) use ($received) {
77+
$peer->on('data', function ($chunk) use ($received) {
78+
$received->resolve($chunk);
79+
});
80+
});
81+
82+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
83+
/* @var $client Stream */
84+
85+
$client->write('hello');
86+
87+
// await server to report one "data" event
88+
$data = Block\await($received->promise(), $this->loop);
89+
90+
$client->close();
91+
92+
$this->assertEquals('hello', $data);
93+
}
94+
95+
public function testSendDataWithEndToServerReceivesAllData()
96+
{
97+
$disconnected = new Deferred();
98+
$this->server->on('connection', function (Stream $peer) use ($disconnected) {
99+
$received = '';
100+
$peer->on('data', function ($chunk) use (&$received) {
101+
$received .= $chunk;
102+
});
103+
$peer->on('close', function () use (&$received, $disconnected) {
104+
$disconnected->resolve($received);
105+
});
106+
});
107+
108+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
109+
/* @var $client Stream */
110+
111+
$data = str_repeat('a', 200000);
112+
$client->end($data);
113+
114+
// await server to report connection "close" event
115+
$received = Block\await($disconnected->promise(), $this->loop);
116+
117+
$this->assertEquals($data, $received);
118+
}
119+
120+
public function testSendDataWithoutEndingToServerReceivesAllData()
121+
{
122+
$received = '';
123+
$this->server->on('connection', function (Stream $peer) use (&$received) {
124+
$peer->on('data', function ($chunk) use (&$received) {
125+
$received .= $chunk;
126+
});
127+
});
128+
129+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
130+
/* @var $client Stream */
131+
132+
$data = str_repeat('d', 200000);
133+
$client->write($data);
134+
135+
// buffer incoming data for 0.1s (should be plenty of time)
136+
Block\sleep(0.1, $this->loop);
137+
138+
$client->close();
139+
140+
$this->assertEquals($data, $received);
141+
}
142+
143+
public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
144+
{
145+
$this->server->on('connection', function (Stream $peer) {
146+
$peer->write('hello');
147+
});
148+
149+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
150+
/* @var $client Stream */
151+
152+
// await client to report one "data" event
153+
$receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
154+
Block\await($receive, $this->loop);
155+
156+
$client->close();
157+
}
158+
159+
public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
160+
{
161+
$data = str_repeat('b', 100000);
162+
$this->server->on('connection', function (Stream $peer) use ($data) {
163+
$peer->end($data);
164+
});
165+
166+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
167+
/* @var $client Stream */
168+
169+
// await data from client until it closes
170+
$received = Block\await(BufferedSink::createPromise($client), $this->loop);
171+
172+
$this->assertEquals($data, $received);
173+
}
174+
175+
public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
176+
{
177+
$data = str_repeat('c', 100000);
178+
$this->server->on('connection', function (Stream $peer) use ($data) {
179+
$peer->write($data);
180+
});
181+
182+
$client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop);
183+
/* @var $client Stream */
184+
185+
// buffer incoming data for 0.1s (should be plenty of time)
186+
$received = '';
187+
$client->on('data', function ($chunk) use (&$received) {
188+
$received .= $chunk;
189+
});
190+
Block\sleep(0.1, $this->loop);
191+
192+
$client->close();
193+
194+
$this->assertEquals($data, $received);
195+
}
196+
197+
private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
198+
{
199+
return new Promise(function ($resolve) use ($emitter, $event, $fn) {
200+
$emitter->on($event, function () use ($resolve, $fn) {
201+
$resolve(call_user_func_array($fn, func_get_args()));
202+
});
203+
});
204+
}
205+
}

0 commit comments

Comments
 (0)