Merge branch '2'

This commit is contained in:
Serge Latyntcev 2019-05-15 08:57:07 +12:00
commit 4f68d5bae2
5 changed files with 153 additions and 28 deletions

View File

@ -20,6 +20,11 @@ is a random token stored in the browser session, in order to make the
test session specific to the executing browser, and allow multiple
people using their own test session in the same webroot.
The module also keeps some metadata about the session state in the database,
so that it may be available for the clients as well.
E.g. the silverstripe-behat-extension may use it through this module APIs,
allowing us to introduce some grey-box testing techniques.
The module also serves as an initializer for the
[SilverStripe Behat Extension](https://github.com/silverstripe-labs/silverstripe-behat-extension/).
It is required for Behat because the Behat CLI test runner needs to persist

View File

@ -1,6 +1,7 @@
---
Name: testsessionroutes
---
SilverStripe\Control\Director:
rules:
dev/testsession: SilverStripe\TestSession\TestSessionController
SilverStripe\Dev\DevelopmentAdmin:
registered_controllers:
testsession:
controller: SilverStripe\TestSession\TestSessionController

View File

@ -283,26 +283,7 @@ class TestSessionEnvironment
}
// ensure we have a connection to the database
if (isset($state->database) && $state->database) {
if (!DB::get_conn()) {
// No connection, so try and connect to tmpdb if it exists
if (isset($state->database)) {
$this->oldDatabaseName = $databaseConfig['database'];
$databaseConfig['database'] = $state->database;
}
// Connect to database
DB::connect($databaseConfig);
} else {
// We've already connected to the database, do a fast check to see what database we're currently using
$db = DB::get_conn()->getSelectedDatabase();
if (isset($state->database) && $db != $state->database) {
$this->oldDatabaseName = $databaseConfig['database'];
$databaseConfig['database'] = $state->database;
DB::connect($databaseConfig);
}
}
}
$this->connectToDatabase($state);
// Database
if (!$this->isRunningTests()) {
@ -334,6 +315,8 @@ class TestSessionEnvironment
// Connect to the new database, overwriting the old DB connection (if any)
DB::connect($databaseConfig);
}
TestSessionState::create()->write(); // initialize the session state
}
// Mailer
@ -362,6 +345,7 @@ class TestSessionEnvironment
}
$this->saveState($state);
$this->extend('onAfterApplyState');
}
@ -557,4 +541,69 @@ class TestSessionEnvironment
{
return PUBLIC_PATH . DIRECTORY_SEPARATOR . 'assets_backup';
}
/**
* Ensure that there is a connection to the database
*
* @param mixed $state
*/
public function connectToDatabase($state = null) {
if ($state == null) {
$state = $this->getState();
}
$databaseConfig = DB::getConfig();
if (isset($state->database) && $state->database) {
if (!DB::get_conn()) {
// No connection, so try and connect to tmpdb if it exists
if (isset($state->database)) {
$this->oldDatabaseName = $databaseConfig['database'];
$databaseConfig['database'] = $state->database;
}
// Connect to database
DB::connect($databaseConfig);
} else {
// We've already connected to the database, do a fast check to see what database we're currently using
$db = DB::get_conn()->getSelectedDatabase();
if (isset($state->database) && $db != $state->database) {
$this->oldDatabaseName = $databaseConfig['database'];
$databaseConfig['database'] = $state->database;
DB::connect($databaseConfig);
}
}
}
}
/**
* Wait for pending requests
*
* @param int $await Time to wait (in ms) after the last response (to allow the browser react)
* @param int $timeout For how long (in ms) do we wait before giving up
*
* @return bool Whether there are no more pending requests
*/
public function waitForPendingRequests($await = 700, $timeout = 10000)
{
$timeout = TestSessionState::millitime() + $timeout;
$interval = max(300, $await);
do {
$now = TestSessionState::millitime();
if ($timeout < $now) {
return false;
}
$model = TestSessionState::get()->byID(1);
$pendingRequests = $model->PendingRequests > 0;
$lastRequestAwait = ($model->LastResponseTimestamp + $await) > $now;
$pending = $pendingRequests || $lastRequestAwait;
} while ($pending && (usleep($interval * 1000) || true));
return true;
}
}

View File

@ -39,12 +39,14 @@ class TestSessionHTTPMiddleware implements HTTPMiddleware
// Load test state
$this->loadTestState($request);
TestSessionState::incrementState();
// Call with safe teardown
try {
return $delegate($request);
} finally {
$this->restoreTestState($request);
TestSessionState::decrementState();
}
}
@ -69,6 +71,9 @@ class TestSessionHTTPMiddleware implements HTTPMiddleware
Email::config()->set("send_all_emails_to", null);
}
// Connect to the test session database
$this->testSessionEnvironment->connectToDatabase();
// Allows inclusion of a PHP file, usually with procedural commands
// to set up required test state. The file can be generated
// through {@link TestSessionStubCodeWriter}, and the session state
@ -77,11 +82,6 @@ class TestSessionHTTPMiddleware implements HTTPMiddleware
if (isset($testState->stubfile)) {
$file = $testState->stubfile;
if (!Director::isLive() && $file && file_exists($file)) {
// Connect to the database so the included code can interact with it
$databaseConfig = DB::getConfig();
if ($databaseConfig) {
DB::connect($databaseConfig);
}
include_once($file);
}
}

70
src/TestSessionState.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace SilverStripe\TestSession;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Queries\SQLUpdate;
/**
* The session state keeps some metadata about the current test session.
* This may allow the client (Behat) to get some insight into the
* server side affairs (e.g. if the server is handling some number requests at the moment).
*
* The client side (Behat) must not use this class straightforwardly, but rather
* rely on the API of {@see TestSessionEnvironment} or {@see TestSessionController}.
*
* @property int PendingRequests keeps information about how many requests are in progress
* @property float LastResponseTimestamp microtime of the last response made by the server
*/
class TestSessionState extends DataObject
{
private static $table_name = 'TestSessionState';
private static $db = [
'PendingRequests' => 'Int',
'LastResponseTimestamp' => 'Decimal(14, 0)'
];
/**
* Increments TestSessionState.PendingRequests number by 1
* to indicate we have one more request in progress
*/
public static function incrementState()
{
$schema = DataObject::getSchema();
$update = SQLUpdate::create(sprintf('"%s"', $schema->tableName(self::class)))
->addWhere(['ID' => 1])
->assignSQL('"PendingRequests"', '"PendingRequests" + 1');
$update->execute();
}
/**
* Decrements TestSessionState.PendingRequests number by 1
* to indicate we have one more request in progress.
* Also updates TestSessionState.LastResponseTimestamp
* to the current timestamp.
*/
public static function decrementState()
{
$schema = DataObject::getSchema();
$update = SQLUpdate::create(sprintf('"%s"', $schema->tableName(self::class)))
->addWhere(['ID' => 1])
->assignSQL('"PendingRequests"', '"PendingRequests" - 1')
->assign('"LastResponseTimestamp"', self::millitime());
$update->execute();
}
/**
* Returns unix timestamp in milliseconds
*
* @return float milliseconds since 1970
*/
public static function millitime()
{
return round(microtime(true) * 1000);
}
}