Skip to main content

6 posts tagged with "appveyor"

View All Tags

VSTS... YAML up!

For the longest time I've been using the likes of Travis and AppVeyor to build open source projects that I work on. They rock. I've also recently been dipping my toes back in the water of Visual Studio Team Services. VSTS offers a whole stack of stuff, but my own area of interest has been the Continuous Integration / Continuous Deployment offering.

Historically I have been underwhelmed by the CI proposition of Team Foundation Server / VSTS. It was difficult to debug, difficult to configure, difficult to understand. If it worked... Great! If it didn't (and it often didn't), you were toast. But things done changed! I don't know when it happened, but VSTS is now super configurable. You add tasks / configure them, build and you're done! It's really nice.

However, there's been something I've been missing from Travis, AppVeyor et al. Keeping my build script with my code. Travis has .travis.yml, AppVeyor has appveyor.yml. VSTS, what's up?

The New Dawn#

Up until now, really not much. It just wasn't possible. Until it was:

If you prefer a build definition in YAML then we’re currently hard at work on that. You can enable it as a preview feature: https://t.co/hau9Sv8brf

— Martin Woodward (@martinwoodward) March 4, 2018

When I started testing it out I found things to like and some things I didn't understand. Crucially, my CI now builds based upon .vsts-ci.yml. YAML baby!

It Begins!#

You can get to "Hello World" by looking at the docs here and the examples here. But what you really want is your existing build, configured in the UI, exported to YAML. That doesn't seem to quite exist, but there's something that gets you part way. Take a look:

If you notice, in the top right of the screen, each task now allows you click on a new "View YAML" button. It's kinda Ronseal:

Using this hotness you can build yourself a .vsts-ci.yml file task by task.

A Bump in the Road#

If you look closely at the message above you'll see there's a message about an undefined variable.

#Your build definition references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build definition for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972
steps:
displayName: Restore
inputs:
command: restore
projects: '$(Parameters.RestoreBuildProjects)'

Try as I might, I couldn't locate Parameters.RestoreBuildProjects. So no working CI build for me. Then I remembered Zoltan Erdos. He's hard to forget. Or rather, I remembered an idea of his which I will summarise thusly: "Have a package.json in the root of your repo, use the scripts for individual tasks and you have a cross platform task runner".

This is a powerful idea and one I decided to put to work. My project is React and TypeScript on the front end, and ASP.Net Core on the back. I wanted a package.json in the root of the repo which I could install dependencies, build, test and publish my whole app. I could call into that from my .vsts-ci.yml file. Something like this:

{
"name": "my-amazing-project",
"version": "1.0.0",
"author": "John Reilly <[email protected]>",
"license": "MIT",
"private": true,
"scripts": {
"preinstall": "yarn run install:clientapp && yarn run install:web",
"install:clientapp": "cd MyAmazingProject.ClientApp && yarn install",
"install:web": "dotnet restore",
"prebuild": "yarn install",
"build": "yarn run build:clientapp && yarn run build:web",
"build:clientapp": "cd MyAmazingProject.ClientApp && yarn run build",
"build:web": "dotnet build --configuration Release",
"postbuild": "yarn test",
"test": "yarn run test:clientapp && yarn run test:web",
"test:clientapp": "cd MyAmazingProject.ClientApp && yarn test",
"test:web": "cd MyAmazingProject.Web.Tests && dotnet test",
"publish:web": "cd MyAmazingProject.Web && dotnet publish MyAmazingProject.Web.csproj --configuration Release"
}
}

It doesn't matter if I have "an undefined variable named ‘Parameters.RestoreBuildProjects’". I now have no need to use all the individual tasks in a build. I can convert them into a couple of scripts in my package.json. So here's where I've ended up for now. I've a .vsts-ci.yml file which looks like this:

queue: Hosted VS2017
steps:
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-[email protected]
displayName: install yarn itself
inputs:
checkLatest: true
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-[email protected]
displayName: yarn build and test
inputs:
Arguments: build
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-[email protected]
displayName: yarn publish:web
inputs:
Arguments: 'run publish:web --output $(build.artifactstagingdirectory)/MyAmazingProject'
displayName: publish build artifact
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'

