xref: /dokuwiki/_test/core/DokuWikiTest.php (revision 83b3acccb42578eaa33f84e6b13612436320090b)
1f8369d7dSTobias Sarnowski<?php
2e1d9dcc8SAndreas Gohr
33a7140a1SAndreas Gohruse dokuwiki\Extension\PluginController;
4cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
5e1d9dcc8SAndreas Gohruse dokuwiki\Extension\EventHandler;
6f577a2efSAndreas Gohruse dokuwiki\Logger;
74027a91aSSatoshi Saharause dokuwiki\Search\Indexer;
8e1d9dcc8SAndreas Gohr
9f8369d7dSTobias Sarnowski/**
10f8369d7dSTobias Sarnowski * Helper class to provide basic functionality for tests
113c1490b3SPhy *
123c1490b3SPhy * @uses PHPUnit_Framework_TestCase and thus PHPUnit 5.7+ is required
13f8369d7dSTobias Sarnowski */
14c1803f3dSSatoshi Saharaabstract class DokuWikiTest extends PHPUnit\Framework\TestCase
15bb38a884SSatoshi Sahara{
16f8369d7dSTobias Sarnowski    /**
17f8369d7dSTobias Sarnowski     * tests can override this
18f8369d7dSTobias Sarnowski     *
19f8369d7dSTobias Sarnowski     * @var array plugins to enable for test class
20f8369d7dSTobias Sarnowski     */
21f8369d7dSTobias Sarnowski    protected $pluginsEnabled = array();
22f8369d7dSTobias Sarnowski
23f8369d7dSTobias Sarnowski    /**
24f8369d7dSTobias Sarnowski     * tests can override this
25f8369d7dSTobias Sarnowski     *
26f8369d7dSTobias Sarnowski     * @var array plugins to disable for test class
27f8369d7dSTobias Sarnowski     */
28f8369d7dSTobias Sarnowski    protected $pluginsDisabled = array();
29f8369d7dSTobias Sarnowski
30f8369d7dSTobias Sarnowski    /**
31bd9dab32SAndreas Gohr     * setExpectedException was deprecated in PHPUnit 6
32bd9dab32SAndreas Gohr     *
33bd9dab32SAndreas Gohr     * @param string $class
34bd9dab32SAndreas Gohr     * @param null|string $message
35bd9dab32SAndreas Gohr     */
36bd9dab32SAndreas Gohr    public function setExpectedException($class, $message=null) {
37bd9dab32SAndreas Gohr        $this->expectException($class);
38bd9dab32SAndreas Gohr        if(!is_null($message)) {
39bd9dab32SAndreas Gohr            $this->expectExceptionMessage($message);
40bd9dab32SAndreas Gohr        }
41bd9dab32SAndreas Gohr    }
42bd9dab32SAndreas Gohr
43bd9dab32SAndreas Gohr    /**
440644090aSAndreas Gohr     * Setup the data directory
450644090aSAndreas Gohr     *
460644090aSAndreas Gohr     * This is ran before each test class
470644090aSAndreas Gohr     */
481c33cec3SAndreas Gohr    public static function setUpBeforeClass() : void {
490644090aSAndreas Gohr        // just to be safe not to delete something undefined later
500644090aSAndreas Gohr        if(!defined('TMP_DIR')) die('no temporary directory');
510644090aSAndreas Gohr        if(!defined('DOKU_TMP_DATA')) die('no temporary data directory');
520644090aSAndreas Gohr
531c0be3ebSAndreas Gohr        self::setupDataDir();
541c0be3ebSAndreas Gohr        self::setupConfDir();
550644090aSAndreas Gohr    }
560644090aSAndreas Gohr
570644090aSAndreas Gohr    /**
58f8369d7dSTobias Sarnowski     * Reset the DokuWiki environment before each test run. Makes sure loaded config,
59f8369d7dSTobias Sarnowski     * language and plugins are correct.
60f8369d7dSTobias Sarnowski     *
61f8369d7dSTobias Sarnowski     * @throws Exception if plugin actions fail
62f8369d7dSTobias Sarnowski     * @return void
63f8369d7dSTobias Sarnowski     */
641c33cec3SAndreas Gohr    public function setUp() : void {
65dfa189a8SAndreas Gohr        // reset execution time if it's enabled
66dfa189a8SAndreas Gohr        if(ini_get('max_execution_time') > 0) {
67dfa189a8SAndreas Gohr            set_time_limit(90);
68dfa189a8SAndreas Gohr        }
690644090aSAndreas Gohr
70f8369d7dSTobias Sarnowski        // reload config
71f8369d7dSTobias Sarnowski        global $conf, $config_cascade;
72f8369d7dSTobias Sarnowski        $conf = array();
73f8369d7dSTobias Sarnowski        foreach (array('default','local','protected') as $config_group) {
74f8369d7dSTobias Sarnowski            if (empty($config_cascade['main'][$config_group])) continue;
75f8369d7dSTobias Sarnowski            foreach ($config_cascade['main'][$config_group] as $config_file) {
7679e79377SAndreas Gohr                if (file_exists($config_file)) {
77f8369d7dSTobias Sarnowski                    include($config_file);
78f8369d7dSTobias Sarnowski                }
79f8369d7dSTobias Sarnowski            }
80f8369d7dSTobias Sarnowski        }
81f8369d7dSTobias Sarnowski
82f8369d7dSTobias Sarnowski        // reload license config
83f8369d7dSTobias Sarnowski        global $license;
84f8369d7dSTobias Sarnowski        $license = array();
85f8369d7dSTobias Sarnowski
86f8369d7dSTobias Sarnowski        // load the license file(s)
87f8369d7dSTobias Sarnowski        foreach (array('default','local') as $config_group) {
88f8369d7dSTobias Sarnowski            if (empty($config_cascade['license'][$config_group])) continue;
89f8369d7dSTobias Sarnowski            foreach ($config_cascade['license'][$config_group] as $config_file) {
9079e79377SAndreas Gohr                if (file_exists($config_file)) {
91f8369d7dSTobias Sarnowski                    include($config_file);
92f8369d7dSTobias Sarnowski                }
93f8369d7dSTobias Sarnowski            }
94f8369d7dSTobias Sarnowski        }
95f48e16abSGerrit Uitslag        // reload some settings
96f48e16abSGerrit Uitslag        $conf['gzip_output'] &= (strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') !== false);
97f8369d7dSTobias Sarnowski
98f48e16abSGerrit Uitslag        if ($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) {
99f48e16abSGerrit Uitslag            $conf['compression'] = 'gz';
100f48e16abSGerrit Uitslag        }
101f48e16abSGerrit Uitslag        if ($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) {
102f48e16abSGerrit Uitslag            $conf['compression'] = 0;
103f48e16abSGerrit Uitslag        }
104f8369d7dSTobias Sarnowski        // make real paths and check them
1053366d071SAndreas Gohr        init_creationmodes();
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     */
154bb38a884SSatoshi Sahara    public static function setupDataDir()
155bb38a884SSatoshi Sahara    {
1561c0be3ebSAndreas Gohr        // remove any leftovers from the last run
1571c0be3ebSAndreas Gohr        if(is_dir(DOKU_TMP_DATA)) {
1581c0be3ebSAndreas Gohr            // clear indexer data and cache
159*83b3acccSAndreas Gohr            (new Indexer)->clear();
1601c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_TMP_DATA);
1611c0be3ebSAndreas Gohr        }
1621c0be3ebSAndreas Gohr
1631c0be3ebSAndreas Gohr        // populate default dirs
1641c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../data/');
1651c0be3ebSAndreas Gohr    }
1661c0be3ebSAndreas Gohr
1671c0be3ebSAndreas Gohr    /**
1681c0be3ebSAndreas Gohr     * Reinitialize the conf directory for this class run
1691c0be3ebSAndreas Gohr     */
170bb38a884SSatoshi Sahara    public static function setupConfDir()
171bb38a884SSatoshi Sahara    {
1721c0be3ebSAndreas Gohr        $defaults = [
1731c0be3ebSAndreas Gohr            'acronyms.conf',
1741c0be3ebSAndreas Gohr            'dokuwiki.php',
1751c0be3ebSAndreas Gohr            'entities.conf',
1761c0be3ebSAndreas Gohr            'interwiki.conf',
1771c0be3ebSAndreas Gohr            'license.php',
1781c0be3ebSAndreas Gohr            'manifest.json',
1791c0be3ebSAndreas Gohr            'mediameta.php',
1801c0be3ebSAndreas Gohr            'mime.conf',
1811c0be3ebSAndreas Gohr            'plugins.php',
1821c0be3ebSAndreas Gohr            'plugins.required.php',
1831c0be3ebSAndreas Gohr            'scheme.conf',
1841c0be3ebSAndreas Gohr            'smileys.conf',
1851c0be3ebSAndreas Gohr            'wordblock.conf'
1861c0be3ebSAndreas Gohr        ];
1871c0be3ebSAndreas Gohr
1881c0be3ebSAndreas Gohr        // clear any leftovers
1891c0be3ebSAndreas Gohr        if (is_dir(DOKU_CONF)) {
1901c0be3ebSAndreas Gohr            TestUtils::rdelete(DOKU_CONF);
1911c0be3ebSAndreas Gohr        }
1921c0be3ebSAndreas Gohr        mkdir(DOKU_CONF);
1931c0be3ebSAndreas Gohr
1941c0be3ebSAndreas Gohr        // copy defaults
1951c0be3ebSAndreas Gohr        foreach ($defaults as $file) {
1961c0be3ebSAndreas Gohr            copy(DOKU_INC . '/conf/' . $file, DOKU_CONF . $file);
1971c0be3ebSAndreas Gohr        }
1981c0be3ebSAndreas Gohr
1991c0be3ebSAndreas Gohr        // copy test files
2001c0be3ebSAndreas Gohr        TestUtils::rcopy(TMP_DIR, __DIR__ . '/../conf');
2011c0be3ebSAndreas Gohr    }
2021c0be3ebSAndreas Gohr
2031c0be3ebSAndreas Gohr    /**
204d732617bSAndreas Gohr     * Waits until a new second has passed
205d732617bSAndreas Gohr     *
2064af692c2SAndreas Gohr     * This tried to be clever about the passing of time and return early if possible. Unfortunately
207e937d004SSatoshi Sahara     * this never worked reliably for unknown reasons. To avoid flaky tests, this now always simply
2084af692c2SAndreas Gohr     * sleeps for a full second on every call.
209d732617bSAndreas Gohr     *
2104af692c2SAndreas Gohr     * @param bool $init no longer used
211d732617bSAndreas Gohr     * @return int new timestamp
212d732617bSAndreas Gohr     */
213bb38a884SSatoshi Sahara    protected function waitForTick($init = false)
214bb38a884SSatoshi Sahara    {
2154af692c2SAndreas Gohr        sleep(1);
2164af692c2SAndreas Gohr        return time();
217d732617bSAndreas Gohr    }
218210ff133SAndreas Gohr
219210ff133SAndreas Gohr    /**
220210ff133SAndreas Gohr     * Allow for testing inaccessible methods (private or protected)
221210ff133SAndreas Gohr     *
222210ff133SAndreas Gohr     * This makes it easier to test protected methods without needing to create intermediate
223210ff133SAndreas Gohr     * classes inheriting and changing the access.
224210ff133SAndreas Gohr     *
225210ff133SAndreas Gohr     * @link https://stackoverflow.com/a/8702347/172068
226210ff133SAndreas Gohr     * @param object $obj Object in which to call the method
227210ff133SAndreas Gohr     * @param string $func The method to call
228210ff133SAndreas Gohr     * @param array $args The arguments to call the method with
229210ff133SAndreas Gohr     * @return mixed
230210ff133SAndreas Gohr     * @throws ReflectionException when the given obj/func does not exist
231210ff133SAndreas Gohr     */
232bb38a884SSatoshi Sahara    protected static function callInaccessibleMethod($obj, $func, array $args)
233bb38a884SSatoshi Sahara    {
234210ff133SAndreas Gohr        $class = new \ReflectionClass($obj);
235210ff133SAndreas Gohr        $method = $class->getMethod($func);
236210ff133SAndreas Gohr        return $method->invokeArgs($obj, $args);
237210ff133SAndreas Gohr    }
238836f6efbSAndreas Gohr
239836f6efbSAndreas Gohr    /**
240836f6efbSAndreas Gohr     * Allow for reading inaccessible properties (private or protected)
241836f6efbSAndreas Gohr     *
242836f6efbSAndreas Gohr     * This makes it easier to check internals of tested objects. This should generally
243836f6efbSAndreas Gohr     * be avoided.
244836f6efbSAndreas Gohr     *
245836f6efbSAndreas Gohr     * @param object $obj Object on which to access the property
246836f6efbSAndreas Gohr     * @param string $prop name of the property to access
247836f6efbSAndreas Gohr     * @return mixed
248836f6efbSAndreas Gohr     * @throws ReflectionException  when the given obj/prop does not exist
249836f6efbSAndreas Gohr     */
250bb38a884SSatoshi Sahara    protected static function getInaccessibleProperty($obj, $prop)
251bb38a884SSatoshi Sahara    {
252836f6efbSAndreas Gohr        $class = new \ReflectionClass($obj);
253836f6efbSAndreas Gohr        $property = $class->getProperty($prop);
254836f6efbSAndreas Gohr        return $property->getValue($obj);
255836f6efbSAndreas Gohr    }
256836f6efbSAndreas Gohr
257836f6efbSAndreas Gohr    /**
258836f6efbSAndreas Gohr     * Allow for reading inaccessible properties (private or protected)
259836f6efbSAndreas Gohr     *
260836f6efbSAndreas Gohr     * This makes it easier to set internals of tested objects. This should generally
261836f6efbSAndreas Gohr     * be avoided.
262836f6efbSAndreas Gohr     *
263836f6efbSAndreas Gohr     * @param object $obj Object on which to access the property
264836f6efbSAndreas Gohr     * @param string $prop name of the property to access
265836f6efbSAndreas Gohr     * @param mixed $value new value to set the property to
266836f6efbSAndreas Gohr     * @return void
267836f6efbSAndreas Gohr     * @throws ReflectionException when the given obj/prop does not exist
268836f6efbSAndreas Gohr     */
269bb38a884SSatoshi Sahara    protected static function setInaccessibleProperty($obj, $prop, $value)
270bb38a884SSatoshi Sahara    {
271836f6efbSAndreas Gohr        $class = new \ReflectionClass($obj);
272836f6efbSAndreas Gohr        $property = $class->getProperty($prop);
273836f6efbSAndreas Gohr        $property->setValue($obj, $value);
274836f6efbSAndreas Gohr    }
275f577a2efSAndreas Gohr
276f577a2efSAndreas Gohr    /**
277f577a2efSAndreas Gohr     * Expect the next log message to contain $message
278f577a2efSAndreas Gohr     *
279f577a2efSAndreas Gohr     * @param string $facility
280f577a2efSAndreas Gohr     * @param string $message
281f577a2efSAndreas Gohr     * @return void
282f577a2efSAndreas Gohr     */
283f577a2efSAndreas Gohr    protected function expectLogMessage(string $message, string $facility = Logger::LOG_ERROR): void
284f577a2efSAndreas Gohr    {
285f577a2efSAndreas Gohr        $logger = Logger::getInstance($facility);
286f577a2efSAndreas Gohr        $logger->expect($message);
287f577a2efSAndreas Gohr    }
288f8369d7dSTobias Sarnowski}
289