Automating PHPUnit Tests in Windows Azure

To start the new year off, I’d like to follow up on a couple of posts I wrote last month: Thoughts on Testing OSS Applications in Windows Azure and Running PHPUnit in Windows Azure. In this post, I’ll show you how to deploy your PHPUnit tests with your application, have the tests run as a start up task, and have the results written to your storage account for analysis. Attached to this post is a .zip file that contains a skeleton project that you can use to automatically run PHPUnit tests when you deploy a PHP application to Azure. I’ll walk you though how to use the skeleton project, then provide a bit more detail as to how it all works (so you can make modifications where necessary).

However, first, a couple of high-level “lessons learned” as I worked through this:

  • It’s all about relative directories. When you deploy an application to Windows Azure, there are no guarantees as to the location of your root directory. You know that it will be called approot, but your don’t know if that will be on the C:\, D:\, E:\, etc. drive (though, in my experience, it has always been E:\approot). In order to make sure your application and startup scripts will run correctly, you need to avoid using absolute paths (which seems to be common in the php.ini file).
  • Know how to script, both batch scripts and Powershell scripts. I’m still relatively new to writing scripts, but they are essential to creating startup tasks in Azure. Powershell especially makes lots of handy Azure-specific information available through various cmdlets.

Anyway, to use the attached skeleton project, here’s what you need to do:

1. Download and unzip the attached AzurePHPWebRole.zip file. You will find the following directory structure:

-AzurePHPWebRole
-AzurePHPWebRole
-bin
-configurephp.ps1
-install-phpmanager.cmd
-runtests.ps1
-setup.cmd
-PHP
-resources
-WebPICmdLine
-(Web PI .dll files)
-tests
-(application files)
-(any external libraries)
-diagnostics.wadcfg
-web.config
-ServiceDefinition.csdef

2. Copy your application files to the AzurePHPWebRole\AzurePHPWebRole directory. This is the application’s root directory. Your unit tests should be put in the tests directory.

3. Copy your local PHP installation to the AzurePHPWebRole\AzurePHPWebRole\bin\PHP directory. I’m assuming that you have PHPUnit installed as a PEAR package. If you followed the default installation of PEAR and PHPUnit, they will be included as part of your custom PHP installation. After copying your PHP installation, you’ll need to make two minor modifications:

a. Make sure that any paths in your php.ini file are relative (e.g. extension_dir=”.\ext”).

b. Edit your phpunit.bat file to eliminate the use of absolute paths. To do this, I’ve modified lines 1 and 7 to use %~dp0 as a substitute for the current directory in the code below:

  1: if "%PHPBIN%" == "" set PHPBIN=%~dp0php.exe
  2: if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
  3: GOTO RUN
  4: :USE_PEAR_PATH
  5: set PHPBIN=%PHP_PEAR_PHP_BIN%
  6: :RUN
  7: "%PHPBIN%" "%~dp0phpunit" %*

4. Generate a skeleton ServiceConfiguration.cscfg file with the following command (run from the AzurePHPWebRole directory):

 cspack ServiceDefinition.csdef /generateConfigurationFile:ServiceConfiguration.cscfg /copyOnly

5. Edit the generated ServiceConfiguration.cscfg file. You will need to specify osfamily=”2” and osversion=”*”, as well as supply your storage account connection information:

 <?xml version="1.0"?>
 <ServiceConfiguration xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" 
                       xmlns:xsd="https://www.w3.org/2001/XMLSchema" 
                       serviceName="AzurePHPWebRole" 
                       xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
                       osFamily="2"
                       osVersion="*">
   <Role name="AzurePHPWebRole">
     <ConfigurationSettings>
       <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="YourStorageAccountConnectionStringHere" />
     </ConfigurationSettings>
     <Instances count="1" />
    </Role>
 </ServiceConfiguration>

6. Package your application for deployment. To create the .cspkg file that you need for deploying your application, run the following command from the AzurePHPWebRole directory:

 cspack ServiceDefinition.csdef

7. Deploy your application. Finally, you can deploy your application. This tutorial will walk you through the steps: https://azurephp.interoperabilitybridges.com/articles/deploying-your-first-php-application-to-windows-azure#new_deploy.

After successful deployment of your application (and I’m assuming you are deploying to the staging environment), results of your tests will be written to your storage account in a blob container called wad-custom. Assuming that your tests pass, you are then ready to promote your deployment to the production environment.

