@@ -37,6 +37,7 @@ private Exec PrepareExec(string command)
3737 {
3838 IBuildEngine2 mockEngine = new MockEngine ( _output ) ;
3939 Exec exec = new Exec ( ) ;
40+ exec . TaskEnvironment = TaskEnvironmentHelper . CreateForTest ( ) ;
4041 exec . BuildEngine = mockEngine ;
4142 exec . Command = command ;
4243 return exec ;
@@ -46,6 +47,7 @@ private ExecWrapper PrepareExecWrapper(string command)
4647 {
4748 IBuildEngine2 mockEngine = new MockEngine ( _output ) ;
4849 ExecWrapper exec = new ExecWrapper ( ) ;
50+ exec . TaskEnvironment = TaskEnvironmentHelper . CreateForTest ( ) ;
4951 exec . BuildEngine = mockEngine ;
5052 exec . Command = command ;
5153 return exec ;
@@ -905,6 +907,7 @@ public void ValidateParametersNoCommand()
905907 public void SetEnvironmentVariableParameter ( )
906908 {
907909 Exec exec = new Exec ( ) ;
910+ exec . TaskEnvironment = TaskEnvironmentHelper . CreateForTest ( ) ;
908911 exec . BuildEngine = new MockEngine ( ) ;
909912 exec . Command = NativeMethodsShared . IsWindows ? "echo [%MYENVVAR%]" : "echo [$myenvvar]" ;
910913 exec . EnvironmentVariables = new [ ] { "myenvvar=myvalue" } ;
@@ -1069,6 +1072,166 @@ public void ConsoleOutputDoesNotTrimLeadingWhitespace()
10691072 exec . ConsoleOutput [ 0 ] . ItemSpec . ShouldBe ( lineWithLeadingWhitespace ) ;
10701073 }
10711074 }
1075+
1076+ /// <summary>
1077+ /// Runs an Exec task that lists directory contents and asserts expected/unexpected files in the output.
1078+ /// </summary>
1079+ /// <param name="taskEnvironment">The TaskEnvironment to configure on the Exec task.</param>
1080+ /// <param name="workingDirectory">The WorkingDirectory to set, or null to use the default.</param>
1081+ /// <param name="expectedFile">A filename that must appear in the output.</param>
1082+ /// <param name="notExpectedFile">A filename that must NOT appear in the output, or null to skip.</param>
1083+ private void ExecuteListCommandInDirectory (
1084+ TaskEnvironment taskEnvironment ,
1085+ string workingDirectory ,
1086+ string expectedFile ,
1087+ string notExpectedFile = null )
1088+ {
1089+ Exec exec = new Exec ( ) ;
1090+ exec . TaskEnvironment = taskEnvironment ;
1091+ exec . BuildEngine = new MockEngine ( _output ) ;
1092+ exec . Command = NativeMethodsShared . IsWindows ? "dir /b" : "ls" ;
1093+ exec . ConsoleToMSBuild = true ;
1094+
1095+ if ( workingDirectory != null )
1096+ {
1097+ exec . WorkingDirectory = workingDirectory ;
1098+ }
1099+
1100+ bool result = exec . Execute ( ) ;
1101+
1102+ result . ShouldBeTrue ( ) ;
1103+ ( ( MockEngine ) exec . BuildEngine ) . AssertLogContains ( expectedFile ) ;
1104+ if ( notExpectedFile != null )
1105+ {
1106+ ( ( MockEngine ) exec . BuildEngine ) . AssertLogDoesntContain ( notExpectedFile ) ;
1107+ }
1108+ }
1109+
1110+ /// <summary>
1111+ /// Verify that Exec resolves relative WorkingDirectory via TaskEnvironment.GetAbsolutePath in multiprocess mode.
1112+ /// </summary>
1113+ [ Fact ]
1114+ public void ExecResolvesRelativeWorkingDirectoryWithMultiProcessDriver ( )
1115+ {
1116+ using ( var testEnv = TestEnvironment . Create ( _output ) )
1117+ {
1118+ var projectDir = testEnv . CreateFolder ( ) ;
1119+ var subDir = Directory . CreateDirectory ( Path . Combine ( projectDir . Path , "subdir" ) ) ;
1120+ File . WriteAllText ( Path . Combine ( subDir . FullName , "testfile.txt" ) , "test content" ) ;
1121+
1122+ var differentDir = testEnv . CreateFolder ( ) ;
1123+ var decoySubDir = Directory . CreateDirectory ( Path . Combine ( differentDir . Path , "subdir" ) ) ;
1124+ File . WriteAllText ( Path . Combine ( decoySubDir . FullName , "decoyfile.txt" ) , "decoy content" ) ;
1125+
1126+ string originalDirectory = Directory . GetCurrentDirectory ( ) ;
1127+ try
1128+ {
1129+ Directory . SetCurrentDirectory ( projectDir . Path ) ;
1130+
1131+ ExecuteListCommandInDirectory (
1132+ TaskEnvironmentHelper . CreateForTest ( ) ,
1133+ workingDirectory : "subdir" ,
1134+ expectedFile : "testfile.txt" ,
1135+ notExpectedFile : "decoyfile.txt" ) ;
1136+ }
1137+ finally
1138+ {
1139+ Directory . SetCurrentDirectory ( originalDirectory ) ;
1140+ }
1141+ }
1142+ }
1143+
1144+ /// <summary>
1145+ /// Verify that Exec uses TaskEnvironment.ProjectDirectory when WorkingDirectory is not specified.
1146+ /// Uses MultiThreadedTaskEnvironmentDriver so process CWD differs from project directory.
1147+ /// </summary>
1148+ [ Fact ]
1149+ public void ExecUsesProjectDirectoryAsDefaultWorkingDirectory ( )
1150+ {
1151+ using ( var testEnv = TestEnvironment . Create ( _output ) )
1152+ {
1153+ var projectDir = testEnv . CreateFolder ( ) ;
1154+ File . WriteAllText ( Path . Combine ( projectDir . Path , "projectfile.txt" ) , "project content" ) ;
1155+
1156+ var differentCwd = testEnv . CreateFolder ( ) ;
1157+ File . WriteAllText ( Path . Combine ( differentCwd . Path , "decoyfile.txt" ) , "decoy content" ) ;
1158+
1159+ string originalDirectory = Directory . GetCurrentDirectory ( ) ;
1160+ TaskEnvironment taskEnvironment = null ;
1161+ try
1162+ {
1163+ Directory . SetCurrentDirectory ( differentCwd . Path ) ;
1164+
1165+ taskEnvironment = TaskEnvironmentHelper . CreateMultithreadedForTest ( projectDir . Path ) ;
1166+ ExecuteListCommandInDirectory (
1167+ taskEnvironment ,
1168+ workingDirectory : null ,
1169+ expectedFile : "projectfile.txt" ,
1170+ notExpectedFile : "decoyfile.txt" ) ;
1171+ }
1172+ finally
1173+ {
1174+ taskEnvironment ? . Dispose ( ) ;
1175+ Directory . SetCurrentDirectory ( originalDirectory ) ;
1176+ }
1177+ }
1178+ }
1179+
1180+ /// <summary>
1181+ /// Verify that Exec correctly handles absolute WorkingDirectory paths.
1182+ /// </summary>
1183+ [ Fact ]
1184+ public void ExecHandlesAbsoluteWorkingDirectory ( )
1185+ {
1186+ using ( var testEnv = TestEnvironment . Create ( _output ) )
1187+ {
1188+ var workDir = testEnv . CreateFolder ( ) ;
1189+ File . WriteAllText ( Path . Combine ( workDir . Path , "absolutedir.txt" ) , "absolute content" ) ;
1190+
1191+ ExecuteListCommandInDirectory (
1192+ TaskEnvironmentHelper . CreateForTest ( ) ,
1193+ workingDirectory : workDir . Path ,
1194+ expectedFile : "absolutedir.txt" ) ;
1195+ }
1196+ }
1197+
1198+ /// <summary>
1199+ /// Verify that Exec resolves relative WorkingDirectory relative to TaskEnvironment.ProjectDirectory,
1200+ /// not the process current directory. Uses MultiThreadedTaskEnvironmentDriver to simulate
1201+ /// multithreaded mode where process CWD differs from project directory.
1202+ /// </summary>
1203+ [ Fact ]
1204+ public void ExecResolvesRelativeWorkingDirectoryRelativeToProjectDirectory ( )
1205+ {
1206+ using ( var testEnv = TestEnvironment . Create ( _output ) )
1207+ {
1208+ var projectDir = testEnv . CreateFolder ( ) ;
1209+ var subDir = Directory . CreateDirectory ( Path . Combine ( projectDir . Path , "builddir" ) ) ;
1210+ File . WriteAllText ( Path . Combine ( subDir . FullName , "multithreaded.txt" ) , "multithreaded content" ) ;
1211+
1212+ var differentCwd = testEnv . CreateFolder ( ) ;
1213+ File . WriteAllText ( Path . Combine ( differentCwd . Path , "decoyfile.txt" ) , "decoy content" ) ;
1214+
1215+ string originalDirectory = Directory . GetCurrentDirectory ( ) ;
1216+ TaskEnvironment taskEnvironment = null ;
1217+ try
1218+ {
1219+ Directory . SetCurrentDirectory ( differentCwd . Path ) ;
1220+
1221+ taskEnvironment = TaskEnvironmentHelper . CreateMultithreadedForTest ( projectDir . Path ) ;
1222+ ExecuteListCommandInDirectory (
1223+ taskEnvironment ,
1224+ workingDirectory : "builddir" ,
1225+ expectedFile : "multithreaded.txt" ,
1226+ notExpectedFile : "decoyfile.txt" ) ;
1227+ }
1228+ finally
1229+ {
1230+ taskEnvironment ? . Dispose ( ) ;
1231+ Directory . SetCurrentDirectory ( originalDirectory ) ;
1232+ }
1233+ }
1234+ }
10721235 }
10731236
10741237 internal sealed class ExecWrapper : Exec
0 commit comments