xref: /dokuwiki/_test/core/DokuWikiTest.php (revision 4af692c25adeba403c19bd84342f212d90051447)
1f8369d7dSTobias Sarnowski<?php
2e1d9dcc8SAndreas Gohr
33a7140a1SAndreas Gohruse dokuwiki\Extension\PluginController;
4cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\EventHandler;
6e1d9dcc8SAndreas Gohr
701ef6ea2SAndreas Gohrif(!class_exists('PHPUnit_Framework_TestCase')) {
801ef6ea2SAndreas Gohr    /**
901ef6ea2SAndreas Gohr     * phpunit 5/6 compatibility
1001ef6ea2SAndreas Gohr     */
1101ef6ea2SAndreas Gohr    class PHPUnit_Framework_TestCase extends PHPUnit\Framework\TestCase {
1201ef6ea2SAndreas Gohr        /**
133c1490b3SPhy         * setExpectedException is deprecated in PHPUnit 6
143c1490b3SPhy         *
1501ef6ea2SAndreas Gohr         * @param string $class
1601ef6ea2SAndreas Gohr         * @param null|string $message
1701ef6ea2SAndreas Gohr         */
1801ef6ea2SAndreas Gohr        public function setExpectedException($class, $message=null) {
1901ef6ea2SAndreas Gohr            $this->expectException($class);
2001ef6ea2SAndreas Gohr            if(!is_null($message)) {
2101ef6ea2SAndreas Gohr                $this->expectExceptionMessage($message);
2201ef6ea2SAndreas Gohr            }
2301ef6ea2SAndreas Gohr        }
2401ef6ea2SAndreas Gohr    }
2501ef6ea2SAndreas Gohr}
2601ef6ea2SAndreas Gohr
27f8369d7dSTobias Sarnowski/**
28f8369d7dSTobias Sarnowski * Helper class to provide basic functionality for tests
293c1490b3SPhy *
303c1490b3SPhy * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required
31f8369d7dSTobias Sarnowski */
3201ef6ea2SAndreas Gohrabstract class DokuWikiTest extends PHPUnit_Framework_TestCase {
33f8369d7dSTobias Sarnowski
34f8369d7dSTobias Sarnowski    /**
35f8369d7dSTobias Sarnowski     * tests can override this
36f8369d7dSTobias Sarnowski     *
37f8369d7dSTobias Sarnowski     * @var array plugins to enable for test class
38f8369d7dSTobias Sarnowski     */
39f8369d7dSTobias Sarnowski    protected $pluginsEnabled = array();
40f8369d7dSTobias Sarnowski
41f8369d7dSTobias Sarnowski    /**
42f8369d7dSTobias Sarnowski     * tests can override this
43f8369d7dSTobias Sarnowski     *
44f8369d7dSTobias Sarnowski     * @var array plugins to disable for test class
45f8369d7dSTobias Sarnowski     */
46f8369d7dSTobias Sarnowski    protected $pluginsDisabled = array();
47f8369d7dSTobias Sarnowski
48f8369d7dSTobias Sarnowski    /**
490644090aSAndreas Gohr     * Setup the data directory
500644090aSAndreas Gohr     *
510644090aSAndreas Gohr     * This is ran before each test class
520644090aSAndreas Gohr     */
530644090aSAndreas Gohr    public static function setUpBeforeClass() {
540644090aSAndreas Gohr        // just to be safe not to delete something undefined later
550644090aSAndreas Gohr        if(!defined('TMP_DIR')) die('no temporary directory');
560644090aSAndreas Gohr        if(!defined('DOKU_TMP_DATA')) die('no temporary data directory');
570644090aSAndreas Gohr
581c0be3ebSAndreas Gohr        self::setupDataDir();
591c0be3ebSAndreas Gohr        self::setupConfDir();
600644090aSAndreas Gohr    }
610644090aSAndreas Gohr
620644090aSAndreas Gohr    /**
63f8369d7dSTobias Sarnowski     * Reset the DokuWiki environment before each test run. Makes sure loaded config,
64f8369d7dSTobias Sarnowski     * language and plugins are correct.
65f8369d7dSTobias Sarnowski     *
66f8369d7dSTobias Sarnowski     * @throws Exception if plugin actions fail
67f8369d7dSTobias Sarnowski     * @return void
68f8369d7dSTobias Sarnowski     */
69f8369d7dSTobias Sarnowski    public function setUp() {
700644090aSAndreas Gohr
71f8369d7dSTobias Sarnowski        // reload config
72f8369d7dSTobias Sarnowski        global $conf, $config_cascade;
73f8369d7dSTobias Sarnowski        $conf = array();
74f8369d7dSTobias Sarnowski        foreach (array('default','local','protected') as $config_group) {
75f8369d7dSTobias Sarnowski            if (empty($config_cascade['main'][$config_group])) continue;
76f8369d7dSTobias Sarnowski            foreach ($config_cascade['main'][$config_group] as $config_file) {
7779e79377SAndreas Gohr                if (file_exists($config_file)) {
78f8369d7dSTobias Sarnowski                    include($config_file);
79f8369d7dSTobias Sarnowski                }
80f8369d7dSTobias Sarnowski            }
81f8369d7dSTobias Sarnowski        }
82f8369d7dSTobias Sarnowski
83f8369d7dSTobias Sarnowski        // reload license config
84f8369d7dSTobias Sarnowski        global $license;
85f8369d7dSTobias Sarnowski        $license = array();
86f8369d7dSTobias Sarnowski
87f8369d7dSTobias Sarnowski        // load the license file(s)
88f8369d7dSTobias Sarnowski        foreach (array('default','local') as $config_group) {
89f8369d7dSTobias Sarnowski            if (empty($config_cascade['license'][$config_group])) continue;
90f8369d7dSTobias Sarnowski            foreach ($config_cascade['license'][$config_group] as $config_file) {
9179e79377SAndreas Gohr                if(file_exists($config_file)){
92f8369d7dSTobias Sarnowski                    include($config_file);
93f8369d7dSTobias Sarnowski                }
94f8369d7dSTobias Sarnowski            }
95f8369d7dSTobias Sarnowski        }
96f48e16abSGerrit Uitslag        // reload some settings
97f48e16abSGerrit Uitslag        $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false);
98f8369d7dSTobias Sarnowski
99f48e16abSGerrit Uitslag        if($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) {
100f48e16abSGerrit Uitslag            $conf['compression'] = 'gz';
101f48e16abSGerrit Uitslag        }
102f48e16abSGerrit Uitslag        if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) {
103f48e16abSGerrit Uitslag            $conf['compression'] = 0;
104f48e16abSGerrit Uitslag        }
105f8369d7dSTobias Sarnowski        // make real paths and check them
106f8369d7dSTobias Sarnowski        init_paths();
107f8369d7dSTobias Sarnowski        init_files();
108f8369d7dSTobias Sarnowski
109f8369d7dSTobias Sarnowski        // reset loaded plugins
110f8369d7dSTobias Sarnowski        global $plugin_controller_class, $plugin_controller;
1113a7140a1SAndreas Gohr        /** @var PluginController $plugin_controller */
112f8369d7dSTobias Sarnowski        $plugin_controller = new $plugin_controller_class();
113f8369d7dSTobias Sarnowski
114f8369d7dSTobias Sarnowski        // disable all non-default plugins
115f8369d7dSTobias Sarnowski        global $default_plugins;
116f8369d7dSTobias Sarnowski        foreach ($plugin_controller->getList() as $plugin) {
117f8369d7dSTobias Sarnowski            if (!in_array($plugin, $default_plugins)) {
118f8369d7dSTobias Sarnowski                if (!$plugin_controller->disable($plugin)) {
119f8369d7dSTobias Sarnowski                    throw new Exception('Could not disable plugin "'.$plugin.'"!');
120f8369d7dSTobias Sarnowski                }
121f8369d7dSTobias Sarnowski            }
122f8369d7dSTobias Sarnowski        }
123f8369d7dSTobias Sarnowski
124f8369d7dSTobias Sarnowski        // disable and enable configured plugins
125f8369d7dSTobias Sarnowski        foreach ($this->pluginsDisabled as $plugin) {
126f8369d7dSTobias Sarnowski            if (!$plugin_controller->disable($plugin)) {
127f8369d7dSTobias Sarnowski                throw new Exception('Could not disable plugin "'.$plugin.'"!');
128f8369d7dSTobias Sarnowski            }
129f8369d7dSTobias Sarnowski        }
130f8369d7dSTobias Sarnowski        foreach ($this->pluginsEnabled as $plugin) {
131f8369d7dSTobias Sarnowski            /*  enable() returns false but works...
132f8369d7dSTobias Sarnowski            if (!$plugin_controller->enable($plugin)) {
133f8369d7dSTobias Sarnowski                throw new Exception('Could not enable plugin "'.$plugin.'"!');
134f8369d7dSTobias Sarnowski            }
135f8369d7dSTobias Sarnowski            */
136f8369d7dSTobias Sarnowski            $plugin_controller->enable($plugin);
137f8369d7dSTobias Sarnowski        }
138f8369d7dSTobias Sarnowski
139f8369d7dSTobias Sarnowski        // reset event handler
140f8369d7dSTobias Sarnowski        global $EVENT_HANDLER;
141e1d9dcc8SAndreas Gohr        $EVENT_HANDLER = new EventHandler();
142f8369d7dSTobias Sarnowski
143f8369d7dSTobias Sarnowski        // reload language
144f8369d7dSTobias Sarnowski        $local = $conf['lang'];
145cbb44eabSAndreas Gohr        Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true);
146c01cdb70SChristopher Smith
147c01cdb70SChristopher Smith        global $INPUT;
148ccc4c71cSAndreas Gohr        $INPUT = new \dokuwiki\Input\Input();
149f8369d7dSTobias Sarnowski    }
150db5867f1SAndreas Gohr
151db5867f1SAndreas Gohr    /**
1521c0be3ebSAndreas Gohr     * Reinitialize the data directory for this class run
1531c0be3ebSAndreas Gohr     */
1541c0be3ebSAndreas Gohr    public static function setupDataDir() {
1551c0be3ebSAndreas Gohr        // remove any leftovers from the last run
1561c0be3ebSAndreas Gohr        if(is_dir(DOKU_TMP_DATA)) {
1571c0be3ebSAndreas Gohr            // clear indexer data and cache
1581c0be3ebSAndreas Gohr            idx_get_indexer()->clear();
1591c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_TMP_DATA);
1601c0be3ebSAndreas Gohr        }
1611c0be3ebSAndreas Gohr
1621c0be3ebSAndreas Gohr        // populate default dirs
1631c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/');
1641c0be3ebSAndreas Gohr    }
1651c0be3ebSAndreas Gohr
1661c0be3ebSAndreas Gohr    /**
1671c0be3ebSAndreas Gohr     * Reinitialize the conf directory for this class run
1681c0be3ebSAndreas Gohr     */
1691c0be3ebSAndreas Gohr    public static function setupConfDir() {
1701c0be3ebSAndreas Gohr        $defaults = [
1711c0be3ebSAndreas Gohr            'acronyms.conf',
1721c0be3ebSAndreas Gohr            'dokuwiki.php',
1731c0be3ebSAndreas Gohr            'entities.conf',
1741c0be3ebSAndreas Gohr            'interwiki.conf',
1751c0be3ebSAndreas Gohr            'license.php',
1761c0be3ebSAndreas Gohr            'manifest.json',
1771c0be3ebSAndreas Gohr            'mediameta.php',
1781c0be3ebSAndreas Gohr            'mime.conf',
1791c0be3ebSAndreas Gohr            'plugins.php',
1801c0be3ebSAndreas Gohr            'plugins.required.php',
1811c0be3ebSAndreas Gohr            'scheme.conf',
1821c0be3ebSAndreas Gohr            'smileys.conf',
1831c0be3ebSAndreas Gohr            'wordblock.conf'
1841c0be3ebSAndreas Gohr        ];
1851c0be3ebSAndreas Gohr
1861c0be3ebSAndreas Gohr        // clear any leftovers
1871c0be3ebSAndreas Gohr        if(is_dir(DOKU_CONF)) {
1881c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_CONF);
1891c0be3ebSAndreas Gohr        }
1901c0be3ebSAndreas Gohr        mkdir(DOKU_CONF);
1911c0be3ebSAndreas Gohr
1921c0be3ebSAndreas Gohr        // copy defaults
1931c0be3ebSAndreas Gohr        foreach($defaults as $file) {
1941c0be3ebSAndreas Gohr            copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file);
1951c0be3ebSAndreas Gohr        }
1961c0be3ebSAndreas Gohr
1971c0be3ebSAndreas Gohr        // copy test files
1981c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf');
1991c0be3ebSAndreas Gohr    }
2001c0be3ebSAndreas Gohr
2011c0be3ebSAndreas Gohr    /**
202d732617bSAndreas Gohr     * Waits until a new second has passed
203d732617bSAndreas Gohr     *
204*4af692c2SAndreas Gohr     * This tried to be clever about the passing of time and return early if possible. Unfortunately
205*4af692c2SAndreas Gohr     * this never worked reliably fo unknown reasons. To avoid flaky tests, this now always simply
206*4af692c2SAndreas Gohr     * sleeps for a full second on every call.
207d732617bSAndreas Gohr     *
208*4af692c2SAndreas Gohr     * @param bool $init no longer used
209d732617bSAndreas Gohr     * @return int new timestamp
210d732617bSAndreas Gohr     */
211d732617bSAndreas Gohr    protected function waitForTick($init = false) {
212*4af692c2SAndreas Gohr        sleep(1);
213*4af692c2SAndreas Gohr        return time();
214d732617bSAndreas Gohr    }
215210ff133SAndreas Gohr
216210ff133SAndreas Gohr    /**
217210ff133SAndreas Gohr     * Allow for testing inaccessible methods (private or protected)
218210ff133SAndreas Gohr     *
219210ff133SAndreas Gohr     * This makes it easier to test protected methods without needing to create intermediate
220210ff133SAndreas Gohr     * classes inheriting and changing the access.
221210ff133SAndreas Gohr     *
222210ff133SAndreas Gohr     * @link https://stackoverflow.com/a/8702347/172068
223210ff133SAndreas Gohr     * @param object $obj Object in which to call the method
224210ff133SAndreas Gohr     * @param string $func The method to call
225210ff133SAndreas Gohr     * @param array $args The arguments to call the method with
226210ff133SAndreas Gohr     * @return mixed
227210ff133SAndreas Gohr     * @throws ReflectionException when the given obj/func does not exist
228210ff133SAndreas Gohr     */
229210ff133SAndreas Gohr    protected static function callInaccessibleMethod($obj, $func, array $args) {
230210ff133SAndreas Gohr        $class = new \ReflectionClass($obj);
231210ff133SAndreas Gohr        $method = $class->getMethod($func);
232210ff133SAndreas Gohr        $method->setAccessible(true);
233210ff133SAndreas Gohr        return $method->invokeArgs($obj, $args);
234210ff133SAndreas Gohr    }
235836f6efbSAndreas Gohr
236836f6efbSAndreas Gohr    /**
237836f6efbSAndreas Gohr     * Allow for reading inaccessible properties (private or protected)
238836f6efbSAndreas Gohr     *
239836f6efbSAndreas Gohr     * This makes it easier to check internals of tested objects. This should generally
240836f6efbSAndreas Gohr     * be avoided.
241836f6efbSAndreas Gohr     *
242836f6efbSAndreas Gohr     * @param object $obj Object on which to access the property
243836f6efbSAndreas Gohr     * @param string $prop name of the property to access
244836f6efbSAndreas Gohr     * @return mixed
245836f6efbSAndreas Gohr     * @throws ReflectionException  when the given obj/prop does not exist
246836f6efbSAndreas Gohr     */
247836f6efbSAndreas Gohr    protected static function getInaccessibleProperty($obj, $prop) {
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     */
266836f6efbSAndreas Gohr    protected static function setInaccessibleProperty($obj, $prop, $value) {
267836f6efbSAndreas Gohr        $class = new \ReflectionClass($obj);
268836f6efbSAndreas Gohr        $property = $class->getProperty($prop);
269836f6efbSAndreas Gohr        $property->setAccessible(true);
270836f6efbSAndreas Gohr        $property->setValue($obj, $value);
271836f6efbSAndreas Gohr    }
272f8369d7dSTobias Sarnowski}
273