From 6d712900f4d88f8150f2634e7e5d4f057e97ba9a Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Tue, 6 Oct 2015 01:03:40 -0500 Subject: [PATCH] Modify Travis CI builds to run tests on multiple CPU architectures The ramsey/uuid library has requirements to run on 32-bit and 64-bit systems, as well as little-endian and big-endian systems. Travis CI only provides hardware for testing 64-bit, little-endian CPU architectures, but they recently announced support for Docker[^1]. After some research, I found it is possible to use [QEMU][] with [chroot][] jails on Travis CI to emulate other CPU architectures and run tests through these chroot jails[^2]. Unfortunately, it takes an awful long time to set up a chroot jail, build PHP within it, and run the tests. It wasn't reasonable to expect to do this on every single push to the ramsey/uuid repository. This is where Docker comes in. I was able to create chroot jails for each architecture and version of PHP combination I need to test and then turn these into Docker images, which Travis is able to download and run fairly quickly. The instances on which Travis CI is running their Docker services do not currently support phpenv or php-build, so I needed to masquerade as a Python project and specify all PHP versions and CPU architectures in environment variables in my `.travis.yml` configuration. I run `composer install` and report to Coveralls outside of the Docker images, but all tests are run within the Docker images. The Docker image I created for PHP 7.0.0RC4 on MIPS architecture produces segfaults when attempting to build PHP with Phar. PHP builds okay without Phar, but then it produces segfaults when running any PHP script on the image, so I'm leaving out 7.0.0RC4 on MIPS for now. The Docker images for use with ramsey/uuid tests are available here: . [^1]: "Using Docker on Travis CI," [^2]: "Running Travis CI tests on ARM architecture," [qemu]: http://wiki.qemu.org/Main_Page [chroot]: https://en.wikipedia.org/wiki/Chroot --- .gitattributes | 1 + .gitignore | 3 +- .travis.yml | 41 ++++++------ CONTRIBUTING.md | 16 +++++ tests/src/UuidTest.php | 1 + util/build-docker-image.sh | 129 +++++++++++++++++++++++++++++++++++++ util/run-tests.sh | 42 ++++++++++++ 7 files changed, 214 insertions(+), 19 deletions(-) create mode 100755 util/build-docker-image.sh create mode 100755 util/run-tests.sh diff --git a/.gitattributes b/.gitattributes index 31c95b3..bbfa8e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,4 @@ /tests export-ignore /phpunit.xml.dist export-ignore /phpcs.xml export-ignore +/util export-ignore diff --git a/.gitignore b/.gitignore index 0a59599..9d4b4c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -.idea/ *.phar .DS_Store +.idea/ +.vagrant/ build composer.lock docs diff --git a/.travis.yml b/.travis.yml index e1712a6..ee70a23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,35 @@ -language: php +# Fake out Travis CI, since PHP isn't supported when using their Docker services +language: python -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - hhvm +env: + - PHP_VERSION=5.4.45 ARCH=x86_64 + - PHP_VERSION=5.4.45 ARCH=mips + - PHP_VERSION=5.5.30 ARCH=x86_64 + - PHP_VERSION=5.5.30 ARCH=mips + - PHP_VERSION=5.6.14 ARCH=x86_64 + - PHP_VERSION=5.6.14 ARCH=mips + - PHP_VERSION=7.0.0RC4 ARCH=x86_64 + - PHP_VERSION=hhvm ARCH=x86_64 -sudo: false +sudo: required addons: apt: packages: - - uuid-dev + - php5-cli + - php5-curl + - qemu-user-static -before_script: - - travis_retry composer self-update - - travis_retry composer install --no-interaction --prefer-dist - - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then printf "\n" | pecl install uuid; fi;' - - phpenv rehash +services: + - docker + +install: + - curl -sS https://getcomposer.org/installer | php + - travis_retry php composer.phar install --no-interaction --prefer-dist + - mkdir -p build/logs script: - - mkdir -p build/logs - - ./vendor/bin/parallel-lint src tests - - ./vendor/bin/phpunit --verbose - - ./vendor/bin/phpcs src --standard=psr2 -sp + - bash -ex ./util/run-tests.sh after_script: - php vendor/bin/coveralls diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7390e2..0977458 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,3 +60,19 @@ The following tests must pass before we will accept a pull request. If any of th ./vendor/bin/phpunit --verbose ./vendor/bin/phpcs src --standard=psr2 -sp ``` + +### Locally Test With Emulated MIPS Architecture + +The following commands use [Vagrant](https://www.vagrantup.com/) to start an Ubuntu VM, install necessary dependencies, and then run the `util/run-tests.sh` script that will download a Docker image emulating the MIPS architecture. This is especially helpful for testing UUID generation in a big-endian environment. + +``` +vagrant init ubuntu/trusty64 +vagrant up +vagrant ssh +sudo apt-get install docker.io qemu-user-static php5-cli php5-curl +cd /vagrant +curl -sS https://getcomposer.org/installer | php +php composer.phar install --no-interaction --prefer-dist +mkdir -p build/logs +ARCH=mips PHP_VERSION=5.6.14 TRAVIS_BUILD_DIR=/vagrant ./util/run-tests.sh +``` diff --git a/tests/src/UuidTest.php b/tests/src/UuidTest.php index 7b0ae15..ea976cf 100644 --- a/tests/src/UuidTest.php +++ b/tests/src/UuidTest.php @@ -838,6 +838,7 @@ class UuidTest extends TestCase $previous = $factory->uuid4(); for ($i = 0; $i < 1000; $i ++) { + usleep(10); $uuid = $factory->uuid4(); $this->assertGreaterThan($previous->toString(), $uuid->toString()); diff --git a/util/build-docker-image.sh b/util/build-docker-image.sh new file mode 100755 index 0000000..5134e12 --- /dev/null +++ b/util/build-docker-image.sh @@ -0,0 +1,129 @@ +#!/bin/bash +### Build a Docker image for a target PHP version, CPU arch, and Debian version +# +# Based on: https://github.com/docker-32bit/debian +# +# See also: +# https://www.tomaz.me/2013/12/02/running-travis-ci-tests-on-arm.html +# +# Note: Building HHVM with this script is not supported. See instead: +# https://gist.github.com/ramsey/04cb15ff955d54484980 +# +# Recommended approach for running this script to build Docker images: +# +# vagrant init ubuntu/trusty64 +# vagrant up +# vagrant ssh +# sudo apt-get install docker.io +# sudo docker login +# cd /vagrant +# sudo ./util/build-docker-image.sh 5.6.14 mips mips wheezy +# +# or (for 64-bit, standard Debian): +# +# sudo ./util/build-docker-image.sh 5.6.14 x86_64 amd64 wheezy +# + +if [ $EUID -ne 0 ]; then + echo "This script must be run as root" 1>&2 + exit 1 +fi + +### settings +php_version=${1:-5.6.14} +qemu_arch=${2:-mips} +deb_arch=${3:-mips} +suite=${4:-wheezy} + +chroot_dir="/tmp/chroot/${qemu_arch}-${suite}-php-${php_version}" +apt_mirror="http://ftp.us.debian.org/debian" +docker_image="benramsey/ramsey-uuid:${qemu_arch}-${suite}-php-${php_version}" +tmp_package="/tmp/${qemu_arch}-${suite}-php-${php_version}.tgz" + +if [ `echo "${php_version}" | cut -c 1` = "7" ]; then + php_package="https://downloads.php.net/~ab/php-${php_version}.tar.bz2" +else + php_package="https://secure.php.net/distributions/php-${php_version}.tar.bz2" +fi + +### make sure that the required tools are installed +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y wget debootstrap qemu-user-static binfmt-support \ + docker.io php5-cli php5-curl + +### install a minbase system with debootstrap +debootstrap --foreign --arch=$deb_arch $suite $chroot_dir $apt_mirror +cp "/usr/bin/qemu-${qemu_arch}-static" $chroot_dir/usr/bin/ +chroot $chroot_dir ./debootstrap/debootstrap --second-stage + +### update the list of package sources +cat < $chroot_dir/etc/apt/sources.list +deb $apt_mirror $suite main contrib non-free +deb $apt_mirror $suite-updates main contrib non-free +deb http://security.debian.org/ $suite/updates main contrib non-free +EOF + +### upgrade packages +chroot $chroot_dir apt-get update -qq +chroot $chroot_dir apt-get upgrade -qq -y + +### locale configuration +chroot $chroot_dir apt-get install -qq -y debconf +chroot $chroot_dir bash -c 'echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen' +chroot $chroot_dir dpkg-reconfigure locales + +### install dependencies to build PHP +chroot $chroot_dir apt-get --allow-unauthenticated install -qq -y \ + autoconf build-essential libcurl3-openssl-dev libgmp-dev libmcrypt-dev \ + libreadline-dev libxml2-dev uuid-dev curl git + +### download, build, and install the PHP version needed for this chroot +mkdir -p $chroot_dir/php-src +cd $chroot_dir/php-src +wget $php_package +tar jxf "php-${php_version}.tar.bz2" +chroot $chroot_dir bash -c "cd /php-src/php-${php_version} && ./configure --disable-all --enable-bcmath --with-gmp --disable-cgi --enable-xml --enable-libxml --enable-dom --enable-filter --enable-ctype --enable-json --with-openssl --enable-phar --enable-hash --with-curl --enable-simplexml --enable-tokenizer --enable-xmlwriter --enable-zip" +chroot $chroot_dir bash -c "cd /php-src/php-${php_version} && make && make install" +chroot $chroot_dir cp "/php-src/php-${php_version}/php.ini-development" /usr/local/lib/php.ini + +### download, build, and install the PECL UUID extension +wget https://pecl.php.net/get/uuid-1.0.4.tgz +tar zxf uuid-1.0.4.tgz +chroot $chroot_dir bash -c "cd /php-src/uuid-1.0.4 && phpize && ./configure && make && make install" +chroot $chroot_dir bash -c 'printf "date.timezone=UTC\n" >> /usr/local/lib/php.ini' +chroot $chroot_dir bash -c 'printf "extension=uuid.so\n" >> /usr/local/lib/php.ini' + +if [ `echo "${php_version}" | cut -c 1` != "7" ]; then + ### download, build, and install Xdebug, if not PHP 7 + wget http://xdebug.org/files/xdebug-2.3.3.tgz + tar zxf xdebug-2.3.3.tgz + chroot $chroot_dir bash -c "cd /php-src/xdebug-2.3.3 && phpize && ./configure --enable-xdebug && make && make install" + chroot $chroot_dir bash -c "printf \"zend_extension=\$(php -r \"echo ini_get('extension_dir');\")/xdebug.so\n\" >> /usr/local/lib/php.ini" +fi + +### globally install Composer +chroot $chroot_dir bash -c "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer" + +### cleanup +chroot $chroot_dir apt-get autoclean +chroot $chroot_dir apt-get clean +chroot $chroot_dir apt-get autoremove + +cd /tmp +rm -rf $chroot_dir/php-src + +### create a tar archive from the chroot directory +tar cfz $tmp_package -C $chroot_dir . + +### import this tar archive into a docker image: +cat $tmp_package | docker import - $docker_image + +### push image to Docker Hub +docker push $docker_image + +### cleanup +rm $tmp_package +rm -rf $chroot_dir + +echo "Done!" diff --git a/util/run-tests.sh b/util/run-tests.sh new file mode 100755 index 0000000..44edd04 --- /dev/null +++ b/util/run-tests.sh @@ -0,0 +1,42 @@ +#!/bin/bash +### Run tests based on CPU architecture +# +# Depending on the ARCH indicated for this test job, run the tests in a +# Docker container based on the PHP version for this job. +# + +if [ -z "${ARCH}" ]; then + echo "The ARCH environment variable must be provided" + exit 1 +fi + +if [ -z "${PHP_VERSION}" ]; then + echo "The PHP_VERSION environment variable must be provided" + exit 1 +fi + +if [ -z "${TRAVIS_BUILD_DIR}" ]; then + echo "The TRAVIS_BUILD_DIR environment variable must be provided" + exit 1 +fi + +if [ "${PHP_VERSION}" = "hhvm" ]; then + docker_tag="${ARCH}-trusty-php-${PHP_VERSION}" +else + docker_tag="${ARCH}-wheezy-php-${PHP_VERSION}" +fi + +declare -a commands +commands[0]="echo \"Environment: \$(uname -a)\"" +commands[1]="php --version" +commands[2]="cd ${TRAVIS_BUILD_DIR}" +commands[3]="./vendor/bin/parallel-lint src tests" +commands[4]="./vendor/bin/phpcs src --standard=psr2 -sp" +commands[5]="./vendor/bin/phpunit --verbose" + +printf -v command "%s && " "${commands[@]}" +command=${command::-4} + +sudo docker run -v "${TRAVIS_BUILD_DIR}":"${TRAVIS_BUILD_DIR}" \ + benramsey/ramsey-uuid:$docker_tag \ + bash -c "${command}"