]> source.dussan.org Git - nextcloud-server.git/commitdiff
Use PHP built-in web server instead of Apache in Drone
authorDaniel Calviño Sánchez <danxuliu@gmail.com>
Tue, 18 Apr 2017 18:24:46 +0000 (20:24 +0200)
committerDaniel Calviño Sánchez <danxuliu@gmail.com>
Fri, 21 Apr 2017 12:24:37 +0000 (14:24 +0200)
Instead of running an additional Drone service with the Nextcloud server
now the Nextcloud server is run in the same Drone step as the acceptance
tests themselves using the PHP built-in web server.

Thanks to this, the Nextcloud server control is no longer needed, as the
acceptance tests can now directly reset, start and stop the Nextcloud
server. Also, the "nextcloudci/php7.0:php7.0-7" image provides
everything needed to run and manage the Nextcloud server (including the
Git command used to restore the directory to a saved state), so the
custom image is no longer needed either.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
.drone.yml
build/acceptance/features/core/NextcloudTestServerDroneHelper.php
build/acceptance/run-drone.sh

index 16f4be6a39a8bfceebf8ae00ee79fa9718158cbc..9996b5df5cfbafe44d63990e49b60592d8b94e98 100644 (file)
@@ -478,19 +478,6 @@ pipeline:
     when:
       matrix:
         TESTS: integration-trashbin
-  # As it needs access to the cloned Git repository it must be defined in the
-  # pipeline as "detached" instead of in the services.
-  service-acceptance-nextcloud-server:
-    image: nextcloudci/acceptance-nextcloud-server-php7.1-apache
-    detach: true
-    commands:
-      # "nextcloud-server-control-setup.sh" can not be set as the entry point in
-      # the image because Drone overrides it.
-      - /usr/local/bin/nextcloud-server-control-setup.sh
-      - su --shell "/bin/sh" --command "php /usr/local/bin/nextcloud-server-control.php 12345" - www-data
-    when:
-      matrix:
-        TESTS: acceptance
   acceptance-access-levels:
       image: nextcloudci/php7.0:php7.0-7
       commands:
index c6b1df809f40767d34e2b9383bb29bc31699f17a..23f6db2df97df108d2de307d67ba98661580f392 100644 (file)
  *
  */
 
