phpunit.php 16.9 KB
Newer Older
1
2
3
4
5
6
<?php
/**
 *
 * @package    mahara
 * @subpackage tests
 * @author     Andrew Nicols
7
8
9
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
 * @copyright  (C) 2009 Penny Leach
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
 *
 */
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
97
98
99
100
101
102
        foreach (get_installed_plugins_paths() as $pluginpath) {
            $location = $pluginpath . '/db/';
            log_info('Uninstalling ' . $location);
            $xmldbfile = $location . 'install.xml';
            if (is_readable($xmldbfile)) {
                uninstall_from_xmldb_file($xmldbfile);
103
104
105
106
107
108
109
            }
        }
        // now uninstall core
        log_info('Uninstalling core');

        // These constraints must be dropped manually as they cannot be
        // created with xmldb due to ordering issues
110
        if (is_postgres()) {
111
112
113
114
115
116
117
118
119
120
            try {
                execute_sql('ALTER TABLE {usr} DROP CONSTRAINT {usr_pro_fk}');
            }
            catch (Exception $e) {
            }
            try {
                execute_sql('ALTER TABLE {institution} DROP CONSTRAINT {inst_log_fk}');
            }
            catch (Exception $e) {
            }
121
122
        }
        else {
123
124
125
126
127
128
129
130
131
132
            try {
                execute_sql('ALTER TABLE {usr} DROP FOREIGN KEY {usr_pro_fk}');
            }
            catch (Exception $e) {
            }
            try {
                execute_sql('ALTER TABLE {institution} DROP FOREIGN KEY {inst_log_fk}');
            }
            catch (Exception $e) {
            }
133
        }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

        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) {
153
            if ($name == 'settings') {
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
                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
 *
179
 * @todo create_test_* methods:
180
181
182
183
184
185
186
187
188
189
190
 * 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 {

191
192
193
194
195
196
197
198
199
    /** @var array list of common last names */
    public $lastnames = array(
        'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
        'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
        'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
        'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
        '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
        '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
    );
200

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    /** @var array list of common first names */
    public $firstnames = array(
        'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
        'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
        'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
        'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
        '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
        '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
    );

    public $loremipsum = <<<EOD
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est.
Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat.
Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim.
In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien.
EOD;

    // Arrays of objects we have created - used to automatically tidy up later.
    protected $testusers = array();
    protected $testgroups = array();
    protected $testinstitutions = array();
223

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    /**
     * Superclass setUp method
     *
     * Takes care of setting up the database correctly as this doesn't
     * happen in unit test through init.php properly.
     *
     * parent::setUp() must always be called if it is overriden in
     * subclasses
     *
     * @return void
     */
    protected function setUp() {
        configure_dbconnection();
    }

239
    /**
240
     * Create a user that can be used in a test.
241
     *
242
243
244
245
     * @param stdclass $userdata data about the user to create - this can take anything that {@link create_user} can take.
     *                 If null then a user called 'testX' will be created, where X is the number of users created so far.
     *                 These will be automatically cleaned up in tearDown, so make sure you call parent::tearDown().
     * @return int new user id.
246
     */
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    protected function create_test_user($userdata = null, $institution = null) {
        $authinstance = get_record('auth_instance', 'institution', 'mahara');
        $testdata = array(
            'username'     => 'test' . count($this->testusers),
            'email'        => 'test' . count($this->testusers) . '@localhost',
            'firstname'    => $this->firstnames[array_rand($this->firstnames)],
            'lastname'     => $this->lastnames[array_rand($this->lastnames)],
            'password'     => 'test',
            'authinstance' => $authinstance->id,
        );

        $combineddata = (object)array_merge($testdata, (array)$userdata);

        if (array_key_exists($combineddata->username, $this->testusers)) {
            throw new MaharaUnitTextException("MaharaUnitTest::create_test_user called with duplicate username {$combineddata->username}");
262
        }
263
264
265
266
        try {
            $newuser = create_user($combineddata, array(), $institution);
            $this->testusers[$combineddata->username] = $newuser;
            return $newuser;
267
        }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
        catch (Exception $e) {
            throw new MaharaUnitTestException("MaharaUnitTest::create_test_user call caught an exception creating a user: " . $e->getMessage());
        }
    }

    /**
     * Create a group that can be used in a test.
     *
     * @param array $groupdata data about the group to create - this can take anything that {@link group_create} can take.
     *              If null then a group called 'groupX' will be created, where X is the number of groups created so far.
     *              These will be automatically cleaned up in tearDown, so make sure you call parent::tearDown().
     * @return int new group id.
     */
    protected function create_test_group($groupdata = null) {
        $testdata = array(
            'name'      => 'group' . count($this->testgroups),
            'grouptype' => 'test' . count($this->testusers) . '@localhost',
        );

        $combineddata = array_merge($testdata, (array)$groupdata);

        if (array_key_exists($combineddata['name'], $this->testgroups)) {
            throw new MaharaUnitTextException("MaharaUnitTest::create_test_group called with duplicate name {$combineddata['name']}");
291
        }
292

293
        try {
294
295
296
            $newgroupid = group_create($combineddata);
            $this->testgroups[$combineddata['name']] = $newgroupid;
            return $newgroupid;
297
298
        }
        catch (Exception $e) {
299
            throw new MaharaUnitTestException("MaharaUnitTest::create_test_group call caught an exception creating a group: " . $e->getMessage());
300
301
302
303
        }
    }

    /**
304
     * Create an institution that can be used in a test.
305
     *
306
307
308
309
     * @param array $instdata data about the institution to create - this can take anything that can go into the institution table.
     *              If null then an institution called 'institutionX' will be created, where X is the number of institutions created so far.
     *              These will be automatically cleaned up in tearDown, so make sure you call parent::tearDown().
     * @return int new institution id.
310
     */
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
    protected function create_test_institution($instdata = null) {
        $testdata = array(
            'name' => 'institution' . count($this->testinstitutions),
            'displayname' => 'institution' . count($this->testinstitutions),
        );

        $combineddata = (object)array_merge($testdata, (array)$instdata);

        if (array_key_exists($combineddata->name, $this->testinstitutions)) {
            throw new MaharaUnitTextException("MaharaUnitTest::create_test_institution called with duplicate name {$combineddata->name}");
        }

        try {
            insert_record('institution', $combineddata);
            $this->testinstitutions[$combineddata->name] = $combineddata->name;
            return get_field('institution', 'id', 'name', $combineddata->name);
        }
        catch (Exception $e) {
            throw new MaharaUnitTestException("MaharaUnitTest::create_test_institution call caught an exception creating an institution: " . $e->getMessage());
        }
331
332
333
    }

    /**
334
     * Superclass tearDown method takes care to delete all data that has been created with any of the create_test_ methods.
335
336
337
338
     *
     * <b>always</b> call this, even if you override it.
     */
    protected function tearDown() {
339
        foreach ($this->testusers as $userid) {
340
341
            delete_user($userid);
        }
342
343
344
345
346
347
        foreach ($this->testgroups as $group) {
            group_delete($group);
        }
        foreach ($this->testinstitutions as $institution) {
            delete_records('institution', 'name', $institution);
        }
348
349
350
351
352
353
354
355
356
357
358
359
360
    }
}

/**
 * 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.
 */
361
class UnitTestBootstrapException extends ConfigException { }