xref: /dokuwiki/_test/core/DokuWikiTest.php (revision cbb44eabe033d70affb048ec0daf4e579e09dd20)
1<?php
2
3use dokuwiki\Extension\Event;
4use dokuwiki\Extension\EventHandler;
5
6if(!class_exists('PHPUnit_Framework_TestCase')) {
7    /**
8     * phpunit 5/6 compatibility
9     */
10    class PHPUnit_Framework_TestCase extends PHPUnit\Framework\TestCase {
11        /**
12         * setExpectedException is deprecated in PHPUnit 6
13         *
14         * @param string $class
15         * @param null|string $message
16         */
17        public function setExpectedException($class, $message=null) {
18            $this->expectException($class);
19            if(!is_null($message)) {
20                $this->expectExceptionMessage($message);
21            }
22        }
23    }
24}
25
26/**
27 * Helper class to provide basic functionality for tests
28 *
29 * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required
30 */
31abstract class DokuWikiTest extends PHPUnit_Framework_TestCase {
32
33    /**
34     * tests can override this
35     *
36     * @var array plugins to enable for test class
37     */
38    protected $pluginsEnabled = array();
39
40    /**
41     * tests can override this
42     *
43     * @var array plugins to disable for test class
44     */
45    protected $pluginsDisabled = array();
46
47    /**
48     * Setup the data directory
49     *
50     * This is ran before each test class
51     */
52    public static function setUpBeforeClass() {
53        // just to be safe not to delete something undefined later
54        if(!defined('TMP_DIR')) die('no temporary directory');
55        if(!defined('DOKU_TMP_DATA')) die('no temporary data directory');
56
57        self::setupDataDir();
58        self::setupConfDir();
59    }
60
61    /**
62     * Reset the DokuWiki environment before each test run. Makes sure loaded config,
63     * language and plugins are correct.
64     *
65     * @throws Exception if plugin actions fail
66     * @return void
67     */
68    public function setUp() {
69
70        // reload config
71        global $conf, $config_cascade;
72        $conf = array();
73        foreach (array('default','local','protected') as $config_group) {
74            if (empty($config_cascade['main'][$config_group])) continue;
75            foreach ($config_cascade['main'][$config_group] as $config_file) {
76                if (file_exists($config_file)) {
77                    include($config_file);
78                }
79            }
80        }
81
82        // reload license config
83        global $license;
84        $license = array();
85
86        // load the license file(s)
87        foreach (array('default','local') as $config_group) {
88            if (empty($config_cascade['license'][$config_group])) continue;
89            foreach ($config_cascade['license'][$config_group] as $config_file) {
90                if(file_exists($config_file)){
91                    include($config_file);
92                }
93            }
94        }
95        // reload some settings
96        $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false);
97
98        if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) {
99            $conf['compression'] = 'gz';
100        }
101        if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) {
102            $conf['compression'] = 0;
103        }
104        // make real paths and check them
105        init_paths();
106        init_files();
107
108        // reset loaded plugins
109        global $plugin_controller_class, $plugin_controller;
110        /** @var Doku_Plugin_Controller $plugin_controller */
111        $plugin_controller = new $plugin_controller_class();
112
113        // disable all non-default plugins
114        global $default_plugins;
115        foreach ($plugin_controller->getList() as $plugin) {
116            if (!in_array($plugin, $default_plugins)) {
117                if (!$plugin_controller->disable($plugin)) {
118                    throw new Exception('Could not disable plugin "'.$plugin.'"!');
119                }
120            }
121        }
122
123        // disable and enable configured plugins
124        foreach ($this->pluginsDisabled as $plugin) {
125            if (!$plugin_controller->disable($plugin)) {
126                throw new Exception('Could not disable plugin "'.$plugin.'"!');
127            }
128        }
129        foreach ($this->pluginsEnabled as $plugin) {
130            /*  enable() returns false but works...
131            if (!$plugin_controller->enable($plugin)) {
132                throw new Exception('Could not enable plugin "'.$plugin.'"!');
133            }
134            */
135            $plugin_controller->enable($plugin);
136        }
137
138        // reset event handler
139        global $EVENT_HANDLER;
140        $EVENT_HANDLER = new EventHandler();
141
142        // reload language
143        $local = $conf['lang'];
144        Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true);
145
146        global $INPUT;
147        $INPUT = new \dokuwiki\Input\Input();
148    }
149
150    /**
151     * Reinitialize the data directory for this class run
152     */
153    public static function setupDataDir() {
154        // remove any leftovers from the last run
155        if(is_dir(DOKU_TMP_DATA)) {
156            // clear indexer data and cache
157            idx_get_indexer()->clear();
158            TestUtils::rdelete(DOKU_TMP_DATA);
159        }
160
161        // populate default dirs
162        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/');
163    }
164
165    /**
166     * Reinitialize the conf directory for this class run
167     */
168    public static function setupConfDir() {
169        $defaults = [
170            'acronyms.conf',
171            'dokuwiki.php',
172            'entities.conf',
173            'interwiki.conf',
174            'license.php',
175            'manifest.json',
176            'mediameta.php',
177            'mime.conf',
178            'plugins.php',
179            'plugins.required.php',
180            'scheme.conf',
181            'smileys.conf',
182            'wordblock.conf'
183        ];
184
185        // clear any leftovers
186        if(is_dir(DOKU_CONF)) {
187            TestUtils::rdelete(DOKU_CONF);
188        }
189        mkdir(DOKU_CONF);
190
191        // copy defaults
192        foreach($defaults as $file) {
193            copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file);
194        }
195
196        // copy test files
197        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf');
198    }
199
200    /**
201     * Waits until a new second has passed
202     *
203     * The very first call will return immeadiately, proceeding calls will return
204     * only after at least 1 second after the last call has passed.
205     *
206     * When passing $init=true it will not return immeadiately but use the current
207     * second as initialization. It might still return faster than a second.
208     *
209     * @param bool $init wait from now on, not from last time
210     * @return int new timestamp
211     */
212    protected function waitForTick($init = false) {
213        static $last = 0;
214        if($init) $last = time();
215        while($last === $now = time()) {
216            usleep(100000); //recheck in a 10th of a second
217        }
218        $last = $now;
219        return $now;
220    }
221
222    /**
223     * Allow for testing inaccessible methods (private or protected)
224     *
225     * This makes it easier to test protected methods without needing to create intermediate
226     * classes inheriting and changing the access.
227     *
228     * @link https://stackoverflow.com/a/8702347/172068
229     * @param object $obj Object in which to call the method
230     * @param string $func The method to call
231     * @param array $args The arguments to call the method with
232     * @return mixed
233     * @throws ReflectionException when the given obj/func does not exist
234     */
235    protected static function callInaccessibleMethod($obj, $func, array $args) {
236        $class = new \ReflectionClass($obj);
237        $method = $class->getMethod($func);
238        $method->setAccessible(true);
239        return $method->invokeArgs($obj, $args);
240    }
241
242    /**
243     * Allow for reading inaccessible properties (private or protected)
244     *
245     * This makes it easier to check internals of tested objects. This should generally
246     * be avoided.
247     *
248     * @param object $obj Object on which to access the property
249     * @param string $prop name of the property to access
250     * @return mixed
251     * @throws ReflectionException  when the given obj/prop does not exist
252     */
253    protected static function getInaccessibleProperty($obj, $prop) {
254        $class = new \ReflectionClass($obj);
255        $property = $class->getProperty($prop);
256        $property->setAccessible(true);
257        return $property->getValue($obj);
258    }
259
260    /**
261     * Allow for reading inaccessible properties (private or protected)
262     *
263     * This makes it easier to set internals of tested objects. This should generally
264     * be avoided.
265     *
266     * @param object $obj Object on which to access the property
267     * @param string $prop name of the property to access
268     * @param mixed $value new value to set the property to
269     * @return void
270     * @throws ReflectionException when the given obj/prop does not exist
271     */
272    protected static function setInaccessibleProperty($obj, $prop, $value) {
273        $class = new \ReflectionClass($obj);
274        $property = $class->getProperty($prop);
275        $property->setAccessible(true);
276        $property->setValue($obj, $value);
277    }
278}
279