From e6e28b74b5ffc64bc5be41e9e4e5e89d69d80037 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 19 Aug 2018 13:47:43 +0100 Subject: [PATCH 1/2] Nuke the TesterPlugin submodule --- .gitmodules | 3 --- tests/plugins/PocketMine-TesterPlugin | 1 - 2 files changed, 4 deletions(-) delete mode 160000 tests/plugins/PocketMine-TesterPlugin 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 From 0cdf4d0c553233c4566ddf77ecf99aec040b6f91 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 19 Aug 2018 13:56:17 +0100 Subject: [PATCH 2/2] Spoon the TesterPlugin into the main repository I am eating my own words this once, because having the tester plugin as a separate repository makes no sense - it is just added barriers to writing proper tests with no actual benefit. Since the tester plugin is specifically intended for CI, it doesn't make sense for it to be in its own module. --- tests/plugins/TesterPlugin/plugin.yml | 7 ++ .../TesterPlugin/CheckTestCompletionTask.php | 50 ++++++++ .../src/pmmp/TesterPlugin/Main.php | 109 ++++++++++++++++++ .../src/pmmp/TesterPlugin/Test.php | 87 ++++++++++++++ .../pmmp/TesterPlugin/TestFailedException.php | 28 +++++ .../tests/AsyncTaskMainLoggerTest.php | 69 +++++++++++ .../tests/AsyncTaskMemoryLeakTest.php | 60 ++++++++++ tests/travis.sh | 2 +- 8 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/TesterPlugin/plugin.yml create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/CheckTestCompletionTask.php create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/TestFailedException.php create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/tests/AsyncTaskMainLoggerTest.php create mode 100644 tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/tests/AsyncTaskMemoryLeakTest.php 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")