Skip to content

Commit

Permalink
Merge pull request #4 from Microsoft/users/davidstaheli/improveXamari…
Browse files Browse the repository at this point in the history
…nTasks

Xplat support for Xamarin Test Cloud (and other Xamarin improvements)
  • Loading branch information
bryanmacfarlane committed Apr 7, 2015
2 parents 5c12677 + 34d6600 commit c135d87
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Tasks/XamarinAndroid/task.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "27EDD013-36FD-43AA-96A3-7D73E1E35285",
"name": "XamarinAndroid",
"friendlyName": "Xamarin Android",
"friendlyName": "Xamarin.Android",
"description": "Build an Android app with Xamarin",
"category": "Build",
"author": "Microsoft Corporation",
Expand Down
170 changes: 170 additions & 0 deletions Tasks/XamarinTestCloud/XamarinTestCloud.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
var fs = require('fs');
var glob = require('glob');
var path = require('path');
var shell = require('shelljs');
var tl = require('vso-task-lib');

// Get inputs
var app = tl.getInput('app', true);
var dsym = tl.getInput('dsym', false);
var teamApiKey = tl.getInput('teamApiKey', true);
var user = tl.getInput('user', true);
var devices = tl.getInput('devices', true);
var series = tl.getInput('series', true);
var testDir = tl.getInput('testDir', true);
var parallelization = tl.getInput('parallelization', true);
var locale = tl.getInput('locale', true);
var testCloudLocation = tl.getInput('testCloudLocation', true);
var optionalArgs = tl.getInput('optionalArgs', false);

// Output debug information for inputs
tl.debug('app: ' + app);
tl.debug('dsym: ' + dsym);
tl.debug('teamApiKey: ' + teamApiKey);
tl.debug('user: ' + user);
tl.debug('devices: ' + devices);
tl.debug('series: ' + series);
tl.debug('testDir: ' + testDir);
tl.debug('parallelization: ' + parallelization);
tl.debug('locale: ' + locale);
tl.debug('testCloudLocation: ' + testCloudLocation);
tl.debug('optionalArgs: ' + optionalArgs);

// Define error handler
var onError = function (errorMsg) {
tl.error(errorMsg);
tl.exit(1);
}

// Resolve apps for the specified value or pattern
if (app.indexOf('*') == -1 && app.indexOf('?') == -1) {
// Check literal path to a single app file
if (!fs.existsSync(app)) {
onError('The specified app file does not exist: ' + app);
}

// Use the single specified app file
var appFiles = [app];
}
else {
// Find app files matching the specified pattern
tl.debug('Invoking glob with path: ' + app);
var appFiles = glob.sync(app);

// Fail if no matching app files were found
if (!appFiles || appFiles.length == 0) {
onError('No matching app files were found with search pattern: ' + app);
}
}

// Check and add parameter for test assembly directory
if (!shell.test('-d', testDir)) {
onError('The test assembly directory does not exist: ' + testDir);
}

// Ensure that $testCloudLocation specifies test-cloud.exe (case-sensitive)
if (path.basename(testCloudLocation) != 'test-cloud.exe') {
throw "test-cloud.exe location must end with '\\test-cloud.exe'."
}

// Locate test-cloud.exe (part of the Xamarin.UITest NuGet package)
if (testCloudLocation.indexOf('*') == -1 && testCloudLocation.indexOf('?') == -1) {
// Check literal path to test-cloud.exe
if (!fs.existsSync(testCloudLocation)) {
onError('test-cloud.exe does not exist at the specified location: ' + testCloudLocation);
}

// Use literal path to test-cloud.exe
var testCloud = testCloudLocation;
}
else {
// Find test-cloud.exe under the specified directory pattern
tl.debug('Invoking glob with path: ' + testCloudLocation);
var testCloudExecutables = glob.sync(testCloudLocation);

// Fail if not found
if (!testCloudExecutables || testCloudExecutables.length == 0) {
onError('test-cloud.exe could not be found with search pattern ' + testCloudLocation);
}

// Use first found path to test-cloud.exe
var testCloud = testCloudExecutables[0];
}

// Find location of mono
var monoPath = tl.which('mono');
if (!monoPath) {
onError('mono was not found in the path.');
}