This file does the following:

  1. Installs yarn. (By the way VSTS, what's with not having yarn installed by default? I'll say this for the avoidance of doubt: in the npm cli space: yarn has won.)
  2. Install our dependencies, build the front end and back end, run all the tests. Effectively yarn build.
  3. Publish our web app to a directory. Effectively yarn run publish:web. This is only separate because we want to pass in the output directory and so it's just easier for it to be a separate step.
  4. Publish the build artefact to TFS. (This will go on to be picked up by the continuous deployment mechanism and published out to Azure.)

I much prefer this to what I had before. I feel there's much more that can be done here as well. I'm looking forward to the continuous deployment piece becoming scriptable too.

Thanks to Zoltan and props to the TFVS team!

Setting Build Version Using AppVeyor and ASP.Net Core

AppVeyor has support for setting the version of a binary during a build. However - this deals with the classic ASP.Net world of AssemblyInfo. I didn't find any reference to support for doing the same with dot net core. Remember, dot net core relies upon a &lt;Version&gt; or a &lt;VersionPrefix&gt; setting in the .csproj file. Personally, &lt;Version&gt; is my jam.

However, coming up with your own bit of powershell that stamps the version during the build is a doddle; here we go:

Param($projectFile, $buildNum)
$content = [IO.File]::ReadAllText($projectFile)
$regex = new-object System.Text.RegularExpressions.Regex ('(<version>)([\d]+.[\d]+.[\d]+)(.[\d]+)(<\/Version>)',
[System.Text.RegularExpressions.RegexOptions]::MultiLine)
$version = $null
$match = $regex.Match($content)
if($match.Success) {
# from "<version>1.0.0.0</version>" this will extract "1.0.0"
$version = $match.groups[2].value
}
# suffix build number onto $version. eg "1.0.0.15"
$version = "$version.$buildNum"
# update "<version>1.0.0.0</version>" to "<version>$version</version>"
$content = $regex.Replace($content, '${1}' + $version + '${4}')
# update csproj file
[IO.File]::WriteAllText($projectFile, $content)
# update AppVeyor build
Update-AppveyorBuild -Version $version
</version>

You can invoke this script as part of the build process in AppVeyor by adding something like this to your appveyor.yml.

before_build:
- ps: .\ModifyVersion.ps1 $env:APPVEYOR_BUILD_FOLDER\src\Proverb.Web\Proverb.Web.csproj $env:APPVEYOR_BUILD_NUMBER

It will keep the first 3 parts of the version in your .csproj (eg "1.0.0") and suffix on the build number supplied by AppVeyor.

The Convent with Continuous Delivery

I've done it. I've open sourced the website that I maintain for my aunt what is a nun. Because I think we can all agree that nuns need open source and continuous integration about as much as anyone else.

For a long time now I've been maintaining a website for one of my (many) aunts that is a Poor Clare. (That's a subtype of "nun" you OO enthusiasts.) It's not a terribly exciting site - it's mostly static content. It's built with a combination of AngularJS / TypeScript / Bootstrap and ASP.Net MVC. It's hosted on Azure Websites. In fact I have written about it (slightly more cagily) before here.

I'll say up front: presentation-wise the site is not a work of art. However the nuns seem pretty happy with it. (Or perhaps secretly they're forgiving me the shonkiness and sparing my feelings - who can say?) If I put my mind to it the site could look much more lovely. But there's only so much time I can spare - and that's actually one of the reasons I've set up Continuous Delivery.

Why on earth did you bother?#

Well, you'd be surprised how often tweaks can be requested. Sometimes it appears to be forgotten for months at a time, and then all of a sudden my inbox is daily filled with a list of minor alterations. You know, slight text changes and the like.

So what I was generally doing was getting home of an evening, waiting until the children were in bed, chomping down some food and then firing up Visual Studio to make the changes and hit "Publish". Yes that's right; I was essentially using Visual Studio to edit text files and push a website out to Azure. The very definition of using a sledgehammer to crack a nut I think we can all agree.

