// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Mike Two (2@thoughtworks.com or mike2@nunit.org)
// Tomas Restrepo (tomasr@mvps.org)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using NUnit.Core;
using TestCase = NUnit.Core.TestCase;
using TestOutput = NUnit.Core.TestOutput;
using NUnit.Framework;
using NUnit.Util;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Util;
using NAnt.NUnit.Types;
using NAnt.NUnit2.Types;
namespace NAnt.NUnit2.Tasks {
///
/// Runs tests using the NUnit V2.2 framework.
///
///
///
/// The attribute is only useful when more
/// than one test suite is used, and you want to continue running other
/// test suites although a test failed.
///
///
/// Set to to
/// ignore any errors and continue the build.
///
///
/// In order to run a test assembly built with NUnit 2.0 or 2.1 using
/// , you must add the following node to your
/// test config file :
///
///
///
/// ...
///
///
///
///
///
///
///
///
///
/// ...
///
/// ]]>
///
///
/// See the NUnit home page for more
/// information.
///
///
///
///
/// Run tests in the MyProject.Tests.dll assembly.
///
///
///
///
///
///
/// ]]>
///
///
///
///
/// Only run tests that are not known to fail in files listed in the tests.txt
/// file.
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("nunit2")]
public class NUnit2Task : Task {
#region Private Instance Fields
private bool _haltOnFailure = false;
private NUnit2TestCollection _tests = new NUnit2TestCollection();
private FormatterElementCollection _formatterElements = new FormatterElementCollection();
#endregion Private Instance Fields
#region Public Instance Properties
///
/// Stop the test run if a test fails. The default is .
///
[TaskAttribute("haltonfailure")]
[BooleanValidator()]
public bool HaltOnFailure {
get { return _haltOnFailure; }
set { _haltOnFailure = value; }
}
///
/// Tests to run.
///
[BuildElementArray("test")]
public NUnit2TestCollection Tests {
get { return _tests; }
}
///
/// Formatters to output results of unit tests.
///
[BuildElementArray("formatter")]
public FormatterElementCollection FormatterElements {
get { return _formatterElements; }
}
#endregion Public Instance Properties
#region Override implementation of Task
///
/// Runs the tests and sets up the formatters.
///
protected override void ExecuteTask() {
if (FormatterElements.Count == 0) {
FormatterElement defaultFormatter = new FormatterElement();
defaultFormatter.Project = Project;
defaultFormatter.NamespaceManager = NamespaceManager;
defaultFormatter.Type = FormatterType.Plain;
defaultFormatter.UseFile = false;
FormatterElements.Add(defaultFormatter);
Log(Level.Warning, "No element was specified." +
" A plain-text formatter was added to prevent losing output of the" +
" test results.");
Log(Level.Warning, "Add a element to the" +
" task to prevent this warning from being output and" +
" to ensure forward compatibility with future revisions of NAnt.");
}
LogWriter logWriter = new LogWriter(this, Level.Info, CultureInfo.InvariantCulture);
EventListener listener = new EventCollector(logWriter, logWriter);
foreach (NUnit2Test testElement in Tests) {
IFilter categoryFilter = null;
// include or exclude specific categories
string categories = testElement.Categories.Includes.ToString();
if (!StringUtils.IsNullOrEmpty(categories)) {
categoryFilter = new CategoryFilter(categories.Split(','), false);
} else {
categories = testElement.Categories.Excludes.ToString();
if (!StringUtils.IsNullOrEmpty(categories)) {
categoryFilter = new CategoryFilter(categories.Split(','), true);
}
}
foreach (string testAssembly in testElement.TestAssemblies) {
NUnit2TestDomain domain = new NUnit2TestDomain();
try {
TestRunner runner = domain.CreateRunner(new FileInfo(testAssembly), testElement.AppConfigFile);
Test test = null;
if (testElement.TestName != null) {
test = runner.Load(testAssembly, testElement.TestName);
} else {
test = runner.Load(testAssembly);
}
if (test == null) {
Log(Level.Warning, "Assembly \"{0}\" contains no tests.",
testAssembly);
continue;
}
// set category filter
if (categoryFilter != null) {
runner.Filter = categoryFilter;
}
// run test
TestResult result = runner.Run(listener);
// flush test output to log
logWriter.Flush();
// format test results using specified formatters
FormatResult(testElement, result);
if (result.IsFailure && (testElement.HaltOnFailure || HaltOnFailure)) {
throw new BuildException("Tests Failed.", Location);
}
} catch (BuildException) {
// re-throw build exceptions
throw;
} catch (Exception ex) {
if (!FailOnError) {
// just log error and continue with next test
Log(Level.Error, LogPrefix + "NUnit Error: " + ex.ToString());
continue;
}
Version nunitVersion = typeof(TestResult).Assembly.GetName().Version;
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Failure executing test(s). If you assembly is not built using"
+ " NUnit version {0}, then ensure you have redirected assembly"
+ " bindings. Consult the documentation of the task"
+ " for more information.", nunitVersion), Location, ex);
} finally {
domain.Unload();
// flush test output to log
logWriter.Flush();
}
}
}
}
#endregion Override implementation of Task
#region Private Instance Methods
private void FormatResult(NUnit2Test testElement, TestResult result) {
// temp file for storing test results
string xmlResultFile = Path.GetTempFileName();
// permanent file for storing test results
string outputFile = null;
try {
XmlResultVisitor resultVisitor = new XmlResultVisitor(xmlResultFile, result);
result.Accept(resultVisitor);
resultVisitor.Write();
foreach (FormatterElement formatter in FormatterElements) {
if (formatter.Type == FormatterType.Xml) {
if (formatter.UseFile) {
// determine file name for output file
outputFile = result.Name + "-results" + formatter.Extension;
if (formatter.OutputDirectory != null) {
// ensure output directory exists
if (!formatter.OutputDirectory.Exists) {
formatter.OutputDirectory.Create();
}
// combine output directory and result filename
outputFile = Path.Combine(formatter.OutputDirectory.FullName,
Path.GetFileName(outputFile));
}
// copy the temp result file to permanent location
File.Copy(xmlResultFile, outputFile, true);
} else {
using (StreamReader reader = new StreamReader(xmlResultFile)) {
// strip off the xml header
reader.ReadLine();
StringBuilder builder = new StringBuilder();
while (reader.Peek() > -1) {
builder.Append(reader.ReadLine().Trim()).Append(
Environment.NewLine);
}
Log(Level.Info, builder.ToString());
}
}
} else if (formatter.Type == FormatterType.Plain) {
TextWriter writer;
if (formatter.UseFile) {
// determine file name for output file
outputFile = result.Name + "-results" + formatter.Extension;
if (formatter.OutputDirectory != null) {
// ensure output directory exists
if (!formatter.OutputDirectory.Exists) {
formatter.OutputDirectory.Create();
}
// combine output directory and result filename
outputFile = Path.Combine(formatter.OutputDirectory.FullName,
Path.GetFileName(outputFile));
}
writer = new StreamWriter(outputFile);
} else {
writer = new LogWriter(this, Level.Info, CultureInfo.InvariantCulture);
}
CreateSummaryDocument(xmlResultFile, writer, testElement);
writer.Close();
}
}
} catch (Exception ex) {
throw new BuildException("Test results could not be"
+ " formatted.", Location, ex);
} finally {
// make sure temp file with test results is removed
File.Delete(xmlResultFile);
}
}
private void CreateSummaryDocument(string resultFile, TextWriter writer, NUnit2Test test) {
XPathDocument originalXPathDocument = new XPathDocument(resultFile);
XslTransform summaryXslTransform = new XslTransform();
XmlTextReader transformReader = GetTransformReader(test);
summaryXslTransform.Load(transformReader);
summaryXslTransform.Transform(originalXPathDocument, null, writer);
}
private XmlTextReader GetTransformReader(NUnit2Test test) {
XmlTextReader transformReader;
if (test.XsltFile == null) {
Assembly assembly = Assembly.GetAssembly(typeof(XmlResultVisitor));
ResourceManager resourceManager = new ResourceManager("NUnit.Util.Transform", assembly);
string xmlData = (string) resourceManager.GetObject("Summary.xslt", CultureInfo.InvariantCulture);
transformReader = new XmlTextReader(new StringReader(xmlData));
} else {
if (!test.XsltFile.Exists) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Transform file '{0}' does not exist.", test.XsltFile.FullName),
Location);
}
transformReader = new XmlTextReader(test.XsltFile.FullName);
}
return transformReader;
}
#endregion Private Instance Methods
private class EventCollector : LongLivingMarshalByRefObject, EventListener {
private TextWriter outWriter;
private TextWriter errorWriter;
private string currentTestName;
public EventCollector(TextWriter outWriter, TextWriter errorWriter) {
this.outWriter = outWriter;
this.errorWriter = errorWriter;
this.currentTestName = string.Empty;
}
public void RunStarted(Test[] tests) {
}
public void RunFinished(TestResult[] results) {
}
public void RunFinished(Exception exception) {
}
public void TestFinished(TestCaseResult testResult) {
currentTestName = string.Empty;
}
public void TestStarted(TestCase testCase) {
currentTestName = testCase.FullName;
}
public void SuiteStarted(TestSuite suite) {
}
public void SuiteFinished(TestSuiteResult suiteResult) {
}
public void UnhandledException( Exception exception ) {
string msg = string.Format("##### Unhandled Exception while running {0}", currentTestName);
errorWriter.WriteLine(msg);
errorWriter.WriteLine(exception.ToString());
}
public void TestOutput(TestOutput output) {
switch (output.Type) {
case TestOutputType.Out:
outWriter.Write(output.Text);
break;
case TestOutputType.Error:
errorWriter.Write(output.Text);
break;
}
}
}
}
}