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