Running php unit tests on Windows using AppVeyor and Chocolatey

Travis is the go to CI for run your tests on as an opensource project, but it is limited to only Linux (and with some hassle you can also run your tests on OS X). But it doesn't do Windows, and while popular opinion states you shouldn't run (PHP on) Windows, there is a significant amount of shops and developers that do. In this post I'll walk you through my configuration file for AppVeyor, a Windows CI.

AppVeyor && Chocolatey

AppVeyor, Travis for Windows but with a twist

At first glance Travis and AppVeyor look a like, ignoring the looks, they both lets you run builds to test or do others thing, and both are controlled by a YAML file. But AppVeyor has a few gotches you need to be aware of. First of all you can create more then one build setup, so be sure to double check if you haven't already create a build for a project.

Setting up the configuration file

The basics

AppVeyor needs to know a few basics such as the platform we want to run on, since 64bits is widely used these days I'm configuring my builds to only test on x64, but if you want/need to test on 32bits systems you can add x86 to the list. We're also giving it a generic clone folder. Initially I had a different folder for each project but that turns messy when copying the config between repositories.

build: false
  - x64
clone_folder: c:\projects\php-project-workspace

The build matrix

The build matrix contains a vital piece of information, namely the php_ver_target, we need when installing PHP. Bare in mind that Chocolatey pulls it's downloads from windows.php.net so you're limited to PHP versions available there. At the time of writing that is PHP 5.6 and 7.0. This build matrix is based on my lowest, current, highest build matrix for travis.

## Build matrix for lowest and highest possible targets
  - dependencies: lowest
    php_ver_target: 7.0
  - dependencies: current
    php_ver_target: 7.0
  - dependencies: highest
    php_ver_target: 7.0

The composer cache (and only the composer cache)

Now we want to cache composers downloaded files cache to speed up composer install. I can hear you think after reading the installation for PHP we could probably cache that as well, don't! AppVeyor has a limit of 1GB of cache space per user (free plan), so caching composers dist files isn't much of a problem. But when you start caching Chocolatey files it quickly ramps up to 40 to 50MB per job. So in the config in this post we have 3 jobs, which is 120 to 150MB in total. Add PHP 5.6 to your build matrix and you ramp it up to 300MB. So that's a third of your cache gone. Instead only caching composers dist files is about 5MB per repo in my situation. Going to up 30MB when running 6 jobs, giving you up to ten times as much cache available. This of course isn't an issue when you have a few packages you test on AppVeyor, but in my case, well I have a lot... Note that we link composers cache location to the lock file, whenever the lock file changes the cache is wiped.

## Cache composer bits
    - '%LOCALAPPDATA%\Composer\files -> composer.lock'

Environment variables

To work correctly, and for some magic beyond my investigations we need to set a few environment variables. (Tried removing the PHP one but it didn't have the desired effect.)

## Set up environment variables
    - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH%
    - SET PHP=1
    - SET ANSICON=121x90 (121x90)

Installing PHP and run composer install

This is where the true magic happens. This section ensures PHP, composer, and your package's dependencies are installed. It looks like a lot but what is important is cinst, appveyor DownloadFile, and the composer calls.

## Install PHP and composer, and run the appropriate composer command
    - IF EXIST c:\tools\php (SET PHP=0)
    - ps: appveyor-retry cinst --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $Env:php_ver_target | Select-Object -first 1) -replace '[php|]','')
    - cd c:\tools\php
    - IF %PHP%==1 copy php.ini-production php.ini /Y
    - IF %PHP%==1 echo date.timezone="UTC" >> php.ini
    - IF %PHP%==1 echo extension_dir=ext >> php.ini
    - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
    - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
    - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
    - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat
    - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar
    - cd c:\projects\php-project-workspace
    - IF %dependencies%==lowest appveyor-retry composer update --prefer-lowest --no-progress --profile -n
    - IF %dependencies%==current appveyor-retry composer install --no-progress --profile
    - IF %dependencies%==highest appveyor-retry composer update --no-progress --profile -n
    - composer show

First cinst. cinst is part of the pre installed Chocolatey software, it is in reality a short cut to choco install. With some help from Rob Reynolds from Chocolatey and some PowerShell magic I've came to the following one liner:

cinst --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $Env:php_ver_target | Select-Object -first 1) -replace '[php|]','')

Lets dissect that. First we run choco search php --exact --all-versions -r which lists all PHP versions, one version a line in the following format php|7.0.12. It isn't perfect but it is a good start. Next we filter out only the versions we want, in our case 7.0.*. By using select-string we can do exactly that, we pass it the php_ver_target environment variable as the pattern select-string -pattern $Env:php_ver_target. This leaves us with a list of only PHP 7.0.x versions. Because we only want the latest version we run it through Select-Object to select the top line using Select-Object -first 1. We wrap all of these commands in brackets so we can perform the next operation on it. cinst doesn't understand a version formatted php|7.0.12 we have to strip the php| off, we can do that with the build in $var -replace. Be case we wrapped the previous operations in brackets it is treated as $var in powershell and we can run the replace over it. -replace '[php|]',''. Once again we wrap the result of that operation in brackets so it is presented to cinst as a $var. Now we can do the actual installation with cinst. Running cinst with -y will confirm all prompts, --ignore-checksums don't run the checksum checks which is could be needed at time, the second argument php is the package we want to install, and --version uses the version we determent with the previous commands. (We're also putting a appveyor-retry in front of it to ensure it will do 3 attempts before giving up. Giving windows can be flaky at times we need the retries, and --ignore-checksums to ensure the build doesn't fail on trivial errors.) All of that combined will install the latest PHP 7.0 version we'll run our tests against.

Beyond that it is all fairly simple, we enable a few extensions, downloaded composer, install dependencies, and show them (useful for debugging dependencies issues).

Running your tests

All what is left now is run out tests:

## Run the actual test
    - cd c:\projects\php-project-workspace
    - vendor/bin/phpunit -c phpunit.xml.dist


This configuration file gives you the way on configuring PHP versions as you might be used to on Travis. You can see the api-clients/psr7-oauth1 package is one of the repo's using it. You can see it on AppVeyor here.

api-clients/psr7-oauth1 jobs


  • 8 January 2017 - worded notes about appveyor-retry and --ignore-checksums into the cinst part of the post. Thanks to Niklas Keller's efforts and related post.

Categories: PHP - AppVeyor - ChocolateyTags: PHP - AppVeyor - Chocolatey