diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs index 9706138701..10a47ab395 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs @@ -7,7 +7,7 @@ using ICSharpCode.SharpDevelop.Util; namespace ICSharpCode.UnitTesting { - public interface IUnitTestProcessRunner + public interface IUnitTestProcessRunner : IDisposable { bool LogStandardOutputAndError { get; set; } string WorkingDirectory { get; set; } diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs index 3d60a93130..3467ecd5d8 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs +++ b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs @@ -54,5 +54,10 @@ namespace ICSharpCode.UnitTesting { runner.Kill(); } + + public void Dispose() + { + runner.Dispose(); + } } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs index 5ae1495847..e9b3a3fc19 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitConsoleApplication.cs @@ -141,9 +141,9 @@ namespace ICSharpCode.UnitTesting public string Test; /// - /// File to write test results to. + /// Pipe to write test results to. /// - public string Results; + public string ResultsPipe; /// /// The namespace that tests need to be a part of if they are to @@ -206,9 +206,9 @@ namespace ICSharpCode.UnitTesting b.Append(XmlOutputFile); b.Append('"'); } - if (Results != null) { - b.Append(" /results=\""); - b.Append(Results); + if (ResultsPipe != null) { + b.Append(" /pipe=\""); + b.Append(ResultsPipe); b.Append('"'); } string run = null; diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs index d0886ce178..f94a4e8667 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestDebugger.cs @@ -18,16 +18,16 @@ namespace ICSharpCode.UnitTesting public NUnitTestDebugger() : this(new UnitTestDebuggerService(), SD.MessageService, - new TestResultsMonitor(), + new TestResultsReader(), UnitTestingOptions.Instance.Clone()) { } public NUnitTestDebugger(IUnitTestDebuggerService debuggerService, IMessageService messageService, - ITestResultsMonitor testResultsMonitor, + ITestResultsReader testResultsReader, UnitTestingOptions options) - : base(debuggerService, messageService, testResultsMonitor) + : base(debuggerService, messageService, testResultsReader) { this.options = options; } @@ -35,7 +35,7 @@ namespace ICSharpCode.UnitTesting protected override ProcessStartInfo GetProcessStartInfo(IEnumerable selectedTests) { NUnitConsoleApplication app = new NUnitConsoleApplication(selectedTests, options); - app.Results = base.TestResultsMonitor.FileName; + app.ResultsPipe = base.TestResultsReader.PipeName; return app.GetProcessStartInfo(); } @@ -43,5 +43,10 @@ namespace ICSharpCode.UnitTesting { return new NUnitTestResult(testResult); } + + public override int GetExpectedNumberOfTestResults(IEnumerable selectedTests) + { + return NUnitTestRunner.GetNumberOfTestMethods(selectedTests); + } } } diff --git a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs index cecc5eb4e4..171de60d4e 100644 --- a/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs +++ b/src/AddIns/Analysis/UnitTesting/NUnit/NUnitTestRunner.cs @@ -30,7 +30,7 @@ namespace ICSharpCode.UnitTesting protected override ProcessStartInfo GetProcessStartInfo(IEnumerable selectedTests) { NUnitConsoleApplication app = new NUnitConsoleApplication(selectedTests, options); - app.Results = base.TestResultsMonitor.FileName; + app.ResultsPipe = base.TestResultsReader.PipeName; return app.GetProcessStartInfo(); } @@ -38,5 +38,22 @@ namespace ICSharpCode.UnitTesting { return new NUnitTestResult(testResult); } + + public override int GetExpectedNumberOfTestResults(IEnumerable selectedTests) + { + return GetNumberOfTestMethods(selectedTests); + } + + public static int GetNumberOfTestMethods(IEnumerable selectedTests) + { + int count = 0; + foreach (ITest test in selectedTests) { + if (test is NUnitTestMethod) + count++; + else + count += GetNumberOfTestMethods(test.NestedTests); + } + return count; + } } } diff --git a/src/AddIns/Analysis/UnitTesting/Test/NUnit/NUnitConsoleCommandLineTests.cs b/src/AddIns/Analysis/UnitTesting/Test/NUnit/NUnitConsoleCommandLineTests.cs index 9ea19fbeb0..b90bd4d3f9 100644 --- a/src/AddIns/Analysis/UnitTesting/Test/NUnit/NUnitConsoleCommandLineTests.cs +++ b/src/AddIns/Analysis/UnitTesting/Test/NUnit/NUnitConsoleCommandLineTests.cs @@ -38,9 +38,9 @@ namespace UnitTesting.Tests.NUnit app.NoLogo = false; app.ShadowCopy = true; app.NoXmlOutputFile = false; - app.Results = @"C:\results.txt"; + app.ResultsPipe = @"C:\results.txt"; - string expectedCommandLine = "\"C:\\Projects\\MyTests\\MyTests.dll\" /results=\"C:\\results.txt\""; + string expectedCommandLine = "\"C:\\Projects\\MyTests\\MyTests.dll\" /pipe=\"C:\\results.txt\""; Assert.AreEqual(expectedCommandLine, app.GetArguments()); } @@ -229,13 +229,13 @@ namespace UnitTesting.Tests.NUnit app.Assemblies.Add("SecondAssembly.dll"); app.NoLogo = false; app.ShadowCopy = true; - app.Results = @"C:\results.txt"; + app.ResultsPipe = @"C:\results.txt"; app.NoXmlOutputFile = false; string expectedCommandLine = "\"C:\\Projects\\MyTests\\MyTests.dll\" " + "\"SecondAssembly.dll\" " + - "/results=\"C:\\results.txt\""; + "/pipe=\"C:\\results.txt\""; Assert.AreEqual(expectedCommandLine, app.GetArguments()); } diff --git a/src/AddIns/Analysis/UnitTesting/Test/TestRunner/TestResultsReaderTests.cs b/src/AddIns/Analysis/UnitTesting/Test/TestRunner/TestResultsReaderTests.cs index ddb9204ea9..45ddb56468 100644 --- a/src/AddIns/Analysis/UnitTesting/Test/TestRunner/TestResultsReaderTests.cs +++ b/src/AddIns/Analysis/UnitTesting/Test/TestRunner/TestResultsReaderTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using ICSharpCode.UnitTesting; using NUnit.Framework; @@ -17,10 +18,12 @@ namespace UnitTesting.Tests.TestRunner string resultsText = "Name: MyTest\r\n" + "Result: Success\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("MyTest", result.Name); @@ -34,10 +37,12 @@ namespace UnitTesting.Tests.TestRunner string resultsText = "Name: MyTest\r\n" + "Result: Ignored\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("MyTest", result.Name); @@ -45,7 +50,7 @@ namespace UnitTesting.Tests.TestRunner Assert.IsFalse(result.IsSuccess); Assert.AreEqual(TestResultType.Ignored, result.ResultType); } - + /* [Test] public void OneTestPassInParts() { @@ -70,17 +75,19 @@ namespace UnitTesting.Tests.TestRunner Assert.AreEqual("MyTest", result.Name); Assert.IsTrue(result.IsSuccess); } - + */ [Test] public void OneTestFailure() { string resultsText = "Name: MyTest\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("MyTest", result.Name); @@ -97,10 +104,12 @@ namespace UnitTesting.Tests.TestRunner "Message: Should not be 0.\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("Test", result.Name); @@ -115,10 +124,12 @@ namespace UnitTesting.Tests.TestRunner "StackTrace: stack trace\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("Test", result.Name); @@ -131,10 +142,12 @@ namespace UnitTesting.Tests.TestRunner { string resultsText = "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(0, results.Length); + Assert.AreEqual(0, results.Count); } [Test] @@ -144,10 +157,12 @@ namespace UnitTesting.Tests.TestRunner "Name: Test\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("Test", result.Name); @@ -162,10 +177,12 @@ namespace UnitTesting.Tests.TestRunner " Should be 1.\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("Test", result.Name); @@ -182,10 +199,12 @@ namespace UnitTesting.Tests.TestRunner " End of message.\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(1, results.Length); + Assert.AreEqual(1, results.Count); TestResult result = results[0]; Assert.AreEqual("Test", result.Name); @@ -201,10 +220,12 @@ namespace UnitTesting.Tests.TestRunner "Name: MyTest2\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(2, results.Length); + Assert.AreEqual(2, results.Count); TestResult result1 = results[0]; Assert.AreEqual("MyTest1", result1.Name); @@ -228,10 +249,12 @@ namespace UnitTesting.Tests.TestRunner " ThirdLine\r\n" + "Result: Failure\r\n"; - TestResultsReader reader = new TestResultsReader(); - TestResult[] results = reader.Read(resultsText); + TestResultsReader reader = new TestResultsReader(new StringReader(resultsText)); + List results = new List(); + reader.TestFinished += (sender, e) => results.Add(e.Result); + reader.Run(); - Assert.AreEqual(2, results.Length); + Assert.AreEqual(2, results.Count); TestResult result1 = results[0]; Assert.AreEqual("MyTest1", result1.Name); diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/ITestResultsMonitor.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/ITestResultsMonitor.cs deleted file mode 100644 index 6a67c16e74..0000000000 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/ITestResultsMonitor.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; - -namespace ICSharpCode.UnitTesting -{ - public interface ITestResultsMonitor : IDisposable - { - event EventHandler TestFinished; - - string FileName { get; set; } - - void Stop(); - void Start(); - void Read(); - - long InitialFilePosition { get; set; } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs index 865aeabfeb..acb320b8b1 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs @@ -16,29 +16,29 @@ namespace ICSharpCode.UnitTesting IUnitTestDebuggerService debuggerService; IMessageService messageService; IDebugger debugger; - ITestResultsMonitor testResultsMonitor; + ITestResultsReader testResultsReader; public TestDebuggerBase() : this(new UnitTestDebuggerService(), SD.MessageService, - new TestResultsMonitor()) + new TestResultsReader()) { } public TestDebuggerBase(IUnitTestDebuggerService debuggerService, IMessageService messageService, - ITestResultsMonitor testResultsMonitor) + ITestResultsReader testResultsReader) { this.debuggerService = debuggerService; this.messageService = messageService; - this.testResultsMonitor = testResultsMonitor; + this.testResultsReader = testResultsReader; this.debugger = debuggerService.CurrentDebugger; - testResultsMonitor.TestFinished += OnTestFinished; + testResultsReader.TestFinished += OnTestFinished; } - protected ITestResultsMonitor TestResultsMonitor { - get { return testResultsMonitor; } + protected ITestResultsReader TestResultsReader { + get { return testResultsReader; } } public override void Start(IEnumerable selectedTests) @@ -67,7 +67,7 @@ namespace ICSharpCode.UnitTesting void Start(ProcessStartInfo startInfo) { - testResultsMonitor.Start(); + testResultsReader.Start(); StartDebugger(startInfo); } @@ -90,6 +90,7 @@ namespace ICSharpCode.UnitTesting void DebugStopped(object source, EventArgs e) { debugger.DebugStopped -= DebugStopped; + testResultsReader.Join(); OnAllTestsFinished(source, e); } @@ -98,15 +99,12 @@ namespace ICSharpCode.UnitTesting if (debugger.IsDebugging) { debugger.Stop(); } - - testResultsMonitor.Stop(); - testResultsMonitor.Read(); } public override void Dispose() { - Stop(); - testResultsMonitor.Dispose(); + testResultsReader.Dispose(); + testResultsReader.TestFinished -= OnTestFinished; } } } diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs index 9595482b47..c76080b0c6 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs @@ -11,17 +11,17 @@ using ICSharpCode.SharpDevelop.Util; namespace ICSharpCode.UnitTesting { - public class TestProcessRunnerBase : TestRunnerBase + public abstract class TestProcessRunnerBase : TestRunnerBase { IUnitTestProcessRunner processRunner; - ITestResultsMonitor testResultsMonitor; + ITestResultsReader testResultsReader; IFileSystem fileSystem; IMessageService messageService; public TestProcessRunnerBase(TestProcessRunnerBaseContext context) { this.processRunner = context.TestProcessRunner; - this.testResultsMonitor = context.TestResultsMonitor; + this.testResultsReader = context.TestResultsReader; this.fileSystem = context.FileSystem; this.messageService = context.MessageService; @@ -29,11 +29,11 @@ namespace ICSharpCode.UnitTesting processRunner.OutputLineReceived += OutputLineReceived; processRunner.ErrorLineReceived += OutputLineReceived; processRunner.ProcessExited += OnAllTestsFinished; - testResultsMonitor.TestFinished += OnTestFinished; + testResultsReader.TestFinished += OnTestFinished; } - protected ITestResultsMonitor TestResultsMonitor { - get { return testResultsMonitor; } + protected ITestResultsReader TestResultsReader { + get { return testResultsReader; } } protected IUnitTestProcessRunner ProcessRunner { @@ -56,7 +56,7 @@ namespace ICSharpCode.UnitTesting LogCommandLine(processStartInfo); if (ApplicationFileNameExists(processStartInfo.FileName)) { - testResultsMonitor.Start(); + testResultsReader.Start(); processRunner.WorkingDirectory = processStartInfo.WorkingDirectory; processRunner.Start(processStartInfo.FileName, processStartInfo.Arguments); } else { @@ -75,17 +75,23 @@ namespace ICSharpCode.UnitTesting messageService.ShowErrorFormatted(resourceString, fileName); } + protected override void OnAllTestsFinished(object source, EventArgs e) + { + testResultsReader.Join(); + base.OnAllTestsFinished(source, e); + } + public override void Stop() { + SD.Log.Info("Killing unit test runner"); processRunner.Kill(); - testResultsMonitor.Stop(); - testResultsMonitor.Read(); } public override void Dispose() { - testResultsMonitor.Dispose(); - testResultsMonitor.TestFinished -= OnTestFinished; + processRunner.Dispose(); + testResultsReader.Dispose(); + testResultsReader.TestFinished -= OnTestFinished; processRunner.ErrorLineReceived -= OutputLineReceived; processRunner.OutputLineReceived -= OutputLineReceived; } diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs index 56f2e6c38d..2a8288b55e 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs @@ -11,25 +11,25 @@ namespace ICSharpCode.UnitTesting public class TestProcessRunnerBaseContext { IUnitTestProcessRunner processRunner; - ITestResultsMonitor testResultsMonitor; + ITestResultsReader testResultsReader; IFileSystem fileSystem; IMessageService messageService; public TestProcessRunnerBaseContext() : this(new UnitTestProcessRunner(), - new TestResultsMonitor(), + new TestResultsReader(), new UnitTestFileService(), SD.MessageService) { } public TestProcessRunnerBaseContext(IUnitTestProcessRunner processRunner, - ITestResultsMonitor testResultsMonitor, + ITestResultsReader testResultsMonitor, IFileSystem fileSystem, IMessageService messageService) { this.processRunner = processRunner; - this.testResultsMonitor = testResultsMonitor; + this.testResultsReader = testResultsMonitor; this.fileSystem = fileSystem; this.messageService = messageService; } @@ -38,8 +38,8 @@ namespace ICSharpCode.UnitTesting get { return processRunner; } } - public ITestResultsMonitor TestResultsMonitor { - get { return testResultsMonitor; } + public ITestResultsReader TestResultsReader { + get { return testResultsReader; } } public IFileSystem FileSystem { diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsMonitor.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsMonitor.cs deleted file mode 100644 index 297dc87fb5..0000000000 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsMonitor.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.IO; -using System.Text; - -namespace ICSharpCode.UnitTesting -{ - /// - /// Watches for new test results as they occur. Test results - /// are written to a file and read in by this class. - /// - public class TestResultsMonitor : ITestResultsMonitor - { - FileInfo fileInfo; - TestResultsReader testResultsReader; - FileSystemWatcher fileSystemWatcher; - - long initialFilePosition = 3; - long filePosition; - - const int BytesBufferLength = 1024; - byte[] bytes = new byte[BytesBufferLength]; - - /// - /// Raised when a single test has been completed. - /// - public event EventHandler TestFinished; - - public TestResultsMonitor(string fileName) - { - fileInfo = new FileInfo(fileName); - ResetFilePosition(); - } - - public TestResultsMonitor() - : this(Path.GetTempFileName()) - { - ResetFilePosition(); - } - - public long InitialFilePosition { - get { return initialFilePosition; } - set { initialFilePosition = value; } - } - - /// - /// Gets or sets the test results filename. - /// - public string FileName { - get { return fileInfo.FullName; } - set { fileInfo = new FileInfo(value); } - } - - /// - /// Starts monitoring for test results. - /// - public void Start() - { - testResultsReader = new TestResultsReader(); - ResetFilePosition(); - - string filter = fileInfo.Name; - fileSystemWatcher = new FileSystemWatcher(fileInfo.DirectoryName, filter); - - if (File.Exists(fileInfo.FullName)) { - fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite; - fileSystemWatcher.Changed += FileChanged; - } else { - fileSystemWatcher.Created += FileCreated; - } - fileSystemWatcher.Error += FileSystemWatcherError; - fileSystemWatcher.EnableRaisingEvents = true; - } - - /// - /// Stops monitoring. - /// - public void Stop() - { - if (fileSystemWatcher != null) { - fileSystemWatcher.Dispose(); - fileSystemWatcher = null; - } - } - - /// - /// Reads the rest of the file from the current position. - /// Raises the TestFinished event for each test result - /// still in the file. - /// - public void Read() - { - string text = ReadTextAdded(); - if (text != null) { - TestResult[] results = testResultsReader.Read(text); - OnTestResultsReceived(results); - } - } - - /// - /// Stops monitoring and releases any resources used - /// by the TestResultsMonitor. - /// - public void Dispose() - { - Stop(); - - try { - File.Delete(FileName); - } catch { } - } - - void FileCreated(object source, FileSystemEventArgs e) - { - fileSystemWatcher.Created -= FileCreated; - fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite; - fileSystemWatcher.Changed += FileChanged; - } - - void FileChanged(object source, FileSystemEventArgs e) - { - Read(); - } - - void OnTestResultsReceived(TestResult[] results) - { - if ((results.Length > 0) && (TestFinished != null)) { - foreach (TestResult result in results) { - TestFinished(this, new TestFinishedEventArgs(result)); - } - } - } - - /// - /// Reads the text added to the end of the file from the last - /// position we read from. - /// - string ReadTextAdded() - { - StringBuilder text = null; - try { - using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - if (fs.Length > 0) { - text = new StringBuilder(); - int bytesRead = 0; - fs.Seek(filePosition, SeekOrigin.Begin); - do { - bytesRead = fs.Read(bytes, 0, BytesBufferLength); - if (bytesRead > 0) { - filePosition += bytesRead; - text.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytesRead)); - } - } while ((bytesRead > 0) && (filePosition < fs.Length)); - } - } - } catch (FileNotFoundException) { - // Test was aborted before it even started execution - return null; - } - if (text != null) { - return text.ToString(); - } - return null; - } - - void FileSystemWatcherError(object source, ErrorEventArgs e) - { - Console.WriteLine(e.GetException().ToString()); - } - - void ResetFilePosition() - { - filePosition = initialFilePosition; - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs index 35b13b7ee7..c3bffa260d 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs @@ -2,52 +2,118 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; -using System.Collections.Generic; +using System.IO; +using System.IO.Pipes; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; namespace ICSharpCode.UnitTesting { - /// - /// Reads the test results file produced by the custom - /// nunit-console application. - /// - public class TestResultsReader + public interface ITestResultsReader : IDisposable { - StringBuilder nameBuilder = new StringBuilder(); - StringBuilder valueBuilder = new StringBuilder(); - bool firstNameChar = true; - TestResult result; + event EventHandler TestFinished; + string PipeName { get; } + void Start(); + void Join(); + } + + public class TestResultsReader : ITestResultsReader + { + TextReader reader; + readonly NamedPipeServerStream namedPipe; + readonly string pipeName; + TaskCompletionSource tcs = new TaskCompletionSource(); - enum State { - WaitingForEndOfName = 0, - WaitingForStartOfValue = 1, - WaitingForEndOfValue = 2 + public TestResultsReader() + { + pipeName = Guid.NewGuid().ToString(); + namedPipe = new NamedPipeServerStream(this.PipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte); } - State state = State.WaitingForEndOfName; + /// + /// Creates a new TestResultsReader that reads from an existing text reader. + /// + public TestResultsReader(TextReader reader) + { + this.reader = reader; + } - public TestResultsReader() + public string PipeName { + get { return pipeName; } + } + + public void Dispose() + { + reader.Dispose(); + namedPipe.Dispose(); + } + + public event EventHandler TestFinished; + + Thread thread; + + /// + /// Runs the pipe reader on a background thread. + /// The returned task will be signalled as completed when the worker process closes the pipe, after + /// all contents have been read from the pipe. + /// + public void Start() { + thread = new Thread(Run); + thread.Name = "UnitTesting Pipe Reader"; + thread.Start(); + } + + public void Join() + { + SD.Log.Debug("Waiting for pipe reader to finish"); + thread.Join(); + SD.Log.Debug("Pipe reader has finished"); } /// - /// Returns any TestResults that are in the text. + /// Run the pipe reader on the current thread. + /// This method blocks until the worker process closes the pipe. /// - /// The text read in from the - /// TestResults file. - public TestResult[] Read(string text) + public void Run() { - List results = new List(); - foreach (char ch in text) { - if (ReadNameValuePair(ch)) { - if (ReadTestResult()) { - results.Add(result); + if (reader == null) { + SD.Log.Debug("Waiting for connection to pipe"); + namedPipe.WaitForConnection(); + SD.Log.Debug("Start reading pipe"); + reader = new StreamReader(namedPipe); + } + char[] buffer = new char[1024]; + int read; + while ((read = reader.Read(buffer, 0, buffer.Length)) != 0) { + for (int i = 0; i < read; i++) { + if (ReadNameValuePair(buffer[i])) { + if (ReadTestResult()) { + if (TestFinished != null) + TestFinished(this, new TestFinishedEventArgs(result)); + } } } } - return results.ToArray(); + SD.Log.Debug("End of pipe"); + } + + StringBuilder nameBuilder = new StringBuilder(); + StringBuilder valueBuilder = new StringBuilder(); + bool firstNameChar = true; + TestResult result; + + enum State { + WaitingForEndOfName = 0, + WaitingForStartOfValue = 1, + WaitingForEndOfValue = 2 } + State state = State.WaitingForEndOfName; + /// /// Reads a name-value pair of the form: /// @@ -119,8 +185,8 @@ namespace ICSharpCode.UnitTesting /// True if a TestResult is ready to be returned /// to the caller. /// - /// The first name-value pair for a test result is the - /// test name. The last name-value pair is the result of + /// The first name-value pair for a test result is the + /// test name. The last name-value pair is the result of /// the test (Success, Failure or Ignored). bool ReadTestResult() { diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs index 2a4352d5c6..4923c61e42 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,12 +12,17 @@ namespace ICSharpCode.UnitTesting { public abstract class TestRunnerBase : ITestRunner { + IProgress progress; + double progressPerTest; + int testsFinished; TaskCompletionSource tcs; CancellationTokenRegistration cancellationTokenRegistration; bool wasCancelled; public Task RunAsync(IEnumerable selectedTests, IProgress progress, CancellationToken cancellationToken) { + this.progress = progress; + progressPerTest = 1.0 / GetExpectedNumberOfTestResults(selectedTests); tcs = new TaskCompletionSource(); Start(selectedTests); cancellationTokenRegistration = cancellationToken.Register(Cancel, true); @@ -45,7 +51,7 @@ namespace ICSharpCode.UnitTesting return String.Format("\"{0}\" {1}", startInfo.FileName, startInfo.Arguments); } - protected void OnAllTestsFinished(object source, EventArgs e) + protected virtual void OnAllTestsFinished(object source, EventArgs e) { cancellationTokenRegistration.Dispose(); if (wasCancelled) @@ -62,6 +68,8 @@ namespace ICSharpCode.UnitTesting TestResult testResult = CreateTestResultForTestFramework(e.Result); TestFinished(source, new TestFinishedEventArgs(testResult)); } + if (!double.IsInfinity(progressPerTest)) + progress.Report(progressPerTest * Interlocked.Increment(ref testsFinished)); } protected virtual TestResult CreateTestResultForTestFramework(TestResult testResult) @@ -78,6 +86,7 @@ namespace ICSharpCode.UnitTesting } } + public abstract int GetExpectedNumberOfTestResults(IEnumerable selectedTests); public abstract void Dispose(); public abstract void Stop(); public abstract void Start(IEnumerable selectedTests); diff --git a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj index 689f7266f0..a94333e6f4 100644 --- a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj +++ b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj @@ -107,17 +107,15 @@ - + - - diff --git a/src/Main/Base/Project/Src/Util/ProcessRunner.cs b/src/Main/Base/Project/Src/Util/ProcessRunner.cs index 31d90542ad..e2c0fda14a 100644 --- a/src/Main/Base/Project/Src/Util/ProcessRunner.cs +++ b/src/Main/Base/Project/Src/Util/ProcessRunner.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.SharpDevelop.Util Process process; StringBuilder standardOutput = new StringBuilder(); StringBuilder standardError = new StringBuilder(); - ManualResetEvent endOfOutput = new ManualResetEvent(false); + ManualResetEventSlim endOfOutput = new ManualResetEventSlim(false); int outputStreamsFinished; /// @@ -91,7 +91,7 @@ namespace ICSharpCode.SharpDevelop.Util public void Dispose() { process.Dispose(); - endOfOutput.Close(); + endOfOutput.Dispose(); } /// @@ -130,7 +130,7 @@ namespace ICSharpCode.SharpDevelop.Util bool exited = process.WaitForExit(timeout); if (exited) { - endOfOutput.WaitOne(timeout == int.MaxValue ? Timeout.Infinite : timeout, false); + endOfOutput.Wait(timeout == int.MaxValue ? Timeout.Infinite : timeout); } return exited; @@ -216,14 +216,15 @@ namespace ICSharpCode.SharpDevelop.Util { if (process != null) { if (!process.HasExited) { - process.Kill(); - process.Close(); - process.Dispose(); - process = null; - endOfOutput.WaitOne(); - } else { - process = null; + try { + process.Kill(); + } catch (InvalidOperationException) { + // race condition (if the process has already exited) + } + // don't call process.Dispose() here - that causes the OnProcessExited + // event not to fire correctly } + endOfOutput.Wait(); } } @@ -234,7 +235,7 @@ namespace ICSharpCode.SharpDevelop.Util { if (ProcessExited != null) { if (endOfOutput != null) { - endOfOutput.WaitOne(); + endOfOutput.Wait(); } ProcessExited(this, e); diff --git a/src/Tools/NUnit/buildnunitconsole.bat b/src/Tools/NUnit/buildnunitconsole.bat index 313b203d35..7ab9fe4557 100644 --- a/src/Tools/NUnit/buildnunitconsole.bat +++ b/src/Tools/NUnit/buildnunitconsole.bat @@ -1,6 +1,6 @@ %windir%\microsoft.net\framework\v4.0.30319\msbuild /Target:Rebuild /property:Configuration=Release nunit-console\nunit-console.sln copy nunit-console.exe nunit-console-x86.exe -corflags /32bit+ nunit-console-x86.exe +"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\corflags" /32bit+ nunit-console-x86.exe @IF %ERRORLEVEL% NEQ 0 GOTO err copy nunit-console.exe nunit-console-dotnet2.exe copy nunit-console-x86.exe nunit-console-dotnet2-x86.exe diff --git a/src/Tools/NUnit/nunit-console-dotnet2-x86.exe b/src/Tools/NUnit/nunit-console-dotnet2-x86.exe index 00416949ec..499ed1807c 100755 Binary files a/src/Tools/NUnit/nunit-console-dotnet2-x86.exe and b/src/Tools/NUnit/nunit-console-dotnet2-x86.exe differ diff --git a/src/Tools/NUnit/nunit-console-dotnet2.exe b/src/Tools/NUnit/nunit-console-dotnet2.exe index 468fa7ec48..c564243903 100755 Binary files a/src/Tools/NUnit/nunit-console-dotnet2.exe and b/src/Tools/NUnit/nunit-console-dotnet2.exe differ diff --git a/src/Tools/NUnit/nunit-console-x86.exe b/src/Tools/NUnit/nunit-console-x86.exe index 00416949ec..499ed1807c 100644 Binary files a/src/Tools/NUnit/nunit-console-x86.exe and b/src/Tools/NUnit/nunit-console-x86.exe differ diff --git a/src/Tools/NUnit/nunit-console.exe b/src/Tools/NUnit/nunit-console.exe index 468fa7ec48..c564243903 100644 Binary files a/src/Tools/NUnit/nunit-console.exe and b/src/Tools/NUnit/nunit-console.exe differ diff --git a/src/Tools/NUnit/nunit-console/ExtendedConsoleOptions.cs b/src/Tools/NUnit/nunit-console/ExtendedConsoleOptions.cs index 271685b383..a746b41cf0 100644 --- a/src/Tools/NUnit/nunit-console/ExtendedConsoleOptions.cs +++ b/src/Tools/NUnit/nunit-console/ExtendedConsoleOptions.cs @@ -14,14 +14,14 @@ namespace NUnit.ConsoleRunner { public ExtendedConsoleOptions(string[] args) : base(args) {} - [Option(Description="File to receive test results as each test is run")] - public string results; + [Option(Description="Named pipe to receive test results as each test is run")] + public string pipe; - public bool IsResults + public bool UsePipe { get { - return (results != null) && (results.Length != 0); + return !string.IsNullOrEmpty(pipe); } } } diff --git a/src/Tools/NUnit/nunit-console/ExtendedConsoleUi.cs b/src/Tools/NUnit/nunit-console/ExtendedConsoleUi.cs index 12a0199e4a..f67955ee8c 100644 --- a/src/Tools/NUnit/nunit-console/ExtendedConsoleUi.cs +++ b/src/Tools/NUnit/nunit-console/ExtendedConsoleUi.cs @@ -1,3 +1,4 @@ +using System.IO.Pipes; // **************************************************************** // This is free software licensed under the NUnit license. You // may obtain a copy of the license as well as information regarding @@ -64,9 +65,11 @@ namespace NUnit.ConsoleRunner } TextWriter testResultWriter = null; - if ( options.IsResults ) + if ( options.UsePipe ) { - testResultWriter = new StreamWriter ( options.results, false, Encoding.UTF8 ); + var namedPipe = new NamedPipeClientStream(".", options.pipe, PipeDirection.Out, PipeOptions.WriteThrough); + namedPipe.Connect(); + testResultWriter = new StreamWriter ( namedPipe, Encoding.UTF8 ); ((StreamWriter)testResultWriter).AutoFlush = true; } TestPackage package = MakeTestPackage(options); @@ -184,7 +187,7 @@ namespace NUnit.ConsoleRunner outWriter.Close(); if ( redirectError ) errorWriter.Close(); - if ( options.IsResults ) + if ( testResultWriter != null ) testResultWriter.Close(); Environment.CurrentDirectory = savedDirectory; diff --git a/src/Tools/NUnit/nunit-console/nunit-console.csproj b/src/Tools/NUnit/nunit-console/nunit-console.csproj index a37d0c32b9..17a9fd5463 100644 --- a/src/Tools/NUnit/nunit-console/nunit-console.csproj +++ b/src/Tools/NUnit/nunit-console/nunit-console.csproj @@ -1,4 +1,5 @@ - + + Exe NUnit.ConsoleRunner @@ -14,7 +15,7 @@ 512 4 false - v2.0 + v3.5 App.ico C:\Users\matt\AppData\Roaming\ICSharpCode/SharpDevelop3.0\Settings.SourceAnalysis @@ -54,6 +55,9 @@ False + + 3.5 +