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