xref: /dokuwiki/_test/core/DokuWikiTest.php (revision c1803f3da174a1e102253af2bf9c53f5f26de2e9)
1f8369d7dSTobias Sarnowski<?php
2e1d9dcc8SAndreas Gohr
33a7140a1SAndreas Gohruse dokuwiki\Extension\PluginController;
4cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\EventHandler;
64027a91aSSatoshi Saharause dokuwiki\Search\Indexer;
7e1d9dcc8SAndreas Gohr
8f8369d7dSTobias Sarnowski/**
9f8369d7dSTobias Sarnowski * Helper class to provide basic functionality for tests
103c1490b3SPhy *
113c1490b3SPhy * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required
12f8369d7dSTobias Sarnowski */
13*c1803f3dSSatoshi Saharaabstract class DokuWikiTest extends PHPUnit\Framework\TestCase
14bb38a884SSatoshi Sahara{
15f8369d7dSTobias Sarnowski    /**
16f8369d7dSTobias Sarnowski     * tests can override this
17f8369d7dSTobias Sarnowski     *
18f8369d7dSTobias Sarnowski     * @var array plugins to enable for test class
19f8369d7dSTobias Sarnowski     */
20f8369d7dSTobias Sarnowski    protected $pluginsEnabled = array();
21f8369d7dSTobias Sarnowski
22f8369d7dSTobias Sarnowski    /**
23f8369d7dSTobias Sarnowski     * tests can override this
24f8369d7dSTobias Sarnowski     *
25f8369d7dSTobias Sarnowski     * @var array plugins to disable for test class
26f8369d7dSTobias Sarnowski     */
27f8369d7dSTobias Sarnowski    protected $pluginsDisabled = array();
28f8369d7dSTobias Sarnowski
29f8369d7dSTobias Sarnowski    /**
30bd9dab32SAndreas Gohr     * setExpectedException was deprecated in PHPUnit 6
31bd9dab32SAndreas Gohr     *
32bd9dab32SAndreas Gohr     * @param string $class
33bd9dab32SAndreas Gohr     * @param null|string $message
34bd9dab32SAndreas Gohr     */
35bd9dab32SAndreas Gohr    public function setExpectedException($class, $message=null) {
36bd9dab32SAndreas Gohr        $this->expectException($class);
37bd9dab32SAndreas Gohr        if(!is_null($message)) {
38bd9dab32SAndreas Gohr            $this->expectExceptionMessage($message);
39bd9dab32SAndreas Gohr        }
40bd9dab32SAndreas Gohr    }
41bd9dab32SAndreas Gohr
42bd9dab32SAndreas Gohr    /**
430644090aSAndreas Gohr     * Setup the data directory
440644090aSAndreas Gohr     *
450644090aSAndreas Gohr     * This is ran before each test class
460644090aSAndreas Gohr     */
471c33cec3SAndreas Gohr    public static function setUpBeforeClass() : void {
480644090aSAndreas Gohr        // just to be safe not to delete something undefined later
490644090aSAndreas Gohr        if(!defined('TMP_DIR')) die('no temporary directory');
500644090aSAndreas Gohr        if(!defined('DOKU_TMP_DATA')) die('no temporary data directory');
510644090aSAndreas Gohr
521c0be3ebSAndreas Gohr        self::setupDataDir();
531c0be3ebSAndreas Gohr        self::setupConfDir();
540644090aSAndreas Gohr    }
550644090aSAndreas Gohr
560644090aSAndreas Gohr    /**
57f8369d7dSTobias Sarnowski     * Reset the DokuWiki environment before each test run. Makes sure loaded config,
58f8369d7dSTobias Sarnowski     * language and plugins are correct.
59f8369d7dSTobias Sarnowski     *
60f8369d7dSTobias Sarnowski     * @throws Exception if plugin actions fail
61f8369d7dSTobias Sarnowski     * @return void
62f8369d7dSTobias Sarnowski     */
631c33cec3SAndreas Gohr    public function setUp() : void {
640644090aSAndreas Gohr
65f8369d7dSTobias Sarnowski        // reload config
66f8369d7dSTobias Sarnowski        global $conf, $config_cascade;
67f8369d7dSTobias Sarnowski        $conf = array();
68f8369d7dSTobias Sarnowski        foreach (array('default','local','protected') as $config_group) {
69f8369d7dSTobias Sarnowski            if (empty($config_cascade['main'][$config_group])) continue;
70f8369d7dSTobias Sarnowski            foreach ($config_cascade['main'][$config_group] as $config_file) {
7179e79377SAndreas Gohr                if (file_exists($config_file)) {
72f8369d7dSTobias Sarnowski                    include($config_file);
73f8369d7dSTobias Sarnowski                }
74f8369d7dSTobias Sarnowski            }
75f8369d7dSTobias Sarnowski        }
76f8369d7dSTobias Sarnowski
77f8369d7dSTobias Sarnowski        // reload license config
78f8369d7dSTobias Sarnowski        global $license;
79f8369d7dSTobias Sarnowski        $license = array();
80f8369d7dSTobias Sarnowski
81f8369d7dSTobias Sarnowski        // load the license file(s)
82f8369d7dSTobias Sarnowski        foreach (array('default','local') as $config_group) {
83f8369d7dSTobias Sarnowski            if (empty($config_cascade['license'][$config_group])) continue;
84f8369d7dSTobias Sarnowski            foreach ($config_cascade['license'][$config_group] as $config_file) {
8579e79377SAndreas Gohr                if (file_exists($config_file)) {
86f8369d7dSTobias Sarnowski                    include($config_file);
87f8369d7dSTobias Sarnowski                }
88f8369d7dSTobias Sarnowski            }
89f8369d7dSTobias Sarnowski        }
90f48e16abSGerrit Uitslag        // reload some settings
91f48e16abSGerrit Uitslag        $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false);
92f8369d7dSTobias Sarnowski
93f48e16abSGerrit Uitslag        if ($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) {
94f48e16abSGerrit Uitslag            $conf['compression'] = 'gz';
95f48e16abSGerrit Uitslag        }
96f48e16abSGerrit Uitslag        if ($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) {
97f48e16abSGerrit Uitslag            $conf['compression'] = 0;
98f48e16abSGerrit Uitslag        }
99f8369d7dSTobias Sarnowski        // make real paths and check them
1003366d071SAndreas Gohr        init_creationmodes();
101f8369d7dSTobias Sarnowski        init_paths();
102f8369d7dSTobias Sarnowski        init_files();
103f8369d7dSTobias Sarnowski
104f8369d7dSTobias Sarnowski        // reset loaded plugins
105f8369d7dSTobias Sarnowski        global $plugin_controller_class, $plugin_controller;
1063a7140a1SAndreas Gohr        /** @var PluginController $plugin_controller */
107f8369d7dSTobias Sarnowski        $plugin_controller = new $plugin_controller_class();
108f8369d7dSTobias Sarnowski
109f8369d7dSTobias Sarnowski        // disable all non-default plugins
110f8369d7dSTobias Sarnowski        global $default_plugins;
111f8369d7dSTobias Sarnowski        foreach ($plugin_controller->getList() as $plugin) {
112f8369d7dSTobias Sarnowski            if (!in_array($plugin, $default_plugins)) {
113f8369d7dSTobias Sarnowski                if (!$plugin_controller->disable($plugin)) {
114f8369d7dSTobias Sarnowski                    throw new Exception('Could not disable plugin "'.$plugin.'"!');
115f8369d7dSTobias Sarnowski                }
116f8369d7dSTobias Sarnowski            }
117f8369d7dSTobias Sarnowski        }
118f8369d7dSTobias Sarnowski
119f8369d7dSTobias Sarnowski        // disable and enable configured plugins
120f8369d7dSTobias Sarnowski        foreach ($this->pluginsDisabled as $plugin) {
121f8369d7dSTobias Sarnowski            if (!$plugin_controller->disable($plugin)) {
122f8369d7dSTobias Sarnowski                throw new Exception('Could not disable plugin "'.$plugin.'"!');
123f8369d7dSTobias Sarnowski            }
124f8369d7dSTobias Sarnowski        }
125f8369d7dSTobias Sarnowski        foreach ($this->pluginsEnabled as $plugin) {
126f8369d7dSTobias Sarnowski            /*  enable() returns false but works...
127f8369d7dSTobias Sarnowski            if (!$plugin_controller->enable($plugin)) {
128f8369d7dSTobias Sarnowski                throw new Exception('Could not enable plugin "'.$plugin.'"!');
129f8369d7dSTobias Sarnowski            }
130f8369d7dSTobias Sarnowski            */
131f8369d7dSTobias Sarnowski            $plugin_controller->enable($plugin);
132f8369d7dSTobias Sarnowski        }
133f8369d7dSTobias Sarnowski
134f8369d7dSTobias Sarnowski        // reset event handler
135f8369d7dSTobias Sarnowski        global $EVENT_HANDLER;
136e1d9dcc8SAndreas Gohr        $EVENT_HANDLER = new EventHandler();
137f8369d7dSTobias Sarnowski
138f8369d7dSTobias Sarnowski        // reload language
139f8369d7dSTobias Sarnowski        $local = $conf['lang'];
140cbb44eabSAndreas Gohr        Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true);
141c01cdb70SChristopher Smith
142c01cdb70SChristopher Smith        global $INPUT;
143ccc4c71cSAndreas Gohr        $INPUT = new \dokuwiki\Input\Input();
144f8369d7dSTobias Sarnowski    }
145db5867f1SAndreas Gohr
146db5867f1SAndreas Gohr    /**
1471c0be3ebSAndreas Gohr     * Reinitialize the data directory for this class run
1481c0be3ebSAndreas Gohr     */
149bb38a884SSatoshi Sahara    public static function setupDataDir()
150bb38a884SSatoshi Sahara    {
1511c0be3ebSAndreas Gohr        // remove any leftovers from the last run
1521c0be3ebSAndreas Gohr        if(is_dir(DOKU_TMP_DATA)) {
1531c0be3ebSAndreas Gohr            // clear indexer data and cache
154a32da6ddSSatoshi Sahara            (new Indexer())->clear();
1551c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_TMP_DATA);
1561c0be3ebSAndreas Gohr        }
1571c0be3ebSAndreas Gohr
1581c0be3ebSAndreas Gohr        // populate default dirs
1591c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/');
1601c0be3ebSAndreas Gohr    }
1611c0be3ebSAndreas Gohr
1621c0be3ebSAndreas Gohr    /**
1631c0be3ebSAndreas Gohr     * Reinitialize the conf directory for this class run
1641c0be3ebSAndreas Gohr     */
165bb38a884SSatoshi Sahara    public static function setupConfDir()
166bb38a884SSatoshi Sahara    {
1671c0be3ebSAndreas Gohr        $defaults = [
1681c0be3ebSAndreas Gohr            'acronyms.conf',
1691c0be3ebSAndreas Gohr            'dokuwiki.php',
1701c0be3ebSAndreas Gohr            'entities.conf',
1711c0be3ebSAndreas Gohr            'interwiki.conf',
1721c0be3ebSAndreas Gohr            'license.php',
1731c0be3ebSAndreas Gohr            'manifest.json',
1741c0be3ebSAndreas Gohr            'mediameta.php',
1751c0be3ebSAndreas Gohr            'mime.conf',
1761c0be3ebSAndreas Gohr            'plugins.php',
1771c0be3ebSAndreas Gohr            'plugins.required.php',
1781c0be3ebSAndreas Gohr            'scheme.conf',
1791c0be3ebSAndreas Gohr            'smileys.conf',
1801c0be3ebSAndreas Gohr            'wordblock.conf'
1811c0be3ebSAndreas Gohr        ];
1821c0be3ebSAndreas Gohr
1831c0be3ebSAndreas Gohr        // clear any leftovers
1841c0be3ebSAndreas Gohr        if (is_dir(DOKU_CONF)) {
1851c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_CONF);
1861c0be3ebSAndreas Gohr        }
1871c0be3ebSAndreas Gohr        mkdir(DOKU_CONF);
1881c0be3ebSAndreas Gohr
1891c0be3ebSAndreas Gohr        // copy defaults
1901c0be3ebSAndreas Gohr        foreach ($defaults as $file) {
1911c0be3ebSAndreas Gohr            copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file);
1921c0be3ebSAndreas Gohr        }
1931c0be3ebSAndreas Gohr
1941c0be3ebSAndreas Gohr        // copy test files
1951c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf');
1961c0be3ebSAndreas Gohr    }
1971c0be3ebSAndreas Gohr
1981c0be3ebSAndreas Gohr    /**
199d732617bSAndreas Gohr     * Waits until a new second has passed
200d732617bSAndreas Gohr     *
2014af692c2SAndreas Gohr     * This tried to be clever about the passing of time and return early if possible. Unfortunately
2024af692c2SAndreas Gohr     * this never worked reliably fo unknown reasons. To avoid flaky tests, this now always simply
2034af692c2SAndreas Gohr     * sleeps for a full second on every call.
204d732617bSAndreas Gohr     *
2054af692c2SAndreas Gohr     * @param bool $init no longer used
206d732617bSAndreas Gohr     * @return int new timestamp
207d732617bSAndreas Gohr     */
208bb38a884SSatoshi Sahara    protected function waitForTick($init = false)
209bb38a884SSatoshi Sahara    {
2104af692c2SAndreas Gohr        sleep(1);
2114af692c2SAndreas Gohr        return time();
212d732617bSAndreas Gohr    }
213210ff133SAndreas Gohr
214210ff133SAndreas Gohr    /**
215210ff133SAndreas Gohr     * Allow for testing inaccessible methods (private or protected)
216210ff133SAndreas Gohr     *
217210ff133SAndreas Gohr     * This makes it easier to test protected methods without needing to create intermediate
218210ff133SAndreas Gohr     * classes inheriting and changing the access.
219210ff133SAndreas Gohr     *
220210ff133SAndreas Gohr     * @link https://stackoverflow.com/a/8702347/172068
221210ff133SAndreas Gohr     * @param object $obj Object in which to call the method
222210ff133SAndreas Gohr     * @param string $func The method to call
223210ff133SAndreas Gohr     * @param array $args The arguments to call the method with
224210ff133SAndreas Gohr     * @return mixed
225210ff133SAndreas Gohr     * @throws ReflectionException when the given obj/func does not exist
226210ff133SAndreas Gohr     */
227bb38a884SSatoshi Sahara    protected static function callInaccessibleMethod($obj, $func, array $args)
228bb38a884SSatoshi Sahara    {
229210ff133SAndreas Gohr        $class = new \ReflectionClass($obj);
230210ff133SAndreas Gohr        $method = $class->getMethod($func);
231210ff133SAndreas Gohr        $method->setAccessible(true);
232210ff133SAndreas Gohr        return $method->invokeArgs($obj, $args);
233210ff133SAndreas Gohr    }
234836f6efbSAndreas Gohr
235836f6efbSAndreas Gohr    /**
236836f6efbSAndreas Gohr     * Allow for reading inaccessible properties (private or protected)
237836f6efbSAndreas Gohr     *
238836f6efbSAndreas Gohr     * This makes it easier to check internals of tested objects. This should generally
239836f6efbSAndreas Gohr     * be avoided.
240836f6efbSAndreas Gohr     *
241836f6efbSAndreas Gohr     * @param object $obj Object on which to access the property
242836f6efbSAndreas Gohr     * @param string $prop name of the property to access
243836f6efbSAndreas Gohr     * @return mixed
244836f6efbSAndreas Gohr     * @throws ReflectionException  when the given obj/prop does not exist
245836f6efbSAndreas Gohr     */
246bb38a884SSatoshi Sahara    protected static function getInaccessibleProperty($obj, $prop)
247bb38a884SSatoshi Sahara    {
248836f6efbSAndreas Gohr        $class = new \ReflectionClass($obj);
249836f6efbSAndreas Gohr        $property = $class->getProperty($prop);
250836f6efbSAndreas Gohr        $property->setAccessible(true);
251836f6efbSAndreas Gohr        return $property->getValue($obj);
252836f6efbSAndreas Gohr    }
253836f6efbSAndreas Gohr
254836f6efbSAndreas Gohr    /**
255836f6efbSAndreas Gohr     * Allow for reading inaccessible properties (private or protected)
256836f6efbSAndreas Gohr     *
257836f6efbSAndreas Gohr     * This makes it easier to set internals of tested objects. This should generally
258836f6efbSAndreas Gohr     * be avoided.
259836f6efbSAndreas Gohr     *
260836f6efbSAndreas Gohr     * @param object $obj Object on which to access the property
261836f6efbSAndreas Gohr     * @param string $prop name of the property to access
262836f6efbSAndreas Gohr     * @param mixed $value new value to set the property to
263836f6efbSAndreas Gohr     * @return void
264836f6efbSAndreas Gohr     * @throws ReflectionException when the given obj/prop does not exist
265836f6efbSAndreas Gohr     */
266bb38a884SSatoshi Sahara    protected static function setInaccessibleProperty($obj, $prop, $value)
267bb38a884SSatoshi Sahara    {
268836f6efbSAndreas Gohr        $class = new \ReflectionClass($obj);
269836f6efbSAndreas Gohr        $property = $class->getProperty($prop);
270836f6efbSAndreas Gohr        $property->setAccessible(true);
271836f6efbSAndreas Gohr        $property->setValue($obj, $value);
272836f6efbSAndreas Gohr    }
273f8369d7dSTobias Sarnowski}
274