From d5dd12b8f8bc7d08813fee3e22831daf45c9052d Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Mon, 16 Sep 2019 14:47:49 +0200 Subject: [PATCH] Add Drone CI - Add drone test environment - Add drone config - apt phpunit - Fix api.php - Fix item.php - Fix DBStructure - Check if caching is possible during tests --- .drone.yml | 34 ++++ .travis.yml | 2 + autotest.sh | 161 ++++++++++++++++++ bin/wait-for-connection | 40 +++++ include/api.php | 6 +- mod/api.php | 3 +- mod/item.php | 2 +- src/Core/Cache/MemcacheCache.php | 2 +- src/Core/Cache/RedisCache.php | 4 +- src/Database/DBStructure.php | 2 +- src/Database/Database.php | 2 +- tests/Util/Database/StaticDatabase.php | 2 +- phpunit.xml => tests/phpunit.xml | 20 +-- tests/src/Core/Cache/MemcacheCacheTest.php | 10 +- tests/src/Core/Cache/MemcachedCacheTest.php | 10 +- tests/src/Core/Cache/RedisCacheTest.php | 10 +- tests/src/Core/Lock/MemcacheCacheLockTest.php | 15 +- .../src/Core/Lock/MemcachedCacheLockTest.php | 15 +- tests/src/Core/Lock/RedisCacheLockTest.php | 15 +- 19 files changed, 321 insertions(+), 34 deletions(-) create mode 100644 .drone.yml create mode 100755 autotest.sh create mode 100755 bin/wait-for-connection rename phpunit.xml => tests/phpunit.xml (63%) diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000000..0d826cf694 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,34 @@ +kind: pipeline +name: mysql-php7.1 + +steps: +- name: mysql-php7.1 + image: friendicaci/php7.1:php7.1 + commands: + - NOCOVERAGE=true ./autotest.sh + environment: + MYSQL_USERNAME: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mysql + +services: +- name: mysql + image: mysql:8.0 + command: [ "--default-authentication-plugin=mysql_native_password" ] + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + tmpfs: + - /var/lib/mysql + +#trigger: +# branch: +# - master +# - develop +# - "*-rc" +# event: +# - pull_request +# - push diff --git a/.travis.yml b/.travis.yml index e2aa84f5c8..493ba652e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,4 +26,6 @@ before_script: - phpenv config-add .travis/redis.ini - phpenv config-add .travis/memcached.ini +script: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml + after_success: bash <(curl -s https://codecov.io/bash) diff --git a/autotest.sh b/autotest.sh new file mode 100755 index 0000000000..796572d809 --- /dev/null +++ b/autotest.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +DATABASENAME=${MYSQL_DATABASE:-test} +DATABASEUSER=${MYSQL_USERNAME:-friendica} +DATABASEHOST=${MYSQL_HOST:-localhost} +BASEDIR=$PWD + +export MYSQL_DATABASE="$DATABASENAME" +export MYSQL_USERNAME="$DATABASEUSER" +export MYSQL_PASSWORD="friendica" + +if [ -z "$PHP_EXE" ]; then + PHP_EXE=php +fi +PHP=$(which "$PHP_EXE") +# Use the Friendica internal composer +COMPOSER="$BASEDIR/bin/composer.phar" + +set -e + +_XDEBUG_CONFIG=$XDEBUG_CONFIG +unset XDEBUG_CONFIG + +if [ -x "$PHP" ]; then + echo "Using PHP executable $PHP" +else + echo "Could not find PHP executable $PHP_EXE" >&2 + exit 3 +fi + +echo "Installing depdendencies" +$PHP "$COMPOSER" install + +PHPUNIT="$BASEDIR/vendor/bin/phpunit" + +if [ -x "$PHPUNIT" ]; then + echo "Using PHPUnit executable $PHPUNIT" +else + echo "Could not find PHPUnit executable after composer $PHPUNIT" >&2 + exit 3 +fi + +if ! [ \( -w config -a ! -f config/local.config.php \) -o \( -f config/local.config.php -a -w config/local.config.php \) ]; then + echo "Please enable write permissions on config and config/config.php" >&2 + exit 1 +fi + +# Back up existing (dev) config if one exists and backup not already there +if [ -f config/local.config.php ] && [ ! -f config/local.config-autotest-backup.php ]; then + mv config/local.config.php config/local.config-autotest-backup.php +fi + +function cleanup_config { + + if [ -n "$DOCKER_CONTAINER_ID" ]; then + echo "Kill the docker $DOCKER_CONTAINER_ID" + docker stop "$DOCKER_CONTAINER_ID" + docker rm -f "$DOCKER_CONTAINER_ID" + fi + + cd "$BASEDIR" + + # Restore existing config + if [ -f config/local.config-autotest-backup.php ]; then + mv config/local.config-autotest-backup.php config/local.config.php + fi +} + +# restore config on exit +trap cleanup_config EXIT + +function execute_tests { + echo "Setup environment for MariaDB testing ..." + # back to root folder + cd "$BASEDIR" + + # backup current config + if [ -f config/local.config.php ]; then + mv config/local.config.php config/local.config-autotest-backup.php + fi + + if [ -n "$USEDOCKER" ]; then + echo "Fire up the mysql docker" + DOCKER_CONTAINER_ID=$(docker run \ + -e MYSQL_ROOT_PASSWORD=friendica \ + -e MYSQL_USER="$DATABASEUSER" \ + -e MYSQL_PASSWORD=friendica \ + -e MYSQL_DATABASE="$DATABASENAME" \ + -d mysql) + DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") + else + if [ -z "$DRONE" ]; then # no need to drop the DB when we are on CI + if [ "mysql" != "$(mysql --version | grep -o mysql)" ]; then + echo "Your mysql binary is not provided by mysql" + echo "To use the docker container set the USEDOCKER environment variable" + exit 3 + fi + mysql -u "$DATABASEUSER" -pfriendica -e "DROP DATABASE IF EXISTS $DATABASENAME" + mysql -u "$DATABASEUSER" -pfriendica -e "CREATE DATABASE $DATABASENAME DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" + else + DATABASEHOST=mysql + fi + fi + + echo "Waiting for MySQL $DATABASEHOST initialization..." + if ! bin/wait-for-connection $DATABASEHOST 3306 300; then + echo "[ERROR] Waited 300 seconds, no response" >&2 + exit 1 + fi + + if [ -n "$USEDOCKER" ]; then + echo "Initialize database..." + docker exec $DOCKER_CONTAINER_ID mysql -u root -pfriendica -e 'CREATE DATABASE IF NOT EXISTS $DATABASENAME;' + fi + + export MYSQL_HOST="$DATABASEHOST" + + #call installer + echo "Installing Friendica..." + "$PHP" ./bin/console.php autoinstall --dbuser="$DATABASEUSER" --dbpass=friendica --dbdata="$DATABASENAME" --dbhost="$DATABASEHOST" --url=https://friendica.local --admin=admin@friendica.local + + #test execution + echo "Testing..." + rm -fr "coverage-html" + mkdir "coverage-html" + if [[ "$_XDEBUG_CONFIG" ]]; then + export XDEBUG_CONFIG=$_XDEBUG_CONFIG + fi + + COVER='' + if [ -z "$NOCOVERAGE" ]; then + COVER="--coverage-clover autotest-clover.xml --coverage-html coverage-html" + else + echo "No coverage" + fi + + INPUT="$BASEDIR/tests" + if [ -n "$1" ]; then + INPUT="$INPUT/$1" + fi + + echo "${PHPUNIT[@]}" --configuration tests/phpunit.xml $COVER --log-junit "autotest-results.xml" "$INPUT" "$2" + "${PHPUNIT[@]}" --configuration tests/phpunit.xml $COVER --log-junit "autotest-results.xml" "$INPUT" "$2" + RESULT=$? + + if [ -n "$DOCKER_CONTAINER_ID" ]; then + echo "Kill the docker $DOCKER_CONTAINER_ID" + docker stop $DOCKER_CONTAINER_ID + docker rm -f $DOCKER_CONTAINER_ID + unset $DOCKER_CONTAINER_ID + fi +} + +# +# Start the test execution +# +if [ -n "$1" ] && [ ! -f "tests/$FILENAME" ] && [ "${FILENAME:0:2}" != "--" ]; then + execute_tests "$FILENAME" "$2" +else + execute_tests +fi diff --git a/bin/wait-for-connection b/bin/wait-for-connection new file mode 100755 index 0000000000..67990f9f93 --- /dev/null +++ b/bin/wait-for-connection @@ -0,0 +1,40 @@ +#!/usr/bin/php + + $timeout) { + $socketTimeout = $timeout; +} +$stopTime = time() + $timeout; +do { + $sock = @fsockopen($host, $port, $errno, $errstr, $socketTimeout); + if ($sock !== false) { + fclose($sock); + fwrite(STDOUT, "\n"); + exit(0); + } + sleep(1); + fwrite(STDOUT, '.'); +} while (time() < $stopTime); +fwrite(STDOUT, "\n"); +exit(1); \ No newline at end of file diff --git a/include/api.php b/include/api.php index bdab20b75a..8b938508bd 100644 --- a/include/api.php +++ b/include/api.php @@ -48,9 +48,9 @@ use Friendica\Util\Proxy as ProxyUtils; use Friendica\Util\Strings; use Friendica\Util\XML; -require_once 'mod/share.php'; -require_once 'mod/item.php'; -require_once 'mod/wall_upload.php'; +require_once __DIR__ . '/../mod/share.php'; +require_once __DIR__ . '/../mod/item.php'; +require_once __DIR__ . '/../mod/wall_upload.php'; define('API_METHOD_ANY', '*'); define('API_METHOD_GET', 'GET'); diff --git a/mod/api.php b/mod/api.php index 4a1db1be55..9a802b515a 100644 --- a/mod/api.php +++ b/mod/api.php @@ -2,6 +2,7 @@ /** * @file mod/api.php */ + use Friendica\App; use Friendica\Core\Config; use Friendica\Core\L10n; @@ -9,7 +10,7 @@ use Friendica\Core\Renderer; use Friendica\Database\DBA; use Friendica\Module\Login; -require_once 'include/api.php'; +require_once __DIR__ . '/../include/api.php'; function oauth_get_client(OAuthRequest $request) { diff --git a/mod/item.php b/mod/item.php index 8bc394bcb9..2ebf5a274b 100644 --- a/mod/item.php +++ b/mod/item.php @@ -42,7 +42,7 @@ use Friendica\Util\Security; use Friendica\Util\Strings; use Friendica\Worker\Delivery; -require_once 'include/items.php'; +require_once __DIR__ . '/../include/items.php'; function item_post(App $a) { if (!local_user() && !remote_user()) { diff --git a/src/Core/Cache/MemcacheCache.php b/src/Core/Cache/MemcacheCache.php index 7171669520..53a6523f56 100644 --- a/src/Core/Cache/MemcacheCache.php +++ b/src/Core/Cache/MemcacheCache.php @@ -37,7 +37,7 @@ class MemcacheCache extends Cache implements IMemoryCache $memcache_host = $config->get('system', 'memcache_host'); $memcache_port = $config->get('system', 'memcache_port'); - if (!$this->memcache->connect($memcache_host, $memcache_port)) { + if (!@$this->memcache->connect($memcache_host, $memcache_port)) { throw new Exception('Expected Memcache server at ' . $memcache_host . ':' . $memcache_port . ' isn\'t available'); } } diff --git a/src/Core/Cache/RedisCache.php b/src/Core/Cache/RedisCache.php index b2638c49f3..3558a38464 100644 --- a/src/Core/Cache/RedisCache.php +++ b/src/Core/Cache/RedisCache.php @@ -37,9 +37,9 @@ class RedisCache extends Cache implements IMemoryCache $redis_pw = $config->get('system', 'redis_password'); $redis_db = $config->get('system', 'redis_db', 0); - if (isset($redis_port) && !$this->redis->connect($redis_host, $redis_port)) { + if (isset($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) { throw new Exception('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available'); - } elseif (!$this->redis->connect($redis_host)) { + } elseif (!@$this->redis->connect($redis_host)) { throw new Exception('Expected Redis server at ' . $redis_host . ' isn\'t available'); } diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index cf707f2055..72b903e076 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -12,7 +12,7 @@ use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Util\DateTimeFormat; -require_once 'include/dba.php'; +require_once __DIR__ . '/../../include/dba.php'; /** * @brief This class contain functions for the database management diff --git a/src/Database/Database.php b/src/Database/Database.php index 813d4e9853..6b4b621d37 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -67,7 +67,7 @@ class Database { // Use environment variables for mysql if they are set beforehand if (!empty($server['MYSQL_HOST']) - && !empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER'])) + && (!empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER']))) && $server['MYSQL_PASSWORD'] !== false && !empty($server['MYSQL_DATABASE'])) { diff --git a/tests/Util/Database/StaticDatabase.php b/tests/Util/Database/StaticDatabase.php index e4ea1122f0..128ecc88c7 100644 --- a/tests/Util/Database/StaticDatabase.php +++ b/tests/Util/Database/StaticDatabase.php @@ -80,7 +80,7 @@ class StaticDatabase extends Database { // Use environment variables for mysql if they are set beforehand if (!empty($server['MYSQL_HOST']) - && !empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER'])) + && (!empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER']))) && $server['MYSQL_PASSWORD'] !== false && !empty($server['MYSQL_DATABASE'])) { diff --git a/phpunit.xml b/tests/phpunit.xml similarity index 63% rename from phpunit.xml rename to tests/phpunit.xml index a46f7be7b9..73b643e13a 100644 --- a/phpunit.xml +++ b/tests/phpunit.xml @@ -1,16 +1,17 @@ - + - - - tests/ - - + + functional/ + include/ + src/ + ./ + - . + .. config/ doc/ @@ -22,9 +23,6 @@ - - - diff --git a/tests/src/Core/Cache/MemcacheCacheTest.php b/tests/src/Core/Cache/MemcacheCacheTest.php index ccc3723153..8abf169aea 100644 --- a/tests/src/Core/Cache/MemcacheCacheTest.php +++ b/tests/src/Core/Cache/MemcacheCacheTest.php @@ -14,16 +14,22 @@ class MemcacheCacheTest extends MemoryCacheTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['MEMCACHE_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'memcache_host') - ->andReturn('localhost'); + ->andReturn($host); $configMock ->shouldReceive('get') ->with('system', 'memcache_port') ->andReturn(11211); - $this->cache = new MemcacheCache('localhost', $configMock); + try { + $this->cache = new MemcacheCache($host, $configMock); + } catch (\Exception $e) { + $this->markTestSkipped('Memcache is not available'); + } return $this->cache; } diff --git a/tests/src/Core/Cache/MemcachedCacheTest.php b/tests/src/Core/Cache/MemcachedCacheTest.php index d887250197..c8c65c9caa 100644 --- a/tests/src/Core/Cache/MemcachedCacheTest.php +++ b/tests/src/Core/Cache/MemcachedCacheTest.php @@ -16,14 +16,20 @@ class MemcachedCacheTest extends MemoryCacheTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['MEMCACHED_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'memcached_hosts') - ->andReturn([0 => 'localhost, 11211']); + ->andReturn([0 => $host . ', 11211']); $logger = new NullLogger(); - $this->cache = new MemcachedCache('localhost', $configMock, $logger); + try { + $this->cache = new MemcachedCache($host, $configMock, $logger); + } catch (\Exception $exception) { + $this->markTestSkipped('Memcached is not available'); + } return $this->cache; } diff --git a/tests/src/Core/Cache/RedisCacheTest.php b/tests/src/Core/Cache/RedisCacheTest.php index df353252df..cddefe3acc 100644 --- a/tests/src/Core/Cache/RedisCacheTest.php +++ b/tests/src/Core/Cache/RedisCacheTest.php @@ -15,10 +15,12 @@ class RedisCacheTest extends MemoryCacheTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['REDIS_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'redis_host') - ->andReturn('localhost'); + ->andReturn($host); $configMock ->shouldReceive('get') ->with('system', 'redis_port') @@ -33,7 +35,11 @@ class RedisCacheTest extends MemoryCacheTest ->with('system', 'redis_password') ->andReturn(null); - $this->cache = new RedisCache('localhost', $configMock); + try { + $this->cache = new RedisCache($host, $configMock); + } catch (\Exception $e) { + $this->markTestSkipped('Redis is not available.'); + } return $this->cache; } diff --git a/tests/src/Core/Lock/MemcacheCacheLockTest.php b/tests/src/Core/Lock/MemcacheCacheLockTest.php index f550ac51a6..4e7dd30340 100644 --- a/tests/src/Core/Lock/MemcacheCacheLockTest.php +++ b/tests/src/Core/Lock/MemcacheCacheLockTest.php @@ -16,15 +16,26 @@ class MemcacheCacheLockTest extends LockTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['MEMCACHE_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'memcache_host') - ->andReturn('localhost'); + ->andReturn($host); $configMock ->shouldReceive('get') ->with('system', 'memcache_port') ->andReturn(11211); - return new CacheLock(new MemcacheCache('localhost', $configMock)); + $lock = null; + + try { + $cache = new MemcacheCache($host, $configMock); + $lock = new CacheLock($cache); + } catch (\Exception $e) { + $this->markTestSkipped('Memcache is not available'); + } + + return $lock; } } diff --git a/tests/src/Core/Lock/MemcachedCacheLockTest.php b/tests/src/Core/Lock/MemcachedCacheLockTest.php index 8b59f91bb7..2249b88b8c 100644 --- a/tests/src/Core/Lock/MemcachedCacheLockTest.php +++ b/tests/src/Core/Lock/MemcachedCacheLockTest.php @@ -17,13 +17,24 @@ class MemcachedCacheLockTest extends LockTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['MEMCACHED_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'memcached_hosts') - ->andReturn([0 => 'localhost, 11211']); + ->andReturn([0 => $host . ', 11211']); $logger = new NullLogger(); - return new CacheLock(new MemcachedCache('localhost', $configMock, $logger)); + $lock = null; + + try { + $cache = new MemcachedCache($host, $configMock, $logger); + $lock = new CacheLock($cache); + } catch (\Exception $e) { + $this->markTestSkipped('Memcached is not available'); + } + + return $lock; } } diff --git a/tests/src/Core/Lock/RedisCacheLockTest.php b/tests/src/Core/Lock/RedisCacheLockTest.php index 0ebc02160e..5cecd8f4c1 100644 --- a/tests/src/Core/Lock/RedisCacheLockTest.php +++ b/tests/src/Core/Lock/RedisCacheLockTest.php @@ -16,10 +16,12 @@ class RedisCacheLockTest extends LockTest { $configMock = \Mockery::mock(Configuration::class); + $host = $_SERVER['REDIS_HOST'] ?? 'localhost'; + $configMock ->shouldReceive('get') ->with('system', 'redis_host') - ->andReturn('localhost'); + ->andReturn($host); $configMock ->shouldReceive('get') ->with('system', 'redis_port') @@ -34,6 +36,15 @@ class RedisCacheLockTest extends LockTest ->with('system', 'redis_password') ->andReturn(null); - return new CacheLock(new RedisCache('localhost', $configMock)); + $lock = null; + + try { + $cache = new RedisCache($host, $configMock); + $lock = new CacheLock($cache); + } catch (\Exception $e) { + $this->markTestSkipped('Redis is not available'); + } + + return $lock; } } -- 2.39.5