It occurred to me that if I had Continuous Delivery set up then I could make these tweaks and not have to worry about the site being published. Which would be nice. I wouldn't need Visual Studio anymore - any text editor would do. Also nice. Finally, if the source control was accessible online then I could probably get away with doing most tweaks on my mobile phone whilst I was travelling home. Timesaver!

How did you go about it?#

Since Visual Studio Online (then "Team Foundation Service") was released I have been using it to host the source code. So the obvious solution was to use the tools offered there to do the deployment. However, this wasn't the smooth experience you might have hoped for. I had quite a frustrating afternoon trying things out before deciding it was becoming more trouble than it was worth. VSO appeared to make it supremely hard to customise builds.

Just recently though I have been having the most wonderful experience with AppVeyor. AppVeyor market themselves as "#1 Continuous Delivery service for Windows" - I think they're right. Their build process is entirely flexible and customisable. It is, in short, a joy to use. (The support is fantastic too - very helpful indeed. Go Feodor!)

If you look just below the header you'll read a very important sentence: "Free for open-source projects". You hear that? By the time I'd finished reading that sentence I'd decided that the Poor Clares website was about to become an open source project.

And now it is.

Where is it?#

The source on GitHub. The builds and deployment are taken care of by AppVeyor.

Will you take pull requests?#

If they're serious, then yes, certainly! My long term plan is to try and get the nuns set up as collaborators in GitHub. That way they can make their own minor tweaks without me getting involved.

On another front, I do wonder if open-sourcing Poor Clares, Arundel might have other hidden benefits. There's a number of things I'm not too keen on in the code. Up until now I think my attitude was possibly "it works so that's good enough". It was only me aware of the shortcomings. Now it's public I'll probably have more of an incentive to tidy up the rough edges. That's the theory anyway - Embarrassment Driven Development anyone? :-)

Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 2

"Automation, automation, automation." Those were and are Tony Blair's priorities for keeping open source projects well maintained.

OK, that's not quite true... But what is certainly true is that maintaining an open source project takes time. And there's only so much free time that anyone has. For that reason, wherever you can it makes sense to AUTOMATE!

Last time we looked at how you can take an essentially static ASP.Net MVC site (in this case my jVUNDemo documentation site) and generate an entirely static version using Wget. This static site has been pushed to GitHub Pages and is serving as the documentation for jQuery Validation Unobtrusive Native (and for bonus points is costing me no money at all).

So what next? Well, automation clearly! If I make a change to jQuery Validation Unobtrusive Native then AppVeyor already bounds in and performs a continuous integration build for me. It picks up the latest source from GitHub, pulls in my dependencies, performs a build and runs my tests. Lovely.

So the obvious thing to do is to take this process and plug in the generation of my static site and the publication thereof to GitHub pages. The minute a change is made to my project the documentation should be updated without me having to break sweat. That's the goal.

GitHub Personal Access Token#

In order to complete our chosen mission we're going to need a GitHub Personal Access Token. We're going to use it when we clone, update and push our GitHub Pages branch. To get one we biff over to Settings / Applications in GitHub and click the "Generate New Token" button.

The token I'm using for my project has the following scopes selected:

appveyor.yml#

With our token in hand we turn our attention to AppVeyor build configuration. This is possible using a file called appveyor.yml stored in the root of your repo. You can also use the AppVeyor web UI to do this. However, for the purposes of ease of demonstration I'm using the file approach. The jQuery Validation Unobtrusive Native appveyor.yml looks like this:

#---------------------------------#
# general configuration #
#---------------------------------#
# version format
version: 1.0.{build}
#---------------------------------#
# environment configuration #
#---------------------------------#
# environment variables
environment:
GithubEmail: [email protected]
GithubUsername: johnnyreilly
GithubPersonalAccessToken:
secure: T4M/N+e/baksVoeWoYKPWIpfahOsiSFw/+Zc81VuThZmWEqmrRtgEHUyin0vCWhl
branches:
only:
- master
install:
- ps: choco install wget
build:
verbosity: minimal
after_test:
- ps: ./makeStatic.ps1 $env:APPVEYOR_BUILD_FOLDER
- ps: ./pushStatic.ps1 $env:APPVEYOR_BUILD_FOLDER $env:GithubEmail $env:GithubUsername $env:GithubPersonalAccessToken

