Commit 1bfb006e authored by Francois Marier's avatar Francois Marier Committed by Gerrit Code Review
Browse files

Merge changes Ibdd88485,Id3328a3c,I1fe53d0e,I2568a7b9,I160b4c68,I21d5c318

* changes:
  Bug #819396: Add Example unit test for views/pages
  Add unit testing framework (Bug #819396)
  Bug #841723: Increase precision of user deletion times
  Bug #841694: Add uninstall_from_xmldb support
  Bug #841711: ADODB modification: add __wakeup to connection object
  Bug #841708: view::_create: respect title if it's passed in with $viewdata
parents 6ee3058e b14367d7
......@@ -11,3 +11,4 @@ Modifications:
- adodb/drivers/adodb-postgres64.inc.php (d6d8884fabc598aec58e633a6a1e258cd07b3da1)
- Add check for valid columns in MetaIndexes function of
adodb/drivers/adodb-postgres64.inc.php (see bug #796102)
- adodb/adodb.inc.php add wakeup suppport (see bug #841711)
......@@ -333,7 +333,7 @@
var $database = ''; /// Name of database to be used.
var $host = ''; /// The hostname of the database server
var $user = ''; /// The username which is used to connect to the database server.
var $password = ''; /// Password for the username. For security, we no longer store it.
private $password = ''; /// Password for the username. This is required for __wakeup
var $debug = false; /// if set to true will output sql statements
var $maxblobsize = 262144; /// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro
var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase
......@@ -514,7 +514,7 @@
{
if ($argHostname != "") $this->host = $argHostname;
if ($argUsername != "") $this->user = $argUsername;
if ($argPassword != "") $this->password = 'not stored'; // not stored for security reasons
if ($argPassword != "") $this->password = $argPassword;
if ($argDatabaseName != "") $this->database = $argDatabaseName;
$this->_isPersistentConnection = false;
......@@ -2705,6 +2705,9 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
return $rs;
}
function __wakeup() {
$this->Connect($this->host, $this->user, $this->password, $this->database, true);
}
} // end class ADOConnection
......@@ -4413,4 +4416,4 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
}
?>
\ No newline at end of file
?>
......@@ -545,6 +545,57 @@ function find_sequence_name($table) {
return $sequencename;
}
/**
* This function will load one entire XMLDB file, generating all the needed
* SQL statements, specific for each RDBMS ($CFG->dbtype) and, finally, it
* will execute all those statements against the DB, to drop all tables.
*
* @param $file full path to the XML file to be used
* @return boolean (true on success, false on error)
*/
function uninstall_from_xmldb_file($file) {
global $CFG, $db;
$status = true;
$xmldb_file = new XMLDBFile($file);
if (!$xmldb_file->fileExists()) {
throw new InstallationException($xmldb_file->path . " doesn't exist.");
}
$loaded = $xmldb_file->loadXMLStructure();
if (!$loaded || !$xmldb_file->isLoaded()) {
throw new InstallationException("Could not load " . $xmldb_file->path);
}
$structure = $xmldb_file->getStructure();
if ($tables = array_reverse($structure->getTables())) {
foreach ($tables as $table) {
if ($indexes = $table->getIndexes()) {
foreach (array_reverse($indexes) as $index) {
if ($index->getName() == 'usernameuk' && is_postgres()) {
// this is a giant hack, but adodb cannot handle resolving
// the column for indexes that include lower() or something similar
// and i can't find a nice way to do it.
execute_sql('DROP INDEX {usr_use_uix}');
continue;
}
drop_index($table, $index);
}
}
if ($keys = $table->getKeys()) {
foreach (array_reverse($keys) as $key) {
drop_key($table, $key);
}
}
drop_table($table);
}
}
return true;
}
/**
* This function will load one entire XMLDB file, generating all the needed
* SQL statements, specific for each RDBMS ($CFG->dbtype) and, finally, it
......
......@@ -553,7 +553,11 @@ class MaharaException extends Exception {
echo json_encode(array('error' => true, 'message' => $this->render_exception()));
exit;
}
if (defined('TESTSRUNNING')) {
exit; // let it be caught by phpunit
}
if (defined('CRON')) {
echo $this->render_exception();
exit;
......@@ -741,7 +745,7 @@ class SQLException extends SystemException {
}
parent::__construct($message, $code);
if (empty($DB_IGNORE_SQL_EXCEPTIONS)) {
if (empty($DB_IGNORE_SQL_EXCEPTIONS) && !defined('TESTSRUNNING')) {
log_warn($this->getMessage());
}
}
......
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2009 Penny Leach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage tests
* @author Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*
*/
if (!defined('TESTSRUNNING')) {
define('TESTSRUNNING', 1);
}
/**
* Small class to handle all things necessary to bootstrap Mahara
* to create an environment to run tests in.
* Handles munging the database, config etc
*/
class UnitTestBootstrap {
/**
* original config loaded from the db
* we need to hang on to this so we can unset it
*
* @todo investigate running Mahara with different types of config on
*/
private $originaldbconfig = array();
/**
* constructor, make sure phpunit.xml settings are sane
*/
public function __construct() {
// small sanity check that the test db prefix is configured
if (empty($GLOBALS['TESTDBPREFIX'])) {
throw new UnitTestBootstrapException('No test prefix defined, refusing to run tests');
}
}
/**
* munge the Mahara config.
*
* @uses $CFG
*/
public function jimmy_config() {
global $CFG;
$this->originaldbconfig = get_records_array('config');
$CFG->dbprefix = $GLOBALS['TESTDBPREFIX'];
$CFG->prefix = $GLOBALS['TESTDBPREFIX'];
$CFG->libdir = get_config('libroot');
try {
db_ignore_sql_exceptions(true);
load_config();
db_ignore_sql_exceptions(false);
}
catch (SQLException $e) {
db_ignore_sql_exceptions(false);
}
// now reload the config since $CFG is dirty with the real config table
foreach ($this->originaldbconfig as $c) {
unset($CFG->{$c->field});
}
}
/**
* detect and clean up any old test tables lying around
* as of phpunit 3.4, there's no corollary to bootstrap to clean up,
* so this will actually be invoked every single time
* which is quite annoying
*/
public function clean_stale_tables() {
if (table_exists(new XMLDBTable('config'))) {
if (empty($GLOBALS['TESTDROPSTALEDB']) || $GLOBALS['TESTDROPSTALEDB'] !== true) {
throw new UnitTestBootstrapException('Stale test tables found, and drop option not set. Refusing to run tests');
}
log_info('Stale test tables found, and drop option is set. Dropping them before running tests');
$this->uninstall_mahara();
log_info('Done');
}
}
/**
* completely uninstall mahara, drop all tables.
* this just does what install does, but in reverse order
* reversing the order of tables, and indexes
* to respect referential integrity
*/
public function uninstall_mahara() {
// this can't be done in a transaction because sometimes
// things exist in the database that aren't in the file or the other way around
// in the case where there are stale tables and then the code is upgraded
foreach (array_reverse(plugin_types_installed()) as $t) {
if ($installed = plugins_installed($t, true)) {
foreach ($installed as $p) {
$location = get_config('docroot') . $t . '/' . $p->name. '/db/';
log_info('Uninstalling ' . $location);
if (is_readable($location . 'install.xml')) {
uninstall_from_xmldb_file($location . 'install.xml');
}
}
}
}
// now uninstall core
log_info('Uninstalling core');
// These constraints must be dropped manually as they cannot be
// created with xmldb due to ordering issues
execute_sql('ALTER TABLE {usr} DROP CONSTRAINT {usr_pro_fk}');
execute_sql('ALTER TABLE {institution} DROP CONSTRAINT {inst_log_fk}');
uninstall_from_xmldb_file(get_config('docroot') . 'lib/db/install.xml');
}
/**
* Install mahara from scratch. Does both database tables and core data.
* Exactly the same as the web-based installer
* except for logging the current user in.
*/
public function install_mahara() {
log_info('Installing Mahara');
db_ignore_sql_exceptions(true);
$upgrades = check_upgrades();
db_ignore_sql_exceptions(false);
$upgrades['firstcoredata'] = true;
$upgrades['lastcoredata'] = true;
uksort($upgrades, 'sort_upgrades');
foreach ($upgrades as $name => $data) {
if ($name == 'disablelogin') {
continue;
}
log_info('Installing ' . $name);
if ($name == 'firstcoredata' || $name == 'lastcoredata') {
$funname = 'core_install_' . $name . '_defaults';
$funname();
continue;
}
else {
if ($name == 'core') {
$funname = 'upgrade_core';
}
else {
$funname = 'upgrade_plugin';
}
$data->name = $name;
$funname($data);
}
}
}
}
/**
* Superclass for Mahara unit tests to provide helper methods to create data
*
* @todo require_* methods:
* views
* groups (takes plugins)
* artefacts (takes plugins)
* interactions (takes plugins)
*
* @todo think about:
* mocking events (or just ignoring them)
* mocking the file system
*/
class MaharaUnitTest extends PHPUnit_Framework_TestCase {
/** array of users we have created */
protected $users = array();
/** required user data for creating users **/
private static $userdata = array('username', 'email', 'firstname', 'lastname');
/**
* require a user to be created in order for the tests to run
*
* @param stdclass $userdata data about the user to create
* this can take anything that {@link create_user} can take
* these will be automatically cleaned up in tearDown
* so make sure you call parent::tearDown()
* @return void
*/
protected function require_user($userdata) {
foreach (self::$userdata as $field) {
if (!isset($userdata->{$field})) {
throw new MaharaUnitTestException("MaharaUnitTest::require_user call missing required field $field");
}
}
if (array_key_exists($userdata->username, $userdata)) {
throw new MaharaUnitTextException("MaharaUnitTest::require_user called with duplicate username {$userdata->username}");
}
if (empty($userdata->password)) {
$userdata->password = 'test';
}
try {
$this->users[$userdata->username] = create_user($userdata);
}
catch (Exception $e) {
throw new MaharaUnitTestException("MaharaUnitTest::require_user call caught an exception creating a user: " . $e->getMessage());
}
}
/**
* tiny wrapper around {@link require_user} to just create a test user
* with a default username ('test')
* this can only be called once
*
* @return void
*/
protected function require_test_user() {
$authinstance = get_record('auth_instance', 'institution', 'mahara');
return $this->require_user((object)array(
'username' => 'test',
'email' => 'test@localhost',
'firstname' => 'Test',
'lastname' => 'User',
'authinstance' => $authinstance->id,
));
}
/**
* superclass tearDown method
* takes care to delete all data that has been created
* with any of the require_ methods
*
* <b>always</b> call this, even if you override it.
*/
protected function tearDown() {
foreach ($this->users as $userid) {
delete_user($userid);
}
}
}
/**
* Test exceptions. Usually the fault of the test author
* So they extend SystemException.
*/
class MaharaUnitTestException extends SystemException { }
/**
* Bootstrap exceptions. Usually the fault of the phpunit.xml author
* So they extend ConfigException.
*/
class UnitTestBootstrapException extends ConfigException { }
This requires phpunit 3.3 or later.
It needs to be run like:
php -c /etc/php5/apache2/php.ini /usr/bin/phpunit .
Or if you've configured your cli php.ini properly, you can just do
phpunit .
From the main directory of mahara (eg, outside htdocs)
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2009 Penny Leach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage tests
* @author Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*/
require_once(get_config('libroot') . 'view.php');
/**
* Test the view class
*
* TODO this just sets basic creating, setting a fiew fields and deleting
* need to test access and blocks and all sorts of other things.
*/
class ViewTest extends MaharaUnitTest {
/** the view created in setUp */
private $view;
/** the id of our shared view. kept for persistency after we delete the view */
private $viewid;
/** array of values to set in the view object. TODO: add more */
private $fields = array(
'title' => array(
'Test View',
'Test View new title'
),
'description' => array(
'A view to test with!',
'a new description to test with'
),
// ....
);
/**
* shared setUp method
* require a test user, and
* create a view to test with
*/
public function setUp() {
$this->require_test_user();
// set the owner of the view to the test user we created
$this->fields['owner'] = array($this->users['test'], $this->users['test']);
$this->view = View::create(array(
'title' => $this->fields['title'][0],
'description' => $this->fields['description'][0],
), $this->users['test']);
$this->viewid = $this->view->get('id');
}
/**
* make sure what got created makes sense
*/
public function testViewCreating() {
$this->assertType('int', (int) $this->viewid);
$this->assertGreaterThan(0, $this->viewid);
// now get it again and make sure it matches
try {
$createdview = new View($this->view->get('id'));
}
catch (Exception $e) {
$this->fail("Couldn't find new view I created");
}
foreach ($this->fields as $field => $values) {
// make sure both the values in the db and in the return object match what we said
$this->assertEquals($values[0], $createdview->get($field));
$this->assertEquals($values[0], $this->view->get($field));
}
}
/**
* test that the view setters work (without committing)
*/
public function testViewSetters() {
foreach ($this->fields as $field => $values) {
$this->view->set($field, $values[1]);
$this->assertEquals($values[1], $this->view->get($field));
}
}
/**
* test that the setters work and commit to the db
* and when we get the view back it matches
*/
public function testViewCommitting() {
// now commit to db and test again
foreach ($this->fields as $field => $values) {
$this->view->set($field, $values[1]);
}
$this->view->commit();
$createdview = new View($this->view->get('id'));
foreach ($this->fields as $field => $values) {
$this->assertEquals($values[1], $createdview->get($field));
}
}
/**
* test that when we delete a view,
* it actually gets deleted from the database
*/
public function testViewDeleting() {
$todelete = View::create(array(
'title' => $this->fields['title'][0],
'description' => $this->fields['description'][0],
), $this->users['test']);
$todeleteid = $todelete->get('id');
$todelete->delete();
$todelete->commit();
try {
$deleted = new View($todeleteid);
$this->fail("View wasn't deleted properly!");
}
catch (Exception $e) {}
}
/**
* clean up after ourselves,
* just delete the test view we made
* and call the parent method
*/
public function tearDown() {
$this->view->delete();
$this->view->commit();
parent::tearDown();
}
}
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2009 Penny Leach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage tests
* @author Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*
*/
define('TESTSRUNNING', 1);
define('INTERNAL', 1);
define('PUBLIC', 1);
// necessary since we're running in a limited scope
global $CFG, $db, $SESSION, $USER, $THEME;
require(dirname(dirname(dirname(dirname(__FILE__)))) . '/init.php');
require_once(get_config('libroot') . 'ddl.php');
require_once(get_config('libroot') . 'upgrade.php');
require_once(get_config('libroot') . 'phpunit.php');
$bootstrap = new UnitTestBootstrap();
$bootstrap->jimmy_config();
$bootstrap->clean_stale_tables();
$bootstrap->install_mahara();
......@@ -1140,7 +1140,7 @@ function delete_user($userid) {
// We want to append 'deleted.timestamp' to some unique fields in the usr
// table, so they can be reused by new accounts
$fieldstomunge = array('username', 'email');
$datasuffix = '.deleted.' . time();
$datasuffix = '.deleted.' . microtime(true);
$user = get_record('usr', 'id', $userid, null, null, null, null, implode(', ', $fieldstomunge));
......
......@@ -362,7 +362,7 @@ class View {
'numcolumns' => 3,
'template' => 0,
'type' => 'portfolio',
'title' => self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
'title' => (array_key_exists('title', $viewdata)) ? $viewdata['title'] : self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
);
$data = (object)array_merge($defaultdata, $viewdata);
......
<phpunit bootstrap="htdocs/lib/tests/phpunit/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnFailure="true">
<filter>
<whitelist addUncoveredFilesFromWhitelist="true"> <!-- code coverage -->
<directory suffix=".php">htdocs</directory>
</whitelist>
</filter>
<php>
<var name="TESTDBPREFIX" value="tst_" />