// Invoke test-cloud.exe for each app file
var appFileIndex = 0;
var onFailedExecution = function (err) {
// Error executing
tl.debug('ToolRunner execution failure: ' + err);
tl.exit(1);
}
var onSuccessfulExecution = function (code) {
// Executed successfully
appFileIndex++;

if (appFileIndex >= appFiles.length) {
tl.exit(0); // Done submitting all app files
}

// Submit next app file
submitToTestCloud(appFileIndex);
}
var submitToTestCloud = function (index) {
// Form basic arguments
var mdtoolRunner = new tl.ToolRunner(monoPath);
mdtoolRunner.arg(testCloud);
mdtoolRunner.arg('submit');
mdtoolRunner.arg(appFiles[index]);
mdtoolRunner.arg(teamApiKey);
mdtoolRunner.arg('--user');
mdtoolRunner.arg(user);
mdtoolRunner.arg('--devices');
mdtoolRunner.arg(devices);
mdtoolRunner.arg('--series');
mdtoolRunner.arg('"' + series + '"');
mdtoolRunner.arg('--locale');
mdtoolRunner.arg(locale);
mdtoolRunner.arg('--assembly-dir');
mdtoolRunner.arg(testDir);
if (parallelization != 'none') {
mdtoolRunner.arg(parallelization);
}
if (optionalArgs) {
mdtoolRunner.arg(optionalArgs.split(' '));
}

// For an iOS .ipa app, look for an accompanying dSYM file
if (dsym && path.extname(appFiles[index]) == '.ipa') {
// Find dSYM files matching the specified pattern
var dsymPath = path.join(path.dirname(appFiles[index]), dsym);
tl.debug('Invoking glob with path: ' + dsymPath);
var dsymFiles = glob.sync(dsymPath);

// Fail if no matching dSYM files were found in path
if (!dsymFiles || dsymFiles.length == 0) {
onError('No matching dSYM files were found with pattern: ' + dsymPath);
}

// Fail if more than one dSYM file was found in path
if (dsymFiles.length > 1) {
onError('More than one matching dSYM file was found with pattern: ' + dsymPath);
}

// Include dSYM file in Test Cloud arguments
mdtoolRunner.arg('--dsym');
mdtoolRunner.arg(dsymFiles[0]);
}

// Submit to Test Cloud
tl.debug('Submitting to Xamarin Test Cloud: ' + appFiles[index]);
mdtoolRunner.exec()
.then(onSuccessfulExecution)
.fail(onFailedExecution)
}
submitToTestCloud(appFileIndex);
10 changes: 7 additions & 3 deletions Tasks/XamarinTestCloud/XamarinTestCloud.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
[string]$devices,
[string]$series,
[string]$locale,
[string]$appName,
[string]$testCloudLocation,
[string]$parallelization,
[string]$optionalArgs
Expand All @@ -20,7 +19,6 @@ Write-Verbose "user = $user"
Write-Verbose "devices = $devices"
Write-Verbose "series = $series"
Write-Verbose "locale = $locale"
Write-Verbose "appName = $locale"
Write-Verbose "testCloudLocation = $testCloudLocation"
Write-Verbose "parallelization = $parallelization"
Write-Verbose "optionalArgs = $optionalArgs"
Expand Down Expand Up @@ -69,7 +67,7 @@ if ($app.Contains("*") -or $app.Contains("?"))
}
else
{
Write-Verbose "No Pattern found in app parameter."
Write-Verbose "No pattern found in app input."
$appFiles = ,$app
}

Expand All @@ -90,6 +88,12 @@ if ("none" -ne $parallelization)
$parameters = "$parameters $parallelization"
}

