diff --git a/.gitmodules b/.gitmodules index 2a18da0ae..cf20af141 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "tests/plugins/PocketMine-DevTools"] path = tests/plugins/PocketMine-DevTools url = https://github.com/pmmp/PocketMine-DevTools.git -[submodule "tests/plugins/PocketMine-TesterPlugin"] - path = tests/plugins/PocketMine-TesterPlugin - url = https://github.com/pmmp/PocketMine-TesterPlugin.git [submodule "src/pocketmine/resources/vanilla"] path = src/pocketmine/resources/vanilla url = https://github.com/pmmp/BedrockData.git diff --git a/tests/plugins/PocketMine-TesterPlugin b/tests/plugins/PocketMine-TesterPlugin deleted file mode 160000 index 5fd76d571..000000000 --- a/tests/plugins/PocketMine-TesterPlugin +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5fd76d57187baa0e80325f268ec2fd3ead405ce2 diff --git a/tests/plugins/TesterPlugin/plugin.yml b/tests/plugins/TesterPlugin/plugin.yml new file mode 100644 index 000000000..48a9dd2e3 --- /dev/null +++ b/tests/plugins/TesterPlugin/plugin.yml @@ -0,0 +1,7 @@ +name: TesterPlugin +main: pmmp\TesterPlugin\Main +version: 0.1.0 +api: [3.0.0] +load: POSTWORLD +author: pmmp +description: Plugin used to run tests on PocketMine-MP diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/CheckTestCompletionTask.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/CheckTestCompletionTask.php new file mode 100644 index 000000000..dc65a9a72 --- /dev/null +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/CheckTestCompletionTask.php @@ -0,0 +1,50 @@ +plugin = $plugin; + } + + public function onRun(int $currentTick){ + $test = $this->plugin->getCurrentTest(); + if($test === null){ + if(!$this->plugin->startNextTest()){ + $this->plugin->getScheduler()->cancelTask($this->getHandler()->getTaskId()); + $this->plugin->onAllTestsCompleted(); + } + }elseif($test->isFinished() or $test->isTimedOut()){ + $this->plugin->onTestCompleted($test); + }else{ + $test->tick(); + } + } +} diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php new file mode 100644 index 000000000..7ef0bcee6 --- /dev/null +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php @@ -0,0 +1,109 @@ +getServer()->getPluginManager()->registerEvents($this, $this); + $this->getScheduler()->scheduleRepeatingTask(new CheckTestCompletionTask($this), 10); + + $this->waitingTests = [ + new tests\AsyncTaskMemoryLeakTest($this), + new tests\AsyncTaskMainLoggerTest($this) + ]; + } + + public function onServerCommand(ServerCommandEvent $event){ + //The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to + //run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may + //be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests + //have completed. + if($event->getCommand() === "stop"){ + $event->setCancelled(); + } + } + + /** + * @return Test|null + */ + public function getCurrentTest(){ + return $this->currentTest; + } + + public function startNextTest() : bool{ + $this->currentTest = array_shift($this->waitingTests); + if($this->currentTest !== null){ + $this->getLogger()->notice("Running test #" . (++$this->currentTestNumber) . " (" . $this->currentTest->getName() . ")"); + $this->currentTest->start(); + return true; + } + + return false; + } + + public function onTestCompleted(Test $test){ + $message = "Finished test #" . $this->currentTestNumber . " (" . $test->getName() . "): "; + switch($test->getResult()){ + case Test::RESULT_OK: + $message .= "PASS"; + break; + case Test::RESULT_FAILED: + $message .= "FAIL"; + break; + case Test::RESULT_ERROR: + $message .= "ERROR"; + break; + case Test::RESULT_WAITING: + $message .= "TIMEOUT"; + break; + default: + $message .= "UNKNOWN"; + break; + } + + $this->getLogger()->notice($message); + + $this->completedTests[$this->currentTestNumber] = $test; + $this->currentTest = null; + } + + public function onAllTestsCompleted(){ + $this->getLogger()->notice("All tests finished, stopping the server"); + $this->getServer()->shutdown(); + } +} diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php new file mode 100644 index 000000000..5ddb3d8a6 --- /dev/null +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php @@ -0,0 +1,87 @@ +plugin = $plugin; + } + + public function getPlugin() : Main{ + return $this->plugin; + } + + final public function start(){ + $this->startTime = time(); + try{ + $this->run(); + }catch(TestFailedException $e){ + $this->getPlugin()->getLogger()->error($e->getMessage()); + $this->setResult(Test::RESULT_FAILED); + }catch(\Throwable $e){ + $this->getPlugin()->getLogger()->logException($e); + $this->setResult(Test::RESULT_ERROR); + } + } + + public function tick(){ + + } + + abstract public function run(); + + public function isFinished() : bool{ + return $this->result !== Test::RESULT_WAITING; + } + + public function isTimedOut() : bool{ + return !$this->isFinished() and time() - $this->timeout > $this->startTime; + } + + protected function setTimeout(int $timeout){ + $this->timeout = $timeout; + } + + public function getResult() : int{ + return $this->result; + } + + public function setResult(int $result){ + $this->result = $result; + } + + abstract public function getName() : string; + + abstract public function getDescription() : string; +} diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/TestFailedException.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/TestFailedException.php new file mode 100644 index 000000000..c90751806 --- /dev/null +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/TestFailedException.php @@ -0,0 +1,28 @@ +getPlugin()->getServer()->getAsyncPool()->submitTask(new class($this) extends AsyncTask{ + + /** @var bool */ + protected $success = false; + + public function __construct(AsyncTaskMainLoggerTest $testObject){ + $this->storeLocal($testObject); + } + + public function onRun(){ + ob_start(); + MainLogger::getLogger()->info("Testing"); + if(strpos(ob_get_contents(), "Testing") !== false){ + $this->success = true; + } + ob_end_flush(); + } + + public function onCompletion(Server $server){ + /** @var AsyncTaskMainLoggerTest $test */ + $test = $this->fetchLocal(); + $test->setResult($this->success ? Test::RESULT_OK : Test::RESULT_FAILED); + } + }); + } + + public function getName() : string{ + return "MainLogger::getLogger() works in AsyncTasks"; + } + + public function getDescription() : string{ + return "Verifies that the MainLogger is accessible by MainLogger::getLogger() in an AsyncTask"; + } + + +} diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/tests/AsyncTaskMemoryLeakTest.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/tests/AsyncTaskMemoryLeakTest.php new file mode 100644 index 000000000..2b7fd4eab --- /dev/null +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/tests/AsyncTaskMemoryLeakTest.php @@ -0,0 +1,60 @@ +getPlugin()->getServer()->getAsyncPool()->submitTask(new TestAsyncTask()); + } + + public function tick(){ + if(TestAsyncTask::$destroyed === true){ + $this->setResult(Test::RESULT_OK); + } + } + + public function getName() : string{ + return "AsyncTask memory leak after completion"; + } + + public function getDescription() : string{ + return "Regression test for AsyncTasks objects not being destroyed after completion"; + } +} + +class TestAsyncTask extends AsyncTask{ + public static $destroyed = false; + + public function onRun(){ + usleep(50 * 1000); //1 server tick + } + + public function __destruct(){ + self::$destroyed = true; + } +} diff --git a/tests/travis.sh b/tests/travis.sh index 8a590c844..d975014a1 100755 --- a/tests/travis.sh +++ b/tests/travis.sh @@ -47,7 +47,7 @@ fi mkdir "$DATA_DIR" mkdir "$PLUGINS_DIR" mv DevTools.phar "$PLUGINS_DIR" -cp -r tests/plugins/PocketMine-TesterPlugin "$PLUGINS_DIR" +cp -r tests/plugins/TesterPlugin "$PLUGINS_DIR" echo -e "stop\n" | "$PHP_BINARY" PocketMine-MP.phar --no-wizard --disable-ansi --disable-readline --debug.level=2 --data="$DATA_DIR" --plugins="$PLUGINS_DIR" --anonymous-statistics.enabled=0 --settings.async-workers="$PM_WORKERS" --settings.enable-dev-builds=1 output=$(grep '\[TesterPlugin\]' "$DATA_DIR/server.log")