There's a number of things you should notice from the yml file:

  • We create 3 environment variables: GithubEmail, GithubUsername and GithubPersonalAccessToken (more on this in a moment).
  • We only build the master branch.
  • We use Chocolatey to install Wget which is used by the makeStatic.ps1 Powershell script.
  • After the tests have completed we run 2 Powershell scripts. First <a href="https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/makeStatic.ps1">makeStatic.ps1</a> which builds the static version of our site. This is the exact same script we discussed in the previous post - we're just passing it the build folder this time (one of AppVeyor's environment variables). Second, we run <a href="https://github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native/blob/master/pushStatic.ps1">pushStatic.ps1</a> which publishes the static site to GitHub Pages.

We pass 4 arguments to pushStatic.ps1: the build folder, my email address, my username and my personal access token. For the sake of security the GithubPersonalAccessToken has been encrypted as indicated by the secure keyword. This is a capability available in AppVeyor here.

This allows me to mask my personal access token rather than have it available as free text for anyone to grab.

pushStatic.ps1#

Finally we can turn our attention to how our Powershell script pushStatic.ps1 goes about pushing our changes up to GitHub Pages:

param([string]$buildFolder, [string]$email, [string]$username, [string]$personalAccessToken)
Write-Host "- Set config settings...."
git config --global user.email $email
git config --global user.name $username
git config --global push.default matching
Write-Host "- Clone gh-pages branch...."
cd "$($buildFolder)\..\"
mkdir gh-pages
git clone --quiet --branch=gh-pages https://$($username):$($personalAccessToken)@github.com/johnnyreilly/jQuery.Validation.Unobtrusive.Native.git .\gh-pages\
cd gh-pages
git status
Write-Host "- Clean gh-pages folder...."
Get-ChildItem -Attributes !r | Remove-Item -Recurse -Force
Write-Host "- Copy contents of static-site folder into gh-pages folder...."
copy-item -path ..\static-site\* -Destination $pwd.Path -Recurse
git status
$thereAreChanges = git status | select-string -pattern "Changes not staged for commit:","Untracked files:" -simplematch
if ($thereAreChanges -ne $null) {
Write-host "- Committing changes to documentation..."
git add --all
git status
git commit -m "skip ci - static site regeneration"
git status
Write-Host "- Push it...."
git push --quiet
Write-Host "- Pushed it good!"
}
else {
write-host "- No changes to documentation to commit"
}

