Browse Source

Better ProcessRunner that supports using the same pipe for stdout+stderr, and provides async APIs.

pull/32/merge
Daniel Grunwald 13 years ago
parent
commit
de381fe654
  1. 88
      SharpDevelop.Tests.sln
  2. 2
      src/AddIns/Analysis/MachineSpecifications/MachineSpecifications/src/MSpecTestProcessRunnerContext.cs
  3. 24
      src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs
  4. 63
      src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs
  5. 12
      src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs
  6. 21
      src/AddIns/Analysis/UnitTesting/TestRunner/MessageReceivedEventArgs.cs
  7. 2
      src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs
  8. 9
      src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs
  9. 29
      src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs
  10. 8
      src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs
  11. 20
      src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs
  12. 3
      src/AddIns/Analysis/UnitTesting/UnitTesting.csproj
  13. 2
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ChangeMarkerMargin/DefaultChangeWatcher.cs
  14. 43
      src/AddIns/VersionControl/GitAddIn/Src/Git.cs
  15. 82
      src/AddIns/VersionControl/GitAddIn/Src/GitStatusCache.cs
  16. 133
      src/AddIns/VersionControl/GitAddIn/Src/GitVersionProvider.cs
  17. 22
      src/AddIns/VersionControl/GitAddIn/Src/RegisterEventsCommand.cs
  18. 8
      src/AddIns/VersionControl/SubversionAddIn/Src/SvnVersionProvider.cs
  19. 5
      src/Main/Base/Project/Editor/IDocumentVersionProvider.cs
  20. 1
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  21. 46
      src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs
  22. 38
      src/Main/Base/Project/Src/Gui/Dialogs/ReferenceDialog/ServiceReference/SvcUtilRunner.cs
  23. 42
      src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategoryTextWriter.cs
  24. 3
      src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/DefaultFileNodeCommands.cs
  25. 15
      src/Main/Base/Project/Util/NativeMethods.cs
  26. 678
      src/Main/Base/Project/Util/ProcessRunner.cs
  27. 22
      src/Main/Base/Project/Util/ReactiveExtensions.cs
  28. 9
      src/Main/Base/Project/Util/SharpDevelopExtensions.cs
  29. 60
      src/Main/Base/Test/AbstractEntityIsOverridableTestFixture.cs
  30. 46
      src/Main/Base/Test/CheckAssemblyFlags.cs
  31. 20
      src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj
  32. 51
      src/Main/Base/Test/LineReceivedFromProcessTestFixture.cs
  33. 74
      src/Main/Base/Test/ProcessExitedTestFixture.cs
  34. 6
      src/Main/Base/Test/ProcessRunner/CancelLongRunningAppTestFixture.cs
  35. 5
      src/Main/Base/Test/ProcessRunner/ConsoleAppTestFixtureBase.cs
  36. 6
      src/Main/Base/Test/ProcessRunner/ExitCodeTestFixture.cs
  37. 4
      src/Main/Base/Test/ProcessRunner/NoSuchExecutableTestFixture.cs
  38. 34
      src/Main/Base/Test/ProcessRunner/ProcessExitedTestFixture.cs
  39. 19
      src/Main/Base/Test/ProcessRunner/ProcessRunnerNotStartedTestFixture.cs
  40. 123
      src/Main/Base/Test/ProcessRunner/StandardOutputFromProcessTestFixture.cs

88
SharpDevelop.Tests.sln

