summaryrefslogtreecommitdiffstats
path: root/tests/acceptance/run.sh
blob: 3e7e0a570ff11ad02cfc927007506fb5decd3dbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#!/usr/bin/env bash

# @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
#
# @license GNU AGPL version 3 or any later version
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Helper script to run the acceptance tests, which test a running Nextcloud
# instance from the point of view of a real user.
#
# The acceptance tests are run in its own Docker container; the grandparent
# directory of the acceptance tests directory (that is, the root directory of
# the Nextcloud server) is copied to the container and the acceptance tests are
# run inside it. Once the tests end the container is stopped. The acceptance
# tests also use the Selenium server to control a web browser, so the Selenium
# server is also launched before the tests start in its own Docker container (it
# will be stopped automatically too once the tests end).
#
# To perform its job, the script requires the "docker" command to be available.
#
# The Docker Command Line Interface (the "docker" command) requires special
# permissions to talk to the Docker daemon, and those permissions are typically
# available only to the root user. Please see the Docker documentation to find
# out how to give access to a regular user to the Docker daemon:
# https://docs.docker.com/engine/installation/linux/linux-postinstall/
#
# Note, however, that being able to communicate with the Docker daemon is the
# same as being able to get root privileges for the system. Therefore, you must
# give access to the Docker daemon (and thus run this script as) ONLY to trusted
# and secure users:
# https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
#
# Finally, take into account that this script will automatically remove the
# Docker containers named "selenium-nextcloud-local-test-acceptance" and
# "nextcloud-local-test-acceptance", even if the script did not create them
# (probably you will not have containers nor images with those names, but just
# in case).

# Sets the variables that abstract the differences in command names and options
# between operating systems.
#
# Switches between timeout on GNU/Linux and gtimeout on macOS (same for mktemp
# and gmktemp).
function setOperatingSystemAbstractionVariables() {
	case "$OSTYPE" in
		darwin*)
			if [ "$(which gtimeout)" == "" ]; then
				echo "Please install coreutils (brew install coreutils)"
				exit 1
			fi

			MKTEMP=gmktemp
			TIMEOUT=gtimeout
			DOCKER_OPTIONS="-e no_proxy=localhost "
			;;
		linux*)
			MKTEMP=mktemp
			TIMEOUT=timeout
			DOCKER_OPTIONS=" "
			;;
		*)
			echo "Operating system ($OSTYPE) not supported"
			exit 1
			;;
	esac
}

# Launches the Selenium server in a Docker container.
#
# The acceptance tests use Firefox by default but, unfortunately, Firefox >= 48
# does not provide yet the same level of support as earlier versions for certain
# features related to automated testing. Therefore, the Docker image used is not
# the latest one, but an older version known to work.
#
# The acceptance tests expect the Selenium server to be accessible at
# "127.0.0.1:4444"; as the Selenium server container and the container in which
# the acceptance tests are run share the same network nothing else needs to be
# done for the acceptance tests to access the Selenium server and for the
# Selenium server to access the Nextcloud server. However, in order to ensure
# from this script that the Selenium server was started the 4444 port of its
# container is mapped to the 4444 port of the host.
#
# Besides the Selenium server, the Docker image also provides a VNC server, so
# the 5900 port of the container is also mapped to the 5900 port of the host.
#
# The Docker container started here will be automatically stopped when the
# script exits (see cleanUp). If the Selenium server can not be started then the
# script will be exited immediately with an error state; the most common cause
# for the Selenium server to fail to start is that another server is already
# using the mapped ports in the host.
#
# As the web browser is run inside the Docker container it is not visible by
# default. However, it can be viewed using VNC (for example,
# "vncviewer 127.0.0.1:5900"); when asked for the password use "secret".
function prepareSelenium() {
	SELENIUM_CONTAINER=selenium-nextcloud-local-test-acceptance

	echo "Starting Selenium server"
	docker run --detach --name=$SELENIUM_CONTAINER --publish 4444:4444 --publish 5900:5900 $DOCKER_OPTIONS selenium/standalone-firefox-debug:2.53.1-beryllium

	echo "Waiting for Selenium server to be ready"
	if ! $TIMEOUT 10s bash -c "while ! curl 127.0.0.1:4444 >/dev/null 2>&1; do sleep 1; done"; then
		echo "Could not start Selenium server; running" \
		     "\"docker run --rm --publish 4444:4444 --publish 5900:5900 $DOCKER_OPTIONS selenium/standalone-firefox-debug:2.53.1-beryllium\"" \
		     "could give you a hint of the problem"

		exit 1
	fi
}

