moved unit test website code to branches/resources/ from trunk/utils/

[[Split portion of a mixed commit.]]
This commit is contained in:
Pauli Nieminen 2008-08-12 22:44:15 +00:00
parent fffd8f131d
commit 4327e0e843
28 changed files with 2103 additions and 0 deletions

View file

@ -0,0 +1,64 @@
Contents:
1. Requirements
2. Setting up directories
3. Configuring database
4. Configuring apache
5. Configuring website and autotester
1. Requirements
You will need libs required to build wesnoth client.
php >= 5.0 (both apache mod and cli)
mysql >= 5.0
scons
smarty (http://www.smarty.net)
adodb lite (http://adodblite.sourceforge.net/)
2. Setting up directories
Go to directory where you want to settup auto unit testing.
Do a svn checkout from wesnoth trunk.
svn co svn://svn.gna.org/svn/wesnoth/trunk/ trunk
Export website from svn work directory
svn export trunk/utils/tests tests
3. Configuring database
You need to create new database and user for auto unit tester.User will need right for SELECT, UPDATE, DELETE, ALTER TABLE, DROP TABLE in the created database.
4. Configuring apache
Make sure that php is loaded for apache
Add to apache site configuration:
Alias /wesnoth_test/ "/path/to/export/tests/htdocs/"
<Directory "/path/to/export/tests/htdocs/">
AllowOverride None
Order allow,deny
Allow from all
</directory>
restart apache
5. Configuring website and autotester
Extract Smarty to tests/smarty_workdir/
Extract Adodb to tests/adodb_lite/
Settup smarty cache directories so that apache can write to them:
sudo chown www-data:www-data tests/smarty_workdir/tempalte_c tests/smarty_workdir/cache
copy tests/autotester/path_settup.sh.template to tests/autoster/path_settup.sh
Edit variables in path_settup.sh to have correct path to directories.
copy tests/include/settup.php.template to tests/include/settup.php
Edit database login information in settup.php.
execute tests/autotester/run_unit_test.sh
Check that website is visible.
Make
add tests/autotester/run_unit_tests.sh to crontab so it is executed regulary.

View file

@ -0,0 +1,19 @@
# This file is loaded by all unit test scripts to settup correct worknig paths
#/*
# Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
# Part of the Battle for Wesnoth Project http://www.wesnoth.org/
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# or at your option any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY.
#
# See the COPYING file for more details.
#*/
SVNDIR="/path/to/wesnoth/trunk"
WEBDIR="/path/to/tests/htdocs"
AUTOTESTDIR="/path/to/tests/autotester"

View file

@ -0,0 +1,54 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
if ($argc != 2)
{
echo "Wrong number of parameters given";
die;
}
$root_dir = $argv[1];
require_once $root_dir . '/../include/settup.php';
// used to show all queries to db when debuging;
//$db->debug = true;
// Upgrade the database format to the newest
$creator = new DBCreator();
$creator->checkDB();
// do svn up
$svn = new SVNUpdater();
if ($svn->getRevision() === false)
{
trigger_error("SVN is down", E_USER_ERROR);
}
$build = new Build($svn->getRevision());
if (!$build->Exists()
|| $build->getStatus() != Build::S_GOOD)
{
// Only run tests if build doesn't exists
if ($build->compile($svn->getRevision()))
{
$test_runner = new TestRunner();
$test_runner->run($build);
}
$db->CompleteTrans();
}
$config = new Config('last_autotest_run_time');
$config->set(time());
?>

View file

@ -0,0 +1,23 @@
#!/bin/bash
#/*
# Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
# Part of the Battle for Wesnoth Project http://www.wesnoth.org/
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# or at your option any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY.
#
# See the COPYING file for more details.
#*/
FULL_PATH=`dirname $(readlink -f $0)`
source $FULL_PATH/path_settup.sh
export SSH_AUTH_SOCK=`find /tmp/keyring* -name ssh`
export DISPLAY=:0.0
cd $SVNDIR
nice php -f ${AUTOTESTDIR}/run_unit_tests.php $WEBDIR
# > $FULL_PATH/err.log

View file

@ -0,0 +1,31 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
$root_dir = './';
require_once($root_dir . '../include/settup.php');
$header = new Header('build_history');
$footer = new Footer('build_history');
$header->show();
$user_params = new ParameterValidator($_GET);
$smarty->assign(Build::GetVisibleBuilds($user_params));
$smarty->display('build_history.tpl');
$footer->show();
?>

View file

@ -0,0 +1,35 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
$root_dir = './';
require_once($root_dir . '../include/settup.php');
$header = new Header('index');
$footer = new Footer('index');
$header->show();
$build = new Build();
$build->fetchLast();
$smarty->assign($build->getStatistics());
$smarty->display('index.tpl');
$footer->show();
?>

View file

@ -0,0 +1,195 @@
/**
* Use to make long text to have short description and then on mouse over
* show whole text over the page
**/
var autohide_next_id = 0;
var autohide_store = new Array();
var onload_registered = false;
function addOnloadFunction(func)
{
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
function onload_autohide()
{
for (var hide in autohide_store)
{
autohide_store[hide].make_hide_text();
}
}
function Autohide(text, length, split_from_space, take_end)
{
this.id = autohide_store.length;
this.text = text;
this.length = length;
this.split_from_space = split_from_space;
this.take_end = take_end;
document.write('<div id="autohide' + this.id + '"></div>');
if (!onload_registered)
{
addOnloadFunction(onload_autohide);
onload_registered = true;
}
}
Autohide.prototype.make_hide_text = function()
{
var short_text;
var split_text;
if (this.split_from_space)
{
split_text = this.text.split(' ');
short_text = "";
var i = 0;
while(i < split_text.length && short_text.length + split_text[i].length < this.length)
{
if (i>0)
short_text += ' ';
short_text += split_text[i];
++i;
}
short_text = document.createTextNode(short_text);
} else {
var start=0;
if (this.take_end)
{
start = this.text.length - this.length;
if (start < 0)
start =0;
}
short_text = document.createTextNode(this.text.substr(start,this.length));
}
this.text = this.text.split("\n");
split_text = new Array();
for(var index in this.text)
{
if (this.text[index].length)
{
split_text.push(document.createTextNode(this.text[index]));
split_text.push(document.createElement('BR'));
}
}
if (split_text.length)
split_text.pop();
// Create short text element
var new_elem = document.createElement('DIV');
new_elem.id='autohide_' + this.id;
new_elem.className="autohide";
// create full text element
var elem_over = new_elem.cloneNode(false);
elem_over.id='autohide_over_' + this.id;
elem_over.onmouseout=autohide_mouseout;
elem_over.style.visibility = 'hidden';
for(var i in split_text)
{
elem_over.appendChild(split_text[i]);
}
var target = document.getElementById('autohide'+this.id);
// Fill short text element
new_elem.appendChild(short_text);
new_elem.onmouseover=autohide_mouseover;
elem_over.className="autohide_over";
// add elements to document
target.parentNode.replaceChild(new_elem, target);
new_elem.parentNode.appendChild(elem_over);
// Make over element apear in same place
var max_iter = 3;
do {
var is_hitting_side = (elem_over.offsetLeft + elem_over.offsetWidth >= document.width)
|| (elem_over.offsetTop + elem_over.offsetHeight > document.height);
var y_pos = getYpos(new_elem);
var x_pos = getXpos(new_elem);
var y_pos2 = y_pos + new_elem.scrollHeight;
var x_pos2 = x_pos + new_elem.scrollWidth;
y_pos = (y_pos+y_pos2)/2;
x_pos = (x_pos+x_pos2)/2;
y_pos = y_pos - elem_over.scrollHeight/2;
x_pos = x_pos - elem_over.scrollWidth/2;
// console.log(this.id + ' x:' + x_pos + ' y:' + y_pos + ' ny:' + new_elem.scrollWidth + ' gy:' + getYpos(new_elem));
elem_over.style.top = y_pos + 'px';
elem_over.style.left = x_pos + 'px';
} while(is_hitting_side && --max_iter);
}
function getYpos(element)
{
if (!element.offsetParent)
return element.offsetTop;
else
return element.offsetTop + getYpos(element.offsetParent);
}
function getXpos(element)
{
if (!element.offsetParent)
return element.offsetLeft;
else
return element.offsetLeft + getXpos(element.offsetParent);
}
function autohide_mouseover(e)
{
var new_elem;
if(!e) e = window.event;
if (e.target) new_elem = e.target;
else if (e.srcElement) new_elem = e.srcElement;
if (new_elem.nodeType == 3) // defeat Safari bug
new_elem = new_elem.parentNode;
var over_id = new String(new_elem.id).replace('hide','hide_over');
var elem_over = document.getElementById(over_id);
var y_pos = getYpos(new_elem);
var x_pos = getXpos(new_elem);
var y_pos2 = y_pos + new_elem.scrollHeight;
var x_pos2 = x_pos + new_elem.scrollWidth;
y_pos = (y_pos+y_pos2)/2;
x_pos = (x_pos+x_pos2)/2;
y_pos = y_pos - elem_over.scrollHeight/2;
x_pos = x_pos - elem_over.scrollWidth/2;
// console.log(this.id + ' x:' + x_pos + ' y:' + y_pos + ' ny:' + new_elem.scrollWidth + ' gy:' + getYpos(new_elem));
elem_over.style.top = y_pos + 'px';
elem_over.style.left = x_pos + 'px';
elem_over.style.visibility='visible';
new_elem.style.visibility='hidden';
}
function autohide_mouseout(e)
{
var elem_over;
if(!e) e = window.event;
if (e.target) elem_over = e.target;
else if (e.srcElement) elem_over = e.srcElement;
if (elem_over.nodeType == 3) // defeat Safari bug
elem_over = elem_over.parentNode;
elem_over.style.visibility='hidden';
var new_elem_id = new String(elem_over.id).replace('_over','');
var new_elem = document.getElementById(new_elem_id);
new_elem.style.visibility='visible';
}

View file

@ -0,0 +1,113 @@
div.menu {
position:absolute;
margin-left: 3em;
width: 7em;
border: 1px solid #AAAAAA;
}
.unit_test h3 {
text-align:center;
}
table.build {
margin: auto;
margin-right: auto;
}
.build td {
text-align: center;
}
td.time {
font-weight: bold;
}
.build .passed {
color: #00FF00;
}
.build .failed, .aborted {
color: #FF0000;
}
div.autohide {
border: 1px solid #225599;
padding-left: 0.3em;
padding-right: 0.3em;
}
div.autohide_over {
position: absolute;
background:#FFFBF0 none repeat scroll 0 0;
border: 1px solid #225599;
padding: 0.5em;
padding-left: 1.5em;
padding-right: 1.5em;
/* max-width: 60em;*/
text-align: left;
}
table.test_error {
margin-left:auto;
margin-right:auto;
}
.test_error .Error {
color: #FF0000;
}
.test_error .Warning {
color: #FF4000;
}
.test_error .Exception {
color: #A00000;
}
.test_error .Message {
color: #00FF00;
}
.test_error td {
text-align: center;
}
table.build_history {
width: auto;
max-width:60em;
min-width:10em;
margin: auto auto auto auto;
}
div.paginate {
color: #225599;
font-weight: bold;
text-align: right;
margin-left: auto;
margin-right: 0;
font-size: 0.8em;
}
.paginate a {
color: #225599;
font-weight: bold;
text-decoration: none;
}
.paginate a:hoover {
text-decoration: underline;
font-weight: bold;
color: #225599;
}
.paginate .active {
color: #104060;
}
.paginate a.hidden {
visibility: hidden;
}
/*.paginate span {
color: #225599;
}*/

View file

@ -0,0 +1,341 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class Build {
private $db;
private $id;
private $svn_version;
private $time;
private $status;
private $error_msg;
private $binary_name;
private $previous_id;
private $result;
private $errors;
const S_GOOD = 0;
const S_ERROR = 1;
function __construct($revision = -1)
{
global $db;
$this->db = $db;
$this->binary_name = false;
$this->previous_id = -1;
$this->result = null;
$this->errors = null;
if ($revision >= 0)
$this->fetch("WHERE svn_version=?", array($revision));
}
private function fetch($where, $params = array())
{
$result = $this->db->Execute('SELECT * FROM builds ' . $where . ' LIMIT 1',$params);
if ($result !== false && !$result->EOF())
{
$this->init($result->fields);
} else {
$this->reset();
}
}
private static function multiFetch($where, $params = array())
{
global $db;
$res = array();
$id_list = array();
$result = $db->Execute('SELECT * FROM builds ' . $where ,$params);
if ($result === false)
return $res;
while (!$result->EOF())
{
$build = new Build();
$build->init($result->fields);
$id_list[] = $build->getId();
$res[] = $build;
$result->moveNext();
}
return $res;
}
public function fetchLast()
{
$this->fetch('ORDER BY id DESC');
}
private static function fetchVisibleBuilds($page, $builds_per_page)
{
return self::multiFetch('ORDER BY id DESC LIMIT ?,?',
array(($page-1)*$builds_per_page, $builds_per_page));
}
private static function getNumberOfVisiblePages($builds_per_page)
{
global $db;
$result = $db->Execute('SELECT COUNT(*) as number FROM builds');
return ceil($result->fields['number']/$builds_per_page);
}
public function init($values)
{
foreach($values as $key => $value)
{
$this->$key = $value;
}
$this->time = $this->db->UnixTimeStamp($this->time);
$this->result = null;
$this->errors = null;
}
public function reset()
{
$this->init(array('id' => -1,
'svn_version' => -1,
'time' => $this->db->DBTimeStamp(0),
'status' => -1,
'error_msg' => ""));
$this->previous_id = -1;
}
public function exists()
{
return $this->id > -1;
}
public function getId()
{
return $this->id;
}
public function getStatus()
{
return $this->status;
}
public function getPreviousId()
{
if ($this->previous_id == -1)
{
$result = $this->db->Execute('SELECT MAX(id) as previous_id
FROM builds
WHERE id<?
AND status=?',
array($this->id,
self::S_GOOD));
$this->previous_id = $result->fields['previous_id'];
}
return $this->previous_id;
}
public function getLastWorkingId()
{
if ($this->status == self::S_GOOD)
{
return $this->id;
}
return $this->getPreviousId();
}
public function compile($revision)
{
$compiler_log = shell_exec('scons test 2>&1');
$m =array();
$this->error_msg = '';
$this->status = self::S_GOOD;
if (strpos($compiler_log, "`test' is up to date.") !== false)
{
return false;
}
// build/debug/editor2/
$compiler_log = FilenameConverter::stripBuildDirs($compiler_log);
if (preg_match_all('/^.*(error:|warning:|note:|undefined reference|ld returned \d exit status).*$/mi',$compiler_log, $m,PREG_SET_ORDER))
{
foreach($m as $match)
{
$this->error_msg .= $match[0] . "\n";
}
if (strpos($this->error_msg,'error') !== false
|| strpos($this->error_msg,'ld returned'))
$this->status = self::S_ERROR;
}
if(preg_match('/test to \.\/([a-zA-Z0-9_-]*)/',$compiler_log, $m))
{
$this->binary_name = $m[1];
}
$this->time = time();
$this->svn_revision = $revision;
$this->db->StartTrans();
$this->insert();
return $this->status == self::S_GOOD;
}
public function getTestName()
{
return $this->binary_name;
}
private function insert()
{
$result = $this->db->Execute('INSERT INTO builds
(svn_version, status, error_msg)
VALUES (?, ?, ?)',
array($this->svn_revision,
$this->status,
$this->error_msg));
$this->id = $this->db->Insert_ID();
}
public function insertNull()
{
if ($result = $this->db->Execute('SELECT id FROM builds WHERE id=?', array(0)))
{
if ($result->EOF())
{
$result = $this->db->Execute('INSERT INTO builds
(svn_version, status, error_msg)
VALUES (?, ?, ?)',
array(0,
self::S_GOOD,
''));
$this->id = $this->db->Insert_ID();
$this->db->Execute('UPDATE builds SET id=? WHERE id=?', array(0, $this->id));
}
}
}
private function checkResult()
{
if (is_null($this->result))
{
$this->result = new TestResult($this->getLastWorkingId());
}
}
private function getErrorStatistics()
{
$ret = array();
foreach($this->errors as $err)
{
$ret[] = $err->getStatistics();
}
return $ret;
}
public static function getVisibleBuilds(ParameterValidator $user_params)
{
$ret = array();
$builds_per_page = 6; // TODO: get from config
$ret['paginate']['number_of_pages'] = self::getNumberOfVisiblePages($builds_per_page);
$page = $user_params->getInt('page', 1);
if ($page < 0)
$page = 1;
if ($page > $ret['paginate']['number_of_pages'])
$page = $ret['paginate']['number_of_pages'];
$ret['paginate']['page'] = $page;
$ret['paginate']['link'] = 'build_history.php?page=';
$visible = 5;
$visible_minus = 0;
$start = $page - floor(($visible-1)/2);
if ($start <= 1)
{
$start = 1;
$visible_minus = 1;
}
$last = $start + $visible + $visible_minus;
if ($last >= $ret['paginate']['number_of_pages'] + 1)
{
$last = $ret['paginate']['number_of_pages'] + 1;
$start = $last - $visible - 1;
if ($start < 1)
$start = 1;
}
$ret['paginate']['first_page'] = $start;
$ret['paginate']['last_page'] = $last;
$ret['paginate']['visible'] = $last - $start;
$ret['builds'] = array();
$builds = self::fetchVisibleBuilds($page, $builds_per_page);
foreach($builds as $build)
{
$ret['builds'][] = $build->getBuildStats();
}
return $ret;
}
private function getBuildStats()
{
$this->checkResult();
$build_result = '';
if ($this->status == self::S_GOOD)
{
$build_result = "Build successed." //. " Kaak Test Test"
;
} else {
$build_result = "Build failed:\n" . $this->error_msg;
}
$build_result = str_replace("\n"," \\n",$build_result);
return array('result' => $build_result,
'time' => $this->time,
'style' => ($this->status == self::S_GOOD?"passed":"failed"),
'id' => $this->id,
'result_style' => $this->result->getResult(),
'error_msg' => $this->error_msg,
'svn_rev' => $this->svn_version,
'result_passed' => $this->result->getAssertionsPassed(),
'result_failed' => $this->result->getAssertionsFailed());
}
public function getStatistics()
{
if (is_null($this->errors))
$this->errors = TestError::getErrorsForBuild($this->getLastWorkingId());
return array('builds' => array($this->getBuildStats()),
'errors' => $this->getErrorStatistics());
}
}
?>

View file

@ -0,0 +1,36 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class Config {
private $name;
function __construct($name = null)
{
$this->name = $name;
}
public function insertDefaults()
{
}
public function set()
{
}
public function get()
{
}
}
?>

View file

@ -0,0 +1,373 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class DBForeignKey {
private $name;
private $field_name;
private $reference;
function __construct($name, $field_name, $reference)
{
$this->field_name = $field_name;
$this->name = $name;
$this->reference = $reference;
}
public function getCreateSQL()
{
return "CONSTRAINT $this->name FOREIGN KEY ($this->field_name) REFERENCES $this->reference";
}
public function match_and_alter($db, &$create_sql, $table_name)
{
$m = array();
$needle = preg_replace("/[()]/","\\\\$0","/^.*CONSTRAINT `$this->name` FOREIGN KEY ($this->field_name) REFERENCES $this->reference.*$/im");
if (!preg_match($needle,$create_sql))
{
// echo "$needle\n$create_sql\n";
// echo "Creating foreign key $this->field_name in $table_name!\n";
if (!$db->Execute("ALTER TABLE $table_name
ADD CONSTRAINT $this->name
FOREIGN KEY ($this->field_name)
REFERENCES $this->reference"))
{
// echo "Failed to create foreign key!";
}
} else {
$create_sql = preg_replace($needle,'',$create_sql);
$create_sql = preg_replace("/^.*KEY (`$this->name`)? \\($this->field_name\\).*$/im",'',$create_sql);
}
}
}
class DBIndex {
private $field_name;
private $type;
private $key_name;
function __construct($field_name, $type, $key_name = '')
{
$this->field_name = $field_name;
$this->type = $type;
if (empty($key_name))
$key_name = $field_name;
$this->key_name = $key_name;
}
public function getCreateSQL()
{
return "$this->type $this->key_name ($this->field_name)";
}
public function match_and_alter($db, &$create_sql, $table_name)
{
$m = array();
$needle = "/^.*$this->type (`$this->key_name`)? \\(`$this->field_name`\\).*$/im";
if (!preg_match($needle,$create_sql))
{
// echo "$needle\n$create_sql\n";
// echo "Creating index $this->field_name in $table_name!\n";
if (!$db->Execute("ALTER TABLE $table_name
ADD $this->type $this->key_name ($this->field_name)"))
{
// echo "Failed to create key!\n";
}
} else {
$create_sql = preg_replace($needle,'',$create_sql);
}
}
}
class DBField {
private $name;
private $type;
private $default;
function __construct($name, $type, $default = '')
{
$this->name = $name;
$this->type = $type;
$this->default = $default;
}
public function getCreateSQL()
{
$default = '';
if (!empty($this->default))
{
$default = 'DEFAULT ' . $this->default;
}
return "$this->name $this->type $default";
}
public function match_and_alter($db, &$create_sql, $table_name)
{
$m = array();
$default = '';
if (!empty($this->default))
{
$default = ' DEFAULT ' . $this->default;
}
$needle = "/^\\s*`$this->name`(.*),?$/im";
if (preg_match($needle,$create_sql, $m))
{
// Found the field checking for correct type
$type = preg_replace(array("/[()]/","/^([a-zA-Z1-9_]*) /"),array("\\\\$0","$1(\\(\\d*\\))? "), $this->type);
if (!preg_match("/$type$default/i",$m[1]))
{
// echo $m[1] . "\n" . $type . "\n";
// echo "Updateing column $this->name in $table_name!\n";
if (!$db->Execute("ALTER TABLE $table_name
MODIFY COLUMN $this->name $this->type $default"))
{
// echo "Failed to create column!\n";
}
}
$create_sql = preg_replace($needle,'',$create_sql);
} else {
// Add new field
// echo "Creating column $this->name in $table_name!\n";
if (!$db->Execute("ALTER TABLE $table_name
ADD COLUMN $this->name $this->type $default"))
{
// echo "Failed to modify column!\n";
}
}
}
}
class DBTable {
private $fields;
private $name;
private $engine;
function __construct($name, $engine = 'MYISAM')
{
$this->name = $name;
$this->engine = $engine;
$this->fields = array();
}
public function addChild($child)
{
$this->fields[] = $child;
}
public function getName()
{
return $this->name;
}
public function check($db)
{
$result = $db->Execute("SHOW CREATE TABLE $this->name");
if ($result === false)
{
echo "Error reading create info for $this->name table\n";
exit;
}
$create_sql = $result->fields['Create Table'];
foreach($this->fields as $id => $field)
{
$field->match_and_alter($db,$create_sql, $this->name);
}
$m = array();
if (preg_match_all('/^\s*`(.*)` .*(int|varchar|text|blob|char|NULL|NOT NULL|timestamp|date).*$/m',$create_sql,$m,PREG_SET_ORDER))
{
foreach($m as $col)
{
try {
$colname = $col[1];
$db->Execute("ALTER TABLE $this->name
DROP COLUMN $colname");
} catch(exception $e) {
}
}
}
if (preg_match_all('/^\s*(CONSTRAINT|KEY|PRIMARY KEY|UNIQUE KEY) (`(.*)`)? .*$/m',$create_sql,$m,PREG_SET_ORDER))
{
foreach($m as $index)
{
try {
$indexname = '';
if (isset($index[3]))
$indexname = $index[3];
$type = $index[1];
switch($type)
{
case "CONSTRAINT":
$db->Execute("ALTER TABLE $this->name
DREP FOREIGN KEY $indexname");
break;
case "PRIMARY KEY":
$db->Execute("ALTER TABLE $this->name
DROP PRIMARY KEY");
break;
case "UNIQUE KEY":
case "KEY":
$db->Execute("ALTER TABLE $this->name
DROP KEY $indexname");
break;
}
} catch(exception $e)
{
}
}
}
}
public function createTable($db)
{
// echo "Creating table '$this->name'\n";
$sql = "CREATE TABLE $this->name (";
foreach($this->fields as $field)
{
$sql .= $field->getCreateSQL() . ',';
}
$sql = rtrim($sql, ',') . ") ENGINE=$this->engine";
if($db->Execute($sql) === false)
{
echo "Error creating table $this->name!\n";
exit;
}
}
}
class DBFormat {
private $tables;
function __construct()
{
$this->tables = array();
}
public function addChild($table)
{
$this->tables[] = $table;
}
public function checkDB($db)
{
$result = $db->Execute('show tables');
$dbtables = array();
if ($result !== false)
{
while(!$result->EOF())
{
$dbtables[$result->fields['Tables_in_wesnoth_unit_test']] = true;
$result->MoveNext();
}
} else {
echo 'show tables failed';
exit;
}
// check for missing and old tables
foreach($this->tables as $table)
{
if (!isset($dbtables[$table->getName()]))
{
// create missing table
$table->createTable($db);
} else {
// check if this table needs updateing
$table->check($db);
unset($dbtables[$table->getname()]);
}
}
foreach($dbtables as $name => $bool)
{
$db->Execute('DROP TABLE ' . $name);
}
}
}
class DBCreator {
private $db;
private $format;
function __construct()
{
global $db;
$this->format = new DBFormat();
$configtable = new DBTable('configs', 'InnoDB');
$configtable->addChild(new DBField('name', 'VARCHAR(255) NOT NULL'));
$configtable->addChild(new DBField('value', 'VARCHAR(255) NOT NULL'));
$configtable->addChild(new DBIndex('name', 'PRIMARY KEY'));
$this->format->addChild($configtable);
$buildstable = new DBTable('builds', 'InnoDB');
$buildstable->addChild(new DBField('id', 'INT NOT NULL AUTO_INCREMENT'));
$buildstable->addChild(new DBField('svn_version', 'INT NOT NULL'));
$buildstable->addChild(new DBField('time', 'TIMESTAMP NOT NULL', 'CURRENT_TIMESTAMP'));
$buildstable->addChild(new DBField('status', 'INT NOT NULL'));
$buildstable->addChild(new DBField('error_msg', 'BLOB NOT NULL'));
$buildstable->addChild(new DBIndex('id', 'PRIMARY KEY'));
$buildstable->addChild(new DBIndex('time', 'KEY'));
$this->format->addChild($buildstable);
$errortable = new DBTable('test_errors', 'InnoDB');
$errortable->addChild(new DBField('id', 'INT NOT NULL AUTO_INCREMENT'));
$errortable->addChild(new DBField('before_id', 'INT NOT NULL'));
$errortable->addChild(new DBField('last_id', 'INT NOT NULL'));
$errortable->addChild(new DBField('error_type', 'VARCHAR(10) NOT NULL'));
$errortable->addChild(new DBField('file', 'VARCHAR(255) NOT NULL'));
$errortable->addChild(new DBField('line', 'INT NOT NULL'));
$errortable->addChild(new DBField('error_msg', 'BLOB NOT NULL'));
$errortable->addChild(new DBIndex('id', 'PRIMARY KEY'));
$errortable->addChild(new DBForeignKey('test_errors_before_id_key','`before_id`', '`builds` (`id`)'));
$errortable->addChild(new DBForeignKey('test_errors_last_id_key','`last_id`', '`builds` (`id`)'));
$this->format->addChild($errortable);
$resulttable = new DBTable('test_results', 'InnoDB');
$resulttable->addChild(new DBField('id', 'INT NOT NULL AUTO_INCREMENT'));
$resulttable->addChild(new DBField('build_id', 'INT NOT NULL'));
$resulttable->addChild(new DBField('result', 'VARCHAR(10) NOT NULL'));
$resulttable->addChild(new DBField('assertions_passed', 'INT NOT NULL'));
$resulttable->addChild(new DBField('assertions_failed', 'INT NOT NULL'));
$resulttable->addChild(new DBField('test_cases_passed', 'INT NOT NULL'));
$resulttable->addChild(new DBField('test_cases_failed', 'INT NOT NULL'));
$resulttable->addChild(new DBField('test_cases_skipped', 'INT NOT NULL'));
$resulttable->addChild(new DBField('test_cases_aborted', 'INT NOT NULL'));
$resulttable->addChild(new DBIndex('id', 'PRIMARY KEY'));
$resulttable->addChild(new DBForeignKey('test_results_build_id_key', '`build_id`', '`builds` (`id`)'));
$this->format->addChild($resulttable);
$this->db = $db;
}
public function checkDB()
{
$this->format->checkDB($this->db);
$build = new Build();
$build->insertNull();
$config = new Config();
$config->insertDefaults();
}
}
?>

View file

@ -0,0 +1,22 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class FilenameConverter {
public static function stripBuildDirs($text)
{
return preg_replace('/^(build\/[^\/]*\/)?(.*[^\/]+:[0-9]+:.*)$/m','$2',$text);
}
}
?>

View file

@ -0,0 +1,32 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class Footer
{
private $file;
private $smarty;
function __construct($file)
{
global $smarty;
$this->file = $file;
$this->smarty = $smarty;
}
public function show()
{
$this->smarty->display('footer.tpl');
}
}
?>

View file

@ -0,0 +1,33 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class Header
{
private $file;
private $smarty;
function __construct($file)
{
global $smarty;
$this->file = $file;
$this->smarty = $smarty;
}
public function show()
{
$this->smarty->assign('extra_title', "| The report site for automated unit tests");
$this->smarty->display('header.tpl');
}
}
?>

View file

@ -0,0 +1,37 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
// posible class to set default values
//class ParameterLimit {
//}
class ParameterValidator {
private $user_params;
function __construct(array $user_params)
{
$this->user_params = $user_params;
}
function getInt($name, $default)
{
if (isset($this->user_params[$name])
&& is_numeric($this->user_params[$name]))
{
return (int)$this->user_params[$name];
} else {
return $default;
}
}
}
?>

View file

@ -0,0 +1,50 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class SVNUpdater {
private $revision;
function __construct()
{
$this->updateSVN();
}
public function getRevision()
{
return $this->revision;
}
private function updateSVN()
{
$success = false;
$m = array();
$tries = 3;
while(!$success && $tries--)
{
$svnlog = shell_exec('svn up 2>&1');
global $db;
if ($db->debug)
echo $svnlog;
$success = preg_match('/At revision ([0-9]*)\./m', $svnlog, $m);
if (!$success)
sleep(5);
}
if ($success)
$this->revision = (int)$m[1];
else
$this->revision = false;
}
}
?>

View file

@ -0,0 +1,150 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class TestError {
private $db;
private $id;
private $before_id;
private $last_id;
private $error_type;
private $file;
private $line;
private $error_msg;
private $start_version;
private $end_version;
function __construct($name = null, $data = null, Build $build = null)
{
global $db;
$this->db = $db;
$this->start_version = -1;
$this->end_version = -1;
if (!is_null($name))
{
$this->error_type = $name;
$this->file = FilenameConverter::stripBuildDirs((string)$data->attributes()->file);
$this->line = (string)$data->attributes()->line;
$this->error_msg = (string)$data[0];
$result = $this->db->Execute('SELECT t.id as id, before_id, last_id FROM test_errors t, builds b
WHERE t.error_type=?
AND t.file=?
AND t.error_msg=?
AND t.last_id=b.id
AND b.time > ?
LIMIT 1',
array($this->error_type,
$this->file,
$this->error_msg,
$this->db->DBTimeStamp(time() - 24*60*60)
));
if (!$result->EOF())
{
$this->id = $result->fields['id'];
$this->before_id = $result->fields['before_id'];
$this->last_id = $result->fields['last_id'];
}
}
}
private function fetch($where, $params =array(), $fields = '*')
{
$result = $this->db->Execute("SELECT $fields FROM test_errors $where LIMIT 1",$params);
if (!$result->EOF())
{
$this->init($result->fields);
}
}
private function init($values)
{
$this->start_version = -1;
$this->end_version = -1;
foreach($values as $key => $value)
{
$this->$key = $value;
}
}
public static function getErrorsForBuild($id)
{
global $db;
$ret = array();
$result = $db->Execute('SELECT * FROM test_errors
WHERE before_id<? AND last_id >=?',
array($id,$id));
while(!$result->EOF())
{
$error = new TestError();
$error->init($result->fields);
$ret[] = $error;
$result->moveNext();
}
return $ret;
}
public function updateDB(Build $build)
{
if (is_null($this->id))
{
$this->before_id = $build->getPreviousId();
$this->last_id = $build->getId();
$this->insert();
} else {
$this->db->Execute('UPDATE test_errors SET last_id=?, line=? WHERE id=?', array($build->getid(), $this->line, $this->id));
}
}
private function insert()
{
$this->db->Execute('INSERT INTO test_errors (before_id, last_id, error_type, file, line, error_msg)
VALUES (?, ?, ?, ?, ?, ?)',
array($this->before_id,$this->last_id,$this->error_type,$this->file,$this->line,$this->error_msg));
$this->id = $this->db->Insert_ID();
}
public function getStatistics()
{
return array('error_type' => $this->error_type,
'start_version' => $this->getStartVersion(),
'end_version' => $this->getEndVersion(),
'file' => $this->file,
'line' => $this->line,
'error_msg' => $this->error_msg);
}
public function getStartVersion()
{
if ($this->start_version == -1)
{
$result = $this->db->Execute('SELECT svn_version as start_version FROM builds WHERE id=?',array($this->before_id));
$this->start_version = $result->fields['start_version'];
}
return $this->start_version;
}
public function getEndVersion()
{
if ($this->end_version == -1)
{
$result = $this->db->Execute('SELECT MIN(svn_version) as end_version FROM builds WHERE id>? AND status=?',array($this->before_id, Build::S_GOOD));
$this->end_version = $result->fields['end_version'];
}
return $this->end_version;
}
}
?>

View file

@ -0,0 +1,123 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class TestResult {
private $db;
private $id;
private $build_id;
private $name;
private $result;
private $assertions_passed;
private $assertions_failed;
private $test_cases_passed;
private $test_cases_failed;
private $test_cases_skipped;
private $test_cases_aborted;
function __construct($data = null, Build $build = null)
{
global $db;
$this->db = $db;
if (!is_null($data))
{
if ($data instanceof SimpleXMLElement)
{
$this->build_id = $build->getId();
$attr = $data->attributes();
foreach($attr as $name => $value)
{
$this->$name = (string)$value;
if (is_numeric($this->name))
$this->$name = (int)$this->$name;
}
} else {
$this->fetch('WHERE build_id = ?', array($data));
}
}
}
private function fetch($where, $params = array())
{
$result = $this->db->Execute('SELECT * FROM test_results '
. $where . ' LIMIT 1', $params);
if (!$result->EOF())
{
$this->init($result->fields);
} else {
$this->reset();
}
}
private function init($values)
{
foreach($values as $key => $value)
{
$this->$key = $value;
}
}
private function reset()
{
$this->id = -1;
$this->build_id = -1;
$this->name = '';
$this->result = '';
$this->assertions_passed = -1;
$this->assertions_failed = -1;
$this->test_cases_passed = -1;
$this->test_cases_failed = -1;
$this->test_cases_skipped = -1;
$this->test_cases_aborted = -1;
}
public function updateDB()
{
if (!empty($this->result))
$this->insert();
}
private function insert()
{
$this->db->Execute('INSERT INTO test_results
(build_id, result, assertions_passed,
assertions_failed, test_cases_passed,
test_cases_failed, test_cases_skipped,
test_cases_aborted)
VALUES(?,?,?,
?,?,
?,?,
?)',
array($this->build_id, $this->result, $this->assertions_passed,
$this->assertions_failed, $this->test_cases_passed,
$this->test_cases_failed, $this->test_cases_skipped,
$this->test_cases_aborted));
}
public function getResult()
{
return $this->result;
}
public function getAssertionsPassed()
{
return $this->assertions_passed;
}
public function getAssertionsFailed()
{
return $this->assertions_failed;
}
}
?>

View file

@ -0,0 +1,66 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
class TestRunner {
function __construct()
{
}
public function run(Build $build)
{
$binary_name = $build->getTestName();
if ($binary_name === false)
{
trigger_error("No executeable name for tests");
return;
}
$test_output = '<UnitTest>'.shell_exec("./$binary_name --log_format=XML --report_format=XML 2>&1").'</UnitTest>';
//$test_output = '<UnitTest><TestLog><Message file="./boost/test/impl/results_collector.ipp" line="220">Test case test_user_team_name doesn&apos;t include any assertions</Message><Error file="build/debug/tests/test_config_cache.cpp" line="84">check defines_map.size() == cache.get_preproc_map().size() failed [2 != 0]</Error></TestLog>
//<TestResult><TestSuite name="wesnoth unit tests master suite"
//result="failed"
//assertions_passed="112"
//assertions_failed="1"
//expected_failures="0"
//test_cases_passed="17"
//test_cases_failed="1"
//test_cases_skipped="0"
//test_cases_aborted="0"></TestSuite>
//</TestResult></UnitTest>';
$xml = simplexml_load_string($test_output);
if (!($xml instanceof SimpleXMLElement)
|| !isset($xml->TestLog[0]) )
{
global $db;
if ($db->debug)
echo $test_output;
$db->FailTrans();
return;
}
foreach($xml->TestLog[0] as $name => $data)
{
$test_error = new TestError($name, $data, $build);
$test_error->updateDB($build);
}
foreach($xml->TestResult[0] as $name => $data)
{
$test_report = new TestResult($data, $build);
$test_report->updateDB();
}
}
}
?>

View file

@ -0,0 +1,30 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
global $smarty_dir;
require($smarty_dir . 'libs/Smarty.class.php');
class WesnothSmarty extends Smarty {
function __construct($work_dir)
{
parent::__construct();
$this->template_dir = $work_dir . '/templates';
$this->compile_dir = $work_dir . '/templates_c';
$this->cache_dir = $work_dir . '/cache';
$this->config_dir = $work_dir . '/configs';
$this->plugins_dir[] = $work_dir . '/plugins';
}
}
?>

View file

@ -0,0 +1,50 @@
<?php
// debug
error_reporting(E_ALL | E_STRICT);
// production error_loging to system log
//ini_set('syslog');
// settup auto class loader
function __autoload($class)
{
global $root_dir;
require_once($root_dir . '/../include/'.$class .'.php');
}
// initialize database connection
$adodb_dir = $root_dir . '/../adodb_lite/';
include($adodb_dir . "adodb-exceptions.inc.php");
require_once($adodb_dir . 'adodb.inc.php');
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
$driver = 'mysqli';
$username = 'username';
$password = 'password';
$hostname = 'localhost';
$database = 'wesnoth_unit_test';
$flags = MYSQL_CLIENT_COMPRESS;
$extra_options = '';
//$debug = 'debug&persit&'
$modules = '#date:transaction';
try {
$db = ADONewConnection("$driver://$username:$password@$hostname/$database?${extra_options}${modules}");
}
catch(exception $e)
{
echo "Connection to database failed!";
exit;
}
// initialize tempalte parser
$smarty_dir = $root_dir . '/../smarty_workdir/';
$smarty = new WesnothSmarty($smarty_dir);
?>

View file

@ -0,0 +1,32 @@
<?php
/*
Copyright (C) 2008 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
function smarty_modifier_autohide($text, $min_length_to_hide, $split_from_space, $take_end = false)
{
if (mb_strlen($text) > $min_length_to_hide)
{
$split_from_space = $split_from_space?"true":"false";
$take_end = $take_end?"true":"false";
return "<script type='text/javascript'>
/*<![CDATA[*/
autohide_store.push(new Autohide('$text', $min_length_to_hide, $split_from_space, $take_end));
/*]]>*/
</script>";
} else {
return $text;
}
}
?>

View file

@ -0,0 +1,8 @@
<h3>Build history</h3>
<table class="build_history">
<tr><td>
{include file='paginate.tpl'}
{include file='build_list.tpl'}
{include file='paginate.tpl'}
</td></tr>
</table>

View file

@ -0,0 +1,17 @@
<table class="build" border="1">
<tr>
<th>Time</th>
<th>Revision</th>
<th>Error message</th>
<th>Passed/Total</th>
</tr>
{foreach from=$builds item=build}
<tr>
<td class="time">{$build.time|date_format:"%H:%M %b %e, %Y"}</td>
<td class="revision {$build.style}">r{$build.svn_rev}</td>
<td class="message {$build.style}">{$build.result|autohide:20:true}</td>
<td class="testresult {$build.result_style}">{$build.result_passed}/{$build.result_passed+$build.result_failed}</td>
</tr>
{/foreach}
</table>

View file

@ -0,0 +1,28 @@
<div id="content">
</div> <!-- content -->
<div class="visualClear"></div>
<div id="footer">
<div id="note">
<p><a href="http://www.wesnoth.org/wiki/Site_Map">Site map</a></p>
<p><a href="http://www.wesnoth.org/wiki/Wesnoth:Copyrights">Copyright</a> &copy; 2003-2008 The Battle for Wesnoth</p>
<p>Supported by <a href="http://www.jexiste.fr/">Jexiste</a>.</p>
</div>
</div>
</div> <!-- main -->
</div> <!-- global -->
<script type="text/javascript">
_uacct = "UA-1872754-3";
</script>
<script type="text/javascript">
urchinTracker();
</script>
</body>
</html>

View file

@ -0,0 +1,57 @@
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<!--
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-->
<head>
<meta http-equiv="content-type" content="text/xhtml; charset=utf-8" />
<link rel="shortcut icon" type="image/png" href="http://www.wesnoth.org/mw/skins/glamdrol/ico.png" />
<style type="text/css">@import url('http://www.wesnoth.org/mw/skins/glamdrol/main.css');</style>
<style type="text/css">@import url('http://www.wesnoth.org/mw/skins/glamdrol/home.css');</style>
<style type="text/css">@import url('styles/unit_test.css');</style>
<script src="js/autohide.js" type="text/javascript"></script>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<link rel="alternate" type="application/rss+xml" title="news" href="http://feed43.com/wesnoth.xml" />
<title>Battle for Wesnoth {$extra_title}</title>
</head>
<body>
<div id="global">
<div id="header">
<div id="logo">
<a href="home"><img alt="Wesnoth logo" src="http://www.wesnoth.org/mw/skins/glamdrol/wesnoth-logo.jpg" /></a>
</div>
</div>
<div id="nav">
<ul>
<li><a href="http://www.wesnoth.org/">Home</a></li>
<li><a href="http://www.wesnoth.org/wiki/Play">Play</a></li>
<li><a href="http://www.wesnoth.org/wiki/Create">Create</a></li>
<li><a href="http://www.wesnoth.org/forum/">Forums</a></li>
<li><a href="http://www.wesnoth.org/wiki/Support">Support</a></li>
<li><a href="http://www.wesnoth.org/wiki/Project">Project</a></li>
<li><a href="http://www.wesnoth.org/wiki/Credits">Credits</a></li>
</ul>
</div>
<div id="menu" class="menu">
<table class="menu">
<tr><td><a href="index.php">Status</a></td></tr>
<tr><td><a href="build_history.php">Build history</a></td></tr>
<tr><td><a href="index.php">Test Graphs</a></td></tr>
</table>
</div>
<div id="main" class="unit_test">

View file

@ -0,0 +1,26 @@
<h3>The status from the latest build</h3>
{include file='build_list.tpl'}
<br/>
{if count($errors) eq 0}
<h3>All unit test passed! Congratulations!</h3>
{else}
<h3>Current list of errors in unit tests</h3>
<table class="test_error" border="1">
<tr>
<th>Type</th>
<th>First broken</th>
<th>File</th>
<th>Line</th>
<th>Message</th>
</tr>
{foreach from=$errors item=err}
<tr>
<td class="{$err.error_type}">{$err.error_type}</td>
<td class="{$err.error_type}">r{$err.start_version} - r{$err.end_version}</td>
<td class="{$err.error_type}">{$err.file|autohide:25:false:true}</td>
<td class="{$err.error_type}">{$err.line}</td>
<td class="{$err.error_type}">{$err.error_msg|autohide:40:true}</td>
</tr>
{/foreach}
{/if}
</table>

View file

@ -0,0 +1,58 @@
{strip}
<div class="paginate">
{if $paginate.visible > 1}
{if $paginate.page > 1}
<a href="{$paginate.link}{$paginate.page-1}">
{else}
<span class="active">
{/if}
Previous
{if $paginate.page > 1}
</a>
{else}
</span>
{/if}
&nbsp;
{if $paginate.first_page > 1}
<a href="{$paginate.link}1">1</a>
{if $paginate.first_page > 2}
...
{else}
,&nbsp;
{/if}
{/if}
{section name=pager loop=$paginate.last_page start=$paginate.first_page max=$paginate.visible}
<a href="{$paginate.link}{$smarty.section.pager.index}"
{if $paginate.page == $smarty.section.pager.index}
class="active"
{/if}
>{$smarty.section.pager.index}</a>
{if $smarty.section.pager.last == false}
,&nbsp;
{/if}
{/section}
{if $paginate.last_page <= $paginate.number_of_pages}
{if $paginate.last_page <= $paginate.number_of_pages - 1}
...
{else}
,&nbsp;
{/if}
<a href="{$paginate.link}{$paginate.number_of_pages}">{$paginate.number_of_pages}</a>
{/if}
&nbsp;
{if $paginate.page < $paginate.number_of_pages}
<a href="{$paginate.link}{$paginate.page+1}">
{else}
<span class="active">
{/if}
next
{if $paginate.page < $paginate.number_of_pages}
</a>
{else}
</span>
{/if}
{/if}
</div>
{/strip}