@ -1,10 +1,8 @@ @@ -1,10 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
# SharpDevelop 4.3
# SharpDevelop 5.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Main", "Main", "{256F5C28-532C-44C0-8AB8-D8EC5E492E01}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.SharpDevelop.BuildWorker35", "src\Main\ICSharpCode.SharpDevelop.BuildWorker35\ICSharpCode.SharpDevelop.BuildWorker35.csproj", "{B5F54272-49F0-40DB-845A-8D837875D3BA}"
EndProject
@ -27,8 +25,6 @@ EndProject @@ -27,8 +25,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpDevelop", "src\Main\SharpDevelop\SharpDevelop.csproj", "{1152B71B-3C05-4598-B20D-823B5D40559E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{2A232EF1-EB95-41C6-B63A-C106E0C95D3C}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Cecil", "src\Libraries\cecil\Mono.Cecil.csproj", "{D68133BD-1E63-496E-9EDE-4FBDBF77B486}"
EndProject
@ -58,20 +54,14 @@ EndProject @@ -58,20 +54,14 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler", "src\Libraries\ICSharpCode.Decompiler\ICSharpCode.Decompiler.csproj", "{984CC812-9470-4A13-AFF9-CC44068D666C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AddIns", "AddIns", "{39327899-ED91-4F7F-988C-4FE4E17C014D}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Version Control", "Version Control", "{F208FF4F-E5D8-41D5-A7C7-B463976F156E}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitAddIn", "src\AddIns\VersionControl\GitAddIn\GitAddIn.csproj", "{83F15BA7-8478-4664-81BB-A82F146D88B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubversionAddIn", "src\AddIns\VersionControl\SubversionAddIn\SubversionAddIn.csproj", "{17F4D7E0-6933-4C2E-8714-FD7E98D625D5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Language Bindings", "Language Bindings", "{E0646C25-36F2-4524-969F-FA621353AB94}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpBinding", "src\AddIns\BackendBindings\CSharpBinding\Project\CSharpBinding.csproj", "{1F1AC7CD-D154-45BB-8EAF-804CA8055F5A}"
EndProject
@ -96,8 +86,6 @@ EndProject @@ -96,8 +86,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Scripting.Tests", "src\AddIns\BackendBindings\Scripting\Test\ICSharpCode.Scripting.Tests.csproj", "{85C09AD8-183B-403A-869A-7226646218A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{F3662720-9EA2-4591-BBC6-97361DCE50A9}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelpViewer", "src\AddIns\Misc\HelpViewer\HelpViewer.csproj", "{80F76D10-0B44-4D55-B4BD-DAEB5464090C}"
EndProject
@ -114,8 +102,6 @@ EndProject @@ -114,8 +102,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegExpTk", "src\AddIns\Misc\RegExpTk\Project\RegExpTk.csproj", "{64A3E5E6-90BF-47F6-94DF-68C94B62C817}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UsageDataCollector", "UsageDataCollector", "{DEFC8584-BEC3-4921-BD0F-40482E450B7B}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsageDataCollector", "src\AddIns\Misc\UsageDataCollector\UsageDataCollector\UsageDataCollector.csproj", "{6B1CFE35-DA17-4DEB-9C6E-227E5E251DA0}"
EndProject
@ -128,8 +114,6 @@ EndProject @@ -128,8 +114,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextTemplating", "src\AddIns\Misc\TextTemplating\Project\TextTemplating.csproj", "{B5D8C3E6-42EC-4D4B-AD05-3644B32563EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PackageManagement", "PackageManagement", "{485A4CCF-55CF-49F4-BD6D-A22B788C67DA}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagement", "src\AddIns\Misc\PackageManagement\Project\PackageManagement.csproj", "{AE4AB0FA-6087-4480-AF37-0FA1452B3DA1}"
EndProject
@ -144,8 +128,6 @@ EndProject @@ -144,8 +128,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagement.Cmdlets.Tests", "src\AddIns\Misc\PackageManagement\Cmdlets\Test\PackageManagement.Cmdlets.Tests.csproj", "{11115C83-3DB1-431F-8B98-59040359238D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Display Bindings", "Display Bindings", "{11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvalonEdit.AddIn", "src\AddIns\DisplayBindings\AvalonEdit.AddIn\AvalonEdit.AddIn.csproj", "{0162E499-42D0-409B-AA25-EED21F75336B}"
EndProject
@ -160,8 +142,6 @@ EndProject @@ -160,8 +142,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpyAddIn", "src\AddIns\DisplayBindings\ILSpyAddIn\ILSpyAddIn.csproj", "{8AA421C8-D7AF-4957-9F43-5135328ACB24}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{0F5192F2-0744-4BA9-A074-6BE82D111B8D}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Data.Addin", "src\AddIns\DisplayBindings\Data\ICSharpCode.Data.Addin\ICSharpCode.Data.Addin.csproj", "{A9F12710-24E4-46D4-832C-6ECB395B9EAD}"
EndProject
@ -176,8 +156,6 @@ EndProject @@ -176,8 +156,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Data.SQLServer", "src\AddIns\DisplayBindings\Data\ICSharpCode.Data.SQLServer\ICSharpCode.Data.SQLServer.csproj", "{AFE34868-AFA1-4E1C-9450-47AB4BE329D5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WpfDesign", "WpfDesign", "{83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfDesign", "src\AddIns\DisplayBindings\WpfDesign\WpfDesign\Project\WpfDesign.csproj", "{66A378A1-E9F4-4AD5-8946-D0EC06C2902F}"
EndProject
@ -192,8 +170,6 @@ EndProject @@ -192,8 +170,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingsEditor", "src\AddIns\DisplayBindings\SettingsEditor\Project\SettingsEditor.csproj", "{85226AFB-CE71-4851-9A75-7EEC663A8E8A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Debugger", "Debugger", "{AF5E0DC1-1FA0-4346-A436-0C817C68F7C1}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Debugger.Core", "src\AddIns\Debugger\Debugger.Core\Debugger.Core.csproj", "{1D18D788-F7EE-4585-A23B-34DC8EC63CB8}"
EndProject
@ -202,8 +178,6 @@ EndProject @@ -202,8 +178,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Debugger.Tests", "src\AddIns\Debugger\Debugger.Tests\Debugger.Tests.csproj", "{A4C858C8-51B6-4265-A695-A20FCEBA1D19}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analysis", "Analysis", "{B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}"
ProjectSection(SolutionItems) = postProject
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTesting", "src\AddIns\Analysis\UnitTesting\UnitTesting.csproj", "{1F261725-6318-4434-A1B1-6C70CE4CD324}"
EndProject
@ -633,6 +607,22 @@ Global @@ -633,6 +607,22 @@ Global
{56E98A01-8398-4A08-9578-C7337711A52B}.Release|Any CPU.Build.0 = Release|x86
{56E98A01-8398-4A08-9578-C7337711A52B}.Release|x86.ActiveCfg = Release|x86
{56E98A01-8398-4A08-9578-C7337711A52B}.Release|x86.Build.0 = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|Any CPU.ActiveCfg = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|Any CPU.Build.0 = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|x86.ActiveCfg = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|x86.Build.0 = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|Any CPU.ActiveCfg = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|Any CPU.Build.0 = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|x86.ActiveCfg = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|x86.Build.0 = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|Any CPU.ActiveCfg = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|Any CPU.Build.0 = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|x86.ActiveCfg = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|x86.Build.0 = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|Any CPU.ActiveCfg = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|Any CPU.Build.0 = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|x86.ActiveCfg = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|x86.Build.0 = Release|x86
{0162E499-42D0-409B-AA25-EED21F75336B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0162E499-42D0-409B-AA25-EED21F75336B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0162E499-42D0-409B-AA25-EED21F75336B}.Debug|x86.ActiveCfg = Debug|Any CPU
@ -881,22 +871,6 @@ Global @@ -881,22 +871,6 @@ Global
{3DF4060F-5EE0-41CF-8096-F27355FD5511}.Release|Any CPU.Build.0 = Release|Any CPU
{3DF4060F-5EE0-41CF-8096-F27355FD5511}.Release|x86.ActiveCfg = Release|Any CPU
{3DF4060F-5EE0-41CF-8096-F27355FD5511}.Release|x86.Build.0 = Release|Any CPU
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|Any CPU.Build.0 = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|Any CPU.ActiveCfg = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|x86.Build.0 = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Debug|x86.ActiveCfg = Debug|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|Any CPU.Build.0 = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|Any CPU.ActiveCfg = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|x86.Build.0 = Release|x86
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D}.Release|x86.ActiveCfg = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|Any CPU.Build.0 = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|Any CPU.ActiveCfg = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|x86.Build.0 = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Debug|x86.ActiveCfg = Debug|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|Any CPU.Build.0 = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|Any CPU.ActiveCfg = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|x86.Build.0 = Release|x86
{11115C83-3DB1-431F-8B98-59040359238D}.Release|x86.ActiveCfg = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -915,24 +889,19 @@ Global @@ -915,24 +889,19 @@ Global
{D68133BD-1E63-496E-9EDE-4FBDBF77B486} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{6222A3A1-83CE-47A3-A4E4-A018F82D44D8} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{3B2A5653-EC97-4001-BB9B-D90F1AF2C371} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{53DCA265-3C3C-42F9-B647-F72BA678122B} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{63D3B27A-D966-4902-90B3-30290E1692F1} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{2FF700C2-A38A-48BD-A637-8CAFD4FE6237} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{4139CCF6-FB49-4A9D-B2CF-331E9EA3198D} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{DDE2A481-8271-4EAC-A330-8FA6A38D13D1} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{984CC812-9470-4A13-AFF9-CC44068D666C} = {2A232EF1-EB95-41C6-B63A-C106E0C95D3C}
{3B2A5653-EC97-4001-BB9B-D90F1AF2C371} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{53DCA265-3C3C-42F9-B647-F72BA678122B} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{63D3B27A-D966-4902-90B3-30290E1692F1} = {E5A0F4D8-37FD-4A30-BEB0-4409DC4E0865}
{F208FF4F-E5D8-41D5-A7C7-B463976F156E} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{E0646C25-36F2-4524-969F-FA621353AB94} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{F3662720-9EA2-4591-BBC6-97361DCE50A9} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{11BF9245-88A3-4A0A-9A8A-EC9D98036B0F} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{AF5E0DC1-1FA0-4346-A436-0C817C68F7C1} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{B3352C08-3CB4-4DD9-996F-B9DCE4356BB9} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{E2FD63DA-8478-4066-934C-DA82A852C83A} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{83F15BA7-8478-4664-81BB-A82F146D88B3} = {F208FF4F-E5D8-41D5-A7C7-B463976F156E}
{17F4D7E0-6933-4C2E-8714-FD7E98D625D5} = {F208FF4F-E5D8-41D5-A7C7-B463976F156E}
{E0646C25-36F2-4524-969F-FA621353AB94} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{1F1AC7CD-D154-45BB-8EAF-804CA8055F5A} = {E0646C25-36F2-4524-969F-FA621353AB94}
{E954F3CB-A446-492F-A664-2B376EBC86E8} = {E0646C25-36F2-4524-969F-FA621353AB94}
{70966F84-74C9-4067-A379-0C674A929233} = {E0646C25-36F2-4524-969F-FA621353AB94}
@ -944,6 +913,7 @@ Global @@ -944,6 +913,7 @@ Global
{388E7B64-0393-4EB4-A3E3-5C474F141853} = {E0646C25-36F2-4524-969F-FA621353AB94}
{7048AE18-EB93-4A84-82D0-DD60EB58ADBD} = {E0646C25-36F2-4524-969F-FA621353AB94}
{85C09AD8-183B-403A-869A-7226646218A9} = {E0646C25-36F2-4524-969F-FA621353AB94}
{F3662720-9EA2-4591-BBC6-97361DCE50A9} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{80F76D10-0B44-4D55-B4BD-DAEB5464090C} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{9196DD8A-B4D4-4780-8742-C5762E547FC2} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{7D5C266F-D6FF-4D14-B315-0C0FC6C4EF51} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
@ -952,18 +922,19 @@ Global @@ -952,18 +922,19 @@ Global
{D022A6CE-7438-41E8-AC64-F2DE18EC54C6} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{64A3E5E6-90BF-47F6-94DF-68C94B62C817} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{DEFC8584-BEC3-4921-BD0F-40482E450B7B} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{6B1CFE35-DA17-4DEB-9C6E-227E5E251DA0} = {DEFC8584-BEC3-4921-BD0F-40482E450B7B}
{0008FCE9-9EB4-4E2E-979B-553278E5BBA6} = {DEFC8584-BEC3-4921-BD0F-40482E450B7B}
{A569DCC1-C608-45FD-B770-4F79335EF154} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{5186325C-DD7F-4246-9BE7-3F384EFBF5A6} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{B5D8C3E6-42EC-4D4B-AD05-3644B32563EF} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{485A4CCF-55CF-49F4-BD6D-A22B788C67DA} = {F3662720-9EA2-4591-BBC6-97361DCE50A9}
{6B1CFE35-DA17-4DEB-9C6E-227E5E251DA0} = {DEFC8584-BEC3-4921-BD0F-40482E450B7B}
{0008FCE9-9EB4-4E2E-979B-553278E5BBA6} = {DEFC8584-BEC3-4921-BD0F-40482E450B7B}
{AE4AB0FA-6087-4480-AF37-0FA1452B3DA1} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{A406803B-C584-43A3-BCEE-A0BB3132CB5F} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{C3F15E22-5793-4129-AF8C-6229112B86D2} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{56E98A01-8398-4A08-9578-C7337711A52B} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{E0A5E80A-003B-4335-A9DC-A76E2E46D38D} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{11115C83-3DB1-431F-8B98-59040359238D} = {485A4CCF-55CF-49F4-BD6D-A22B788C67DA}
{11BF9245-88A3-4A0A-9A8A-EC9D98036B0F} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{0162E499-42D0-409B-AA25-EED21F75336B} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{E618A9CD-A39F-4925-A538-E8A3FEF24E54} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{DCA2703D-250A-463E-A68A-07ED105AE6BD} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
@ -971,22 +942,24 @@ Global @@ -971,22 +942,24 @@ Global
{DFB936AD-90EE-4B4F-941E-4F4A636F0D92} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{8AA421C8-D7AF-4957-9F43-5135328ACB24} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{0F5192F2-0744-4BA9-A074-6BE82D111B8D} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{83BAB756-1010-4A2F-9B9D-7F9EBCB288F5} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{85226AFB-CE71-4851-9A75-7EEC663A8E8A} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{A9F12710-24E4-46D4-832C-6ECB395B9EAD} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{B7823AE9-4B43-4859-8796-2EBDC116FBB8} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{BAD94D6E-4159-4CB6-B991-486F412D9BB6} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{5C70D6AB-0A33-43F9-B8B5-54558C35BBB1} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{EEF5E054-4192-4A57-8FBF-E860D808A51D} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{AFE34868-AFA1-4E1C-9450-47AB4BE329D5} = {0F5192F2-0744-4BA9-A074-6BE82D111B8D}
{83BAB756-1010-4A2F-9B9D-7F9EBCB288F5} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{66A378A1-E9F4-4AD5-8946-D0EC06C2902F} = {83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}
{9A9D6FD4-6A2E-455D-ACC3-DDA775FE9865} = {83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}
{943DBBB3-E84E-4CF4-917C-C05AFA8743C1} = {83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}
{78CC29AC-CC79-4355-B1F2-97936DF198AC} = {83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}
{88DA149F-21B2-48AB-82C4-28FB6BDFD783} = {83BAB756-1010-4A2F-9B9D-7F9EBCB288F5}
{85226AFB-CE71-4851-9A75-7EEC663A8E8A} = {11BF9245-88A3-4A0A-9A8A-EC9D98036B0F}
{AF5E0DC1-1FA0-4346-A436-0C817C68F7C1} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{1D18D788-F7EE-4585-A23B-34DC8EC63CB8} = {AF5E0DC1-1FA0-4346-A436-0C817C68F7C1}
{EC06F96A-AEEC-49D6-B03D-AB87C6EB674C} = {AF5E0DC1-1FA0-4346-A436-0C817C68F7C1}
{A4C858C8-51B6-4265-A695-A20FCEBA1D19} = {AF5E0DC1-1FA0-4346-A436-0C817C68F7C1}
{B3352C08-3CB4-4DD9-996F-B9DCE4356BB9} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
{1F261725-6318-4434-A1B1-6C70CE4CD324} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
{44A8DE09-CAB9-49D8-9CFC-5EB0A552F181} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
{3EAA45A9-735C-4AC7-A799-947B93EA449D} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
@ -995,5 +968,6 @@ Global @@ -995,5 +968,6 @@ Global
{08CE9972-283B-44F4-82FA-966F7DFA6B7A} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
{172AE35D-2051-4977-AC13-0BF1B76374D5} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
{D1DA3B8F-7313-4BDA-8880-461C5F007751} = {B3352C08-3CB4-4DD9-996F-B9DCE4356BB9}
{E2FD63DA-8478-4066-934C-DA82A852C83A} = {39327899-ED91-4F7F-988C-4FE4E17C014D}
EndGlobalSection
EndGlobal

2
src/AddIns/Analysis/MachineSpecifications/MachineSpecifications/src/MSpecTestProcessRunnerContext.cs

@ -12,7 +12,7 @@ namespace ICSharpCode.MachineSpecifications @@ -12,7 +12,7 @@ namespace ICSharpCode.MachineSpecifications
public MSpecTestProcessRunnerContext(TestExecutionOptions options)
: base(
options,
new UnitTestProcessRunner(),
new ProcessRunner(),
new MSpecUnitTestMonitor(),
SD.FileSystem,
SD.MessageService)

24
src/AddIns/Analysis/UnitTesting/Interfaces/IUnitTestProcessRunner.cs

@ -1,24 +0,0 @@ @@ -1,24 +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.Collections.Generic;
using ICSharpCode.SharpDevelop;
namespace ICSharpCode.UnitTesting
{
public interface IUnitTestProcessRunner : IDisposable
{
bool LogStandardOutputAndError { get; set; }
string WorkingDirectory { get; set; }
Dictionary<string, string> EnvironmentVariables { get; }
void Start(string command, string arguments);
void Kill();
event EventHandler<LineReceivedEventArgs> OutputLineReceived;
event EventHandler<LineReceivedEventArgs> ErrorLineReceived;
event EventHandler ProcessExited;
}
}

63
src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestProcessRunner.cs

@ -1,63 +0,0 @@ @@ -1,63 +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.Collections.Generic;
using ICSharpCode.SharpDevelop;
namespace ICSharpCode.UnitTesting
{
public class UnitTestProcessRunner : IUnitTestProcessRunner
{
ProcessRunner runner;
public event EventHandler<LineReceivedEventArgs> OutputLineReceived {
add { runner.OutputLineReceived += value; }
remove { runner.OutputLineReceived -= value; }
}
public event EventHandler<LineReceivedEventArgs> ErrorLineReceived {
add { runner.ErrorLineReceived += value; }
remove { runner.ErrorLineReceived -= value; }
}
public event EventHandler ProcessExited {
add { runner.ProcessExited += value; }
remove { runner.ProcessExited -= value; }
}
public UnitTestProcessRunner()
{
runner = new ProcessRunner();
}
public bool LogStandardOutputAndError {
get { return runner.LogStandardOutputAndError; }
set { runner.LogStandardOutputAndError = value; }
}
public string WorkingDirectory {
get { return runner.WorkingDirectory; }
set { runner.WorkingDirectory = value; }
}
public Dictionary<string, string> EnvironmentVariables {
get { return runner.EnvironmentVariables; }
}
public void Start(string command, string arguments)
{
runner.Start(command, arguments);
}
public void Kill()
{
runner.Kill();
}
public void Dispose()
{
runner.Dispose();
}
}
}

12
src/AddIns/Analysis/UnitTesting/TestRunner/ITestRunner.cs

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.SharpDevelop.Gui;
@ -12,8 +13,15 @@ namespace ICSharpCode.UnitTesting @@ -12,8 +13,15 @@ namespace ICSharpCode.UnitTesting
public interface ITestRunner : IDisposable
{
event EventHandler<TestFinishedEventArgs> TestFinished;
event EventHandler<MessageReceivedEventArgs> MessageReceived;
Task RunAsync(IEnumerable<ITest> selectedTests, IProgress<double> progress, CancellationToken cancellationToken);
/// <summary>
/// Runs the unit tests.
/// </summary>
/// <param name="selectedTests">The tests that were selected for execution.</param>
/// <param name="progress">Object for reporting progress within the run (percentage value between 0 and 1)</param>
/// <param name="output">Output text writer </param>
/// <param name="cancellationToken"></param>
/// <returns>Returns a task that gets marked as completed when all tests have finished execution.</returns>
Task RunAsync(IEnumerable<ITest> selectedTests, IProgress<double> progress, TextWriter output, CancellationToken cancellationToken);
}
}

21
src/AddIns/Analysis/UnitTesting/TestRunner/MessageReceivedEventArgs.cs

@ -1,21 +0,0 @@ @@ -1,21 +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 class MessageReceivedEventArgs : EventArgs
{
string message;
public MessageReceivedEventArgs(string message)
{
this.message = message;
}
public string Message {
get { return message; }
}
}
}

2
src/AddIns/Analysis/UnitTesting/TestRunner/TestDebuggerBase.cs

@ -91,7 +91,7 @@ namespace ICSharpCode.UnitTesting @@ -91,7 +91,7 @@ namespace ICSharpCode.UnitTesting
{
debugger.DebugStopped -= DebugStopped;
testResultsReader.Join();
OnAllTestsFinished(source, e);
OnAllTestsFinished();
}
public override void Stop()

9
src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs

@ -82,9 +82,9 @@ namespace ICSharpCode.UnitTesting.Frameworks @@ -82,9 +82,9 @@ namespace ICSharpCode.UnitTesting.Frameworks
progressMonitor.Progress = GetProgress(projectsLeftToRun);
using (testProgressMonitor = progressMonitor.CreateSubTask(1.0 / testsByProject.Count)) {
using (ITestRunner testRunner = currentProjectBeingTested.CreateTestRunner(options)) {
testRunner.MessageReceived += testRunner_MessageReceived;
testRunner.TestFinished += testRunner_TestFinished;
await testRunner.RunAsync(g, testProgressMonitor, testProgressMonitor.CancellationToken);
var writer = new MessageViewCategoryTextWriter(testService.UnitTestMessageView);
await testRunner.RunAsync(g, testProgressMonitor, writer, testProgressMonitor.CancellationToken);
}
}
projectsLeftToRun--;
@ -156,11 +156,6 @@ namespace ICSharpCode.UnitTesting.Frameworks @@ -156,11 +156,6 @@ namespace ICSharpCode.UnitTesting.Frameworks
return (double)(totalProjectCount - projectsLeftToRunCount) / totalProjectCount;
}
void testRunner_MessageReceived(object sender, MessageReceivedEventArgs e)
{
testService.UnitTestMessageView.AppendLine(e.Message);
}
void testRunner_TestFinished(object sender, TestFinishedEventArgs e)
{
mainThread.InvokeAsyncAndForget(delegate {

29
src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBase.cs

@ -5,6 +5,7 @@ using System; @@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
@ -13,7 +14,7 @@ namespace ICSharpCode.UnitTesting @@ -13,7 +14,7 @@ namespace ICSharpCode.UnitTesting
public abstract class TestProcessRunnerBase : TestRunnerBase
{
TestExecutionOptions executionOptions;
IUnitTestProcessRunner processRunner;
IProcessRunner processRunner;
ITestResultsReader testResultsReader;
IFileSystem fileSystem;
IMessageService messageService;
@ -26,10 +27,6 @@ namespace ICSharpCode.UnitTesting @@ -26,10 +27,6 @@ namespace ICSharpCode.UnitTesting
this.fileSystem = context.FileSystem;
this.messageService = context.MessageService;
processRunner.LogStandardOutputAndError = false;
processRunner.OutputLineReceived += OutputLineReceived;
processRunner.ErrorLineReceived += OutputLineReceived;
processRunner.ProcessExited += OnAllTestsFinished;
testResultsReader.TestFinished += OnTestFinished;
}
@ -37,15 +34,10 @@ namespace ICSharpCode.UnitTesting @@ -37,15 +34,10 @@ namespace ICSharpCode.UnitTesting
get { return testResultsReader; }
}
protected IUnitTestProcessRunner ProcessRunner {
protected IProcessRunner ProcessRunner {
get { return processRunner; }
}
void OutputLineReceived(object source, LineReceivedEventArgs e)
{
OnMessageReceived(e.Line);
}
public override void Start(IEnumerable<ITest> selectedTests)
{
ProcessStartInfo startInfo = GetProcessStartInfo(selectedTests);
@ -64,8 +56,12 @@ namespace ICSharpCode.UnitTesting @@ -64,8 +56,12 @@ namespace ICSharpCode.UnitTesting
if (ApplicationFileNameExists(processStartInfo.FileName)) {
testResultsReader.Start();
processRunner.WorkingDirectory = processStartInfo.WorkingDirectory;
processRunner.Start(processStartInfo.FileName, processStartInfo.Arguments);
processRunner.WorkingDirectory = DirectoryName.Create(processStartInfo.WorkingDirectory);
processRunner.RedirectStandardOutputAndErrorToSingleStream = true;
processRunner.StartCommandLine("\"" + processStartInfo.FileName + "\" " + processStartInfo.Arguments);
Task.WhenAll(
processRunner.OpenStandardOutputReader().CopyToAsync(output),
processRunner.WaitForExitAsync()).ContinueWith(_ => OnAllTestsFinished()).FireAndForget();
} else {
ShowApplicationDoesNotExistMessage(processStartInfo.FileName);
}
@ -82,10 +78,10 @@ namespace ICSharpCode.UnitTesting @@ -82,10 +78,10 @@ namespace ICSharpCode.UnitTesting
messageService.ShowErrorFormatted(resourceString, fileName);
}
protected override void OnAllTestsFinished(object source, EventArgs e)
protected override void OnAllTestsFinished()
{
testResultsReader.Join();
base.OnAllTestsFinished(source, e);
base.OnAllTestsFinished();
}
public override void Stop()
@ -96,11 +92,8 @@ namespace ICSharpCode.UnitTesting @@ -96,11 +92,8 @@ namespace ICSharpCode.UnitTesting
public override void Dispose()
{
processRunner.Dispose();
testResultsReader.Dispose();
testResultsReader.TestFinished -= OnTestFinished;
processRunner.ErrorLineReceived -= OutputLineReceived;
processRunner.OutputLineReceived -= OutputLineReceived;
}
}
}

8
src/AddIns/Analysis/UnitTesting/TestRunner/TestProcessRunnerBaseContext.cs

@ -10,14 +10,14 @@ namespace ICSharpCode.UnitTesting @@ -10,14 +10,14 @@ namespace ICSharpCode.UnitTesting
public class TestProcessRunnerBaseContext
{
TestExecutionOptions executionOptions;
IUnitTestProcessRunner processRunner;
IProcessRunner processRunner;
ITestResultsReader testResultsReader;
IFileSystem fileSystem;
IMessageService messageService;
public TestProcessRunnerBaseContext(TestExecutionOptions executionOptions)
: this(executionOptions,
new UnitTestProcessRunner(),
new ProcessRunner(),
new TestResultsReader(),
SD.FileSystem,
SD.MessageService)
@ -25,7 +25,7 @@ namespace ICSharpCode.UnitTesting @@ -25,7 +25,7 @@ namespace ICSharpCode.UnitTesting
}
public TestProcessRunnerBaseContext(TestExecutionOptions executionOptions,
IUnitTestProcessRunner processRunner,
IProcessRunner processRunner,
ITestResultsReader testResultsMonitor,
IFileSystem fileSystem,
IMessageService messageService)
@ -41,7 +41,7 @@ namespace ICSharpCode.UnitTesting @@ -41,7 +41,7 @@ namespace ICSharpCode.UnitTesting
get { return executionOptions; }
}
public IUnitTestProcessRunner TestProcessRunner {
public IProcessRunner TestProcessRunner {
get { return processRunner; }
}

20
src/AddIns/Analysis/UnitTesting/TestRunner/TestRunnerBase.cs

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -18,10 +19,12 @@ namespace ICSharpCode.UnitTesting @@ -18,10 +19,12 @@ namespace ICSharpCode.UnitTesting
TaskCompletionSource<object> tcs;
CancellationTokenRegistration cancellationTokenRegistration;
bool wasCancelled;
protected TextWriter output;
public Task RunAsync(IEnumerable<ITest> selectedTests, IProgress<double> progress, CancellationToken cancellationToken)
public Task RunAsync(IEnumerable<ITest> selectedTests, IProgress<double> progress, TextWriter output, CancellationToken cancellationToken)
{
this.progress = progress;
this.output = output;
progressPerTest = 1.0 / GetExpectedNumberOfTestResults(selectedTests);
tcs = new TaskCompletionSource<object>();
Start(selectedTests);
@ -43,7 +46,7 @@ namespace ICSharpCode.UnitTesting @@ -43,7 +46,7 @@ namespace ICSharpCode.UnitTesting
protected void LogCommandLine(ProcessStartInfo startInfo)
{
string commandLine = GetCommandLine(startInfo);
OnMessageReceived(commandLine);
output.WriteLine(commandLine);
}
protected string GetCommandLine(ProcessStartInfo startInfo)
@ -51,13 +54,13 @@ namespace ICSharpCode.UnitTesting @@ -51,13 +54,13 @@ namespace ICSharpCode.UnitTesting
return String.Format("\"{0}\" {1}", startInfo.FileName, startInfo.Arguments);
}
protected virtual void OnAllTestsFinished(object source, EventArgs e)
protected virtual void OnAllTestsFinished()
{
cancellationTokenRegistration.Dispose();
if (wasCancelled)
tcs.SetCanceled();
else
tcs.SetResult(e);
tcs.SetResult(null);
}
public event EventHandler<TestFinishedEventArgs> TestFinished;
@ -77,15 +80,6 @@ namespace ICSharpCode.UnitTesting @@ -77,15 +80,6 @@ namespace ICSharpCode.UnitTesting
return testResult;
}
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
protected virtual void OnMessageReceived(string message)
{
if (MessageReceived != null) {
MessageReceived(this, new MessageReceivedEventArgs(message));
}
}
public abstract int GetExpectedNumberOfTestResults(IEnumerable<ITest> selectedTests);
public abstract void Dispose();
public abstract void Stop();

3
src/AddIns/Analysis/UnitTesting/UnitTesting.csproj

@ -58,8 +58,6 @@ @@ -58,8 +58,6 @@
<Compile Include="Commands\UnitTestCommands.cs" />
<Compile Include="Pad\UnitTestNodeFactory.cs" />
<Compile Include="Service\ITestService.cs" />
<Compile Include="Interfaces\IUnitTestProcessRunner.cs" />
<Compile Include="Interfaces\UnitTestProcessRunner.cs" />
<Compile Include="Model\ITest.cs" />
<Compile Include="Model\ITestFramework.cs" />
<Compile Include="Model\ITestProject.cs" />
@ -92,7 +90,6 @@ @@ -92,7 +90,6 @@
<Compile Include="Service\TestFrameworkDoozer.cs" />
<Compile Include="Service\SDTestService.cs" />
<Compile Include="TestRunner\ITestRunner.cs" />
<Compile Include="TestRunner\MessageReceivedEventArgs.cs" />
<Compile Include="TestRunner\TestDebuggerBase.cs" />
<Compile Include="TestRunner\TestExecutionManager.cs" />
<Compile Include="TestRunner\TestExecutionOptions.cs" />

2
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ChangeMarkerMargin/DefaultChangeWatcher.cs

@ -143,7 +143,7 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -143,7 +143,7 @@ namespace ICSharpCode.AvalonEdit.AddIn
Stream GetBaseVersion(FileName fileName)
{
foreach (IDocumentVersionProvider provider in VersioningServices.Instance.DocumentVersionProviders) {
var result = provider.OpenBaseVersion(fileName);
var result = provider.OpenBaseVersionAsync(fileName).GetAwaiter().GetResult();
if (result != null) {
usedProvider = provider;
return result;

43
src/AddIns/VersionControl/GitAddIn/Src/Git.cs

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui;
@ -38,56 +39,36 @@ namespace ICSharpCode.GitAddIn @@ -38,56 +39,36 @@ namespace ICSharpCode.GitAddIn
return null;
}
public static void Add(string fileName, Action<int> callback)
public static Task AddAsync(string fileName)
{
string wcRoot = FindWorkingCopyRoot(fileName);
if (wcRoot == null)
return;
RunGit(wcRoot, "add " + AdaptFileName(wcRoot, fileName), callback);
return Task.FromResult(false);
return RunGitAsync(wcRoot, "add", AdaptFileName(wcRoot, fileName));
}
public static void Remove(string fileName, bool indexOnly, Action<int> callback)
public static Task RemoveAsync(string fileName, bool indexOnly)
{
string wcRoot = FindWorkingCopyRoot(fileName);
if (wcRoot == null)
return;
return Task.FromResult(false);
if (indexOnly)
RunGit(wcRoot, "rm --cached " + AdaptFileName(wcRoot, fileName), callback);
return RunGitAsync(wcRoot, "rm", "--cached", AdaptFileName(wcRoot, fileName));
else
RunGit(wcRoot, "rm " + AdaptFileName(wcRoot, fileName), callback);
return RunGitAsync(wcRoot, "rm", AdaptFileName(wcRoot, fileName));
}
public static string AdaptFileName(string wcRoot, string fileName)
{
return '"' + AdaptFileNameNoQuotes(wcRoot, fileName) + '"';
}
public static string AdaptFileNameNoQuotes(string wcRoot, string fileName)
{
string relFileName = FileUtility.GetRelativePath(wcRoot, fileName);
return relFileName.Replace('\\', '/');
}
public static void RunGit(string workingDir, string arguments, Action<int> finished)
public static Task<int> RunGitAsync(string workingDir, params string[] arguments)
{
GitMessageView.AppendLine(workingDir + "> git " + arguments);
string git = FindGit();
if (git == null) {
GitMessageView.AppendLine("Could not find git.exe");
return;
}
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = workingDir;
runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += (sender, e) => GitMessageView.AppendLine(e.Line);
runner.ErrorLineReceived += (sender, e) => GitMessageView.AppendLine(e.Line);
runner.ProcessExited += delegate {
GitMessageView.AppendLine("Done. (exit code " + runner.ExitCode + ")");
if (finished != null)
finished(runner.ExitCode);
};
runner.Start(git, arguments);
ProcessRunner p = new ProcessRunner();
p.WorkingDirectory = workingDir;
return p.RunInOutputPadAsync(GitMessageView.Category, "git", arguments);
}
/// <summary>

82
src/AddIns/VersionControl/GitAddIn/Src/GitStatusCache.cs

@ -6,8 +6,10 @@ using System.Collections.Generic; @@ -6,8 +6,10 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.GitAddIn
{
@ -43,7 +45,7 @@ namespace ICSharpCode.GitAddIn @@ -43,7 +45,7 @@ namespace ICSharpCode.GitAddIn
if (wcroot == null)
return GitStatus.None;
GitStatusSet gss = GetStatusSet(wcroot);
return gss.GetStatus(Git.AdaptFileNameNoQuotes(wcroot, fileName));
return gss.GetStatus(Git.AdaptFileName(wcroot, fileName));
}
public static GitStatusSet GetStatusSet(string wcRoot)
@ -76,26 +78,40 @@ namespace ICSharpCode.GitAddIn @@ -76,26 +78,40 @@ namespace ICSharpCode.GitAddIn
if (git == null)
return;
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = wcRoot;
runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += delegate(object sender, LineReceivedEventArgs e) {
if (!string.IsNullOrEmpty(e.Line)) {
statusSet.AddEntry(e.Line, GitStatus.OK);
using (ProcessRunner runner = new ProcessRunner()) {
runner.WorkingDirectory = DirectoryName.Create(wcRoot);
runner.RedirectStandardOutput = true;
runner.RedirectStandardError = true;
runner.Start(git, "ls-files");
// process stderr in background
var errorTask = DisplayErrorStreamAsync(runner, GitMessageView.Category);
// process stderr on current thread:
using (var reader = runner.OpenStandardOutputReader()) {
string line;
while ((line = reader.ReadLine()) != null) {
if (line.Length > 0) {
statusSet.AddEntry(line, GitStatus.OK);
}
}
}
errorTask.Wait();
}
}
};
string command = "ls-files";
static async Task DisplayErrorStreamAsync(ProcessRunner runner, MessageViewCategory category)
{
using (var reader = runner.OpenStandardErrorReader()) {
bool hasErrors = false;
runner.ErrorLineReceived += delegate(object sender, LineReceivedEventArgs e) {
string line;
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) {
if (!hasErrors) {
hasErrors = true;
GitMessageView.AppendLine(runner.WorkingDirectory + "> git " + command);
GitMessageView.AppendLine(runner.WorkingDirectory + "> " + runner.CommandLine);
}
GitMessageView.AppendLine(line);
}
}
GitMessageView.AppendLine(e.Line);
};
runner.Start(git, command);
runner.WaitForExit();
}
static void GitGetStatus(string wcRoot, GitStatusSet statusSet)
@ -104,36 +120,28 @@ namespace ICSharpCode.GitAddIn @@ -104,36 +120,28 @@ namespace ICSharpCode.GitAddIn
if (git == null)
return;
string command = "status --porcelain --untracked-files=no";
bool hasErrors = false;
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = wcRoot;
runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += delegate(object sender, LineReceivedEventArgs e) {
if (!string.IsNullOrEmpty(e.Line)) {
Match m = statusParseRegex.Match(e.Line);
runner.WorkingDirectory = DirectoryName.Create(wcRoot);
runner.RedirectStandardOutput = true;
runner.RedirectStandardError = true;
runner.Start(git, "status", "--porcelain", "--untracked-files=no");
// process stderr in background
var errorTask = DisplayErrorStreamAsync(runner, GitMessageView.Category);
// process stderr on current thread:
using (var reader = runner.OpenStandardOutputReader()) {
string line;
while ((line = reader.ReadLine()) != null) {
if (line.Length > 0) {
Match m = statusParseRegex.Match(line);
if (m.Success) {
statusSet.AddEntry(m.Groups[2].Value, StatusFromText(m.Groups[1].Value));
} else {
if (!hasErrors) {
// in front of first output line, print the command line we invoked
hasErrors = true;
GitMessageView.AppendLine(runner.WorkingDirectory + "> git " + command);
GitMessageView.AppendLine("unknown git status output: " + line);
}
GitMessageView.AppendLine("unknown output: " + e.Line);
}
}
};
runner.ErrorLineReceived += delegate(object sender, LineReceivedEventArgs e) {
if (!hasErrors) {
hasErrors = true;
GitMessageView.AppendLine(runner.WorkingDirectory + "> git " + command);
}
GitMessageView.AppendLine(e.Line);
};
runner.Start(git, command);
runner.WaitForExit();
errorTask.Wait();
}
static GitStatus StatusFromText(string text)

133
src/AddIns/VersionControl/GitAddIn/Src/GitVersionProvider.cs

@ -7,6 +7,7 @@ using System.IO; @@ -7,6 +7,7 @@ using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Editor;
@ -16,65 +17,7 @@ namespace ICSharpCode.GitAddIn @@ -16,65 +17,7 @@ namespace ICSharpCode.GitAddIn
{
public class GitVersionProvider : IDocumentVersionProvider
{
#region PInvoke
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref StartupInfo lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
const uint STARTF_USESTDHANDLES = 0x00000100;
const int STD_INPUT_HANDLE = -10;
const int STD_OUTPUT_HANDLE = -11;
const int STD_ERROR_HANDLE = -12;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct StartupInfo
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public SafePipeHandle hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
#endregion
public Stream OpenBaseVersion(string fileName)
public async Task<Stream> OpenBaseVersionAsync(FileName fileName)
{
if (!Git.IsInWorkingCopy(fileName))
return null;
@ -83,67 +26,46 @@ namespace ICSharpCode.GitAddIn @@ -83,67 +26,46 @@ namespace ICSharpCode.GitAddIn
if (git == null)
return null;
return OpenOutput(git, fileName, GetBlobHash(git, fileName));
return OpenOutput(git, fileName, await GetBlobHashAsync(git, fileName).ConfigureAwait(false));
}
internal static string GetBlobHash(string gitExe, string fileName)
internal static async Task<string> GetBlobHashAsync(string gitExe, FileName fileName)
{
if (!File.Exists(fileName))
return null;
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(fileName);
runner.Start(gitExe, "ls-tree HEAD " + Path.GetFileName(fileName));
string blobHash = null;
runner.OutputLineReceived += delegate(object sender, LineReceivedEventArgs e) {
string[] parts = e.Line.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
runner.WorkingDirectory = fileName.GetParentDirectory();
runner.RedirectStandardOutput = true;
runner.Start(gitExe, "ls-tree", "HEAD", fileName.GetFileName());
using (var reader = runner.OpenStandardOutputReader()) {
string firstLine = await reader.ReadLineAsync().ConfigureAwait(false);
if (firstLine != null) {
string[] parts = firstLine.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3) {
if (parts[2].Length == 40)
blobHash = parts[2];
return parts[2];
}
}
};
runner.WaitForExit();
return blobHash;
}
return null;
}
Stream OpenOutput(string gitExe, string fileName, string blobHash)
Stream OpenOutput(string gitExe, FileName fileName, string blobHash)
{
if (!File.Exists(fileName))
return null;
if (blobHash == null)
return null;
AnonymousPipeServerStream pipe = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
StartupInfo startupInfo = new GitVersionProvider.StartupInfo();
startupInfo.dwFlags = STARTF_USESTDHANDLES;
startupInfo.hStdOutput = pipe.ClientSafePipeHandle;
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startupInfo.cb = 16;
PROCESS_INFORMATION procInfo;
string commandLine = "\"" + gitExe + "\" cat-file blob " + blobHash;
string workingDir = Path.GetDirectoryName(fileName);
Debug.WriteLine(workingDir + "> " + commandLine);
const uint CREATE_NO_WINDOW = 0x08000000;
if (!CreateProcess(null, commandLine,
IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW, IntPtr.Zero, workingDir, ref startupInfo,
out procInfo)) {
pipe.DisposeLocalCopyOfClientHandle();
pipe.Close();
if (!File.Exists(fileName))
return null;
}
pipe.DisposeLocalCopyOfClientHandle();
return pipe;
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = fileName.GetParentDirectory();
runner.RedirectStandardOutput = true;
runner.Start(gitExe, "cat-file", "blob", blobHash);
return runner.StandardOutput;
}
public IDisposable WatchBaseVersionChanges(string fileName, EventHandler callback)
public IDisposable WatchBaseVersionChanges(FileName fileName, EventHandler callback)
{
if (!File.Exists(fileName))
return null;
@ -154,17 +76,18 @@ namespace ICSharpCode.GitAddIn @@ -154,17 +76,18 @@ namespace ICSharpCode.GitAddIn
if (git == null)
return null;
return new BaseVersionChangeWatcher(fileName, GetBlobHash(git, fileName), callback);
return new BaseVersionChangeWatcher(fileName, GetBlobHashAsync(git, fileName).Result, callback);
}
}
class BaseVersionChangeWatcher : IDisposable
{
EventHandler callback;
string fileName, hash;
FileName fileName;
string hash;
RepoChangeWatcher watcher;
public BaseVersionChangeWatcher(string fileName, string hash, EventHandler callback)
public BaseVersionChangeWatcher(FileName fileName, string hash, EventHandler callback)
{
string root = Git.FindWorkingCopyRoot(fileName);
if (root == null)
@ -179,7 +102,7 @@ namespace ICSharpCode.GitAddIn @@ -179,7 +102,7 @@ namespace ICSharpCode.GitAddIn
void HandleChanges()
{
string newHash = GitVersionProvider.GetBlobHash(Git.FindGit(), fileName);
string newHash = GitVersionProvider.GetBlobHashAsync(Git.FindGit(), fileName).Result;
if (newHash != hash) {
LoggingService.Info(fileName + " was changed!");
callback(this, EventArgs.Empty);

22
src/AddIns/VersionControl/GitAddIn/Src/RegisterEventsCommand.cs

@ -32,27 +32,25 @@ namespace ICSharpCode.GitAddIn @@ -32,27 +32,25 @@ namespace ICSharpCode.GitAddIn
AbstractProjectBrowserTreeNode.OnNewNode += TreeNodeCreated;
}
void AddFile(string fileName)
async void AddFile(string fileName)
{
Git.Add(fileName,
exitcode => SD.MainThread.InvokeAsyncAndForget(() => ClearStatusCacheAndEnqueueFile(fileName))
);
await Git.AddAsync(fileName);
ClearStatusCacheAndEnqueueFile(fileName);
}
void RemoveFile(string fileName)
async void RemoveFile(string fileName)
{
if (GitStatusCache.GetFileStatus(fileName) == GitStatus.Added) {
Git.Remove(fileName, true,
exitcode => SD.MainThread.InvokeAsyncAndForget(() => ClearStatusCacheAndEnqueueFile(fileName)));
await Git.RemoveAsync(fileName, true);
ClearStatusCacheAndEnqueueFile(fileName);
}
}
void RenameFile(string sourceFileName, string targetFileName)
async void RenameFile(string sourceFileName, string targetFileName)
{
Git.Add(targetFileName,
exitcode => SD.MainThread.InvokeAsyncAndForget(() => RemoveFile(sourceFileName))
);
SD.MainThread.InvokeAsyncAndForget(() => ClearStatusCacheAndEnqueueFile(targetFileName));
await Git.AddAsync(targetFileName);
ClearStatusCacheAndEnqueueFile(targetFileName);
RemoveFile(sourceFileName);
}
void TreeNodeCreated(object sender, TreeViewEventArgs e)

8
src/AddIns/VersionControl/SubversionAddIn/Src/SvnVersionProvider.cs

@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
using System;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.Svn.Commands;
using SharpSvn;
@ -11,16 +13,18 @@ namespace ICSharpCode.Svn @@ -11,16 +13,18 @@ namespace ICSharpCode.Svn
{
public class SvnVersionProvider : IDocumentVersionProvider
{
public Stream OpenBaseVersion(string fileName)
public Task<Stream> OpenBaseVersionAsync(FileName fileName)
{
return Task.Run(() => {
if (!SvnClientWrapper.IsInSourceControl(fileName))
return null;
using (SvnClientWrapper client = new SvnClientWrapper())
return client.OpenBaseVersion(fileName);
});
}
public IDisposable WatchBaseVersionChanges(string fileName, EventHandler callback)
public IDisposable WatchBaseVersionChanges(FileName fileName, EventHandler callback)
{
return null;
}

5
src/Main/Base/Project/Editor/IDocumentVersionProvider.cs

@ -5,6 +5,7 @@ using System; @@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui;
@ -21,7 +22,7 @@ namespace ICSharpCode.SharpDevelop.Editor @@ -21,7 +22,7 @@ namespace ICSharpCode.SharpDevelop.Editor
/// Provides the BASE-Version for a file. This can be either the file saved
/// to disk or a base version provided by any VCS.
/// </summary>
Stream OpenBaseVersion(string fileName);
Task<Stream> OpenBaseVersionAsync(FileName fileName);
/// <summary>
/// Starts watching for changes to the BASE-version of the specified file.
@ -30,7 +31,7 @@ namespace ICSharpCode.SharpDevelop.Editor @@ -30,7 +31,7 @@ namespace ICSharpCode.SharpDevelop.Editor
/// <returns>Returns a disposable that can be used to stop watching for changes.
/// You must dispose the disposable to prevent a memory leak, the GC will
/// not help out in this case!</returns>
IDisposable WatchBaseVersionChanges(string fileName, EventHandler callback);
IDisposable WatchBaseVersionChanges(FileName fileName, EventHandler callback);
}
public class VersioningServices

1
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -234,6 +234,7 @@ @@ -234,6 +234,7 @@
<DependentUpon>OutputWindowOptionsPanel.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Src\Gui\Pads\CompilerMessageView\MessageViewCategoryTextWriter.cs" />
<Compile Include="Src\Internal\Templates\StringParserPropertyContainer.cs" />
<Compile Include="Src\Internal\Templates\TemplateCategoryComparer.cs" />
<Compile Include="Src\Project\MSBuildConfigurationOrPlatformNameCollection.cs" />

46
src/Main/Base/Project/Src/Commands/MenuItemBuilders.cs

@ -5,7 +5,9 @@ using System; @@ -5,7 +5,9 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@ -229,49 +231,23 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -229,49 +231,23 @@ namespace ICSharpCode.SharpDevelop.Commands
return;
}
if (string.IsNullOrEmpty(args) || args.Trim('"', ' ').Length == 0) {
args = "";
}
try {
if (tool.UseOutputPad) {
ProcessRunner processRunner = new ProcessRunner();
processRunner.LogStandardOutputAndError = false;
processRunner.ProcessExited += ProcessExitEvent;
processRunner.OutputLineReceived += process_OutputLineReceived;
processRunner.ErrorLineReceived += process_OutputLineReceived;
processRunner.WorkingDirectory = StringParser.Parse(tool.InitialDirectory);
if (args == null || args.Length == 0 || args.Trim('"', ' ').Length == 0) {
processRunner.Start(command);
} else {
processRunner.Start(command, args);
}
} else {
ProcessStartInfo startinfo;
if (args == null || args.Length == 0 || args.Trim('"', ' ').Length == 0) {
startinfo = new ProcessStartInfo(command);
processRunner.WorkingDirectory = DirectoryName.Create(StringParser.Parse(tool.InitialDirectory));
if (tool.UseOutputPad) {
processRunner.RunInOutputPadAsync(TaskService.BuildMessageViewCategory, command, ProcessRunner.CommandLineToArgumentArray(args)).FireAndForget();
} else {
startinfo = new ProcessStartInfo(command, args);
}
startinfo.WorkingDirectory = StringParser.Parse(tool.InitialDirectory);
Process process = new Process();
process.StartInfo = startinfo;
process.Start();
processRunner.CreationFlags = ProcessCreationFlags.CreateNewConsole;
processRunner.Start(command, ProcessRunner.CommandLineToArgumentArray(args));
}
} catch (Exception ex) {
MessageService.ShowError("${res:XML.MainMenu.ToolMenu.ExternalTools.ExecutionFailed} '" + command + " " + args + "'\n" + ex.Message);
}
}
void ProcessExitEvent(object sender, EventArgs e)
{
SD.MainThread.InvokeAsyncAndForget(delegate {
ProcessRunner p = (ProcessRunner)sender;
TaskService.BuildMessageViewCategory.AppendLine(StringParser.Parse("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + p.ExitCode));
p.Dispose();
});
}
void process_OutputLineReceived(object sender, LineReceivedEventArgs e)
{
TaskService.BuildMessageViewCategory.AppendLine(e.Line);
}
}
public class OpenContentsMenuBuilder : IMenuItemBuilder

38
src/Main/Base/Project/Src/Gui/Dialogs/ReferenceDialog/ServiceReference/SvcUtilRunner.cs

@ -18,15 +18,18 @@ namespace ICSharpCode.SharpDevelop.Gui.Dialogs.ReferenceDialog.ServiceReference @@ -18,15 +18,18 @@ namespace ICSharpCode.SharpDevelop.Gui.Dialogs.ReferenceDialog.ServiceReference
public int ExitCode { get; private set; }
public void Run()
public async void Run()
{
SvcUtilMessageView.ClearText();
var commandLine = new SvcUtilCommandLine(Options);
commandLine.Command = GetSvcUtilPath();
SvcUtilMessageView.AppendLine(commandLine.ToString());
ProcessRunner runner = CreateProcessRunner();
runner.Start(commandLine.Command, commandLine.Arguments);
using (ProcessRunner processRunner = new ProcessRunner()) {
this.ExitCode = await processRunner.RunInOutputPadAsync(SvcUtilMessageView.Category, commandLine.Command, ProcessRunner.CommandLineToArgumentArray(commandLine.Arguments));
}
if (ProcessExited != null) {
ProcessExited(this, new EventArgs());
}
}
string GetSvcUtilPath()
@ -45,36 +48,9 @@ namespace ICSharpCode.SharpDevelop.Gui.Dialogs.ReferenceDialog.ServiceReference @@ -45,36 +48,9 @@ namespace ICSharpCode.SharpDevelop.Gui.Dialogs.ReferenceDialog.ServiceReference
SvcUtilMessageView.AppendLine(message);
}
ProcessRunner CreateProcessRunner()
{
var runner = new ProcessRunner();
runner.LogStandardOutputAndError = false;
runner.OutputLineReceived += LineReceived;
runner.ErrorLineReceived += LineReceived;
runner.ProcessExited += SvcUtilProcessExited;
return runner;
}
void LineReceived(object sender, LineReceivedEventArgs e)
{
SvcUtilMessageView.AppendLine(e.Line);
}
void SvcUtilProcessExited(object sender, EventArgs e)
{
SvcUtilMessageView.AppendLine("SvcUtil finished.");
var runner = (ProcessRunner)sender;
ExitCode = runner.ExitCode;
SD.MainThread.InvokeAsyncAndForget(() => OnProcessExited());
}
void OnProcessExited()
{
if (ProcessExited != null) {
ProcessExited(this, new EventArgs());
}
}
}
}

42
src/Main/Base/Project/Src/Gui/Pads/CompilerMessageView/MessageViewCategoryTextWriter.cs

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
// 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.SharpDevelop.Gui
{
/// <summary>
/// TextWriter implementation that writes into a MessageViewCategory.
/// </summary>
public class MessageViewCategoryTextWriter : TextWriter
{
readonly MessageViewCategory target;
public MessageViewCategoryTextWriter(MessageViewCategory target)
{
this.target = target;
}
public override Encoding Encoding {
get { return Encoding.Unicode; }
}
public override void Write(char value)
{
target.AppendText(value.ToString());
}
public override void Write(string value)
{
if (value != null)
target.AppendText(value);
}
public override void Write(char[] buffer, int index, int count)
{
target.AppendText(new string(buffer, index, count));
}
}
}

3
src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/DefaultFileNodeCommands.cs

@ -180,7 +180,8 @@ namespace ICSharpCode.SharpDevelop.Project.Commands @@ -180,7 +180,8 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
public static void OpenFolderInExplorer(string directory)
{
if (directory != null && Directory.Exists(directory)) {
Process.Start(directory);
// don't just Process.Start the directory; that causes problems when an .exe with the same name as the directory exists
Process.Start("explorer.exe", "\"" + directory + "\"");
}
}
}

15
src/Main/Base/Project/Util/NativeMethods.cs

@ -7,6 +7,7 @@ using System.Runtime.InteropServices; @@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
namespace ICSharpCode.SharpDevelop
{
@ -27,6 +28,18 @@ namespace ICSharpCode.SharpDevelop @@ -27,6 +28,18 @@ namespace ICSharpCode.SharpDevelop
[DllImport("user32.dll")]
public static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Ansi)]
internal static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeWaitHandle targetHandle, int dwDesiredAccess, bool bInheritHandle, int dwOptions);
internal const int DUPLICATE_SAME_ACCESS = 2;
#region SHFileOperation
enum FO_FUNC : uint
{
@ -94,6 +107,7 @@ namespace ICSharpCode.SharpDevelop @@ -94,6 +107,7 @@ namespace ICSharpCode.SharpDevelop
}
#endregion
#region Get OEM Encoding
[DllImport("kernel32.dll")]
static extern int GetOEMCP();
@ -108,5 +122,6 @@ namespace ICSharpCode.SharpDevelop @@ -108,5 +122,6 @@ namespace ICSharpCode.SharpDevelop
}
}
}
#endregion
}
}

678
src/Main/Base/Project/Util/ProcessRunner.cs

@ -2,288 +2,602 @@ @@ -2,288 +2,602 @@
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Core;
using Microsoft.Win32.SafeHandles;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Util;
namespace ICSharpCode.SharpDevelop
{
[Flags]
public enum ProcessCreationFlags
{
None = 0,
/// <summary>
/// Runs a process that sends output to standard output and to
/// standard error.
/// Creates a new console instead of inheriting the parent console.
/// </summary>
public class ProcessRunner : IDisposable
{
public static Encoding OemEncoding {
get {
return NativeMethods.OemEncoding;
}
CreateNewConsole = 0x00000010,
/// <summary>
/// Launches a console application without a console window.
/// </summary>
CreateNoWindow = 0x08000000
}
Process process;
StringBuilder standardOutput = new StringBuilder();
StringBuilder standardError = new StringBuilder();
ManualResetEventSlim endOfOutput = new ManualResetEventSlim(false);
int outputStreamsFinished;
public interface IProcessRunner : IDisposable
{
Task<int> RunInOutputPadAsync(MessageViewCategory outputCategory, string program, params string[] arguments);
string WorkingDirectory { get; set; }
ProcessCreationFlags CreationFlags { get; set; }
IDictionary<string, string> EnvironmentVariables { get; }
bool RedirectStandardOutput { get; set; }
bool RedirectStandardError { get; set; }
bool RedirectStandardOutputAndErrorToSingleStream { get; set; }
/// <summary>
/// Triggered when the process has exited.
/// </summary>
public event EventHandler ProcessExited;
void Start(string program, params string[] arguments);
void StartCommandLine(string commandLine);
void Kill();
Task WaitForExitAsync();
/// <summary>
/// Triggered when a line of text is read from the standard output.
/// </summary>
public event EventHandler<LineReceivedEventArgs> OutputLineReceived;
Stream StandardOutput { get; }
Stream StandardError { get; }
StreamReader OpenStandardOutputReader();
StreamReader OpenStandardErrorReader();
}
/// <summary>
/// Triggered when a line of text is read from the standard error.
/// Class for starting external processes.
/// Very similar to System.Diagnostics.Process, but supports binary stdout/stderr (not only text),
/// and allows using the same pipe for both stdout and stderr.
///
/// Also, implements an interface to support mocking in unit tests.
/// </summary>
public event EventHandler<LineReceivedEventArgs> ErrorLineReceived;
public class ProcessRunner : IProcessRunner, IDisposable
{
public static Encoding OemEncoding {
get {
return NativeMethods.OemEncoding;
}
}
/// <summary>
/// Creates a new instance of the <see cref="ProcessRunner"/>.
/// </summary>
public ProcessRunner()
#region SafeProcessHandle
[SecurityCritical]
sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// this private ctor is required for SafeHandle implementations
SafeProcessHandle() : base(true)
{
this.LogStandardOutputAndError = true;
}
/// <summary>
/// Gets or sets the process's working directory.
/// </summary>
public string WorkingDirectory { get; set; }
internal SafeProcessHandle(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
/// <summary>
/// Gets or sets whether standard output is logged to the "StandardOutput" and "StandardError"
/// properties. When this property is false, output is still redirected to the
/// OutputLineReceived and ErrorLineReceived events, but the ProcessRunner uses less memory.
/// The default value is true.
/// </summary>
public bool LogStandardOutputAndError { get; set; }
[SecurityCritical]
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}
#endregion
#region Native structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
protected struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public SafePipeHandle hStdInput;
public SafePipeHandle hStdOutput;
public SafePipeHandle hStdError;
}
[StructLayout(LayoutKind.Sequential)]
protected struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
#endregion
#region Native methods
[DllImport("kernel32.dll", EntryPoint = "CreateProcess", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool NativeCreateProcess(
string lpApplicationName,
StringBuilder lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
uint dwCreationFlags,
string lpEnvironment,
string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool TerminateProcess(SafeProcessHandle processHandle, int exitCode);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
[DllImport("kernel32.dll")]
static extern IntPtr LocalFree(IntPtr hMem);
#endregion
#region CommandLine <-> Argument Array
/// <summary>
/// Gets the standard output returned from the process.
/// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules.
/// </summary>
public string StandardOutput {
get {
lock (standardOutput)
return standardOutput.ToString();
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static unsafe string[] CommandLineToArgumentArray(string commandLine)
{
if (string.IsNullOrEmpty(commandLine))
return new string[0];
int numberOfArgs;
char** arr = CommandLineToArgvW(commandLine, out numberOfArgs);
if (arr == null)
throw new Win32Exception();
try {
string[] result = new string[numberOfArgs];
for (int i = 0; i < numberOfArgs; i++) {
result[i] = new string(arr[i]);
}
return result;
} finally {
// Free memory obtained by CommandLineToArgW.
LocalFree(new IntPtr(arr));
}
}
static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' };
/// <summary>
/// Gets the standard error output returned from the process.
/// Escapes a set of arguments according to the CommandLineToArgvW rules.
/// </summary>
public string StandardError {
get {
lock (standardError)
return standardError.ToString();
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static string ArgumentArrayToCommandLine(params string[] arguments)
{
if (arguments == null)
return null;
StringBuilder b = new StringBuilder();
for (int i = 0; i < arguments.Length; i++) {
if (i > 0)
b.Append(' ');
AppendArgument(b, arguments[i]);
}
return b.ToString();
}
/// <summary>
/// Releases resources held by the <see cref="ProcessRunner"/>
/// </summary>
public void Dispose()
static void AppendArgument(StringBuilder b, string arg)
{
process.Dispose();
endOfOutput.Dispose();
if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0) {
b.Append(arg);
} else {
b.Append('"');
for (int j = 0; ; j++) {
int backslashCount = 0;
while (j < arg.Length && arg[j] == '\\') {
backslashCount++;
j++;
}
if (j == arg.Length) {
b.Append('\\', backslashCount * 2);
break;
} else if (arg[j] == '"') {
b.Append('\\', backslashCount * 2 + 1);
b.Append('"');
} else {
b.Append('\\', backslashCount);
b.Append(arg[j]);
}
}
b.Append('"');
}
}
#endregion
#region RunInOutputPad
public async Task<int> RunInOutputPadAsync(MessageViewCategory outputCategory, string program, params string[] arguments)
{
RedirectStandardOutputAndErrorToSingleStream = true;
Start(program, arguments);
StringBuilder printedCommandLine = new StringBuilder();
if (WorkingDirectory != null) {
printedCommandLine.Append(WorkingDirectory);
printedCommandLine.Append("> ");
}
printedCommandLine.Append(CommandLine);
outputCategory.AppendLine(printedCommandLine.ToString());
using (TextReader reader = OpenStandardOutputReader()) {
await reader.CopyToAsync(new MessageViewCategoryTextWriter(outputCategory));
}
await WaitForExitAsync();
outputCategory.AppendLine(StringParser.Parse("${res:XML.MainMenu.ToolMenu.ExternalTools.ExitedWithCode} " + this.ExitCode));
return this.ExitCode;
}
#endregion
#region Start Info Properties
/// <summary>
/// Gets the process exit code.
/// Gets or sets the process's working directory.
/// </summary>
public int ExitCode {
public string WorkingDirectory { get; set; }
ProcessCreationFlags creationFlags = ProcessCreationFlags.CreateNoWindow;
public ProcessCreationFlags CreationFlags {
get { return creationFlags; }
set { creationFlags = value; }
}
IDictionary<string, string> environmentVariables;
public IDictionary<string, string> EnvironmentVariables {
get {
int exitCode = 0;
if (process != null) {
exitCode = process.ExitCode;
if (environmentVariables == null) {
environmentVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry e in Environment.GetEnvironmentVariables()) {
environmentVariables.Add((string)e.Key, (string)e.Value);
}
}
return exitCode;
return environmentVariables;
}
}
public string CommandLine { get; private set; }
public bool RedirectStandardOutput { get; set; }
public bool RedirectStandardError { get; set; }
/// <summary>
/// Waits for the process to exit.
/// Gets whether to use a single stream for both stdout and stderr.
/// </summary>
public void WaitForExit()
public bool RedirectStandardOutputAndErrorToSingleStream { get; set; }
#endregion
#region Start
bool wasStarted;
SafeProcessHandle safeProcessHandle;
public void Start(string program, params string[] arguments)
{
WaitForExit(Int32.MaxValue);
StringBuilder commandLine = new StringBuilder();
AppendArgument(commandLine, program);
if (arguments != null) {
for (int i = 0; i < arguments.Length; i++) {
commandLine.Append(' ');
AppendArgument(commandLine, arguments[i]);
}
}
StartCommandLine(commandLine.ToString());
}
/// <summary>
/// Waits for the process to exit.
/// </summary>
/// <param name="timeout">A timeout in milliseconds.</param>
/// <returns><see langword="true"/> if the associated process has
/// exited; otherwise, <see langword="false"/></returns>
public bool WaitForExit(int timeout)
public void StartCommandLine(string commandLine)
{
if (process == null) {
throw new InvalidOperationException("no process is running");
lock (lockObj) {
if (wasStarted)
throw new InvalidOperationException();
DoStart(commandLine);
}
}
bool exited = process.WaitForExit(timeout);
protected virtual void DoStart(string commandLine)
{
this.CommandLine = commandLine;
if (exited) {
endOfOutput.Wait(timeout == int.MaxValue ? Timeout.Infinite : timeout);
}
const uint STARTF_USESTDHANDLES = 0x00000100;
return exited;
}
const int STD_INPUT_HANDLE = -10;
const int STD_OUTPUT_HANDLE = -11;
const int STD_ERROR_HANDLE = -12;
public bool IsRunning {
get {
bool isRunning = false;
const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
if (process != null) {
isRunning = !process.HasExited;
}
STARTUPINFO startupInfo = new STARTUPINFO();
startupInfo.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFO));
startupInfo.dwFlags = STARTF_USESTDHANDLES;
return isRunning;
// Create pipes
startupInfo.hStdInput = new SafePipeHandle(GetStdHandle(STD_INPUT_HANDLE), ownsHandle: false);
if (RedirectStandardOutput || RedirectStandardOutputAndErrorToSingleStream) {
standardOutput = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
startupInfo.hStdOutput = standardOutput.ClientSafePipeHandle;
} else {
startupInfo.hStdOutput = new SafePipeHandle(GetStdHandle(STD_OUTPUT_HANDLE), ownsHandle: false);
}
if (RedirectStandardOutputAndErrorToSingleStream) {
standardError = standardOutput;
startupInfo.hStdError = standardError.ClientSafePipeHandle;
} else if (RedirectStandardError) {
standardError = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
startupInfo.hStdError = standardError.ClientSafePipeHandle;
} else {
startupInfo.hStdError = new SafePipeHandle(GetStdHandle(STD_ERROR_HANDLE), ownsHandle: false);
}
Dictionary<string, string> environmentVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
uint flags = (uint)this.CreationFlags;
public Dictionary<string, string> EnvironmentVariables {
get { return environmentVariables; }
string environmentBlock = null;
if (environmentVariables != null) {
environmentBlock = BuildEnvironmentBlock(environmentVariables);
flags |= CREATE_UNICODE_ENVIRONMENT;
}
/// <summary>
/// Starts the process.
/// </summary>
/// <param name="command">The process filename.</param>
/// <param name="arguments">The command line arguments to
/// pass to the command.</param>
public void Start(string command, string arguments)
{
Encoding encoding = OemEncoding;
process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = command;
process.StartInfo.WorkingDirectory = WorkingDirectory;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.StandardOutputEncoding = encoding;
process.OutputDataReceived += OnOutputLineReceived;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.StandardErrorEncoding = encoding;
process.ErrorDataReceived += OnErrorLineReceived;
process.StartInfo.UseShellExecute = false;
process.StartInfo.Arguments = arguments;
foreach (var pair in environmentVariables)
process.StartInfo.EnvironmentVariables.Add(pair.Key, pair.Value);
if (ProcessExited != null) {
process.EnableRaisingEvents = true;
process.Exited += OnProcessExited;
}
bool started = false;
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
try {
process.Start();
started = true;
CreateProcess(null, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, true, flags, environmentBlock, WorkingDirectory, ref startupInfo, out processInfo);
wasStarted = true;
} finally {
if (!started) {
process.Exited -= OnProcessExited;
process = null;
if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != new IntPtr(-1)) {
safeProcessHandle = new SafeProcessHandle(processInfo.hProcess);
}
if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != new IntPtr(-1)) {
NativeMethods.CloseHandle(processInfo.hThread);
}
// Dispose the client side handles of the pipe.
// They got copied into the new process, we don't need our local copies anymore.
startupInfo.hStdInput.Dispose();
startupInfo.hStdOutput.Dispose();
startupInfo.hStdError.Dispose();
if (!wasStarted) {
// In case of error, dispose the server side of the pipes as well
if (standardOutput != null) {
standardOutput.Dispose();
standardOutput = null;
}
if (standardError != null) {
standardError.Dispose();
standardError = null;
}
}
}
//StartStreamCopyAfterProcessCreation();
}
static string BuildEnvironmentBlock(IEnumerable<KeyValuePair<string, string>> environment)
{
StringBuilder b = new StringBuilder();
foreach (var pair in environment.OrderBy(p => p.Key, StringComparer.OrdinalIgnoreCase)) {
b.Append(pair.Key);
b.Append('=');
b.Append(pair.Value);
b.Append('\0');
}
b.Append('\0');
return b.ToString();
}
protected virtual void CreateProcess(
string lpApplicationName,
StringBuilder lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
string lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation)
{
if (!NativeCreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags,
lpEnvironment, lpCurrentDirectory, ref lpStartupInfo, out lpProcessInformation)) {
throw new Win32Exception();
}
}
#endregion
public void Dispose()
{
if (safeProcessHandle != null)
safeProcessHandle.Dispose();
if (standardOutput != null)
standardOutput.Dispose();
if (standardError != null)
standardError.Dispose();
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();
#region HasExited / ExitCode / Kill
public bool HasExited {
get { return WaitForExit(0); }
}
/// <summary>
/// Starts the process.
/// Gets the process exit code.
/// </summary>
/// <param name="command">The process filename.</param>
public void Start(string command)
{
Start(command, String.Empty);
public int ExitCode {
get {
if (!WaitForExit(0))
throw new InvalidOperationException("Process has not yet exited");
return exitCode; // WaitForExit has the side effect of setting exitCode
}
}
/// <summary>
/// Kills the running process.
/// Sends the kill signal to the process.
/// Does not wait for the process to complete to exit after being killed.
/// </summary>
public void Kill()
{
if (process != null) {
if (!process.HasExited) {
try {
process.Kill();
} catch (InvalidOperationException) {
// race condition (if the process has already exited)
if (!wasStarted)
throw new InvalidOperationException("Process was not started");
if (!TerminateProcess(safeProcessHandle, -1)) {
int err = Marshal.GetLastWin32Error();
// If TerminateProcess fails, maybe it's because the process has already exited.
if (!WaitForExit(0))
throw new Win32Exception(err);
}
// don't call process.Dispose() here - that causes the OnProcessExited
// event not to fire correctly
}
endOfOutput.Wait();
#endregion
#region WaitForExit
sealed class ProcessWaitHandle : WaitHandle
{
public ProcessWaitHandle(SafeProcessHandle processHandle)
{
var currentProcess = new HandleRef(this, NativeMethods.GetCurrentProcess());
SafeWaitHandle safeWaitHandle;
if (!NativeMethods.DuplicateHandle(currentProcess, processHandle, currentProcess, out safeWaitHandle, 0, false, 2)) {
throw new Win32Exception();
}
base.SafeWaitHandle = safeWaitHandle;
}
}
/// <summary>
/// Raises the <see cref="ProcessExited"/> event.
/// </summary>
protected void OnProcessExited(object sender, EventArgs e)
bool hasExited;
int exitCode;
public void WaitForExit()
{
if (ProcessExited != null) {
if (endOfOutput != null) {
endOfOutput.Wait();
WaitForExit(Timeout.Infinite);
}
ProcessExited(this, e);
}
}
public bool WaitForExit(int millisecondsTimeout)
{
if (hasExited)
return true;
if (!wasStarted)
throw new InvalidOperationException("Process was not yet started");
if (safeProcessHandle.IsClosed)
throw new ObjectDisposedException("ProcessRunner");
using (var waitHandle = new ProcessWaitHandle(safeProcessHandle)) {
if (waitHandle.WaitOne(millisecondsTimeout, false)) {
if (!GetExitCodeProcess(safeProcessHandle, out exitCode))
throw new Win32Exception();
// Wait until the output is processed
// if (standardOutputTask != null)
// standardOutputTask.Wait();
// if (standardErrorTask != null)
// standardErrorTask.Wait();
hasExited = true;
}
}
return hasExited;
}
readonly object lockObj = new object();
TaskCompletionSource<object> waitForExitTCS;
ProcessWaitHandle waitForExitAsyncWaitHandle;
RegisteredWaitHandle waitForExitAsyncRegisteredWaitHandle;
/// <summary>
/// Raises the <see cref="OutputLineReceived"/> event.
/// Asynchronously waits for the process to exit.
/// </summary>
/// <param name="sender">The event source.</param>
/// <param name="e">The line received event arguments.</param>
protected void OnOutputLineReceived(object sender, DataReceivedEventArgs e)
public Task WaitForExitAsync()
{
if (e.Data == null) {
if (Interlocked.Increment(ref outputStreamsFinished) == 2)
endOfOutput.Set();
return;
if (hasExited)
return Task.FromResult(true);
if (!wasStarted)
throw new InvalidOperationException("Process was not yet started");
if (safeProcessHandle.IsClosed)
throw new ObjectDisposedException("ProcessRunner");
lock (lockObj) {
if (waitForExitTCS == null) {
waitForExitTCS = new TaskCompletionSource<object>();
waitForExitAsyncWaitHandle = new ProcessWaitHandle(safeProcessHandle);
waitForExitAsyncRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitForExitAsyncWaitHandle, WaitForExitAsyncCallback, null, -1, true);
}
return waitForExitTCS.Task;
}
if (LogStandardOutputAndError) {
lock (standardOutput) {
standardOutput.AppendLine(e.Data);
}
void WaitForExitAsyncCallback(object context, bool wasSignaled)
{
waitForExitAsyncRegisteredWaitHandle.Unregister(null);
waitForExitAsyncRegisteredWaitHandle = null;
waitForExitAsyncWaitHandle.Close();
waitForExitAsyncWaitHandle = null;
// Wait until the output is processed
// if (standardOutputTask != null)
// await standardOutputTask;
// if (standardErrorTask != null)
// await standardErrorTask;
waitForExitTCS.SetResult(null);
}
#endregion
#region StandardOutput/StandardError
AnonymousPipeServerStream standardOutput;
AnonymousPipeServerStream standardError;
public Stream StandardOutput {
get {
if (standardOutput == null)
throw new InvalidOperationException(wasStarted ? "stdout was not redirected" : "Process not yet started");
return standardOutput;
}
}
if (OutputLineReceived != null) {
OutputLineReceived(this, new LineReceivedEventArgs(e.Data));
public Stream StandardError {
get {
if (standardError == null)
throw new InvalidOperationException(wasStarted ? "stderr was not redirected" : "Process not yet started");
return standardError;
}
}
/// <summary>
/// Raises the <see cref="ErrorLineReceived"/> event.
/// Opens a text reader around the standard output.
/// </summary>
/// <param name="sender">The event source.</param>
/// <param name="e">The line received event arguments.</param>
protected void OnErrorLineReceived(object sender, DataReceivedEventArgs e)
public StreamReader OpenStandardOutputReader()
{
if (e.Data == null) {
if (Interlocked.Increment(ref outputStreamsFinished) == 2)
endOfOutput.Set();
return;
}
if (LogStandardOutputAndError) {
lock (standardError) {
standardError.AppendLine(e.Data);
}
}
if (ErrorLineReceived != null) {
ErrorLineReceived(this, new LineReceivedEventArgs(e.Data));
return new StreamReader(this.StandardOutput, OemEncoding);
}
/// <summary>
/// Opens a text reader around the standard error.
/// </summary>
public StreamReader OpenStandardErrorReader()
{
return new StreamReader(this.StandardError, OemEncoding);
}
#endregion
}
}

22
src/Main/Base/Project/Util/ReactiveExtensions.cs

@ -258,6 +258,28 @@ namespace ICSharpCode.SharpDevelop @@ -258,6 +258,28 @@ namespace ICSharpCode.SharpDevelop
}
#endregion
#region ForEach
/// <summary>
/// Subscribes to the observable and runs an action for each element.
/// </summary>
public static async Task ForEachAsync<T>(this IObservable<T> source, Action<T> action)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
using (source.Subscribe(delegate (T item) {
try {
action(item);
} catch (Exception ex) {
tcs.TrySetException(ex);
}
},
exception => tcs.TrySetException(exception),
() => tcs.TrySetResult(null)))
{
await tcs.Task.ConfigureAwait(false);
}
}
#endregion
#region AnonymousObserver
public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> onNext, Action<Exception> onError, Action onCompleted)
{

9
src/Main/Base/Project/Util/SharpDevelopExtensions.cs

@ -785,6 +785,15 @@ namespace ICSharpCode.SharpDevelop @@ -785,6 +785,15 @@ namespace ICSharpCode.SharpDevelop
return h;
}
}
public static async Task CopyToAsync(this TextReader reader, TextWriter writer)
{
char[] buffer = new char[2048];
int read;
while ((read = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) {
writer.Write(buffer, 0, read);
}
}
#endregion
#region Service Provider Extensions

60
src/Main/Base/Test/AbstractEntityIsOverridableTestFixture.cs

@ -1,60 +0,0 @@ @@ -1,60 +0,0 @@
#warning
//// 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 ICSharpCode.SharpDevelop.Dom;
//using ICSharpCode.SharpDevelop.Tests.Utils;
//using NUnit.Framework;
//
//namespace ICSharpCode.SharpDevelop.Tests
//{
// /// <summary>
// /// Tests that the IsOverridable property returns the expected value.
// /// </summary>
// [TestFixture]
// public class AbstractEntityIsOverridableTestFixture
// {
// MockEntity entity;
//
// [SetUp]
// public void SetUp()
// {
// entity = new MockEntity();
// }
//
// [Test]
// public void NotOverridableByDefault()
// {
// Assert.IsFalse(entity.IsOverridable);
// }
//
// [Test]
// public void IsOverrideSet()
// {
// entity.Modifiers = ModifierEnum.Override;
// Assert.IsTrue(entity.IsOverridable);
// }
//
// [Test]
// public void IsVirtualSet()
// {
// entity.Modifiers = ModifierEnum.Virtual;
// Assert.IsTrue(entity.IsOverridable);
// }
//
// [Test]
// public void IsAbstractSet()
// {
// entity.Modifiers = ModifierEnum.Abstract;
// Assert.IsTrue(entity.IsOverridable);
// }
//
// [Test]
// public void IsAbstractAndSealedSet()
// {
// entity.Modifiers = ModifierEnum.Abstract | ModifierEnum.Sealed;
// Assert.IsFalse(entity.IsOverridable);
// }
// }
//}

46
src/Main/Base/Test/CheckAssemblyFlags.cs

@ -1,46 +0,0 @@ @@ -1,46 +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 ICSharpCode.Core;
using NUnit.Framework;
using System.Text.RegularExpressions;
namespace ICSharpCode.SharpDevelop.Tests
{
[TestFixture]
public class CheckAssemblyFlags
{
string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..");
bool Get32BitFlags(string assembly)
{
assembly = Path.Combine(binPath, assembly);
string corflags = FileUtility.GetSdkPath("corflags.exe");
Assert.IsNotNull(corflags, "corflags.exe not found, this test requires the .NET SDK!");
ProcessRunner pr = new ProcessRunner();
Console.WriteLine(corflags + " \"" + assembly + "\"");
pr.Start(corflags, "\"" + assembly + "\"");
if (!pr.WaitForExit(5000)) {
pr.Kill();
throw new InvalidOperationException("Timeout running corflags");
} else {
Console.WriteLine(pr.StandardOutput);
Match m = Regex.Match(pr.StandardOutput, @"32BIT(?:REQ)?\s*:\s*([01])");
if (m.Success) {
return m.Groups[1].Value == "1";
} else {
throw new InvalidOperationException("Invalid corflags output");
}
}
}
// All these processes must use the same value for 32-bit-flag:
[Test]
public void CheckSharpDevelop32Bit()
{
Assert.IsTrue(Get32BitFlags("SharpDevelop.exe"));
}
}
}

20
src/Main/Base/Test/ICSharpCode.SharpDevelop.Tests.csproj

@ -74,9 +74,7 @@ @@ -74,9 +74,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AbstractEntityIsOverridableTestFixture.cs" />
<Compile Include="AssemblyInfo.cs" />
<Compile Include="CheckAssemblyFlags.cs" />
<Compile Include="CodeConverterTests.cs" />
<Compile Include="Dom\CSharpModelTests.cs" />
<Compile Include="Dom\ProjectEntityModelContextTests.cs" />
@ -90,6 +88,13 @@ @@ -90,6 +88,13 @@
<Compile Include="OutputTextLineParserTests.cs" />
<Compile Include="OverridableMethodsTestFixture.cs" />
<Compile Include="OverridablePropertiesTestFixture.cs" />
<Compile Include="ProcessRunner\CancelLongRunningAppTestFixture.cs" />
<Compile Include="ProcessRunner\ConsoleAppTestFixtureBase.cs" />
<Compile Include="ProcessRunner\ExitCodeTestFixture.cs" />
<Compile Include="ProcessRunner\NoSuchExecutableTestFixture.cs" />
<Compile Include="ProcessRunner\ProcessExitedTestFixture.cs" />
<Compile Include="ProcessRunner\ProcessRunnerNotStartedTestFixture.cs" />
<Compile Include="ProcessRunner\StandardOutputFromProcessTestFixture.cs" />
<Compile Include="Project\BeforeBuildCustomToolProjectItemsTests.cs" />
<Compile Include="Project\MockSolution.cs" />
<Compile Include="Project\ProjectCustomToolOptionsTests.cs" />
@ -151,14 +156,6 @@ @@ -151,14 +156,6 @@
<Compile Include="Templates\CategorySortOrderTests.cs" />
<Compile Include="Templates\FileTemplateCategoryComparerTests.cs" />
<Compile Include="WebReferences\HttpAuthenticationHeaderTests.cs" />
<Compile Include="StandardOutputFromProcessTestFixture.cs" />
<Compile Include="CancelLongRunningAppTestFixture.cs" />
<Compile Include="ExitCodeTestFixture.cs" />
<Compile Include="LineReceivedFromProcessTestFixture.cs" />
<Compile Include="NoSuchExecutableTestFixture.cs" />
<Compile Include="ProcessExitedTestFixture.cs" />
<Compile Include="ProcessRunnerNotStartedTestFixture.cs" />
<Compile Include="ConsoleAppTestFixtureBase.cs" />
<Compile Include="NavigationServiceTests\INavigationPointTextFixture.cs" />
<Compile Include="NavigationServiceTests\TestNavigationPoint.cs" />
<Compile Include="NavigationServiceTests\NavigationServiceTestFixture.cs" />
@ -197,5 +194,8 @@ @@ -197,5 +194,8 @@
<EmbeddedResource Include="mime_utf-16_be_test.txt" />
<EmbeddedResource Include="mime_utf-16_le_test.txt" />
</ItemGroup>
<ItemGroup>
<Folder Include="ProcessRunner" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
</Project>

51
src/Main/Base/Test/LineReceivedFromProcessTestFixture.cs

@ -1,51 +0,0 @@ @@ -1,51 +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 NUnit.Framework;
using System;
using System.Collections;
using System.IO;
namespace ICSharpCode.SharpDevelop.Tests
{
/// <summary>
/// Tests the <see cref="ProcessRunner.LineReceived"/> event.
/// </summary>
[TestFixture]
public class LineReceivedFromProcessTestFixture : ConsoleAppTestFixtureBase
{
ProcessRunner runner;
ArrayList lines;
[SetUp]
public void Init()
{
lines = new ArrayList();
runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
}
[Test]
public void SingleLineOutput()
{
string echoText = "Test";
string expectedOutput = String.Concat(echoText, "\r\n");
runner.OutputLineReceived += OutputLineReceived;
runner.Start(GetConsoleAppFileName(), String.Concat("-echo:", echoText));
runner.WaitForExit();
Assert.AreEqual(0, runner.ExitCode, "Exit code should be zero.");
Assert.AreEqual(expectedOutput, runner.StandardOutput, "Should have some output.");
Assert.AreEqual(String.Empty, runner.StandardError, "Should not be any error output.");
Assert.AreEqual(1, lines.Count, "Should only have one output line.");
Assert.AreEqual(echoText, lines[0], "Line received is incorrect.");
}
void OutputLineReceived(object sender, LineReceivedEventArgs e)
{
lines.Add(e.Line);
}
}
}

74
src/Main/Base/Test/ProcessExitedTestFixture.cs

@ -1,74 +0,0 @@ @@ -1,74 +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.Threading;
using NUnit.Framework;
namespace ICSharpCode.SharpDevelop.Tests
{
[TestFixture]
//[Ignore("Ignoring since need to run ConsoleApp.exe")]
public class ProcessExitedTestFixture : ConsoleAppTestFixtureBase
{
/// <summary>
/// Stores standard output received by the ProcessExit event.
/// </summary>
string standardOutput;
/// <summary>
/// Stores standard error received by the ProcessExit event.
/// </summary>
string standardError;
/// <summary>
/// Stores exit code received by the ProcessExit event.
/// </summary>
int exitCode = -1;
/// <summary>
/// Event that will be fired when the ProcessExit event occurs.
/// </summary>
AutoResetEvent exitEvent;
/// <summary>
/// Tests the Runner.ProcessExit event works.
/// </summary>
[Test]
public void ProcessExitEvent()
{
exitEvent = new AutoResetEvent(false);
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
string echoText = "Test";
string expectedOutput = String.Concat(echoText, "\r\n");
runner.ProcessExited += new EventHandler(OnProcessExited);
runner.Start(GetConsoleAppFileName(), String.Concat("-echo:", echoText));
bool exited = exitEvent.WaitOne(2500, true);
Assert.IsTrue(exited, "Timed out waiting for exit event.");
Assert.AreEqual(0, exitCode, "Exit code should be zero.");
Assert.AreEqual(expectedOutput, standardOutput, "Should have some output.");
Assert.AreEqual(String.Empty, standardError, "Should not be any error output.");
}
/// <summary>
/// Handles the ProcessExited event.
/// </summary>
void OnProcessExited(object sender, EventArgs e)
{
ProcessRunner runner = (ProcessRunner)sender;
exitCode = runner.ExitCode;
standardOutput = runner.StandardOutput;
standardError = runner.StandardError;
exitEvent.Set();
}
}
}

6
src/Main/Base/Test/CancelLongRunningAppTestFixture.cs → src/Main/Base/Test/ProcessRunner/CancelLongRunningAppTestFixture.cs

@ -42,7 +42,7 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -42,7 +42,7 @@ namespace ICSharpCode.SharpDevelop.Tests
public void Init()
{
runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
}
[Test]
@ -57,9 +57,9 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -57,9 +57,9 @@ namespace ICSharpCode.SharpDevelop.Tests
Process[] runningProcesses = Process.GetProcessesByName(processName);
Assert.AreEqual(1, runningProcesses.Length, "Process is not running.");
Assert.IsTrue(runner.IsRunning, "IsRunning should be true.");
//Assert.IsTrue(runner.IsRunning, "IsRunning should be true.");
runner.Kill();
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
//Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
// Check console app has been shutdown.
runningProcesses = Process.GetProcessesByName(processName);

5
src/Main/Base/Test/ConsoleAppTestFixtureBase.cs → src/Main/Base/Test/ProcessRunner/ConsoleAppTestFixtureBase.cs

@ -2,14 +2,15 @@ @@ -2,14 +2,15 @@
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using ICSharpCode.Core;
namespace ICSharpCode.SharpDevelop.Tests
{
public class ConsoleAppTestFixtureBase
{
public static string GetConsoleAppFileName()
public static FileName GetConsoleAppFileName()
{
return typeof(ICSharpCode.NAntAddIn.Tests.ConsoleApp.ConsoleApp).Assembly.Location;
return FileName.Create(typeof(ICSharpCode.NAntAddIn.Tests.ConsoleApp.ConsoleApp).Assembly.Location);
}
}
}

6
src/Main/Base/Test/ExitCodeTestFixture.cs → src/Main/Base/Test/ProcessRunner/ExitCodeTestFixture.cs

@ -19,17 +19,17 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -19,17 +19,17 @@ namespace ICSharpCode.SharpDevelop.Tests
public void NonZeroExitCode()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
int expectedExitCode = 1;
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
//Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
runner.Start(GetConsoleAppFileName(), String.Concat("-exitcode:", expectedExitCode.ToString()));
runner.WaitForExit();
Assert.AreEqual(expectedExitCode, runner.ExitCode, "Exit code is incorrect.");
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
//Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
}
}
}

4
src/Main/Base/Test/NoSuchExecutableTestFixture.cs → src/Main/Base/Test/ProcessRunner/NoSuchExecutableTestFixture.cs

@ -27,8 +27,8 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -27,8 +27,8 @@ namespace ICSharpCode.SharpDevelop.Tests
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
// "Cannot start process because a file name has not been provided.")]
[ExpectedException(typeof(Win32Exception))]
// "The parameter is incorrect.")]
// - Message depends on system language.
public void RunBlankProcessFilename()
{

34
src/Main/Base/Test/ProcessRunner/ProcessExitedTestFixture.cs

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
// 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.Threading;
using NUnit.Framework;
namespace ICSharpCode.SharpDevelop.Tests
{
[TestFixture]
//[Ignore("Ignoring since need to run ConsoleApp.exe")]
public class ProcessExitedTestFixture : ConsoleAppTestFixtureBase
{
/// <summary>
/// Tests the Runner.ProcessExit event works.
/// </summary>
[Test]
public void ProcessExitEvent()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
string echoText = "Test";
runner.Start(GetConsoleAppFileName(), String.Concat("-echo:", echoText));
bool exited = runner.WaitForExitAsync().Wait(2500);
Assert.IsTrue(exited, "Timed out waiting for exit event.");
Assert.AreEqual(0, runner.ExitCode, "Exit code should be zero.");
}
}
}

19
src/Main/Base/Test/ProcessRunnerNotStartedTestFixture.cs → src/Main/Base/Test/ProcessRunner/ProcessRunnerNotStartedTestFixture.cs

@ -11,7 +11,6 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -11,7 +11,6 @@ namespace ICSharpCode.SharpDevelop.Tests
/// correctly if it has not been started.
/// </summary>
[TestFixture]
//[Ignore("Ignoring since need to run ConsoleApp.exe")]
public class ProcessRunnerNotStartedTestFixture
{
ProcessRunner runner;
@ -23,28 +22,24 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -23,28 +22,24 @@ namespace ICSharpCode.SharpDevelop.Tests
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void ExitCode()
{
Assert.AreEqual(0, runner.ExitCode, "Exit code should be zero.");
}
[Test]
public void StandardOutput()
{
Assert.AreEqual(String.Empty, runner.StandardOutput, "Standard output should be empty.");
int exit = runner.ExitCode;
}
[Test]
public void StandardError()
[ExpectedException(typeof(InvalidOperationException))]
public void WaitForExit()
{
Assert.AreEqual(String.Empty, runner.StandardError, "Standard error should be empty.");
runner.WaitForExit();
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void WaitForExit()
public void WaitForExitAsync()
{
runner.WaitForExit();
runner.WaitForExitAsync();
}
}
}

123
src/Main/Base/Test/StandardOutputFromProcessTestFixture.cs → src/Main/Base/Test/ProcessRunner/StandardOutputFromProcessTestFixture.cs

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
@ -14,28 +15,8 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -14,28 +15,8 @@ namespace ICSharpCode.SharpDevelop.Tests
/// Runs a process that returns some text on the standard output.
/// </summary>
[TestFixture]
//[Ignore("Ignoring since need to run ConsoleApp.exe")]
public class StandardOutputFromProcessTestFixture : ConsoleAppTestFixtureBase
{
int preTestThreadCount;
/// <summary>
/// Initialises each test.
/// </summary>
[SetUp]
public void Init()
{
GetPreTestThreadCount();
}
[TearDown]
public void TearDown()
{
//If we use Output.ReadLine then ThreadPoolCompletion Port threads are
//used rendering a threadcount test useless.
//CheckPostTestThreadCount();
}
/// <summary>
/// Runs a process expecting a single line of output.
/// </summary>
@ -43,17 +24,15 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -43,17 +24,15 @@ namespace ICSharpCode.SharpDevelop.Tests
public void SingleLineOfOutput()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
string echoText = "Test";
string expectedOutput = String.Concat(echoText, "\r\n");
string expectedOutput = "Test\r\n";
runner.RedirectStandardOutput = true;
runner.Start(GetConsoleAppFileName(), String.Concat("-echo:", echoText));
runner.WaitForExit();
string output = runner.OpenStandardOutputReader().ReadToEnd();
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
Assert.AreEqual(0, runner.ExitCode, "Exit code is incorrect.");
Assert.AreEqual(expectedOutput, runner.StandardOutput, "Should have some output.");
Assert.AreEqual(String.Empty, runner.StandardError, "Should not be any error output.");
Assert.AreEqual(expectedOutput, output, "Should have some output.");
}
/// <summary>
@ -63,17 +42,20 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -63,17 +42,20 @@ namespace ICSharpCode.SharpDevelop.Tests
public void NoOutput()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
runner.RedirectStandardOutput = true;
runner.RedirectStandardError = true;
runner.Start(GetConsoleAppFileName());
runner.WaitForExit();
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
//Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
Assert.AreEqual(0, runner.ExitCode, "Exit code is incorrect.");
Assert.AreEqual(String.Empty, runner.StandardOutput, "Should not be any output.");
Assert.AreEqual(String.Empty, runner.StandardError, "Should not be any error output.");
Assert.AreEqual("", runner.OpenStandardOutputReader().ReadToEnd(), "Should not be any output.");
Assert.AreEqual("", runner.OpenStandardErrorReader().ReadToEnd(), "Should not be any error output.");
}
/*
/// <summary>
/// The process that is run tries to send such a large amount of
/// data that it fills the standard output's buffer.
@ -82,20 +64,19 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -82,20 +64,19 @@ namespace ICSharpCode.SharpDevelop.Tests
public void LargeAmountOfOutput()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
string filename = "test.txt";
string fullFilename = Path.Combine(runner.WorkingDirectory, filename);
try
{
string outputText = GetOutputText();
CreateTextFile(fullFilename, outputText);
IEnumerable<string> outputText = GetOutputText();
File.WriteAllLines(fullFilename, outputText);
runner.Start(GetConsoleAppFileName(), String.Concat("-file:", filename));
bool exited = runner.WaitForExit(5000);
bool exited = runner.WaitForExitAsync().Wait(5000);
Assert.IsTrue(exited, "App did not exit.");
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
Assert.AreEqual(0, runner.ExitCode, "Exit code is incorrect.");
Assert.AreEqual(outputText, runner.StandardOutput, "Should have some output.");
Assert.AreEqual(String.Empty, runner.StandardError, "Should not be any error output.");
@ -116,20 +97,19 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -116,20 +97,19 @@ namespace ICSharpCode.SharpDevelop.Tests
public void LargeAmountOfErrorOutput()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
string filename = "test.txt";
string fullFilename = Path.Combine(runner.WorkingDirectory, filename);
try
{
string outputText = GetOutputText();
CreateTextFile(fullFilename, outputText);
IEnumerable<string> outputText = GetOutputText();
File.WriteAllLines(fullFilename, outputText);
runner.Start(GetConsoleAppFileName(), String.Concat("-error.file:", filename));
bool exited = runner.WaitForExit(5000);
bool exited = runner.WaitForExitAsync().Wait(5000);
Assert.IsTrue(exited, "App did not exit.");
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
Assert.AreEqual(0, runner.ExitCode, "Exit code is incorrect.");
Assert.AreEqual(String.Empty, runner.StandardOutput, "Should not be any output.");
Assert.AreEqual(outputText, runner.StandardError, "Should have some error output.");
@ -141,7 +121,7 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -141,7 +121,7 @@ namespace ICSharpCode.SharpDevelop.Tests
}
}
}
*/
/// <summary>
/// Runs a process expecting a single line of output written
/// to standard error.
@ -150,31 +130,15 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -150,31 +130,15 @@ namespace ICSharpCode.SharpDevelop.Tests
public void SingleLineOfErrorOutput()
{
ProcessRunner runner = new ProcessRunner();
runner.WorkingDirectory = Path.GetDirectoryName(GetConsoleAppFileName());
runner.WorkingDirectory = GetConsoleAppFileName().GetParentDirectory();
string echoText = "Test";
string expectedOutput = String.Concat(echoText, "\r\n");
string expectedOutput = "Test\r\n";
runner.RedirectStandardError = true;
runner.Start(GetConsoleAppFileName(), String.Concat("-error.echo:", echoText));
runner.WaitForExit();
Assert.AreEqual(0, runner.ExitCode, "Exit code is incorrect.");
Assert.IsFalse(runner.IsRunning, "IsRunning should be false.");
Assert.AreEqual(String.Empty, runner.StandardOutput, "Should not be any output.");
Assert.AreEqual(expectedOutput, runner.StandardError, "Should have some error output.");
}
/// <summary>
/// Creates a UTF8 text file.
/// </summary>
/// <param name="filename">The filename of the text file.</param>
/// <param name="outputText">The text to store in the file.</param>
void CreateTextFile(string filename, string outputText)
{
FileStream stream = File.Create(filename);
string output = runner.OpenStandardErrorReader().ReadToEnd();
byte[] bytes = UnicodeEncoding.UTF8.GetBytes(outputText);
stream.Write(bytes, 0, bytes.Length);
stream.Close();
Assert.AreEqual(expectedOutput, output, "Should have some output.");
}
/// <summary>
@ -182,36 +146,13 @@ namespace ICSharpCode.SharpDevelop.Tests @@ -182,36 +146,13 @@ namespace ICSharpCode.SharpDevelop.Tests
/// attempt to send us back.
/// </summary>
/// <returns></returns>
string GetOutputText()
IEnumerable<string> GetOutputText()
{
StringBuilder outputText = new StringBuilder();
List<string> outputText = new List<string>();
for (int i = 0; i < 300; ++i) {
outputText.Append("i=");
outputText.Append(i.ToString());
outputText.Append(" A line of text.\r\n");
}
return outputText.ToString();
outputText.Add("i=" + i.ToString() + " A line of text.");
}
/// <summary>
/// Reads the number of threads before the test is run.
/// </summary>
void GetPreTestThreadCount()
{
Process process = Process.GetCurrentProcess();
preTestThreadCount = process.Threads.Count;
}
/// <summary>
/// Reads the number of threads after the test is run.
/// </summary>
void CheckPostTestThreadCount()
{
GC.Collect();
int postThreadCount = Process.GetCurrentProcess().Threads.Count;
Assert.AreEqual(preTestThreadCount, postThreadCount, "Thread count mismatch.");
return outputText;
}
}
}
Loading…
Cancel
Save