1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Reflection;
8 using System.Collections;
9 using Microsoft.Build.UnitTests;
10 using Microsoft.Build.Framework;
11 using Microsoft.Build.Shared;
12 using Xunit;
13 using Microsoft.Build.Utilities;
14 using Shouldly;
15 
16 namespace Microsoft.Build.UnitTests
17 {
18     sealed public class ToolTask_Tests
19     {
20         internal class MyTool : ToolTask, IDisposable
21         {
22             private string _fullToolName;
23             private string _responseFileCommands = String.Empty;
24             private string _commandLineCommands = String.Empty;
25             private string _pathToToolUsed;
26 
MyTool()27             public MyTool()
28                 : base()
29             {
30                 _fullToolName = Path.Combine(
31 #if FEATURE_SPECIAL_FOLDERS
32                     Environment.GetFolderPath(Environment.SpecialFolder.System),
33 #else
34                     FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.System),
35 #endif
36                     NativeMethodsShared.IsUnixLike ? "sh" : "cmd.exe");
37             }
38 
Dispose()39             public void Dispose()
40             {
41             }
42 
43             public string PathToToolUsed
44             {
45                 get { return _pathToToolUsed; }
46             }
47 
48             public string MockResponseFileCommands
49             {
50                 set { _responseFileCommands = value; }
51             }
52 
53             public string MockCommandLineCommands
54             {
55                 set { _commandLineCommands = value; }
56             }
57 
58             public string FullToolName
59             {
60                 set { _fullToolName = value; }
61             }
62 
63             /// <summary>
64             /// Intercepted start info
65             /// </summary>
66             internal ProcessStartInfo StartInfo
67             {
68                 get;
69                 private set;
70             }
71 
72             /// <summary>
73             /// Whether execute was called
74             /// </summary>
75             internal bool ExecuteCalled
76             {
77                 get;
78                 private set;
79             }
80 
81             internal Action<ProcessStartInfo> DoProcessStartInfoMutation {get; set;}
82 
83             protected override string ToolName
84             {
85                 get { return Path.GetFileName(_fullToolName); }
86             }
87 
GenerateFullPathToTool()88             protected override string GenerateFullPathToTool()
89             {
90                 return _fullToolName;
91             }
92 
GenerateResponseFileCommands()93             override protected string GenerateResponseFileCommands()
94             {
95                 return _responseFileCommands;
96             }
97 
GenerateCommandLineCommands()98             override protected string GenerateCommandLineCommands()
99             {
100                 // Default is nothing. This is useful for tools where all the parameters can go into a response file.
101                 return _commandLineCommands;
102             }
103 
GetProcessStartInfo( string pathToTool, string commandLineCommands, string responseFileSwitch )104             override protected ProcessStartInfo GetProcessStartInfo
105             (
106                 string pathToTool,
107                 string commandLineCommands,
108                 string responseFileSwitch
109             )
110             {
111                 var basePSI = base.GetProcessStartInfo(
112                     pathToTool,
113                     commandLineCommands,
114                     responseFileSwitch);
115 
116                 if (DoProcessStartInfoMutation != null)
117                 {
118                     DoProcessStartInfoMutation(basePSI);
119                 }
120 
121                 return basePSI;
122             }
123 
LogEventsFromTextOutput( string singleLine, MessageImportance messageImportance )124             override protected void LogEventsFromTextOutput
125                 (
126                 string singleLine,
127                 MessageImportance messageImportance
128                 )
129             {
130                 if (singleLine.Contains("BADTHINGHAPPENED"))
131                 {
132                     // This is where a customer's tool task implementation could do its own
133                     // parsing of the errors in the stdout/stderr output of the tool being wrapped.
134                     Log.LogError(singleLine);
135                 }
136                 else
137                 {
138                     base.LogEventsFromTextOutput(singleLine, messageImportance);
139                 }
140             }
141 
ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)142             override protected int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
143             {
144                 Console.WriteLine("executetool");
145                 _pathToToolUsed = pathToTool;
146                 ExecuteCalled = true;
147                 if (!NativeMethodsShared.IsWindows && string.IsNullOrEmpty(responseFileCommands) && string.IsNullOrEmpty(commandLineCommands))
148                 {
149                     // Unix makes sh interactive and it won't exit if there is nothing on the command line
150                     commandLineCommands = " -c \"echo\"";
151                 }
152                 int result = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
153                 StartInfo = GetProcessStartInfo(
154                     GenerateFullPathToTool(),
155                     NativeMethodsShared.IsWindows ? "/x" : string.Empty,
156                     null);
157                 return result;
158             }
159         };
160 
161         [Fact]
Regress_Mutation_UserSuppliedToolPathIsLogged()162         public void Regress_Mutation_UserSuppliedToolPathIsLogged()
163         {
164             using (MyTool t = new MyTool())
165             {
166                 MockEngine engine = new MockEngine();
167                 t.BuildEngine = engine;
168                 t.ToolPath = NativeMethodsShared.IsWindows ? @"C:\MyAlternatePath" : "/MyAlternatePath";
169 
170                 t.Execute();
171 
172                 // The alternate path should be mentioned in the log.
173                 engine.AssertLogContains("MyAlternatePath");
174             }
175         }
176 
177         [Fact]
Regress_Mutation_MissingExecutableIsLogged()178         public void Regress_Mutation_MissingExecutableIsLogged()
179         {
180             using (MyTool t = new MyTool())
181             {
182                 MockEngine engine = new MockEngine();
183                 t.BuildEngine = engine;
184                 t.ToolPath = NativeMethodsShared.IsWindows ? @"C:\MyAlternatePath" : "/MyAlternatePath";
185 
186                 t.Execute().ShouldBeFalse();
187 
188                 // There should be an error about invalid task location.
189                 engine.AssertLogContains("MSB6004");
190             }
191         }
192 
193         [Fact]
Regress_Mutation_WarnIfCommandLineTooLong()194         public void Regress_Mutation_WarnIfCommandLineTooLong()
195         {
196             using (MyTool t = new MyTool())
197             {
198                 MockEngine engine = new MockEngine();
199                 t.BuildEngine = engine;
200 
201                 // "cmd.exe" croaks big-time when given a very long command-line.  It pops up a message box on
202                 // Windows XP.  We can't have that!  So use "attrib.exe" for this exercise instead.
203 #if FEATURE_SPECIAL_FOLDERS
204                 t.FullToolName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), NativeMethodsShared.IsWindows ? "attrib.exe" : "ps");
205 #else
206                 t.FullToolName = Path.Combine(FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.System), NativeMethodsShared.IsWindows ? "attrib.exe" : "ps");
207 #endif
208 
209                 t.MockCommandLineCommands = new String('x', 32001);
210 
211                 // It's only a warning, we still succeed
212                 t.Execute().ShouldBeTrue();
213                 t.ExitCode.ShouldBe(0);
214                 // There should be a warning about the command-line being too long.
215                 engine.AssertLogContains("MSB6002");
216             }
217         }
218 
219         /// <summary>
220         /// Exercise the code in ToolTask's default implementation of HandleExecutionErrors.
221         /// </summary>
222         [Fact]
HandleExecutionErrorsWhenToolDoesntLogError()223         public void HandleExecutionErrorsWhenToolDoesntLogError()
224         {
225             using (MyTool t = new MyTool())
226             {
227                 MockEngine engine = new MockEngine();
228                 t.BuildEngine = engine;
229                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows ? "/C garbagegarbagegarbagegarbage.exe" : "-c garbagegarbagegarbagegarbage.exe";
230 
231                 t.Execute().ShouldBeFalse();
232                 t.ExitCode.ShouldBe(NativeMethodsShared.IsWindows ? 1 : 127); // cmd.exe error code is 1, sh error code is 127
233 
234                 // We just tried to run "cmd.exe /C garbagegarbagegarbagegarbage.exe".  This should fail,
235                 // but since "cmd.exe" doesn't log its errors in canonical format, no errors got
236                 // logged by the tool itself.  Therefore, ToolTask's default implementation of
237                 // HandleTaskExecutionErrors should have logged error MSB6006.
238                 engine.AssertLogContains("MSB6006");
239             }
240         }
241 
242         /// <summary>
243         /// Exercise the code in ToolTask's default implementation of HandleExecutionErrors.
244         /// </summary>
245         [Fact]
246         [Trait("Category", "netcore-osx-failing")]
247         [Trait("Category", "netcore-linux-failing")]
248         [Trait("Category", "mono-osx-failing")]
HandleExecutionErrorsWhenToolLogsError()249         public void HandleExecutionErrorsWhenToolLogsError()
250         {
251             using (MyTool t = new MyTool())
252             {
253                 MockEngine engine = new MockEngine();
254                 t.BuildEngine = engine;
255                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows
256                                                 ? "/C echo Main.cs(17,20): error CS0168: The variable 'foo' is declared but never used"
257                                                 : @"-c """"""echo Main.cs\(17,20\): error CS0168: The variable 'foo' is declared but never used""""""";
258 
259                 t.Execute().ShouldBeFalse();
260 
261                 // The above command logged a canonical error message.  Therefore ToolTask should
262                 // not log its own error beyond that.
263                 engine.AssertLogDoesntContain("MSB6006");
264                 engine.AssertLogContains("CS0168");
265                 engine.AssertLogContains("The variable 'foo' is declared but never used");
266                 t.ExitCode.ShouldBe(-1);
267                 engine.Errors.ShouldBe(1);
268             }
269         }
270 
271         /// <summary>
272         /// ToolTask should never run String.Format on strings that are
273         /// not meant to be formatted.
274         /// </summary>
275         [Fact]
DoNotFormatTaskCommandOrMessage()276         public void DoNotFormatTaskCommandOrMessage()
277         {
278             MyTool t = new MyTool();
279             MockEngine engine = new MockEngine();
280             t.BuildEngine = engine;
281             // Unmatched curly would crash if they did
282             t.MockCommandLineCommands = NativeMethodsShared.IsWindows
283                                             ? "/C echo hello world {"
284                                             : @"-c ""echo hello world {""";
285             t.Execute();
286             engine.AssertLogContains("echo hello world {");
287             engine.Errors.ShouldBe(0);
288         }
289 
290         /// <summary>
291         /// When a message is logged to the standard error stream do not error is LogStandardErrorAsError is not true or set.
292         /// </summary>
293         [Fact]
DoNotErrorWhenTextSentToStandardError()294         public void DoNotErrorWhenTextSentToStandardError()
295         {
296             using (MyTool t = new MyTool())
297             {
298                 MockEngine engine = new MockEngine();
299                 t.BuildEngine = engine;
300                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows
301                                                 ? "/C Echo 'Who made you king anyways' 1>&2"
302                                                 : @"-c ""echo Who made you king anyways 1>&2""";
303 
304                 t.Execute().ShouldBeTrue();
305 
306                 engine.AssertLogDoesntContain("MSB");
307                 engine.AssertLogContains("Who made you king anyways");
308                 t.ExitCode.ShouldBe(0);
309                 engine.Errors.ShouldBe(0);
310             }
311         }
312 
313         /// <summary>
314         /// When a message is logged to the standard output stream do not error is LogStandardErrorAsError is  true
315         /// </summary>
316         [Fact]
DoNotErrorWhenTextSentToStandardOutput()317         public void DoNotErrorWhenTextSentToStandardOutput()
318         {
319             using (MyTool t = new MyTool())
320             {
321                 MockEngine engine = new MockEngine();
322                 t.BuildEngine = engine;
323                 t.LogStandardErrorAsError = true;
324                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows
325                                                 ? "/C Echo 'Who made you king anyways'"
326                                                 : @"-c ""echo Who made you king anyways""";
327 
328                 t.Execute().ShouldBeTrue();
329 
330                 engine.AssertLogDoesntContain("MSB");
331                 engine.AssertLogContains("Who made you king anyways");
332                 t.ExitCode.ShouldBe(0);
333                 engine.Errors.ShouldBe(0);
334             }
335         }
336 
337         /// <summary>
338         /// When a message is logged to the standard error stream error if LogStandardErrorAsError is true
339         /// </summary>
340         [Fact]
ErrorWhenTextSentToStandardError()341         public void ErrorWhenTextSentToStandardError()
342         {
343             using (MyTool t = new MyTool())
344             {
345                 MockEngine engine = new MockEngine();
346                 t.BuildEngine = engine;
347                 t.LogStandardErrorAsError = true;
348                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows
349                                                 ? "/C Echo 'Who made you king anyways' 1>&2"
350                                                 : @"-c ""echo 'Who made you king anyways' 1>&2""";
351 
352                 t.Execute().ShouldBeFalse();
353 
354                 engine.AssertLogDoesntContain("MSB3073");
355                 engine.AssertLogContains("Who made you king anyways");
356                 t.ExitCode.ShouldBe(-1);
357                 engine.Errors.ShouldBe(1);
358             }
359         }
360 
361 
362         /// <summary>
363         /// When ToolExe is set, it is used instead of ToolName
364         /// </summary>
365         [Fact]
ToolExeWinsOverToolName()366         public void ToolExeWinsOverToolName()
367         {
368             using (MyTool t = new MyTool())
369             {
370                 MockEngine engine = new MockEngine();
371                 t.BuildEngine = engine;
372                 t.FullToolName = NativeMethodsShared.IsWindows ? "c:\\baz\\foo.exe" : "/baz/foo.exe";
373 
374                 t.ToolExe.ShouldBe("foo.exe");
375                 t.ToolExe = "bar.exe";
376                 t.ToolExe.ShouldBe("bar.exe");
377             }
378         }
379 
380         /// <summary>
381         /// When ToolExe is set, it is appended to ToolPath instead
382         /// of the regular tool name
383         /// </summary>
384         [Fact]
385         [Trait("Category", "mono-osx-failing")]
ToolExeIsFoundOnToolPath()386         public void ToolExeIsFoundOnToolPath()
387         {
388             string shellName = NativeMethodsShared.IsWindows ? "cmd.exe" : "sh";
389             string copyName = NativeMethodsShared.IsWindows ? "xcopy.exe" : "cp";
390             using (MyTool t = new MyTool())
391             {
392                 MockEngine engine = new MockEngine();
393                 t.BuildEngine = engine;
394                 t.FullToolName = shellName;
395 #if FEATURE_SPECIAL_FOLDERS
396                 string systemPath = Environment.GetFolderPath(Environment.SpecialFolder.System);
397 #else
398                 string systemPath = FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.System);
399 #endif
400                 t.ToolPath = systemPath;
401 
402                 t.Execute();
403                 t.PathToToolUsed.ShouldBe(Path.Combine(systemPath, shellName));
404                 engine.AssertLogContains(shellName);
405                 engine.Log = String.Empty;
406 
407                 t.ToolExe = copyName;
408                 t.Execute();
409                 t.PathToToolUsed.ShouldBe(Path.Combine(systemPath, copyName));
410                 engine.AssertLogContains(copyName);
411                 engine.AssertLogDoesntContain(shellName);
412             }
413         }
414 
415         /// <summary>
416         /// Task is not found on path - regress #499196
417         /// </summary>
418         [Fact]
TaskNotFoundOnPath()419         public void TaskNotFoundOnPath()
420         {
421             using (MyTool t = new MyTool())
422             {
423                 MockEngine engine = new MockEngine();
424                 t.BuildEngine = engine;
425                 t.FullToolName = "doesnotexist.exe";
426 
427                 t.Execute().ShouldBeFalse();
428                 t.ExitCode.ShouldBe(-1);
429                 engine.Errors.ShouldBe(1);
430 
431                 // Does not throw an exception
432             }
433         }
434 
435         /// <summary>
436         /// Task is found on path.
437         /// </summary>
438         [Fact]
439         [Trait("Category", "mono-osx-failing")]
TaskFoundOnPath()440         public void TaskFoundOnPath()
441         {
442             using (MyTool t = new MyTool())
443             {
444                 MockEngine engine = new MockEngine();
445                 t.BuildEngine = engine;
446                 string toolName = NativeMethodsShared.IsWindows ? "cmd.exe" : "sh";
447                 t.FullToolName = toolName;
448 
449                 t.Execute().ShouldBeTrue();
450                 t.ExitCode.ShouldBe(0);
451                 engine.Errors.ShouldBe(0);
452 
453                 engine.AssertLogContains(
454 #if FEATURE_SPECIAL_FOLDERS
455                     Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), toolName));
456 #else
457                     Path.Combine(FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.System), toolName));
458 #endif
459             }
460         }
461 
462         /// <summary>
463         /// StandardOutputImportance set to Low should not show up in our log
464         /// </summary>
465         [Fact]
OverrideStdOutImportanceToLow()466         public void OverrideStdOutImportanceToLow()
467         {
468             string tempFile = FileUtilities.GetTemporaryFile();
469             File.WriteAllText(tempFile, @"hello world");
470 
471             using (MyTool t = new MyTool())
472             {
473                 MockEngine engine = new MockEngine();
474                 engine.MinimumMessageImportance = MessageImportance.High;
475 
476                 t.BuildEngine = engine;
477                 t.FullToolName = NativeMethodsShared.IsWindows ? "findstr.exe" : "grep";
478                 t.MockCommandLineCommands = "\"hello\" \"" + tempFile + "\"";
479                 t.StandardOutputImportance = "Low";
480 
481                 t.Execute().ShouldBeTrue();
482                 t.ExitCode.ShouldBe(0);
483                 engine.Errors.ShouldBe(0);
484 
485                 engine.AssertLogDoesntContain("hello world");
486             }
487             File.Delete(tempFile);
488         }
489 
490         /// <summary>
491         /// StandardOutputImportance set to High should show up in our log
492         /// </summary>
493         [Fact]
OverrideStdOutImportanceToHigh()494         public void OverrideStdOutImportanceToHigh()
495         {
496             string tempFile = FileUtilities.GetTemporaryFile();
497             File.WriteAllText(tempFile, @"hello world");
498 
499             using (MyTool t = new MyTool())
500             {
501                 MockEngine engine = new MockEngine();
502                 engine.MinimumMessageImportance = MessageImportance.High;
503 
504                 t.BuildEngine = engine;
505                 t.FullToolName = NativeMethodsShared.IsWindows ? "findstr.exe" : "grep";
506                 t.MockCommandLineCommands = "\"hello\" \"" + tempFile + "\"";
507                 t.StandardOutputImportance = "High";
508 
509                 t.Execute().ShouldBeTrue();
510                 t.ExitCode.ShouldBe(0);
511                 engine.Errors.ShouldBe(0);
512 
513                 engine.AssertLogContains("hello world");
514             }
515             File.Delete(tempFile);
516         }
517 
518         /// <summary>
519         /// This is to ensure that somebody could write a task that implements ToolTask,
520         /// wraps some .EXE tool, and still have the ability to parse the stdout/stderr
521         /// himself.  This is so that in case the tool doesn't log its errors in canonical
522         /// format, the task can still opt to do something reasonable with it.
523         /// </summary>
524         [Fact]
ToolTaskCanChangeCanonicalErrorFormat()525         public void ToolTaskCanChangeCanonicalErrorFormat()
526         {
527             string tempFile = FileUtilities.GetTemporaryFile();
528             File.WriteAllText(tempFile, @"
529                 Main.cs(17,20): warning CS0168: The variable 'foo' is declared but never used.
530                 BADTHINGHAPPENED: This is my custom error format that's not in canonical error format.
531                 ");
532 
533             using (MyTool t = new MyTool())
534             {
535                 MockEngine engine = new MockEngine();
536                 t.BuildEngine = engine;
537                 // The command we're giving is the command to spew the contents of the temp
538                 // file we created above.
539                 t.MockCommandLineCommands = NativeMethodsShared.IsWindows
540                                                 ? $"/C type \"{tempFile}\""
541                                                 : $"-c \"cat \'{tempFile}\'\"";
542 
543                 t.Execute();
544 
545                 // The above command logged a canonical warning, as well as a custom error.
546                 engine.AssertLogContains("CS0168");
547                 engine.AssertLogContains("The variable 'foo' is declared but never used");
548                 engine.AssertLogContains("BADTHINGHAPPENED");
549                 engine.AssertLogContains("This is my custom error format");
550 
551                 engine.Warnings.ShouldBe(1); // "Expected one warning in log."
552                 engine.Errors.ShouldBe(1); // "Expected one error in log."
553             }
554 
555             File.Delete(tempFile);
556         }
557 
558         /// <summary>
559         /// Passing env vars through the tooltask public property
560         /// </summary>
561         [Fact]
562         [Trait("Category", "mono-osx-failing")]
EnvironmentVariablesToToolTask()563         public void EnvironmentVariablesToToolTask()
564         {
565             MyTool task = new MyTool();
566             task.BuildEngine = new MockEngine();
567             string userVarName = NativeMethodsShared.IsWindows ? "username" : "user";
568             task.EnvironmentVariables = new string[] { "a=b", "c=d", userVarName + "=x" /* built-in */, "path=" /* blank value */};
569             bool result = task.Execute();
570 
571             result.ShouldBe(true);
572             task.ExecuteCalled.ShouldBe(true);
573 
574             ProcessStartInfo startInfo = task.StartInfo;
575 
576 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
577             startInfo.Environment["a"].ShouldBe("b");
578             startInfo.Environment["c"].ShouldBe("d");
579             startInfo.Environment[userVarName].ShouldBe("x");
580             startInfo.Environment["path"].ShouldBe(String.Empty);
581 #else
582             startInfo.EnvironmentVariables["a"].ShouldBe("b");
583             startInfo.EnvironmentVariables["c"].ShouldBe("d");
584             startInfo.EnvironmentVariables[userVarName].ShouldBe("x");
585             startInfo.EnvironmentVariables["path"].ShouldBe(String.Empty);
586 #endif
587 
588             if (NativeMethodsShared.IsWindows)
589             {
590                 Assert.Equal(
591 #if FEATURE_SPECIAL_FOLDERS
592                         Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
593 #else
594                         FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.ProgramFiles),
595 #endif
596 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
597                         startInfo.Environment["programfiles"],
598 #else
599                         startInfo.EnvironmentVariables["programfiles"],
600 #endif
601                         true);
602             }
603         }
604 
605         /// <summary>
606         /// Equals sign in value
607         /// </summary>
608         [Fact]
609         [Trait("Category", "mono-osx-failing")]
EnvironmentVariablesToToolTaskEqualsSign()610         public void EnvironmentVariablesToToolTaskEqualsSign()
611         {
612             MyTool task = new MyTool();
613             task.BuildEngine = new MockEngine();
614             task.EnvironmentVariables = new string[] { "a=b=c" };
615             bool result = task.Execute();
616 
617             result.ShouldBe(true);
618 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
619             task.StartInfo.Environment["a"].ShouldBe("b=c");
620 #else
621             task.StartInfo.EnvironmentVariables["a"].ShouldBe("b=c");
622 #endif
623         }
624 
625         /// <summary>
626         /// No value provided
627         /// </summary>
628         [Fact]
EnvironmentVariablesToToolTaskInvalid1()629         public void EnvironmentVariablesToToolTaskInvalid1()
630         {
631             MyTool task = new MyTool();
632             task.BuildEngine = new MockEngine();
633             task.EnvironmentVariables = new string[] { "x" };
634             bool result = task.Execute();
635 
636             result.ShouldBe(false);
637             task.ExecuteCalled.ShouldBe(false);
638         }
639 
640         /// <summary>
641         /// Empty string provided
642         /// </summary>
643         [Fact]
EnvironmentVariablesToToolTaskInvalid2()644         public void EnvironmentVariablesToToolTaskInvalid2()
645         {
646             MyTool task = new MyTool();
647             task.BuildEngine = new MockEngine();
648             task.EnvironmentVariables = new string[] { "" };
649             bool result = task.Execute();
650 
651             result.ShouldBe(false);
652             task.ExecuteCalled.ShouldBe(false);
653         }
654 
655         /// <summary>
656         /// Empty name part provided
657         /// </summary>
658         [Fact]
EnvironmentVariablesToToolTaskInvalid3()659         public void EnvironmentVariablesToToolTaskInvalid3()
660         {
661             MyTool task = new MyTool();
662             task.BuildEngine = new MockEngine();
663             task.EnvironmentVariables = new string[] { "=a;b=c" };
664             bool result = task.Execute();
665 
666             result.ShouldBe(false);
667             task.ExecuteCalled.ShouldBe(false);
668         }
669 
670         /// <summary>
671         /// Not set should not wipe out other env vars
672         /// </summary>
673         [Fact]
674         [Trait("Category", "mono-osx-failing")]
EnvironmentVariablesToToolTaskNotSet()675         public void EnvironmentVariablesToToolTaskNotSet()
676         {
677             MyTool task = new MyTool();
678             task.BuildEngine = new MockEngine();
679             task.EnvironmentVariables = null;
680             bool result = task.Execute();
681 
682             result.ShouldBe(true);
683             task.ExecuteCalled.ShouldBe(true);
684             Assert.Equal(
685                 true,
686 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
687                 task.StartInfo.Environment["PATH"].Length > 0);
688 #else
689                 task.StartInfo.EnvironmentVariables["PATH"].Length > 0);
690 #endif
691         }
692 
693         /// <summary>
694         /// Verifies that if a directory with the same name of the tool exists that the tool task correctly
695         /// ignores the directory.
696         /// </summary>
697         [Fact]
ToolPathIsFoundWhenDirectoryExistsWithNameOfTool()698         public void ToolPathIsFoundWhenDirectoryExistsWithNameOfTool()
699         {
700             string toolName = NativeMethodsShared.IsWindows ? "cmd" : "sh";
701             string savedCurrentDirectory = Directory.GetCurrentDirectory();
702 
703             try
704             {
705                 using (var env = TestEnvironment.Create())
706                 {
707                     string tempDirectory = env.CreateFolder().FolderPath;
708                     env.SetCurrentDirectory(tempDirectory);
709                     env.SetEnvironmentVariable("PATH", $"{tempDirectory}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}");
710                     Directory.SetCurrentDirectory(tempDirectory);
711 
712                     string directoryNamedSameAsTool = Directory.CreateDirectory(Path.Combine(tempDirectory, toolName)).FullName;
713 
714                     MyTool task = new MyTool
715                     {
716                         BuildEngine = new MockEngine(),
717                         FullToolName = toolName,
718                     };
719                     bool result = task.Execute();
720 
721                     Assert.NotEqual(directoryNamedSameAsTool, task.PathToToolUsed);
722 
723                     result.ShouldBeTrue();
724                 }
725             }
726             finally
727             {
728                 Directory.SetCurrentDirectory(savedCurrentDirectory);
729             }
730         }
731 
732         /// <summary>
733         /// Confirms we can find a file on the PATH.
734         /// </summary>
735         [Fact]
FindOnPathSucceeds()736         public void FindOnPathSucceeds()
737         {
738             string expectedCmdPath;
739             string shellName;
740             if (NativeMethodsShared.IsWindows)
741             {
742 #if FEATURE_SPECIAL_FOLDERS
743                 expectedCmdPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe");
744 #else
745                 expectedCmdPath = Path.Combine(FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.System), "cmd.exe");
746 #endif
747                 shellName = "cmd.exe";
748             }
749             else
750             {
751                 expectedCmdPath = "/bin/sh";
752                 shellName = "sh";
753             }
754 
755             string cmdPath = ToolTask.FindOnPath(shellName);
756 
757             cmdPath.ShouldBe(expectedCmdPath, StringCompareShould.IgnoreCase);
758         }
759 
760         /// <summary>
761         /// Equals sign in value
762         /// </summary>
763         [Fact]
GetProcessStartInfoCanOverrideEnvironmentVariables()764         public void GetProcessStartInfoCanOverrideEnvironmentVariables()
765         {
766             MyTool task = new MyTool();
767 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
768             task.DoProcessStartInfoMutation = (p) => p.Environment.Remove("a");
769 #else
770             task.DoProcessStartInfoMutation = (p) => p.EnvironmentVariables.Remove("a");
771 #endif
772 
773             task.BuildEngine = new MockEngine();
774             task.EnvironmentVariables = new string[] { "a=b" };
775             bool result = task.Execute();
776 
777             result.ShouldBe(true);
778 #if FEATURE_PROCESSSTARTINFO_ENVIRONMENT
779             task.StartInfo.Environment.ContainsKey("a").ShouldBe(false);
780 #else
781             task.StartInfo.EnvironmentVariables.ContainsKey("a").ShouldBe(false);
782 #endif
783         }
784 
785         [Fact]
VisualBasicLikeEscapedQuotesInCommandAreNotMadeForwardSlashes()786         public void VisualBasicLikeEscapedQuotesInCommandAreNotMadeForwardSlashes()
787         {
788             MyTool t = new MyTool();
789             MockEngine engine = new MockEngine();
790             t.BuildEngine = engine;
791             t.MockCommandLineCommands = NativeMethodsShared.IsWindows
792                                             ? "/C echo \"hello \\\"world\\\"\""
793                                             : "-c echo \"hello \\\"world\\\"\"";
794             t.Execute();
795             engine.AssertLogContains("echo \"hello \\\"world\\\"\"");
796             engine.Errors.ShouldBe(0);
797         }
798 
799         /// <summary>
800         /// Verifies that a ToolTask running under the command processor on Windows has autorun
801         /// disabled or enabled depending on an escape hatch.
802         /// </summary>
803         [Theory]
804         [InlineData("MSBUILDUSERAUTORUNINCMD", null, true)]
805         [InlineData("MSBUILDUSERAUTORUNINCMD", "0", true)]
806         [InlineData("MSBUILDUSERAUTORUNINCMD", "1", false)]
807         [Trait("Category", "nonosxtests")]
808         [Trait("Category", "nonlinuxtests")]
ExecTaskDisablesAutoRun(string environmentVariableName, string environmentVariableValue, bool autoRunShouldBeDisabled)809         public void ExecTaskDisablesAutoRun(string environmentVariableName, string environmentVariableValue, bool autoRunShouldBeDisabled)
810         {
811             using (TestEnvironment testEnvironment = TestEnvironment.Create())
812             {
813                 testEnvironment.SetEnvironmentVariable(environmentVariableName, environmentVariableValue);
814 
815                 ToolTaskThatGetsCommandLine task = new ToolTaskThatGetsCommandLine
816                 {
817                     UseCommandProcessor = true
818                 };
819 
820                 task.Execute();
821 
822                 if (autoRunShouldBeDisabled)
823                 {
824                     task.CommandLineCommands.ShouldContain("/D ");
825                 }
826                 else
827                 {
828                     task.CommandLineCommands.ShouldNotContain("/D ");
829                 }
830             }
831         }
832 
833         /// <summary>
834         /// A simple implementation of <see cref="ToolTask"/> that allows tests to verify the command-line that was generated.
835         /// </summary>
836         internal sealed class ToolTaskThatGetsCommandLine : ToolTask
837         {
838             protected override string ToolName
839             {
840                 get { return "cmd.exe"; }
841             }
842 
GenerateFullPathToTool()843             protected override string GenerateFullPathToTool()
844             {
845                 return null;
846             }
847 
ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)848             protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
849             {
850                 PathToTool = pathToTool;
851                 ResponseFileCommands = responseFileCommands;
852                 CommandLineCommands = commandLineCommands;
853 
854                 return 0;
855             }
LogToolCommand(string message)856             protected override void LogToolCommand(string message)
857             {
858             }
859 
860             public string CommandLineCommands { get; private set; }
861 
862             public string PathToTool { get; private set; }
863 
864             public string ResponseFileCommands { get; private set; }
865         }
866     }
867 }
868