# Ensure that $testCloudLocation specifies test-cloud.exe
if (!$testCloudLocation.EndsWith("\\test-cloud.exe", "OrdinalIgnoreCase")
{
throw "test-cloud.exe location must end with '\\test-cloud.exe'."
}

# locate the test-cloud tool, it is part of the Xamarin.UITest NuGet package
if ($testCloudLocation.Contains("*") -or $testCloudLocation.Contains("?"))
{
Expand Down
34 changes: 19 additions & 15 deletions Tasks/XamarinTestCloud/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,34 @@
"version": {
"Major": 1,
"Minor": 0,
"Patch": 1
"Patch": 2
},
"demands": [
],
"groups": [
{
"name": "advanced",
"displayName": "Advanced",
"isExpanded": true
"isExpanded": true
}
],
"inputs": [
{
"name": "app",
"type": "filePath",
"label": "App",
"label": "App File",
"defaultValue": "",
"required": true,
"helpMarkDown": "Relative path from repo root of the app(s) to test. Wildcards can be used. For example, `**\\*.apk` for all APK files in all subfolders."
},
{
"name": "dsym",
"type": "string",
"label": "dSYM File (iOS only)",
"required": false,
"defaultValue": "",
"helpMarkDown": "To make crash logs easier to read, you can upload a dSYM file that is associated with your app. This field only applies to iOS apps. Example: *.dSYM"
},
{
"name": "teamApiKey",
"type": "string",
Expand Down Expand Up @@ -58,23 +66,15 @@
"label": "Series",
"defaultValue": "master",
"required": true,
"helpMarkDown": "Organize test runs by series (e.g. master, production, beta)."
"helpMarkDown": "The series name for organizing test runs (e.g. master, production, beta)."
},
{
"name": "testDir",
"type": "filePath",
"label": "Test Assembly Directory",
"defaultValue": "",
"required": true,
"helpMarkDown": "Relative path to the folder that contains the test assemblies."
},
{
"name": "appName",
"type": "string",
"label": "App Name",
"defaultValue": "",
"required": false,
"groupName": "advanced"
"helpMarkDown": "Relative path to the folder containing the test assemblies, such as: SolutionName/TestsProjectName/bin/Release"
},
{
"name": "parallelization",
Expand Down Expand Up @@ -109,7 +109,7 @@
},
{
"name": "testCloudLocation",
"type": "string",
"type": "filePath",
"label": "test-cloud.exe Location",
"groupName": "advanced",
"defaultValue": "$(Agent.BuildDirectory)\\**\\packages\\**\\tools\\test-cloud.exe",
Expand All @@ -122,11 +122,15 @@
"required": false,
"defaultValue": "",
"groupName": "advanced",
"helpMarkDown": "Additional arguments passed to the test-cloud tool."
"helpMarkDown": "Additional arguments passed to test-cloud.exe."
}
],
"instanceNameFormat": "Test $(app) with Xamarin.UITest in Xamarin Test Cloud",
"execution": {
"Node": {
"target": "XamarinTestCloud.js",
"argumentFormat": ""
},
"PowerShell": {
"target": "$(currentDirectory)\\XamarinTestCloud.ps1",
"argumentFormat": "",
Expand Down
4 changes: 2 additions & 2 deletions Tasks/XamariniOS/task.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"id": "0F077E3A-AF59-496D-81BC-AD971B7464E0",
"name": "XamariniOS",
"friendlyName": "Xamarin iOS",
"friendlyName": "Xamarin.iOS",
"description": "Build an iOS app with Xamarin on Mac OS",
"category": "Build",
"author": "Microsoft Corporation",
"version": {
"Major": 1,
"Minor": 0,
"Patch": 1
"Patch": 2
},
"demands": [
"Xamarin.iOS"
Expand Down
47 changes: 33 additions & 14 deletions Tasks/XamariniOS/xamariniOS.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,40 @@ if (!fs.existsSync(mdtoolPath)) {
tl.exit(1);
}

// Prepare build command line
var mdtoolRunner = new tl.ToolRunner(mdtoolPath);
mdtoolRunner.arg('--verbose');
mdtoolRunner.arg('build');
mdtoolRunner.arg('--configuration:' + configuration + '|' + device);
mdtoolRunner.arg(solutionPath);

// Execute build
mdtoolRunner.exec()
.then(function (code) {
// Executed successfully
tl.exit(code);
})
.fail(function (err) {
// Find location of nuget
var nugetPath = tl.which('nuget');
if (!nugetPath) {
tl.error('nuget was not found in the path.');
tl.exit(1);
}

// Prepare function for tool execution failure
var onFailedExecution = function (err) {
// Error executing
tl.debug('ToolRunner execution failure: ' + err);
tl.exit(1);
}

// Restore NuGet packages of the solution
var nugetRunner = new tl.ToolRunner(nugetPath);
nugetRunner.arg('restore');
nugetRunner.arg(solutionPath);
nugetRunner.exec()
.then(function (code) {

// Prepare build command line
var mdtoolRunner = new tl.ToolRunner(mdtoolPath);
mdtoolRunner.arg('--verbose');
mdtoolRunner.arg('build');
mdtoolRunner.arg('--configuration:' + configuration + '|' + device);
mdtoolRunner.arg(solutionPath);

// Execute build
mdtoolRunner.exec()
.then(function (code) {
// Executed successfully
tl.exit(code);
})
.fail(onFailedExecution)
})
.fail(onFailedExecution)

0 comments on commit c135d87

Please sign in to comment.