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/3] 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/3] 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") From 21e7b5ea4326d0ebc22e6eb86e1dfa3e6ecada61 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 19 Aug 2018 14:05:26 +0100 Subject: [PATCH 3/3] TesterPlugin: removed usage of deprecated ServerCommandEvent --- tests/plugins/TesterPlugin/plugin.yml | 2 +- tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/plugins/TesterPlugin/plugin.yml b/tests/plugins/TesterPlugin/plugin.yml index 48a9dd2e3..9314ce5f4 100644 --- a/tests/plugins/TesterPlugin/plugin.yml +++ b/tests/plugins/TesterPlugin/plugin.yml @@ -1,7 +1,7 @@ name: TesterPlugin main: pmmp\TesterPlugin\Main version: 0.1.0 -api: [3.0.0] +api: [3.2.0] load: POSTWORLD author: pmmp description: Plugin used to run tests on PocketMine-MP diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php index 7ef0bcee6..12308b23e 100644 --- a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pmmp\TesterPlugin; use pocketmine\event\Listener; -use pocketmine\event\server\ServerCommandEvent; +use pocketmine\event\server\CommandEvent; use pocketmine\plugin\PluginBase; class Main extends PluginBase implements Listener{ @@ -48,7 +48,7 @@ class Main extends PluginBase implements Listener{ ]; } - public function onServerCommand(ServerCommandEvent $event){ + public function onServerCommand(CommandEvent $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