@@ -26,37 +26,114 @@ public TaskHostFactory_Tests(ITestOutputHelper testOutputHelper)
2626 _output = testOutputHelper ;
2727 }
2828
29- [ Fact ]
30- public void TaskNodesDieAfterBuild ( )
29+ [ Theory ]
30+ [ InlineData ( true , false ) ]
31+ [ InlineData ( false , true ) ]
32+ [ InlineData ( true , true ) ]
33+ public void TaskNodesDieAfterBuild ( bool taskHostFactorySpecified , bool envVariableSpecified )
3134 {
3235 using ( TestEnvironment env = TestEnvironment . Create ( ) )
3336 {
37+
38+ string taskFactory = taskHostFactorySpecified ? "TaskHostFactory" : "AssemblyTaskFactory" ;
3439 string pidTaskProject = $@ "
3540<Project>
36- <UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""TaskHostFactory "" />
41+ <UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""{ taskFactory } "" />
3742 <Target Name='AccessPID'>
3843 <ProcessIdTask>
3944 <Output PropertyName=""PID"" TaskParameter=""Pid"" />
4045 </ProcessIdTask>
4146 </Target>
4247</Project>" ;
4348 TransientTestFile project = env . CreateFile ( "testProject.csproj" , pidTaskProject ) ;
49+
50+ if ( envVariableSpecified )
51+ {
52+ env . SetEnvironmentVariable ( "MSBUILDFORCEALLTASKSOUTOFPROC" , "1" ) ;
53+ }
4454 ProjectInstance projectInstance = new ( project . Path ) ;
55+
4556 projectInstance . Build ( ) . ShouldBeTrue ( ) ;
4657 string processId = projectInstance . GetPropertyValue ( "PID" ) ;
4758 string . IsNullOrEmpty ( processId ) . ShouldBeFalse ( ) ;
4859 Int32 . TryParse ( processId , out int pid ) . ShouldBeTrue ( ) ;
49- Process . GetCurrentProcess ( ) . Id . ShouldNotBe < int > ( pid ) ;
50- try
60+ Process . GetCurrentProcess ( ) . Id . ShouldNotBe ( pid ) ;
61+
62+ if ( taskHostFactorySpecified )
5163 {
64+ try
65+ {
66+ Process taskHostNode = Process . GetProcessById ( pid ) ;
67+ taskHostNode . WaitForExit ( 3000 ) . ShouldBeTrue ( $ "The executed MSBuild Version: { projectInstance . GetProperty ( "MSBuildVersion" ) } ") ;
68+ }
69+ // We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
70+ catch ( ArgumentException e )
71+ {
72+ e . Message . ShouldBe ( $ "Process with an Id of { pid } is not running.") ;
73+ }
74+ }
75+ else
76+ {
77+ // This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves.
5278 Process taskHostNode = Process . GetProcessById ( pid ) ;
53- taskHostNode . WaitForExit ( 2000 ) . ShouldBeTrue ( ) ;
79+ taskHostNode . WaitForExit ( 3000 ) . ShouldBeFalse ( $ "The executed MSBuild Version: { projectInstance . GetProperty ( "MSBuildVersion" ) } ") ;
80+ taskHostNode . Kill ( ) ;
81+ }
82+ }
83+ }
84+
85+ [ Fact ]
86+ public void TransientandSidecarNodeCanCoexist ( )
87+ {
88+ using ( TestEnvironment env = TestEnvironment . Create ( ) )
89+ {
90+ string pidTaskProject = $@ "
91+ <Project>
92+ <UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""TaskHostFactory"" />
93+ <UsingTask TaskName=""ProcessIdTaskSidecar"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""AssemblyTaskFactory"" />
94+
95+ <Target Name='AccessPID'>
96+ <ProcessIdTask>
97+ <Output PropertyName=""PID"" TaskParameter=""Pid"" />
98+ </ProcessIdTask>
99+ <ProcessIdTaskSidecar>
100+ <Output PropertyName=""PID2"" TaskParameter=""Pid"" />
101+ </ProcessIdTaskSidecar>
102+ </Target>
103+ </Project>" ;
104+
105+ TransientTestFile project = env . CreateFile ( "testProject.csproj" , pidTaskProject ) ;
106+
107+
108+ env . SetEnvironmentVariable ( "MSBUILDFORCEALLTASKSOUTOFPROC" , "1" ) ;
109+ ProjectInstance projectInstance = new ( project . Path ) ;
110+
111+ projectInstance . Build ( ) . ShouldBeTrue ( ) ;
112+ string processId = projectInstance . GetPropertyValue ( "PID" ) ;
113+ string processIdSidecar = projectInstance . GetPropertyValue ( "PID2" ) ;
114+ processIdSidecar . ShouldNotBe ( processId , "Each task should have it's own TaskHost node." ) ;
115+
116+ string . IsNullOrEmpty ( processId ) . ShouldBeFalse ( ) ;
117+ Int32 . TryParse ( processId , out int pid ) . ShouldBeTrue ( ) ;
118+ Int32 . TryParse ( processIdSidecar , out int pidSidecar ) . ShouldBeTrue ( ) ;
119+
120+ Process . GetCurrentProcess ( ) . Id . ShouldNotBe ( pid ) ;
121+
122+
123+ try
124+ {
125+ Process taskHostNode1 = Process . GetProcessById ( pid ) ;
126+ taskHostNode1 . WaitForExit ( 3000 ) . ShouldBeTrue ( "The node should be dead since this is the transient case." ) ;
54127 }
55- // We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
56128 catch ( ArgumentException e )
57129 {
130+ // We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
58131 e . Message . ShouldBe ( $ "Process with an Id of { pid } is not running.") ;
59132 }
133+ // This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves.
134+ Process taskHostNode2 = Process . GetProcessById ( pidSidecar ) ;
135+ taskHostNode2 . WaitForExit ( 3000 ) . ShouldBeFalse ( $ "The node should be alive since it is the sidecar node.") ;
136+ taskHostNode2 . Kill ( ) ;
60137 }
61138 }
62139
0 commit comments