Skip to content

Commit

Permalink
Migrate to new testing library
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed Feb 18, 2023
1 parent 6c1fd75 commit abc1e2a
Show file tree
Hide file tree
Showing 49 changed files with 578 additions and 602 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
echo "vendor/autoload.php" > composer.pth
- name: Run test suite
run: sh xp-run xp.unittest.TestRunner src/test/php
run: sh xp-run xp.test.Runner src/test/php

- name: Run integration tests
run: sh xp-run xp.unittest.TestRunner src/it/php
run: sh xp-run xp.test.Runner src/it/php
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"php": ">=7.0.0"
},
"require-dev" : {
"xp-framework/unittest": "^11.0 | ^10.1"
"xp-framework/test": "^1.0"
},
"bin": ["bin/xp.xp-forge.web"],
"autoload" : {
Expand Down
151 changes: 76 additions & 75 deletions src/it/php/web/unittest/IntegrationTest.class.php
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
<?php namespace web\unittest;

use unittest\{Assert, Action, Test, Values};
use test\{Assert, After, Test, Values};

#[Action(eval: 'new StartServer("web.unittest.TestingServer", "connected")')]
#[StartServer(TestingServer::class)]
class IntegrationTest {
const FORM_URLENCODED = 'application/x-www-form-urlencoded';

private static $connection;
private $server;

/** @param peer.Socket $client */
public static function connected($client) {
self::$connection= $client;
/** @param web.unittest.StartServer $server */
public function __construct($server) {
$this->server= $server;
}

#[After]
public function shutdown() {
$this->server->shutdown();
}

/**
* Sends a request. Ensure connection is closed for receive() to be able
* to read until EOF.
* Sends a request. Opens connection before sending the request, and closes
* connection after reading the response.
*
* @param string $method
* @param string $uri
Expand All @@ -25,139 +30,135 @@ public static function connected($client) {
* @return void
*/
private function send($method, $uri, $version= '1.0', $headers= [], $body= '') {
self::$connection->write($method.' '.$uri.' HTTP/'.$version."\r\n");
foreach (['Connection' => 'close'] + $headers as $name => $value) {
self::$connection->write($name.': '.$value."\r\n");
$this->server->connection->connect();
try {

// Send request. Ensure `Connection: close` is always sent along in order to
// be able to read until EOF instead of having to parse the response payload.
$this->server->connection->write($method.' '.$uri.' HTTP/'.$version."\r\n");
foreach (['Connection' => 'close'] + $headers as $name => $value) {
$this->server->connection->write($name.': '.$value."\r\n");
}
$this->server->connection->write("\r\n".$body);

// Read response
$response= ['status' => $this->server->connection->readLine(), 'headers' => [], 'body' => ''];
while ('' !== ($line= $this->server->connection->readLine())) {
sscanf($line, "%[^:]: %[^\r]", $name, $value);
$response['headers'][$name]= $value;
}
while (!$this->server->connection->eof()) {
$response['body'].= $this->server->connection->read();
}
return $response;
} finally {
$this->server->connection->close();
}
self::$connection->write("\r\n".$body);
}

/**
* Receives a response
*
* @return [:var]
*/
private function receive() {
$response= ['status' => self::$connection->readLine(), 'headers' => [], 'body' => ''];
while ('' !== ($line= self::$connection->readLine())) {
sscanf($line, "%[^:]: %[^\r]", $name, $value);
$response['headers'][$name]= $value;
}
while (!self::$connection->eof()) {
$response['body'].= self::$connection->read();
#[Test]
public function malformed_protocol() {
$this->server->connection->connect();
try {
$this->server->connection->write("EHLO example.org\r\n");
$status= $this->server->connection->readLine();
} finally {
$this->server->connection->close();
}
return $response;
Assert::equals("HTTP/1.1 400 Bad Request", $status);
}

#[Test, Values(['1.0', '1.1'])]
public function returns_http_version($version) {
$this->send('GET', '/status/200', $version, ['Connection' => 'close']);
Assert::equals("HTTP/$version 200 OK", $this->receive()['status']);
$r= $this->send('GET', '/status/200', $version);
Assert::equals("HTTP/$version 200 OK", $r['status']);
}

#[Test]
public function date_header_always_present() {
$this->send('GET', '/status/200');
Assert::true(isset($this->receive()['headers']['Date']));
$r= $this->send('GET', '/status/200');
Assert::true(isset($r['headers']['Date']));
}

#[Test]
public function server_header_always_present() {
$this->send('GET', '/status/200', '1.1', ['Connection' => 'close']);
Assert::equals('XP', $this->receive()['headers']['Server']);
$r= $this->send('GET', '/status/200', '1.1', ['Connection' => 'close']);
Assert::equals('XP', $r['headers']['Server']);
}

#[Test, Values([[200, '200 OK'], [404, '404 Not Found'], [420, '420 Enhance your calm']])]
public function echo_status($code, $expected) {
$this->send('GET', '/status/'.$code);
Assert::equals("HTTP/1.0 $expected", $this->receive()['status']);
$r= $this->send('GET', '/status/'.$code);
Assert::equals("HTTP/1.0 $expected", $r['status']);
}

#[Test, Values([['', '420 Enhance your calm'], ['message=Custom+message', '420 Custom message']])]
public function custom_status($query, $expected) {
$this->send('GET', '/status/420?'.$query);
Assert::equals("HTTP/1.0 $expected", $this->receive()['status']);
$r= $this->send('GET', '/status/420?'.$query);
Assert::equals("HTTP/1.0 $expected", $r['status']);
}

#[Test, Values([[404, '404 Not Found'], [500, '500 Internal Server Error']])]
public function echo_error($code, $expected) {
$this->send('GET', '/raise/error/'.$code);
Assert::equals("HTTP/1.0 $expected", $this->receive()['status']);
$r= $this->send('GET', '/raise/error/'.$code);
Assert::equals("HTTP/1.0 $expected", $r['status']);
}

#[Test]
public function dispatching_request() {
$this->send('GET', '/dispatch');
Assert::equals("HTTP/1.0 420 Dispatched", $this->receive()['status']);
$r= $this->send('GET', '/dispatch');
Assert::equals("HTTP/1.0 420 Dispatched", $r['status']);
}

#[Test, Values(['lang.IllegalAccessException', 'Exception'])]
public function raising_exception_yield_500($class) {
$this->send('GET', '/raise/exception/'.$class);
Assert::equals("HTTP/1.0 500 Internal Server Error", $this->receive()['status']);
$r= $this->send('GET', '/raise/exception/'.$class);
Assert::equals("HTTP/1.0 500 Internal Server Error", $r['status']);
}

#[Test]
public function unrouted_uris_yield_404() {
$this->send('GET', '/not-routed');
Assert::equals("HTTP/1.0 404 Not Found", $this->receive()['status']);
}

#[Test]
public function malformed_protocol() {
self::$connection->write("EHLO example.org\r\n");
Assert::equals("HTTP/1.1 400 Bad Request", self::$connection->readLine());
$r= $this->send('GET', '/not-routed');
Assert::equals("HTTP/1.0 404 Not Found", $r['status']);
}

#[Test]
public function content_comes_with_length() {
$this->send('GET', '/content?data=Test');
$response= $this->receive();

Assert::equals(['4', 'Test'], [$response['headers']['Content-Length'], $response['body']]);
$r= $this->send('GET', '/content?data=Test');
Assert::equals(['4', 'Test'], [$r['headers']['Content-Length'], $r['body']]);
}

#[Test]
public function post_body_read() {
$headers= ['Content-Type' => self::FORM_URLENCODED, 'Content-Length' => 9];
$this->send('POST', '/content', '1.0', $headers, 'data=Test');
$response= $this->receive();

Assert::equals(['4', 'Test'], [$response['headers']['Content-Length'], $response['body']]);
$r= $this->send('POST', '/content', '1.0', $headers, 'data=Test');
Assert::equals(['4', 'Test'], [$r['headers']['Content-Length'], $r['body']]);
}

#[Test]
public function chunked_body_read() {
$headers= ['Content-Type' => self::FORM_URLENCODED, 'Transfer-Encoding' => 'chunked'];
$this->send('POST', '/content', '1.1', $headers, "9\r\ndata=Test\r\n0\r\n\r\n");
$response= $this->receive();

Assert::equals(['4', 'Test'], [$response['headers']['Content-Length'], $response['body']]);
$r= $this->send('POST', '/content', '1.1', $headers, "9\r\ndata=Test\r\n0\r\n\r\n");
Assert::equals(['4', 'Test'], [$r['headers']['Content-Length'], $r['body']]);
}

#[Test]
public function stream_comes_with_length_in_http10() {
$this->send('GET', '/stream?data=Test', '1.0');
$response= $this->receive();

Assert::equals(['4', 'Test'], [$response['headers']['Content-Length'], $response['body']]);
$r= $this->send('GET', '/stream?data=Test', '1.0');
Assert::equals(['4', 'Test'], [$r['headers']['Content-Length'], $r['body']]);
}

#[Test]
public function stream_comes_with_chunked_te_in_http11() {
$this->send('GET', '/stream?data=Test', '1.1');
$response= $this->receive();

Assert::equals('chunked', $response['headers']['Transfer-Encoding']);
Assert::equals("4\r\nTest\r\n0\r\n\r\n", $response['body']);
$r= $this->send('GET', '/stream?data=Test', '1.1');
Assert::equals('chunked', $r['headers']['Transfer-Encoding']);
Assert::equals("4\r\nTest\r\n0\r\n\r\n", $r['body']);
}

#[Test, Values([1024, 4096, 8192])]
public function with_large_cookie($length) {
$header= 'cookie='.str_repeat('*', $length);
$this->send('GET', '/cookie', '1.0', ['Cookie' => $header]);
$response= $this->receive();

Assert::equals((string)strlen($header), $response['body']);
$r= $this->send('GET', '/cookie', '1.0', ['Cookie' => $header]);
Assert::equals((string)strlen($header), $r['body']);
}
}
67 changes: 18 additions & 49 deletions src/it/php/web/unittest/StartServer.class.php
Original file line number Diff line number Diff line change
@@ -1,77 +1,46 @@
<?php namespace web\unittest;

use lang\{Runtime, XPClass};
use lang\{Runtime, IllegalStateException};
use peer\Socket;
use unittest\{PrerequisitesNotMetError, Test, TestAction, TestClassAction};
use test\Provider;
use test\execution\Context;

class StartServer implements TestAction, TestClassAction {
private $server, $connected, $process, $client;
class StartServer implements Provider {
private $server;
private $process= null;
public $connection= null;

/**
* Constructor
*
* @param string $server Server process main class
* @param string $connected Name of connection callback
*/
public function __construct($server, $connected) {
$this->server= $server;
$this->connected= $connected;
public function __construct($server) {
$this->server= strtr($server, '\\', '.');
}

/**
* Starts server
*
* @param lang.XPClass $c
* @return void
* @throws unittest.PrerequisitesNotMetError
*/
public function beforeTestClass(XPClass $c) {
public function values(Context $context): iterable {
$this->process= Runtime::getInstance()->newInstance(null, 'class', $this->server, []);
$this->process->in->close();

// Check if startup succeeded
$status= $this->process->out->readLine();
if (2 !== sscanf($status, '+ Service %[0-9.]:%d', $host, $port)) {
$this->afterTestClass($c);
throw new PrerequisitesNotMetError('Cannot start server: '.$status, null);
$this->shutdown();
throw new IllegalStateException('Cannot start server: '.$status, null);
}

$this->client= new Socket($host, $port);
$c->getMethod($this->connected)->invoke(null, [$this->client]);
$this->connection= new Socket($host, $port);
yield $this;
}

/**
* This method gets invoked before a test method is invoked, and before
* the setUp() method is called.
*
* @param unittest.Test $t
* @return void
* @throws unittest.PrerequisitesNotMetError
*/
public function beforeTest(Test $t) {
$this->client->connect();
}
/** @return void */
public function shutdown() {
if (null === $this->process) return;

/**
* This method gets invoked after the test method is invoked and regard-
* less of its outcome, after the tearDown() call has run.
*
* @param unittest.Test $t
* @return void
*/
public function afterTest(Test $t) {
$this->client->close();
}

/**
* Shuts down server
*
* @param lang.XPClass $c
* @return void
*/
public function afterTestClass(XPClass $c) {
$this->process->err->close();
$this->process->out->close();
$this->process->terminate();
$this->process= null;
}
}
2 changes: 1 addition & 1 deletion src/it/php/web/unittest/TestingApplication.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php namespace web\unittest;

use lang\XPClass;
use unittest\Assert;
use test\Assert;
use web\{Application, Error};

class TestingApplication extends Application {
Expand Down
2 changes: 1 addition & 1 deletion src/it/php/web/unittest/TestingServer.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use lang\Throwable;
use peer\ServerSocket;
use peer\server\Server;
use unittest\Assert;
use test\Assert;
use util\cmd\Console;
use web\{Environment, Logging};
use xp\web\srv\HttpProtocol;
Expand Down
Loading

0 comments on commit abc1e2a

Please sign in to comment.