So what's happening here? Let's break it down:

  • Git is configured with the passed in username and email address.
  • A folder is created that sits alongside the build folder called "gh-pages".
  • We clone the "gh-pages" branch of jQuery Validation Unobtrusive Native into our "gh-pages" directory. You'll notice that we are using our GitHub personal access token in the clone URL itself.
  • We delete the contents of the "gh-pages" directory leaving it empty.
  • We copy across the contents of the "static-site" folder (created by makeStatic.ps1) into the "gh-pages".
  • We use git status to check if there are any changes. (This method is completely effective but a little crude to my mind - there's probably better approaches to this.... shout me in the comments if you have a suggestion.)
  • If we have no changes then we do nothing.
  • If we have changes then we stage them, commit them and push them to GitHub Pages. Then we sign off with an allusion to 80's East Coast hip-hop... 'Cos that's how we roll.

With this in place, any changes to the docs will be automatically published out to our "gh-pages" branch. Our documentation will always be up to date thanks to the goodness of AppVeyor's Continuous Integration service.

Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 1

There's a small open source project I'm responsible for called jQuery Validation Unobtrusive Native. (A catchy name is a must for any good open source project. Alas I'm not quite meeting my own exacting standards on this particular point... I should have gone with my gut and called it "Livingstone" instead. Too late now...)

The project itself is fairly simple in purpose. It's essentially a bridge between ASP.Net MVC's inbuilt support for driving validation from data attributes and jQuery Validation's native support for the same. It is, in the end, a collection of ASP.Net MVC HTML helper extensions. It is not massively complicated.

I believe it was Tony Blair that said "documentation, documentation, documentation" were his priorities for open source projects. Or maybe it was someone else... Anyway, the point stands. Documentation is supremely important if you want your project to be in any way useful to anyone other than yourself. A project, no matter how fantastic, which lacks decent documentation is a missed opportunity.

Anyway I'm happy to say that jQuery Validation Unobtrusive Native has documentation! And pretty good documentation at that. The documentation takes the form of the jVUNDemo project which is part of the jQuery Validation Unobtrusive Native repo. jVUNDemo is an ASP.Net MVC web application which is built on top of the jQuery Validation Unobtrusive Native helpers. It demonstrates the helpers in action and documents how you might go about using them. It looks a bit like this:

When I first put jVUNDemo together I hosted it on Azure so the world could see it in all it's finery. And that worked just fine. However, there's something you ought to know about me:

I'm quite cheap#

No really, I am. My attention was grabbed by the essentially "free" nature of GitHub Pages. I was immediately seized by the desire to somehow deploy jVUNDemo to GitHub Pages and roll around joyfully in all that lovely free hosting.

"But", I hear you cry, "you can't deploy an ASP.Net MVC application to Git Hub Pages... It only hosts static sites!" Quite right. Sort of. This is where I get to pull my ace of spades: jVUNDemo is not really an "app" so much as a static site. Yup, once the HTML that makes up each page is generated there isn't any app like business to do. It's just a collection of text and 1 screen demo's really.

That being the case, there's no reason why I shouldn't be able to make use of GitHub Pages. So that's what I decided to do. Whilst I was at it I also wanted to automate the deployment process. When I tweak jVUNDemo I don't want to have to manually push out a new version of jVUNDemo to wherever it's being hosted. No, I'm a developer so I'll automate it.

I've broken this up into 2 posts. This first one will cover how you generate a static site from an ASP.Net MVC site. The second will be about how to use AppVeyor to ensure that on each build your documentation is getting republished.

You Wget me?#

So, static site generation. There's a well known Unix utility called Wget which covers exactly that ground and so we're going to use it. It downloads and saves HTML, it recursively walks the links inside the site and grabs those pages too and it converts our routes so they are locally browsable ("Demo/Required" becomes "Demo/Required.html").

You can use Chocolatey to get a copy of Wget. We're also going to need IIS Express to host jVUNDemo whilst Wget converts it. Once jVUNDemo has been built successfully you should be be able to kick off the process like so:

cd C:\projects\jquery-validation-unobtrusive-native
.\makeStatic.ps1 $pwd.path

This invokes the makeStatic Powershell script in the root of the jQuery Validation Unobtrusive Native repo:

param([string]$buildFolder)
$jVUNDemo = "$($buildFolder)\jVUNDemo"
$staticSiteParentPath = (get-item $buildFolder).Parent.FullName
$staticSite = "static-site"
$staticSitePath = "$($staticSiteParentPath)\$($staticSite)"
$wgetLogPath = "$($staticSiteParentPath)\wget.log"
$port = 57612
$servedAt = "http://localhost:$($port)/"
write-host "jVUNDemo location: $jVUNDemo"
write-host "static site parent location: $staticSiteParentPath"
write-host "static site location: $staticSitePath"
write-host "wget log path: $wgetLogPath"
write-host "Spin up jVUNDemo site at $($servedAt)"
$process = Start-Process 'C:\Program Files (x86)\IIS Express\iisexpress.exe' -NoNewWindow -ArgumentList "/path:$($jVUNDemo) /port:$($port)"
write-host "Wait a moment for IIS to startup"
Start-sleep -s 15
if (Test-Path $staticSitePath) {
write-host "Removing $($staticSitePath)..."
Remove-Item -path $staticSitePath -Recurse -Force
}
write-host "Create static version of demo site here: $($staticSitePath)"
Push-Location $staticSiteParentPath
# 2>&1 used to combine stderr and stdout
wget.exe --recursive --convert-links -E --directory-prefix=$staticSite --no-host-directories $servedAt > $wgetLogPath 2>&1
write-host "lastExitCode: $($lastExitCode)"
cat $wgetLogPath
Pop-Location
write-host "Shut down jVUNDemo site"
stop-process -Name iisexpress
if (Test-Path $staticSitePath) {
write-host "Contents of $($staticSitePath)"
ls $staticSitePath
}

The above Powershell does the following:

  1. Starts up IIS Express serving jVUNDemo on http://localhost:57612/
  2. Waits 15 seconds for IIS Express to get itself together (probably a shorter wait time would be sufficient)
  3. Points Wget at jVUNDemo and bellows "go!!!!"
  4. Wget downloads and saves the static version of jVUNDemo to a directory called "static-site"
  5. Stops IIS Express
  6. Prints out the contents of the "static-site" directory

When run, it pumps something like this out:

jVUNDemo location: C:\projects\jquery-validation-unobtrusive-native\jVUNDemo
static site parent location: C:\projects
static site location: C:\projects\static-site
wget log path: C:\projects\wget.log
Spin up jVUNDemo site at http://localhost:57612/
Wait a moment for IIS to startup
Create static version of demo site here: C:\projects\static-site
wget.exe : --2014-12-29 07:49:56-- http://localhost:57612/
Resolving localhost...
127.0.0.1
Connecting to localhost|127.0.0.1|:57612... connected.
HTTP request sent, awaiting response...
200 OK
..... lots of HTTP requests.....
Downloaded: 30 files, 1.0M in 0.09s (10.8 MB/s)
Converting static-site/Demo/CreditCard.html... 34-0
Converting static-site/Demo/Number.html... 34-0
Converting static-site/Demo/Range.html... 34-0
Converting static-site/Demo/Email.html... 34-0
Converting static-site/AdvancedDemo/CustomValidation.html... 35-0
Converting static-site/Demo/Date.html... 36-0
Converting static-site/Home/License.html... 27-0
Converting static-site/index.html... 29-0
Converting static-site/AdvancedDemo/Dynamic.html... 35-0
Converting static-site/Demo/MaxLengthMinLength.html... 34-0
Converting static-site/Demo/Required.html... 34-0
Converting static-site/AdvancedDemo.html... 32-0
Converting static-site/Demo/Remote.html... 35-0
Converting static-site/Demo/EqualTo.html... 34-0
Converting static-site/AdvancedDemo/Globalize.html... 38-0
Converting static-site/Demo/Url.html... 34-0
Converting static-site/Demo.html... 37-0
Converting static-site/Home/GettingStarted.html... 29-0
Converting static-site/Home/Download.html... 27-0
Converting static-site/AdvancedDemo/Tooltip.html... 34-0
Converted 20 files in 0.03 seconds.
Shut down jVUNDemo site
Contents of C:\projects\static-site
Directory: C:\projects\static-site
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 12/29/2014 7:50 AM AdvancedDemo
d---- 12/29/2014 7:50 AM Content
d---- 12/29/2014 7:50 AM Demo
d---- 12/29/2014 7:50 AM Home
d---- 12/29/2014 7:50 AM Scripts
-a--- 12/29/2014 7:50 AM 5967 AdvancedDemo.html
-a--- 12/29/2014 7:50 AM 6802 Demo.html
-a--- 12/29/2014 7:47 AM 12862 favicon.ico
-a--- 12/29/2014 7:50 AM 8069 index.html

And that's it for part 1 my friends! You now have a static version of the ASP.Net MVC site to dazzle the world with. I should say for the purposes of full disclosure that there are 2 pages in the site which are not entirely "static" friendly. For these 2 pages I've put messages in that are displayed when the page is served up in a static format explaining the limitations. Their full glory can still be experienced by cloning the project and running locally.

Next time we'll take the mechanism detailed above and plug it into AppVeyor for some Continuous Integration happiness.

Running JavaScript Unit Tests in AppVeyor

With a little help from Chutzpah...#

AppVeyor (if you're not aware of it) is a Continuous Integration provider. If you like, it's plug-and-play CI for .NET developers. It's lovely. And what's more it's "free for open-source projects with public repositories hosted on GitHub and BitBucket". Boom! I recently hooked up 2 of my GitHub projects with AppVeyor. It took me all of... 10 minutes. If that? It really is *that* good.

But.... There had to be a "but" otherwise I wouldn't have been writing the post you're reading. For a little side project of mine called Proverb there were C# unit tests and there were JavaScript unit tests. And the JavaScript unit tests weren't being run... No fair!!!

Chutzpah is a JavaScript test runner which at this point runs QUnit, Jasmine and Mocha JavaScript tests. I use the Visual Studio extension to run Jasmine tests on my machine during development. I've also been able to use Chutzpah for CI purposes with Visual Studio Online / Team Foundation Server. So what say we try and do the triple and make it work with AppVeyor too?

NuGet me?#

In order that I could run Chutzpah I needed Chutzpah to be installed on the build machine. So I had 2 choices:

  1. Add Chutzpah direct to the repo
  2. Add the Chutzpah Nuget package to the solution

Unsurprisingly I chose #2 - much cleaner.

Now to use Chutzpah#

Time to dust down the PowerShell. I created myself a "before tests script" and added it to my build. It looked a little something like this:

# Locate Chutzpah
$ChutzpahDir = get-childitem chutzpah.console.exe -recurse | select-object -first 1 | select -expand Directory
# Run tests using Chutzpah and export results as JUnit format to chutzpah-results.xml
$ChutzpahCmd = "$($ChutzpahDir)\chutzpah.console.exe $($env:APPVEYOR_BUILD_FOLDER)\AngularTypeScript\Proverb.Web.Tests.JavaScript /junit .\chutzpah-results.xml"
Write-Host $ChutzpahCmd
Invoke-Expression $ChutzpahCmd
# Upload results to AppVeyor one by one
$testsuites = [xml](get-content .\chutzpah-results.xml)
$anyFailures = $FALSE
foreach ($testsuite in $testsuites.testsuites.testsuite) {
write-host " $($testsuite.name)"
foreach ($testcase in $testsuite.testcase){
$failed = $testcase.failure
$time = $testsuite.time
if ($testcase.time) { $time = $testcase.time }
if ($failed) {
write-host "Failed $($testcase.name) $($testcase.failure.message)"
Add-AppveyorTest $testcase.name -Outcome Failed -FileName $testsuite.name -ErrorMessage $testcase.failure.message -Duration $time
$anyFailures = $TRUE
}
else {
write-host "Passed $($testcase.name)"
Add-AppveyorTest $testcase.name -Outcome Passed -FileName $testsuite.name -Duration $time
}
}
}
if ($anyFailures -eq $TRUE){
write-host "Failing build as there are broken tests"
$host.SetShouldExit(1)
}

What this does is:

  1. Run Chutzpah from the installed NuGet package location, passing in the location of my Jasmine unit tests. In the case of my project there is a chutzpah.json file in the project which dictates how Chutzpah should run the tests. Also, the JUnit flag is also passed in order that Chutzpah creates a chutzpah-results.xml file of test results in the JUnit format.
  2. We iterate through test results and tell AppVeyor about the the test passes and failures using the Build Worker API.
  3. If there have been any failed tests then we fail the build. If you look here you can see a deliberately failed build which demo's that this works as it should.

That's a wrap - We now have CI which includes our JavaScript tests! That's right we get to see beautiful screens like these:

Thanks to...#

Thanks to Dan Jones, whose comments on this discussion provided a number of useful pointers which moved me in the right direction. And thanks to Feador Fitzner who has generously said AppVeyor will support JUnit in the future which may simplify use of Chutzpah with AppVeyor even further.