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