Showing with 351 additions and 118 deletions.
  1. +1 −1 README.md
  2. +1 −1 config/version.ini
  3. +1 −0 inc/BrowserInfo.php
  4. +179 −31 inc/Database.php
  5. +1 −1 inc/MaintenanceScript.php
  6. +1 −1 inc/Page.php
  7. +120 −2 inc/TestSwarm.php
  8. +6 −4 inc/actions/InfoAction.php
  9. +3 −1 inc/actions/JobAction.php
  10. +8 −4 inc/actions/SaverunAction.php
  11. +4 −3 inc/actions/WiperunAction.php
  12. +1 −0 inc/init.php
  13. +2 −1 inc/pages/Error500Page.php
  14. +23 −68 inc/utilities.php
@@ -27,7 +27,7 @@ Releases will be numbered in the following format:

`<major>.<minor>.<patch>`

The `-pre` suffix is used to indicate unreleased versions in development.
The `-alpha` suffix is used to indicate unreleased versions in development.

For more information on SemVer, please visit http://semver.org/.

@@ -1 +1 @@
1.0.0-pre
1.0.0-alpha
@@ -44,6 +44,7 @@ public static function getSwarmUAIndex() {
// to an object with boolean values
$swarmUaIndex = new stdClass;
$rawIndex = parse_ini_file( "$swarmInstallDir/config/useragents.ini", true );
natcaseksort( $rawIndex );
foreach ( $rawIndex as $uaID => $uaItem ) {
if ( is_array( $uaItem ) ) {
$uaItem2 = $uaItem;
@@ -2,6 +2,10 @@
/**
* Basic class to abstract database interaction.
*
* Some methods are based on:
* - svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/db/Database.php?view=markup&pathrev=113601
* - svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/db/DatabaseMysql.php?view=markup&pathrev=112598
*
* @author Timo Tijhof, 2012
* @since 0.3.0
* @package TestSwarm
@@ -13,8 +17,12 @@ class Database {
protected $conn;
protected $isOpen = false;

protected $ignoreErrors = false;

private $rawQueryHistory = array();

protected $delimiter = ';';

/**
* Creates a Database object, opens the connection and returns the instance.
*
@@ -37,6 +45,11 @@ public static function newFromContext( TestSwarmContext $context, $connType = SW
return $db;
}

/** @return string: current DB name */
public function getDBname() {
return $this->dbname;
}

/**
* @param $connType int: SWARM_DBCON_DEFAULT or SWARM_DBCON_PERSISTENT.
*/
@@ -71,15 +84,40 @@ public function open( $connType = SWARM_DBCON_DEFAULT ) {
return $this;
}

/** @return array of objects|false */
public function getRows( $sql ) {
/** @return bool */
public function ping() {
$ping = mysql_ping( $this->conn );
if ( $ping ) {
return true;
}

$this->open();
return true;
}

/** @return bool */
public function close() {
$this->isOpen = false;
if ( $this->conn ) {
$ret = $this->closeConn();
$this->conn = null;
return $ret;
} else {
return true;
}
}

/** @return bool */
protected function closeConn() {
return mysql_close( $this->conn );
}

/** @return mixed|false */
public function getOne( $sql ) {
$res = $this->doQuery( $sql );
if ( $res && $this->getNumRows( $res ) ) {
$ret = array();
while ( $res && $row = $this->fetchObject( $res ) ) {
$ret[] = $row;
}
return $ret;
$row = mysql_fetch_array( $res );
return $row ? reset( $row ) : false;
}
return false;
}
@@ -93,18 +131,110 @@ public function getRow( $sql ) {
return false;
}

/** @return mixed|false */
public function getOne( $sql ) {
/** @return array of objects|false */
public function getRows( $sql ) {
$res = $this->doQuery( $sql );
if ( $res && $this->getNumRows( $res ) ) {
$row = mysql_fetch_array( $res );
return $row ? reset( $row ) : false;
$ret = array();
while ( $res && $row = $this->fetchObject( $res ) ) {
$ret[] = $row;
}
$this->freeResult( $res );
return $ret;
}
return false;
}

/** @return bool */
public function tableExists( $table ) {
$table = $this->addIdentifierQuotes( $table );

$prev = $this->ignoreErrors( true );
$response = $this->doQuery( "SELECT 1 FROM $table LIMIT 1;" );
$this->ignoreErrors( $prev );

return (bool)$response;
}

/**
* @param $table string
* @param $fieldName string
* @return bool|object: see also php.net/mysql_fetch_field
*/
public function fieldInfo( $table, $fieldName ) {
$table = $this->addIdentifierQuotes( $table );

$prev = $this->ignoreErrors( true );
$response = $this->doQuery( "SELECT * FROM $table LIMIT 1;" );
$this->ignoreErrors( $prev );

if ( !$response ) {
return false;
}

$n = mysql_num_fields( $response );
for ( $i = 0; $i < $n; $i += 1 ) {
$fieldInfoObj = mysql_fetch_field( $response, $i );
if ( $fieldInfoObj->name === $fieldName ) {
return $fieldInfoObj;
}
}
return false;
}

/**
* Determines whether a field exists in a table.
*
* @param $table string
* @param $fieldName string
* @return bool: whether $table has $field.
*/
public function fieldExists( $table, $fieldName ) {
$info = $this->fieldInfo( $table, $fieldName );
return (bool)$info;
}

/**
* Roughly parse a .sql file and execute it in small batches
* @param $fullSource string: Full source of .sql file
* There should be no more than 1 statement (ending in ;) on a single line.
* @return bool
*/
public function batchQueryFromFile( $fullSource ) {
$lines = explode( "\n", $fullSource );
$sql = "";
$realSql = false;
foreach ( $lines as $line ) {
$line = trim( $line );

// Skip empty lines and comments
if ( $line === '' || ( $line[0] === '-' && $line[1] === '--' ) ) {
continue;
}

if ( $sql !== "" ) {
$sql .= " ";
}

$sql .= "$line\n";

// Is this line the end of statement?
$lineCopy = $line;
$lineCopy = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $lineCopy );
if ( $lineCopy != $line ) {
// Execute what we have so far and reset the sql string
$realSql = $sql;
$sql = "";
$this->query( $realSql );
}
}
return true;
}

/**
* Queries other than SELECT, such as DELETE, UPDATE and INSERT.
* SELECT queries should use getOne, getRow or getRows.
*
* @return resource|false
*/
public function query( $sql ) {
@@ -114,7 +244,7 @@ public function query( $sql ) {
/** @return int */
public function getNumRows( $res ) {
$n = mysql_num_rows( $res );
if ( $this->lastErrNo() ) {
if ( !$this->ignoreErrors && $this->lastErrNo() ) {
throw new SwarmException( 'Error in getNumRows: ' . $this->lastErrMsg() );
}
return intval( $n );
@@ -138,24 +268,31 @@ public function lastErrMsg() {
return $this->conn ? mysql_error( $this->conn ) : mysql_error();
}

/** @return bool */
public function close() {
$this->isOpen = false;
if ( $this->conn ) {
$ret = $this->closeConn();
$this->conn = null;
return $ret;
} else {
return true;
public function strEncode( $str ) {
$encoded = mysql_real_escape_string( $str, $this->conn );
if ( $encoded === false ) {
$this->ping();
$encoded = mysql_real_escape_string( $str, $this->conn );
}
return $encoded;
}

public function addIdentifierQuotes( $s ) {
return "`" . $this->strEncode( $s ) . "`";
}

/** @return MySQL resource|false */
/**
* Execute actual queries. Within this class, this function should be used
* to do queries, not the wrapper function query(). The logger will log
* the caller of the caller of this function. So there should be one function
* in between this and outside this class.
* @return MySQL resource|false
*/
protected function doQuery( $sql ) {
$microtimeStart = microtime( /*get_as_float=*/true );
$queryResponse = mysql_query( $sql, $this->conn );

if ( $queryResponse === false || $this->lastErrNo() ) {
if ( !$this->ignoreErrors && ( $queryResponse === false || $this->lastErrNo() ) ) {
throw new SwarmException( 'Error in doQuery: ' . $this->lastErrMsg() );
}

@@ -166,21 +303,26 @@ protected function doQuery( $sql ) {

protected function fetchObject( $res ) {
$obj = mysql_fetch_object( $res );
if ( $this->lastErrNo() ) {
if ( !$this->ignoreErrors && $this->lastErrNo() ) {
throw new SwarmException( 'Error in fetchObject: ' . $this->lastErrMsg() );
}
return $obj;
}

/** @return bool */
protected function closeConn() {
return mysql_close( $this->conn );
public function freeResult( $res ) {
if ( is_resource( $res ) ) {
$ok = mysql_free_result( $res );
if ( $ok ) {
return true;
}
}
throw new SwarmException( "Unable to free MySQL result" );
}

protected function checkEnvironment() {
if ( !function_exists( "mysql_connect" ) ) {
throw new SwarmException( "MySQL functions missing." );
}
public function ignoreErrors( $setting ) {
$oldVal = $this->ignoreErrors;
$this->ignoreErrors = (bool)$setting;
return $oldVal;
}

/**
@@ -215,6 +357,12 @@ public function getQueryLog() {
return $this->rawQueryHistory;
}

protected function checkEnvironment() {
if ( !function_exists( "mysql_connect" ) ) {
throw new SwarmException( "MySQL functions missing." );
}
}

private function __construct() {
$this->checkEnvironment();
}
@@ -134,7 +134,7 @@ public function run() {
$this->parseCliArguments();

// Generate header
$version = swarmGetVersion( $this->getContext() );
$version = $this->getContext()->getVersion( "bypass-cache" );
$description = wordwrap( $this->description, 72, "\n", true );
$description = explode( "\n", $description );
$description[] = str_repeat( "-", 72 );
@@ -382,7 +382,7 @@ final public static function newFromContext( TestSwarmContext $context ) {
$page = new static();
$page->context = $context;

$page->metaTags[] = array( "name" => "generator", "content" => swarmGetVersion( $context ) );
$page->metaTags[] = array( "name" => "generator", "content" => $context->getVersion() );

return $page;
}