# Creates a Docker container to run both the acceptance tests and the Nextcloud
# server used by them.
#
# This function starts a Docker container with a copy the Nextcloud code from
# the grandparent directory, although ignoring any configuration or data that it
# may provide (for example, if that directory was used directly to deploy a
# Nextcloud instance in a web server). As the Nextcloud code is copied to the
# container instead of referenced the original code can be modified while the
# acceptance tests are running without interfering in them.
function prepareDocker() {
	NEXTCLOUD_LOCAL_CONTAINER=nextcloud-local-test-acceptance

	echo "Starting the Nextcloud container"
	# As the Nextcloud server container uses the network of the Selenium server
	# container the Nextcloud server can be accessed at "127.0.0.1" from the
	# Selenium server.
	# The container exits immediately if no command is given, so a Bash session
	# is created to prevent that.
	docker run --detach --name=$NEXTCLOUD_LOCAL_CONTAINER --network=container:$SELENIUM_CONTAINER --interactive --tty nextcloudci/acceptance-php7.1:acceptance-php7.1-2 bash

	# Use the $TMPDIR or, if not set, fall back to /tmp.
	NEXTCLOUD_LOCAL_TAR="$($MKTEMP --tmpdir="${TMPDIR:-/tmp}" --suffix=.tar nextcloud-local-XXXXXXXXXX)"

	# Setting the user and group of files in the tar would be superfluous, as
	# "docker cp" does not take them into account (the extracted files are set
	# to root).
	echo "Copying local Git working directory of Nextcloud to the container"
	tar --create --file="$NEXTCLOUD_LOCAL_TAR" \
		--exclude=".git" \
		--exclude="./build" \
		--exclude="./config/config.php" \
		--exclude="./data" \
		--exclude="./data-autotest" \
		--exclude="./tests" \
		--exclude="./apps-extra" \
		--exclude="./apps-writable" \
		--exclude="node_modules" \
		--directory=../../ \
		.
	tar --append --file="$NEXTCLOUD_LOCAL_TAR" --directory=../../ tests/acceptance/

	docker exec $NEXTCLOUD_LOCAL_CONTAINER mkdir /nextcloud
	docker cp - $NEXTCLOUD_LOCAL_CONTAINER:/nextcloud/ < "$NEXTCLOUD_LOCAL_TAR"

	# run-local.sh expects a Git repository to be available in the root of the
	# Nextcloud server, but it was excluded when the Git working directory was
	# copied to the container to avoid copying the large and unneeded history of
	# the repository.
	docker exec $NEXTCLOUD_LOCAL_CONTAINER bash -c "cd nextcloud && git init"
}

# Removes/stops temporal elements created/started by this script.
function cleanUp() {
	# Disable (yes, "+" disables) exiting immediately on errors to ensure that
	# all the cleanup commands are executed (well, no errors should occur during
	# the cleanup anyway, but just in case).
	set +o errexit

	echo "Cleaning up"

	if [ -f "$NEXTCLOUD_LOCAL_TAR" ]; then
		echo "Removing $NEXTCLOUD_LOCAL_TAR"
	    rm $NEXTCLOUD_LOCAL_TAR
	fi

	# The name filter must be specified as "^/XXX$" to get an exact match; using
	# just "XXX" would match every name that contained "XXX".
	if [ -n "$(docker ps --all --quiet --filter name="^/$NEXTCLOUD_LOCAL_CONTAINER$")" ]; then
		echo "Removing Docker container $NEXTCLOUD_LOCAL_CONTAINER"
		docker rm --volumes --force $NEXTCLOUD_LOCAL_CONTAINER
	fi

	if [ -n "$(docker ps --all --quiet --filter name="^/$SELENIUM_CONTAINER$")" ]; then
		echo "Removing Docker container $SELENIUM_CONTAINER"
		docker rm --volumes --force $SELENIUM_CONTAINER
	fi
}

# Exit immediately on errors.
set -o errexit

# Execute cleanUp when the script exits, either normally or due to an error.
trap cleanUp EXIT

# Ensure working directory is script directory, as some actions (like copying
# the Git working directory to the container) expect that.
cd "$(dirname $0)"

# "--acceptance-tests-dir XXX" option can be provided to set the directory
# (relative to the root directory of the Nextcloud server) used to look for the
# Behat configuration and the Nextcloud installation script.
# By default it is "tests/acceptance", that is, the acceptance tests for the
# Nextcloud server itself.
ACCEPTANCE_TESTS_DIR_OPTION=""
if [ "$1" = "--acceptance-tests-dir" ]; then
	ACCEPTANCE_TESTS_DIR_OPTION="--acceptance-tests-dir $2"

	shift 2
fi

# "--timeout-multiplier N" option can be provided before the specific scenario
# to run, if any, to set the timeout multiplier to be used in the acceptance
# tests.
TIMEOUT_MULTIPLIER_OPTION=""
if [ "$1" = "--timeout-multiplier" ]; then
	if [[ ! "$2" =~ ^[0-9]+$ ]]; then
		echo "--timeout-multiplier must be followed by a positive integer"

		exit 1
	fi

	TIMEOUT_MULTIPLIER_OPTION="--timeout-multiplier $2"

	shift 2
fi

# If no parameter is provided to this script all the acceptance tests are run.
SCENARIO_TO_RUN=$1

setOperatingSystemAbstractionVariables

prepareSelenium
prepareDocker

echo "Running tests"
docker exec $NEXTCLOUD_LOCAL_CONTAINER bash -c "cd nextcloud && tests/acceptance/run-local.sh $ACCEPTANCE_TESTS_DIR_OPTION $TIMEOUT_MULTIPLIER_OPTION allow-git-repository-modifications $SCENARIO_TO_RUN"