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