-namespace NextcloudServerControl {
-
-class SocketException extends \Exception {
-       public function __construct($message) {
-               parent::__construct($message);
-       }
-}
-
-/**
- * Common class for communication between client and server.
- *
- * Clients and server communicate through messages: a client sends a request and
- * the server answers with a response. Requests and responses all have the same
- * common structure composed by a mandatory header and optional data. The header
- * contains a code that identifies the type of request or response followed by
- * the length of the data (which can be 0). The data is a free form string that
- * depends on each request and response type.
- *
- * The Messenger abstracts all that and provides two public methods: readMessage
- * and writeMessage. For each connection a client first writes the request
- * message and then reads the response message, while the server first reads the
- * request message and then writes the response message. If the client needs to
- * send another request it must connect again to the server.
- *
- * The Messenger class in the server must be kept in sync with the Messenger
- * class in the client. Due to the size of the code and its current use it was
- * more practical, at least for the time being, to keep two copies of the code
- * than creating a library that had to be downloaded and included in the client
- * and in the server.
- */
-class Messenger {
-
-       /**
-        * Reset the Nextcloud server.
-        *
-        * -Request data: empty
-        * -OK response data: empty.
-        * -Failed response data: error information.
-        */
-       const CODE_REQUEST_RESET = 0;
-
-       const CODE_RESPONSE_OK = 0;
-       const CODE_RESPONSE_FAILED = 1;
-
-       const HEADER_LENGTH = 5;
-
-       /**
-        * Reads a message from the given socket.
-        *
-        * The message is returned as an indexed array with keys "code" and "data".
-        *
-        * @param resource $socket the socket to read the message from.
-        * @return array the message read.
-        * @throws SocketException if an error occurs while reading the socket.
-        */
-       public static function readMessage($socket) {
-               $header = self::readSocket($socket, self::HEADER_LENGTH);
-               $header = unpack("Ccode/VdataLength", $header);
-
-               $data = self::readSocket($socket, $header["dataLength"]);
-
-               return [ "code" => $header["code"], "data" => $data ];
-       }
-
-       /**
-        * Reads content from the given socket.
-        *
-        * It blocks until the specified number of bytes were read.
-        *
-        * @param resource $socket the socket to read the message from.
-        * @param int $length the number of bytes to read.
-        * @return string the content read.
-        * @throws SocketException if an error occurs while reading the socket.
-        */
-       private static function readSocket($socket, $length) {
-               if ($socket == null) {
-                       throw new SocketException("Null socket can not be read from");
-               }
-
-               $pendingLength = $length;
-               $content = "";
-
-               while ($pendingLength > 0) {
-                       $readContent = socket_read($socket, $pendingLength);
-                       if ($readContent === "") {
-                               throw new SocketException("Socket could not be read: $pendingLength bytes are pending, but there is no more data to read");
-                       } else if ($readContent == false) {
-                               throw new SocketException("Socket could not be read: " . socket_strerror(socket_last_error()));
-                       }
-
-                       $pendingLength -= strlen($readContent);
-                       $content = $content . $readContent;
-               }
-
-               return $content;
-       }
-
-       /**
-        * Writes a message to the given socket.
-        *
-        * @param resource $socket the socket to write the message to.
-        * @param int $code the message code.
-        * @param string $data the message data, if any.
-        * @throws SocketException if an error occurs while reading the socket.
-        */
-       public static function writeMessage($socket, $code, $data = "") {
-               if ($socket == null) {
-                       throw new SocketException("Null socket can not be written to");
-               }
-
-               $header = pack("CV", $code, strlen($data));
-
-               $message = $header . $data;
-               $pendingLength = strlen($message);
-
-               while ($pendingLength > 0) {
-                       $sent = socket_write($socket, $message, $pendingLength);
-                       if ($sent !== 0 && $sent == false) {
-                               throw new SocketException("Message ($message) could not be written: " . socket_strerror(socket_last_error()));
-                       }
-
-                       $pendingLength -= $sent;
-                       $message = substr($message, $sent);
-               }
-       }
-}
-
-}
-
-namespace {
-
-use NextcloudServerControl\Messenger;
-use NextcloudServerControl\SocketException;
-
 /**
- * Helper to manage the Nextcloud test server running in a Drone service.
+ * Helper to manage a Nextcloud test server when acceptance tests are run in a
+ * Drone step.
  *
- * The NextcloudTestServerDroneHelper controls a Nextcloud test server running
- * in a Drone service. The "setUp" method resets the Nextcloud server to its
- * initial state; nothing needs to be done in the "cleanUp" method. To be able
- * to control the remote Nextcloud server the Drone service must provide the
- * Nextcloud server control server; the port in which the server listens on can
- * be set with the $nextcloudTestServerControlPort parameter of the constructor.
+ * The Nextcloud test server is executed using the PHP built-in web server
+ * directly from the grandparent directory of the acceptance tests directory
+ * (that is, the root directory of the Nextcloud server); note that the
+ * acceptance tests must be run from the acceptance tests directory. The "setUp"
+ * method resets the Nextcloud server to its initial state and starts it, while
+ * the "cleanUp" method stops it. To be able to reset the Nextcloud server to
+ * its initial state a Git repository must be provided in the root directory of
+ * the Nextcloud server; the last commit in that Git repository must provide the
+ * initial state for the Nextcloud server expected by the acceptance tests.
  *
- * Drone services are available at "127.0.0.1", so the Nextcloud server is
- * expected to see "127.0.0.1" as a trusted domain (which would be the case if
- * it was installed by running "occ maintenance:install"). Note, however, that
- * the Nextcloud server does not listen on port "80" but on port "8000" due to
- * internal issues of the Nextcloud server control. In any case, the base URL to
- * access the Nextcloud server can be got from "getBaseUrl".
+ * The Nextcloud server is available at "127.0.0.1", so it is expected to see
+ * "127.0.0.1" as a trusted domain (which would be the case if it was installed
+ * by running "occ maintenance:install"). The base URL to access the Nextcloud
+ * server can be got from "getBaseUrl".
  */
 class NextcloudTestServerDroneHelper implements NextcloudTestServerHelper {
 
        /**
-        * @var int
+        * @var string
         */
-       private $nextcloudTestServerControlPort;
+       private $phpServerPid;
 
        /**
         * Creates a new NextcloudTestServerDroneHelper.
-        *
-        * @param int $nextcloudTestServerControlPort the port in which the
-        *        Nextcloud server control is listening.
         */
-       public function __construct($nextcloudTestServerControlPort) {
-               $this->nextcloudTestServerControlPort = $nextcloudTestServerControlPort;
+       public function __construct() {
+               $this->phpServerPid = "";
        }
 
        /**
         * Sets up the Nextcloud test server.
         *
-        * It resets the Nextcloud test server through the control system provided
-        * by its Drone service and waits for the Nextcloud test server to be
-        * started again; if the server can not be reset or if it does not start
-        * again after some time an exception is thrown (as it is just a warning for
-        * the test runner and nothing to be explicitly catched a plain base
-        * Exception is used).
+        * It resets the Nextcloud test server restoring its last saved Git state
+        * and then waits for the Nextcloud test server to start again; if the
+        * server can not be reset or if it does not start again after some time an
+        * exception is thrown (as it is just a warning for the test runner and
+        * nothing to be explicitly catched a plain base Exception is used).
         *
-        * @throws \Exception if the Nextcloud test server in the Drone service can
-        *         not be reset or started again.
+        * @throws \Exception if the Nextcloud test server can not be reset or
+        *         started again.
         */
        public function setUp() {
-               $resetNextcloudServerCallback = function($socket) {
-                       Messenger::writeMessage($socket, Messenger::CODE_REQUEST_RESET);
+               // Ensure that previous PHP server is not running (as cleanUp may not
+               // have been called).
+               $this->killPhpServer();
 
-                       $response = Messenger::readMessage($socket);
+               $this->execOrException("cd ../../ && git reset --hard HEAD");
+               $this->execOrException("cd ../../ && git clean -d --force");
 
-                       if ($response["code"] == Messenger::CODE_RESPONSE_FAILED) {
-                               throw new Exception("Request to reset Nextcloud server failed: " . $response["data"]);
-                       }
-               };
-               $this->sendRequestAndHandleResponse($resetNextcloudServerCallback);
+               // execOrException is not used because the server is started in the
+               // background, so the command will always succeed even if the server
+               // itself fails.
+               $this->phpServerPid = exec("php -S 127.0.0.1:80 -t ../../ >/dev/null 2>&1 & echo $!");
 
                $timeout = 60;
                if (!Utils::waitForServer($this->getBaseUrl(), $timeout)) {
@@ -223,9 +88,10 @@ class NextcloudTestServerDroneHelper implements NextcloudTestServerHelper {
        /**
         * Cleans up the Nextcloud test server.
         *
-        * Nothing needs to be done when using the Drone service.
+        * It kills the running Nextcloud test server, if any.
         */
        public function cleanUp() {
+               $this->killPhpServer();
        }
 
        /**
@@ -234,39 +100,35 @@ class NextcloudTestServerDroneHelper implements NextcloudTestServerHelper {
         * @return string the base URL of the Nextcloud test server.
         */
        public function getBaseUrl() {
-               return "http://127.0.0.1:8000/index.php";
+               return "http://127.0.0.1/index.php";
        }
 
        /**
-        * Executes the given callback to communicate with the Nextcloud test server
-        * control.
+        * Executes the given command, throwing an Exception if it fails.
         *
-        * A socket is created with the Nextcloud test server control and passed to
-        * the callback to send the request and handle its response.
-        *
-        * @param \Closure $nextcloudServerControlCallback the callback to call with
-        *        the communication socket.
-        * @throws \Exception if any socket-related operation fails.
+        * @param string $command the command to execute.
+        * @throws \Exception if the command fails to execute.
         */
-       private function sendRequestAndHandleResponse($nextcloudServerControlCallback) {
-               $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
-               if ($socket === false) {
-                       throw new Exception("Request socket to reset Nextcloud server could not be created: " . socket_strerror(socket_last_error()));
+       private function execOrException($command) {
+               exec($command . " 2>&1", $output, $returnValue);
+               if ($returnValue != 0) {
+                       throw new Exception("'$command' could not be executed: " . implode("\n", $output));
                }
+       }
 
-               try {
-                       if (socket_connect($socket, "127.0.0.1", $this->nextcloudTestServerControlPort) === false) {
-                               throw new Exception("Request socket to reset Nextcloud server could not be connected: " . socket_strerror(socket_last_error()));
-                       }
-
-                       $nextcloudServerControlCallback($socket);
-               } catch (SocketException $exception) {
-                       throw new Exception("Request socket to reset Nextcloud server failed: " . $exception->getMessage());
-               } finally {
-                       socket_close($socket);
+       /**
+        * Kills the PHP built-in web server started in setUp, if any.
+        */
+       private function killPhpServer() {
+               if ($this->phpServerPid == "") {
+                       return;
                }
-       }
 
-}
+               // execOrException is not used because the PID may no longer exist when
+               // trying to kill it.
+               exec("kill " . $this->phpServerPid);
+
+               $this->phpServerPid = "";
+       }
 
 }
index 93e91c474c6d8f610398dbd3aa8d9dc3f9d602d5..c90a79c95e00d4402d0222709d7d6d46da36da7e 100755 (executable)
 #
 # The acceptance tests are written in Behat so, besides running the tests, this
 # script installs Behat, its dependencies, and some related packages in the
-# "vendor" subdirectory of the acceptance tests. The acceptance tests also use
-# the Selenium server to control a web browser, and they require a Nextcloud
-# server to be available, so this script waits for the Selenium server and the
-# Nextcloud server (both provided in their own Drone service) to be ready before
-# running the tests.
+# "vendor" subdirectory of the acceptance tests. The acceptance tests expect
+# that the last commit in the Git repository provides the default state of the
+# Nextcloud server, so the script installs the Nextcloud server and saves a
+# snapshot of the whole grandparent directory (no .gitignore file is used) in
+# the Git repository. Finally, the acceptance tests also use the Selenium server
+# to control a web browser, so this script waits for the Selenium server
+# (provided in its own Drone service) to be ready before running the tests.
 
 # Exit immediately on errors.
 set -o errexit
@@ -50,26 +52,24 @@ ORIGINAL="\
 REPLACEMENT="\
         - NextcloudTestServerContext:\n\
             nextcloudTestServerHelper: NextcloudTestServerDroneHelper\n\
-            nextcloudTestServerHelperParameters:\n\
-              - $NEXTCLOUD_SERVER_CONTROL_PORT"
+            nextcloudTestServerHelperParameters:"
 sed "s/$ORIGINAL/$REPLACEMENT/" config/behat.yml > config/behat-drone.yml
 
-# Both the Selenium server and the Nextcloud server control should be ready by
-# now, as Composer typically takes way longer to execute than their startup
-# (which is done in parallel in Drone services), but just in case.
+cd ../../
 
-echo "Waiting for Selenium"
-timeout 60s bash -c "while ! curl 127.0.0.1:4444 >/dev/null 2>&1; do sleep 1; done"
+echo "Installing and configuring Nextcloud server"
+build/acceptance/installAndConfigureServer.sh
+
+echo "Saving the default state so acceptance tests can reset to it"
+find . -name ".gitignore" -exec rm --force {} \;
+git add --all && echo 'Default state' | git -c user.name='John Doe' -c user.email='john@doe.org' commit --quiet --file=-
 
-# This just checks if it can connect to the port in which the Nextcloud server
-# control should be listening on.
-NEXTCLOUD_SERVER_CONTROL_PORT="12345"
-PHP_CHECK_NEXTCLOUD_SERVER="\
-if ((\\\$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { exit(1); } \
-if (socket_connect(\\\$socket, \\\"127.0.0.1\\\", \\\"$NEXTCLOUD_SERVER_CONTROL_PORT\\\") === false) { exit(1); } \
-socket_close(\\\$socket);"
+cd build/acceptance
 
-echo "Waiting for Nextcloud server control"
-timeout 60s bash -c "while ! php -r \"$PHP_CHECK_NEXTCLOUD_SERVER\" >/dev/null 2>&1; do sleep 1; done"
+# The Selenium server should be ready by now, as Composer typically takes way
+# longer to execute than its startup (which is done in parallel in a Drone
+# service), but just in case.
+echo "Waiting for Selenium"
+timeout 60s bash -c "while ! curl 127.0.0.1:4444 >/dev/null 2>&1; do sleep 1; done"
 
 vendor/bin/behat --config=config/behat-drone.yml $SCENARIO_TO_RUN