How does it all work?

Here’s how it works: Windows Azure diagnostics can pick up any file you want it to and send it your to storage account. If you look at the ServiceDefinition.csdef file in the attached skeleton project, you’ll see this:

 <LocalResources>
    <LocalStorage name="MyCustomLogs" sizeInMB="10" cleanOnRoleRecycle="false" />
 </LocalResources>

That entry creates a local directory called MyCustomLogs when you deploy your project. If you also look at the diagnostics.wadcfg file in the skeleton project, you’ll see this:

 <DataSources>
    <DirectoryConfiguration container="wad-custom" directoryQuotaInMB="128">
       <LocalResource name="MyCustomLogs" relativePath="customlogs" />
    </DirectoryConfiguration>
 </DataSources>

That tells the Diagnostics module to look in the customlogs directory of the MyCustomLogs local resource and send the results to your storage account in a blob container called wad-custom. The startup tasks will write PHPUnit test results to the customlogs directory, which the Diagnostics module then transfers to your storage account. (For more information about Windows Azure diagnostics, see How to Get Diagnostics Info for Azure/PHP Applications Part 1 and Part 2.)

Of course, the tricky part is writing the scripts to run PHPUnit and write the results to customlogs. Going back to the ServiceDefinition.csdef file, you see it contains these lines:

 <Startup>             
    <Task commandLine="install-phpmanager.cmd" executionContext="elevated" taskType="simple" />
    <Task commandLine="setup.cmd" executionContext="elevated" taskType="simple" />
 </Startup>

Those lines tell Windows Azure to run the install-phpmanager.cmd, and setup.cmd scripts in order (the tasktype=”simple” makes sure they run serially).  Let’s look at these scripts.

The install-phpmanager.cmd script installs the PHP Manager for IIS:

 @echo off
 cd "%~dp0"
 ECHO Starting PHP Manager installation... >;> ..\startup-tasks-log.txt
  
 md "%~dp0appdata"
 cd "%~dp0appdata"
 cd "%~dp0"
  
 reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d "%~dp0appdata" /f
 "..\resources\WebPICmdLine\webpicmdline" /Products:PHPManager /AcceptEula  >;> ..\startup-tasks-log.txt 2>>..\startup-tasks-error-log.txt
 reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d %%USERPROFILE%%\AppData\Local /f
  
 ECHO Completed PHP Manager installation. >> ..\startup-tasks-log.txt

The Powershell cmdlets for PHP Manager are used to modify your PHP installation; specifically the include_path setting. (Remember, we don’t have guarantees about where our application root will be.) You can see how the cmdlets are used in the configurephp.ps1 script (which will get called from the setup.cmd script):

 Add-PsSnapin PHPManagerSnapin
  
 new-phpversion -scriptprocessor .\php
  
 $include_path_setting = get-phpsetting -Name include_path
  
 $include_path = $include_path_setting.Value
  
 $current_dir = get-location
  
 $new_path = '"' + $include_path.Trim('"') + ";" + $current_dir.Path + "\php\PEAR" + '"'
  
 set-phpsetting -Name include_path -Value $new_path
  
 $extension_dir = '"'  + $current_dir.Path + "\php\ext" + '"'
  
 set-phpsetting -Name extension_dir -Value $extension_dir

The setup.cmd simply calls the configurephp.ps1 and the runtests.ps1 scripts with an “Unrestricted” execution policy:

 powershell -command "Set-ExecutionPolicy Unrestricted"
 powershell .\configurephp.ps1
 powershell .\runtests.ps1

The runtests.ps1 script puts PHP in the Path environment variable, gets the location of our MyCustomLogs resource, and runs PHPUnit (with the output written to the customlogs directory):

 Add-PSSnapIn Microsoft.WindowsAzure.ServiceRuntime
  
 [System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";" + $pwd + "\PHP;")
  
 cd ..
  
 $rootpathelement = get-localresource MyCustomLogs
  
 $customlog = join-path $rootpathelement.RootPath "customlogs\test.log"
  
 phpunit tests >$customlog

That’s basically it. If you give this a try, please let me know how it goes. I’m willing to bet there are areas in which this whole process can be improved.

Thanks.

-Brian

Share this on Twitter

AzurePHPWebRole.zip