|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
#if FEATURE_SECURITY_PERMISSIONS
using System.Security.AccessControl;
#endif
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public class Copy_Tests : IDisposable
{
public static IEnumerable<object[]> GetDestinationExists() =>
new List<object[]>
{
new object[] { true },
new object[] { false },
};
public static IEnumerable<object[]> GetNullAndEmptyArrays() =>
new List<object[]>
{
new object[] { null },
new object[] { Array.Empty<ITaskItem>() },
};
/// <summary>
/// Gets data for testing with combinations of isUseHardLinks and isUseSymbolicLinks.
/// Index 0 is the value for isUseHardLinks.
/// Index 1 is the value for isUseSymbolicLinks.
/// </summary>
public static IEnumerable<object[]> GetHardLinksSymLinks() => new List<object[]>
{
new object[] { false, false },
new object[] { false, true },
new object[] { true, false },
/* Cases not covered
new object[] { true, true },
*/
};
/// <summary>
/// Gets data for testing with combinations of isUseHardLinks, isUseSymbolicLinks, and isUseSingleThreadedCopy.
/// Index 0 is the value for isUseHardLinks.
/// Index 1 is the value for isUseSymbolicLinks.
/// Index 2 is the value for isUseSingleThreadedCopy.
/// </summary>
public static IEnumerable<object[]> GetHardLinksSymLinksSingleThreaded() => new List<object[]>
{
new object[] { false, false, false },
new object[] { false, false, true },
new object[] { false, true, false },
new object[] { true, false, false },
/* Cases not covered
new object[] { false, true, true },
new object[] { true, false, true },
new object[] { true, true, false },
new object[] { true, true, true },
*/
};
private const int NoParallelismThreadCount = 1;
private const int DefaultParallelismThreadCount = int.MaxValue;
private int GetParallelismThreadCount(bool isUseSingleThreadedCopy) => isUseSingleThreadedCopy ? NoParallelismThreadCount : DefaultParallelismThreadCount;
/// <summary>
/// Temporarily save off the value of MSBUILDALWAYSOVERWRITEREADONLYFILES, so that we can run
/// the tests isolated from the current state of the environment, but put it back how it belongs
/// once we're done.
/// </summary>
private readonly string _alwaysOverwriteReadOnlyFiles;
/// <summary>
/// Temporarily save off the value of MSBUILDALWAYSRETRY, so that we can run
/// the tests isolated from the current state of the environment, but put it back how it belongs
/// once we're done.
/// </summary>
private readonly string _alwaysRetry;
private readonly ITestOutputHelper _testOutputHelper;
/// <summary>
/// There are a couple of environment variables that can affect the operation of the Copy
/// task. Make sure none of them are set.
/// </summary>
public Copy_Tests(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
_alwaysOverwriteReadOnlyFiles = Environment.GetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar);
_alwaysRetry = Environment.GetEnvironmentVariable(Copy.AlwaysRetryEnvVar);
Environment.SetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar, null);
Environment.SetEnvironmentVariable(Copy.AlwaysRetryEnvVar, null);
Copy.RefreshInternalEnvironmentValues();
}
/// <summary>
/// Restore the environment variables we cleared out at the beginning of the test.
/// </summary>
public void Dispose()
{
Environment.SetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar, _alwaysOverwriteReadOnlyFiles);
Environment.SetEnvironmentVariable(Copy.AlwaysRetryEnvVar, _alwaysRetry);
Copy.RefreshInternalEnvironmentValues();
}
[Fact]
public void CopyWithNoInput()
{
var task = new Copy { BuildEngine = new MockEngine(true), };
task.Execute().ShouldBeTrue();
(task.CopiedFiles == null || task.CopiedFiles.Length == 0).ShouldBeTrue();
(task.DestinationFiles == null || task.DestinationFiles.Length == 0).ShouldBeTrue();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
[Fact]
public void CopyWithMatchingSourceFilesToDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(1);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(1);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithSourceFilesToDestinationFolder(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var sourceFile = env.CreateFile("source.txt");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(1);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(1);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithSourceFoldersToDestinationFolder(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var s0Folder = env.DefaultTestDirectory.CreateDirectory("source0");
s0Folder.CreateFile("00.txt");
s0Folder.CreateFile("01.txt");
var s0AFolder = s0Folder.CreateDirectory("a");
s0AFolder.CreateFile("a0.txt");
s0AFolder.CreateFile("a1.txt");
_ = s0Folder.CreateDirectory("b");
var s0CFolder = s0Folder.CreateDirectory("c");
s0CFolder.CreateFile("c0.txt");
var s1Folder = env.DefaultTestDirectory.CreateDirectory("source1");
s1Folder.CreateFile("10.txt");
s1Folder.CreateFile("11.txt");
var s1AFolder = s1Folder.CreateDirectory("a");
s1AFolder.CreateFile("a0.txt");
s1AFolder.CreateFile("a1.txt");
var s1BFolder = s1Folder.CreateDirectory("b");
s1BFolder.CreateFile("b0.txt");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = new MockEngine(true),
SourceFolders = new ITaskItem[] { new TaskItem(s0Folder.Path), new TaskItem(s1Folder.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
RetryDelayMilliseconds = 1,
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(10);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(10);
task.WroteAtLeastOneFile.ShouldBeTrue();
Directory.Exists(Path.Combine(destinationFolder.Path, "source0")).ShouldBeTrue();
Directory.Exists(Path.Combine(destinationFolder.Path, "source1")).ShouldBeTrue();
}
}
[Fact]
public void CopyWithNoSource()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetDestinationExists))]
public void CopyWithMultipleSourceTypes(bool isDestinationExists)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var sourceFolder = env.DefaultTestDirectory.CreateDirectory("source");
sourceFolder.CreateFile("source.txt");
var aDirectory = sourceFolder.CreateDirectory("a");
aDirectory.CreateFile("a.txt");
sourceFolder.CreateDirectory("b");
var destinationFolder = env.CreateFolder(isDestinationExists);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
SourceFolders = new ITaskItem[] { new TaskItem(sourceFolder.Path) },
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(3);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(3);
task.WroteAtLeastOneFile.ShouldBeTrue();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithEmptySourceFiles(ITaskItem[] sourceFiles)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithEmptySourceFolders(ITaskItem[] sourceFolders)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFolders = sourceFolders,
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeTrue();
task.CopiedFiles.ShouldNotBeNull();
task.CopiedFiles.Length.ShouldBe(0);
task.DestinationFiles.ShouldNotBeNull();
task.DestinationFiles.Length.ShouldBe(0);
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Theory]
[MemberData(nameof(GetNullAndEmptyArrays))]
public void CopyWithNoDestination(ITaskItem[] destinationFiles)
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = destinationFiles,
};
task.Execute().ShouldBeFalse();
// Copy.NeedsDestination (MSB3023) or General.TwoVectorsMustHaveSameLength (MSB3094)
engine.AssertLogContains(destinationFiles == null ? "MSB3023" : "MSB3094");
task.CopiedFiles.ShouldBeNull();
(task.DestinationFiles == null || task.DestinationFiles.Length == 0).ShouldBeTrue();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithMultipleDestinationTypes()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var destinationFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
DestinationFolder = new TaskItem(destinationFolder.Path),
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3022"); // Copy.ExactlyOneTypeOfDestination
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithSourceFoldersAndDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var sourceFolder = env.CreateFolder(true);
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
SourceFolders = new ITaskItem[] { new TaskItem(sourceFolder.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination0.txt"), new TaskItem("destination1.txt") },
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3896"); // Copy.IncompatibleParameters
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
[Fact]
public void CopyWithDifferentLengthSourceFilesToDestinationFiles()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination0.txt"), new TaskItem("destination1.txt") },
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3094"); // General.TwoVectorsMustHaveSameLength
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// Verifies that we error for retries less than 0
/// </summary>
[Fact]
public void CopyWithInvalidRetryCount()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
Retries = -1,
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3028"); // Copy.InvalidRetryCount
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// Verifies that we error for retry delay less than 0
/// </summary>
[Fact]
public void CopyWithInvalidRetryDelay()
{
using (var env = TestEnvironment.Create())
{
var engine = new MockEngine(true);
var sourceFile = env.CreateFile("source.txt");
var task = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile.Path) },
DestinationFiles = new ITaskItem[] { new TaskItem("destination.txt") },
RetryDelayMilliseconds = -1,
};
task.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3029"); // Copy.InvalidRetryDelay
task.CopiedFiles.ShouldBeNull();
task.DestinationFiles.ShouldNotBeNull();
task.WroteAtLeastOneFile.ShouldBeFalse();
}
}
/// <summary>
/// If OnlyCopyIfDifferent is set to "true" then we shouldn't copy over files that
/// have the same date and time.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void DontCopyOverSameFile(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string file = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(file, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a temp file.");
}
ITaskItem f = new TaskItem(file);
ITaskItem[] sourceFiles = { f };
ITaskItem[] destinationFiles = { f };
CopyMonitor m = new CopyMonitor();
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
t.Execute(m.CopyFile, GetParallelismThreadCount(isUseSingleThreadedCopy));
// Expect for there to have been no copies.
Assert.Equal(0, m.copyCount);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(file);
}
}
/// <summary>
/// Question should not copy any files.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void QuestionCopyFile(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile(null, ".tmp", false);
string content = "This is a source file.";
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write(content);
}
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
CopyMonitor m = new CopyMonitor();
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
FailIfNotIncremental = true,
};
Assert.False(t.Execute(m.CopyFile, GetParallelismThreadCount(isUseSingleThreadedCopy)));
// Expect for there to have been no copies.
Assert.Equal(0, m.copyCount);
Assert.False(FileUtilities.FileExistsNoThrow(destination));
}
finally
{
File.Delete(source);
}
}
/// <summary>
/// Question copy should not error if copy did no work.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void QuestionCopyFileSameContent(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
string content = "This is a source file.";
DateTime testTime = DateTime.Now;
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write(content);
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write(content);
}
FileInfo sourcefi = new FileInfo(source);
sourcefi.LastWriteTimeUtc = testTime;
FileInfo destinationfi = new FileInfo(destination);
destinationfi.LastWriteTimeUtc = testTime;
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
CopyMonitor m = new CopyMonitor();
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
SkipUnchangedFiles = true,
FailIfNotIncremental = true,
};
Assert.True(t.Execute(m.CopyFile, GetParallelismThreadCount(isUseSingleThreadedCopy)));
// Expect for there to have been no copies.
Assert.Equal(0, m.copyCount);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// Question copy should error if a copy will occur.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void QuestionCopyFileNotSameContent(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write("This is a source file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write("This is a destination file.");
}
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
CopyMonitor m = new CopyMonitor();
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
SkipUnchangedFiles = true,
FailIfNotIncremental = true,
};
Assert.False(t.Execute(m.CopyFile, GetParallelismThreadCount(isUseSingleThreadedCopy)));
// Expect for there to have been no copies.
Assert.Equal(0, m.copyCount);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// Unless ignore readonly attributes is set, we should not copy over readonly files.
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotNormallyCopyOverReadOnlyFile(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write("This is a source file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write("This is a destination file.");
}
File.SetAttributes(destination, FileAttributes.ReadOnly);
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
// OverwriteReadOnlyFiles defaults to false
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
// Should fail: target is readonly
Assert.False(t.Execute());
// Expect for there to have been no copies.
Assert.Empty(t.CopiedFiles);
string destinationContent = File.ReadAllText(destination);
Assert.Equal("This is a destination file.", destinationContent);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // did not do retries as it was r/o
}
finally
{
File.SetAttributes(source, FileAttributes.Normal);
File.SetAttributes(destination, FileAttributes.Normal);
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// If MSBUILDALWAYSOVERWRITEREADONLYFILES is set, then overwrite read-only even when
/// OverwriteReadOnlyFiles is false
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyOverReadOnlyFileEnvironmentOverride(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
string oldAlwaysOverwriteValue = Environment.GetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar);
try
{
Environment.SetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar, "1 ");
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write("This is a source file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write("This is a destination file.");
}
File.SetAttributes(destination, FileAttributes.ReadOnly);
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
OverwriteReadOnlyFiles = false,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
// Should not fail although target is readonly
Assert.True(t.Execute());
// Should have copied file anyway
Assert.Single(t.CopiedFiles);
string destinationContent = File.ReadAllText(destination);
Assert.Equal("This is a source file.", destinationContent);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
Environment.SetEnvironmentVariable(Copy.AlwaysOverwriteReadOnlyFilesEnvVar, oldAlwaysOverwriteValue);
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// If MSBUILDALWAYSRETRY is set, keep retrying the copy.
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void AlwaysRetryCopyEnvironmentOverride(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
string oldAlwaysRetryValue = Environment.GetEnvironmentVariable(Copy.AlwaysRetryEnvVar);
try
{
Environment.SetEnvironmentVariable(Copy.AlwaysRetryEnvVar, "1 ");
Copy.RefreshInternalEnvironmentValues();
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write("This is a source file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write("This is a destination file.");
}
File.SetAttributes(destination, FileAttributes.ReadOnly);
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
OverwriteReadOnlyFiles = false,
Retries = 5,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
// The file is read-only, so the retries will all fail.
Assert.False(t.Execute());
// 3 warnings per retry, except the last one which has only two.
((MockEngine)t.BuildEngine).AssertLogContains("MSB3026");
Assert.Equal(((t.Retries + 1) * 3) - 1, ((MockEngine)t.BuildEngine).Warnings);
// One error for "retrying failed", one error for "copy failed"
((MockEngine)t.BuildEngine).AssertLogContains("MSB3027");
((MockEngine)t.BuildEngine).AssertLogContains("MSB3021");
Assert.Equal(2, ((MockEngine)t.BuildEngine).Errors);
}
finally
{
Environment.SetEnvironmentVariable(Copy.AlwaysRetryEnvVar, oldAlwaysRetryValue);
Copy.RefreshInternalEnvironmentValues();
File.SetAttributes(destination, FileAttributes.Normal);
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// Unless ignore readonly attributes is set, we should not copy over readonly files.
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyOverReadOnlyFileParameterIsSet(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string source = FileUtilities.GetTemporaryFile();
string destination = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(source, true))
{
sw.Write("This is a source file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination, true))
{
sw.Write("This is a destination file.");
}
File.SetAttributes(destination, FileAttributes.ReadOnly);
ITaskItem sourceItem = new TaskItem(source);
ITaskItem destinationItem = new TaskItem(destination);
ITaskItem[] sourceFiles = { sourceItem };
ITaskItem[] destinationFiles = { destinationItem };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
OverwriteReadOnlyFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
// Should not fail although target is readonly
Assert.True(t.Execute());
// Should have copied file anyway
Assert.Single(t.CopiedFiles);
string destinationContent = File.ReadAllText(destination);
Assert.Equal("This is a source file.", destinationContent);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(source);
File.Delete(destination);
}
}
/// <summary>
/// Unless ignore readonly attributes is set, we should not copy over readonly files.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyOverReadOnlyFileParameterIsSetWithDestinationFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string source1 = FileUtilities.GetTemporaryFile();
string source2 = FileUtilities.GetTemporaryFile();
string destinationFolder = Path.Combine(Path.GetTempPath(), "2A333ED756AF4dc392E728D0F874A398");
string destination1 = Path.Combine(destinationFolder, Path.GetFileName(source1));
string destination2 = Path.Combine(destinationFolder, Path.GetFileName(source2));
try
{
Directory.CreateDirectory(destinationFolder);
using (StreamWriter sw = FileUtilities.OpenWrite(source1, true))
{
sw.Write("This is a source file1.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(source2, true))
{
sw.Write("This is a source file2.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination1, true))
{
sw.Write("This is a destination file1.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destination2, true))
{
sw.Write("This is a destination file2.");
}
// Set one destination readonly.
File.SetAttributes(destination1, FileAttributes.ReadOnly);
ITaskItem sourceItem1 = new TaskItem(source1);
ITaskItem sourceItem2 = new TaskItem(source2);
ITaskItem[] sourceFiles = new ITaskItem[] { sourceItem1, sourceItem2 };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destinationFolder),
OverwriteReadOnlyFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
// Should not fail although one target is readonly
Assert.True(t.Execute());
// Should have copied files anyway
Assert.Equal(2, t.CopiedFiles.Length);
string destinationContent1 = File.ReadAllText(destination1);
Assert.Equal("This is a source file1.", destinationContent1);
string destinationContent2 = File.ReadAllText(destination2);
Assert.Equal("This is a source file2.", destinationContent2);
Assert.NotEqual(FileAttributes.ReadOnly, File.GetAttributes(destination1) & FileAttributes.ReadOnly);
Assert.NotEqual(FileAttributes.ReadOnly, File.GetAttributes(destination2) & FileAttributes.ReadOnly);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.SetAttributes(destination1, FileAttributes.Normal); // just in case
File.SetAttributes(destination2, FileAttributes.Normal); // just in case
File.Delete(source1);
File.Delete(source2);
File.Delete(destination1);
File.Delete(destination2);
FileUtilities.DeleteWithoutTrailingBackslash(destinationFolder, true);
}
}
/*
* Method: DoCopyOverDifferentFile
*
* If OnlyCopyIfDifferent is set to "true" then we should still copy over files that
* have different dates or sizes.
*/
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoCopyOverDifferentFile(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = FileUtilities.GetTemporaryFile();
string destinationFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destinationFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
t.Execute();
string destinationFileContents;
using (StreamReader sr = FileUtilities.OpenRead(destinationFile)) // HIGHCHAR: Test reads ASCII (not ANSI).
{
destinationFileContents = sr.ReadToEnd();
}
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
File.Delete(destinationFile);
}
}
/*
* Method: DoCopyOverCopiedFile
*
* If SkipUnchangedFiles is set to "false" then we should always copy over files that have same dates and sizes.
* If SkipUnchangedFiles is set to "true" then we should never copy over files that have same dates and sizes.
*/
[Theory(Skip = "https://github.com/dotnet/msbuild/issues/4126")]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public void DoCopyOverCopiedFile(bool skipUnchangedFiles, bool isUseHardLinks, bool isUseSymbolicLinks)
{
using (var env = TestEnvironment.Create())
{
var sourceFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, null, "src", false);
var destinationFile = FileUtilities.GetTemporaryFile(env.DefaultTestDirectory.Path, null, "dst", false);
File.WriteAllText(sourceFile, "This is a source temp file.");
// run copy twice, so we test if we are able to overwrite previously copied (or linked) file
for (var i = 0; i < 2; i++)
{
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new[] { new TaskItem(sourceFile) },
DestinationFiles = new[] { new TaskItem(destinationFile) },
SkipUnchangedFiles = skipUnchangedFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var success = t.Execute();
Assert.True(success);
var shouldNotCopy = skipUnchangedFiles &&
i == 1 &&
// SkipUnchanged check will always fail for symbolic links,
// because we compare attributes of real file with attributes of symbolic link.
!isUseSymbolicLinks &&
// On Windows and MacOS File.Copy already preserves LastWriteTime, but on Linux extra step is needed.
// TODO - this need to be fixed on Linux
(!NativeMethodsShared.IsLinux || isUseHardLinks);
if (shouldNotCopy)
{
engine.AssertLogContainsMessageFromResource(AssemblyResources.GetString,
"Copy.DidNotCopyBecauseOfFileMatch",
sourceFile,
destinationFile,
"SkipUnchangedFiles",
"true");
}
else
{
engine.AssertLogDoesntContainMessageFromResource(AssemblyResources.GetString,
"Copy.DidNotCopyBecauseOfFileMatch",
sourceFile,
destinationFile,
"SkipUnchangedFiles",
"true");
}
// "Expected the destination file to contain the contents of source file."
Assert.Equal("This is a source temp file.", File.ReadAllText(destinationFile));
engine.AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
}
}
/*
* Method: DoCopyOverNonExistentFile
*
* If OnlyCopyIfDifferent is set to "true" then we should still copy over files that
* don't exist.
*/
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoCopyOverNonExistentFile(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = FileUtilities.GetTemporaryFile();
string destinationFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
using (StreamWriter sw = FileUtilities.OpenWrite(destinationFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
File.Delete(destinationFile);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
t.Execute();
Assert.True(File.Exists(destinationFile)); // "Expected the destination file to exist."
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
File.Delete(destinationFile);
}
}
/// <summary>
/// Make sure we do not retry when the source file has a misplaced colon
/// </summary>
[WindowsFullFrameworkOnlyTheory(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486. Colon is special only on Windows.")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryCopyNotSupportedException(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = FileUtilities.GetTemporaryFile();
string destinationFile = "foobar:";
try
{
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
engine.AssertLogContains("MSB3021");
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
}
}
/// <summary>
/// Make sure we do not retry when the source file does not exist
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryCopyNonExistentSourceFile(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = "Nannanacat";
string destinationFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(destinationFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
File.Delete(destinationFile);
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
engine.AssertLogContains("MSB3030");
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(destinationFile);
}
}
/// <summary>
/// Make sure we do not retry when the source file is a folder
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryCopyWhenSourceIsFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = Path.GetTempPath();
string destinationFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(destinationFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
File.Delete(destinationFile);
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
engine.AssertLogContains("MSB3025");
engine.AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(destinationFile);
}
}
/// <summary>
/// Most important case is when destination is locked
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoRetryWhenDestinationLocked(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string destinationFile = Path.GetTempFileName();
string sourceFile = Path.GetTempFileName();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(destinationFile, true)) // Keep it locked
{
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFiles = new ITaskItem[] { new TaskItem(destinationFile) },
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3021"); // copy failed
engine.AssertLogContains("MSB3026"); // DID retry
if (NativeMethodsShared.IsWindows)
{
engine.AssertLogContains(Process.GetCurrentProcess().Id.ToString()); // the file is locked by the current process
}
Assert.Equal(2, engine.Errors); // retries failed and the actual failure
Assert.Equal(10, engine.Warnings);
}
}
finally
{
File.Delete(sourceFile);
File.Delete(destinationFile);
}
}
#if FEATURE_SECURITY_PERMISSIONS
/// <summary>
/// When destination is inaccessible due to ACL, do NOT retry
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryWhenDestinationLockedDueToAcl(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string tempDirectory = Path.Combine(Path.GetTempPath(), "DoNotRetryWhenDestinationLockedDueToAcl");
string destinationFile = Path.Combine(tempDirectory, "DestinationFile.txt");
string sourceFile = Path.Combine(tempDirectory, "SourceFile.txt");
if (Directory.Exists(tempDirectory))
{
FileUtilities.DeleteDirectoryNoThrow(tempDirectory, true);
}
Directory.CreateDirectory(tempDirectory);
File.WriteAllText(destinationFile, "Destination");
File.WriteAllText(sourceFile, "SourceFile");
string userAccount = $@"{Environment.UserDomainName}\{Environment.UserName}";
var denyFile = new FileSystemAccessRule(userAccount, FileSystemRights.Write | FileSystemRights.Delete | FileSystemRights.DeleteSubdirectoriesAndFiles | FileSystemRights.WriteData, AccessControlType.Deny);
var denyDirectory = new FileSystemAccessRule(userAccount, FileSystemRights.DeleteSubdirectoriesAndFiles, AccessControlType.Deny);
FileSecurity fSecurity = File.GetAccessControl(destinationFile);
DirectorySecurity dSecurity = Directory.GetAccessControl(tempDirectory);
try
{
fSecurity.AddAccessRule(denyFile);
File.SetAccessControl(destinationFile, fSecurity);
dSecurity.AddAccessRule(denyDirectory);
Directory.SetAccessControl(tempDirectory, dSecurity);
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(sourceFile) },
DestinationFiles = new ITaskItem[] { new TaskItem(destinationFile) },
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3021"); // copy failed
engine.AssertLogDoesntContain("MSB3026"); // Didn't retry
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
}
finally
{
fSecurity.RemoveAccessRule(denyFile);
File.SetAccessControl(destinationFile, fSecurity);
dSecurity.RemoveAccessRule(denyDirectory);
Directory.SetAccessControl(tempDirectory, dSecurity);
if (Directory.Exists(tempDirectory))
{
FileUtilities.DeleteDirectoryNoThrow(tempDirectory, true);
}
}
}
#endif
/// <summary>
/// Make sure we do not retry when the destination file is a folder
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryCopyWhenDestinationFolderIsFile(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string destinationFile = FileUtilities.GetTemporaryFile();
string sourceFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true))
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destinationFile),
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3021"); // copy failed
engine.AssertLogDoesntContain("MSB3026"); // Didn't retry
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
}
finally
{
File.Delete(sourceFile);
}
}
/// <summary>
/// Make sure we do not retry when the destination file is a folder
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotRetryCopyWhenDestinationFileIsFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string destinationFile = Path.GetTempPath();
string sourceFile = FileUtilities.GetTemporaryFile();
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a destination temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
Assert.False(result);
Assert.Equal(1, engine.Errors);
Assert.Equal(0, engine.Warnings);
engine.AssertLogContains("MSB3024");
engine.AssertLogDoesntContain("MSB3026");
}
finally
{
File.Delete(sourceFile);
}
}
/// <summary>
/// CopiedFiles should only include files that were successfully copied
/// (or skipped), not files for which there was an error.
/// </summary>
[WindowsFullFrameworkOnlyTheory(additionalMessage: ".NET Core 2.1+ no longer validates paths: https://github.com/dotnet/corefx/issues/27779#issuecomment-371253486. Under Unix all filenames are valid and this test is not useful.")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void OutputsOnlyIncludeSuccessfulCopies(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string temp = Path.GetTempPath();
string inFile1 = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A392");
string inFile2 = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A393");
const string invalidFile = "!@#$%^&*()|";
string validOutFile = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A394");
try
{
FileStream fs = null;
FileStream fs2 = null;
try
{
fs = File.Create(inFile1);
fs2 = File.Create(inFile2);
}
finally
{
fs?.Dispose();
fs2?.Dispose();
}
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
ITaskItem i1 = new TaskItem(inFile1);
i1.SetMetadata("Locale", "en-GB");
i1.SetMetadata("Color", "taupe");
t.SourceFiles = new[] { new TaskItem(inFile2), i1 };
ITaskItem o1 = new TaskItem(validOutFile);
o1.SetMetadata("Locale", "fr");
o1.SetMetadata("Flavor", "Pumpkin");
t.DestinationFiles = new[] { new TaskItem(invalidFile), o1 };
bool success = t.Execute();
Assert.False(success);
Assert.Single(t.CopiedFiles);
Assert.Equal(validOutFile, t.CopiedFiles[0].ItemSpec);
Assert.Equal(2, t.DestinationFiles.Length);
Assert.Equal("fr", t.DestinationFiles[1].GetMetadata("Locale"));
// Output ItemSpec should not be overwritten.
Assert.Equal(invalidFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(validOutFile, t.DestinationFiles[1].ItemSpec);
Assert.Equal(validOutFile, t.CopiedFiles[0].ItemSpec);
// Sources attributes should be left untouched.
Assert.Equal("en-GB", t.SourceFiles[1].GetMetadata("Locale"));
Assert.Equal("taupe", t.SourceFiles[1].GetMetadata("Color"));
// Attributes not on Sources should be left untouched.
Assert.Equal("Pumpkin", t.DestinationFiles[1].GetMetadata("Flavor"));
Assert.Equal("Pumpkin", t.CopiedFiles[0].GetMetadata("Flavor"));
// Attribute should have been forwarded
Assert.Equal("taupe", t.DestinationFiles[1].GetMetadata("Color"));
Assert.Equal("taupe", t.CopiedFiles[0].GetMetadata("Color"));
// Attribute should not have been updated if it already existed on destination
Assert.Equal("fr", t.DestinationFiles[1].GetMetadata("Locale"));
Assert.Equal("fr", t.CopiedFiles[0].GetMetadata("Locale"));
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(inFile1);
File.Delete(inFile2);
File.Delete(validOutFile);
}
}
/// <summary>
/// Copying a file on top of itself should be a success (no-op) whether
/// or not skipUnchangedFiles is true or false.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyFileOnItself(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string temp = Path.GetTempPath();
string file = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A395");
try
{
FileStream fs = null;
try
{
fs = File.Create(file);
}
finally
{
fs?.Dispose();
}
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(file) },
DestinationFiles = new ITaskItem[] { new TaskItem(file) },
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
Assert.True(success);
Assert.Single(t.DestinationFiles);
Assert.Equal(file, t.DestinationFiles[0].ItemSpec);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries, nothing to do
engine = new MockEngine(_testOutputHelper);
t = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(file) },
DestinationFiles = new ITaskItem[] { new TaskItem(file) },
SkipUnchangedFiles = false,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
success = t.Execute();
Assert.True(success);
Assert.Single(t.DestinationFiles);
Assert.Equal(file, t.DestinationFiles[0].ItemSpec);
Assert.Single(t.CopiedFiles);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries, nothing to do
}
finally
{
File.Delete(file);
}
}
/// <summary>
/// Copying a file on top of itself should be a success (no-op) whether
/// or not skipUnchangedFiles is true or false. Variation with different casing/relativeness.
/// </summary>
[WindowsOnlyTheory(additionalMessage: "File names under Unix are case-sensitive and this test is not useful.")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyFileOnItself2(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string currdir = Directory.GetCurrentDirectory();
string filename = "2A333ED756AF4dc392E728D0F864A396";
string file = Path.Combine(currdir, filename);
try
{
FileStream fs = null;
try
{
fs = File.Create(file);
}
finally
{
fs?.Dispose();
}
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(file) },
DestinationFiles = new ITaskItem[] { new TaskItem(filename.ToLowerInvariant()) },
SkipUnchangedFiles = false,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
Assert.True(success);
Assert.Single(t.DestinationFiles);
Assert.Equal(filename.ToLowerInvariant(), t.DestinationFiles[0].ItemSpec);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries, nothing to do
}
finally
{
File.Delete(file);
}
}
/// <summary>
/// Copying a file on top of itself should be a success (no-op) whether
/// or not skipUnchangedFiles is true or false. Variation with a second copy failure.
/// </summary>
[Theory]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyFileOnItselfAndFailACopy(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string temp = Path.GetTempPath();
string file = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A395");
string invalidFile = NativeMethodsShared.IsUnixLike ? Path.Combine(temp, "!@#$%^&*()|") : "!@#$%^&*()|";
const string dest2 = "whatever";
try
{
FileStream fs = null;
try
{
fs = File.Create(file);
}
finally
{
fs?.Dispose();
}
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(file), new TaskItem(invalidFile) },
DestinationFiles = new ITaskItem[] { new TaskItem(file), new TaskItem(dest2) },
SkipUnchangedFiles = false,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
// Since on Unix there are no invalid file names, the copy will succeed
Assert.False(NativeMethodsShared.IsUnixLike ? !success : success);
Assert.Equal(2, t.DestinationFiles.Length);
Assert.Equal(file, t.DestinationFiles[0].ItemSpec);
Assert.Equal(dest2, t.DestinationFiles[1].ItemSpec);
Assert.Equal(file, t.CopiedFiles[0].ItemSpec);
if (NativeMethodsShared.IsUnixLike)
{
Assert.Equal(2, t.CopiedFiles.Length);
}
else
{
Assert.Single(t.CopiedFiles);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026");
// Didn't do retries, no op then invalid
}
}
finally
{
File.Delete(file);
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyToDestinationFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = FileUtilities.GetTemporaryFile();
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
// Don't create the dest folder, let task do that
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
var me = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile)); // "destination exists"
string destinationFileContents;
using (StreamReader sr = FileUtilities.OpenRead(destFile))
{
destinationFileContents = sr.ReadToEnd();
}
if (!isUseHardLinks)
{
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogDoesntContainMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile, destFile);
}
else
{
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile, destFile);
}
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
Assert.Single(t.DestinationFiles);
Assert.Single(t.CopiedFiles);
Assert.Equal(destFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile, t.CopiedFiles[0].ItemSpec);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
Helpers.DeleteFiles(sourceFile, destFile);
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void CopyDoubleEscapableFileToDestinationFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFileEscaped = Path.GetTempPath() + "a%253A_" + Guid.NewGuid().ToString("N") + ".txt";
string sourceFile = EscapingUtilities.UnescapeAll(sourceFileEscaped);
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
// Don't create the dest folder, let task do that
ITaskItem[] sourceFiles = { new TaskItem(sourceFileEscaped) };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile)); // "destination exists"
string destinationFileContents;
using (StreamReader sr = FileUtilities.OpenRead(destFile))
{
destinationFileContents = sr.ReadToEnd();
}
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
Assert.Single(t.DestinationFiles);
Assert.Single(t.CopiedFiles);
Assert.Equal(destFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile, t.CopiedFiles[0].ItemSpec);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
Helpers.DeleteFiles(sourceFile, destFile);
}
}
/// <summary>
/// Copying duplicates should only perform the actual copy once for each unique source/destination pair
/// but should still produce outputs for all specified source/destination pairs.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void CopyWithDuplicatesUsingFolder(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string tempPath = Path.GetTempPath();
ITaskItem[] sourceFiles =
{
new TaskItem(Path.Combine(tempPath, "a.cs")),
new TaskItem(Path.Combine(tempPath, "b.cs")),
new TaskItem(Path.Combine(tempPath, "a.cs")),
new TaskItem(Path.Combine(tempPath, "a.cs")),
};
foreach (ITaskItem item in sourceFiles)
{
using (StreamWriter sw = FileUtilities.OpenWrite(item.ItemSpec, false)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
}
var filesActuallyCopied = new List<KeyValuePair<FileState, FileState>>();
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(Path.Combine(tempPath, "foo")),
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute(delegate (FileState source, FileState dest)
{
lock (filesActuallyCopied)
{
filesActuallyCopied.Add(new KeyValuePair<FileState, FileState>(source, dest));
}
return true;
}, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.True(success);
Assert.Equal(2, filesActuallyCopied.Count);
Assert.Equal(4, t.CopiedFiles.Length);
// Copy calls to different destinations can come in any order when running in parallel.
filesActuallyCopied.Select(f => Path.GetFileName(f.Key.Name)).ShouldBe(new[] { "a.cs", "b.cs" }, ignoreOrder: true);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// Copying duplicates should only perform the actual copy once for each unique source/destination pair
/// but should still produce outputs for all specified source/destination pairs.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void CopyWithDuplicatesUsingFiles(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
string tempPath = Path.GetTempPath();
ITaskItem[] sourceFiles =
{
new TaskItem(Path.Combine(tempPath, "a.cs")),
new TaskItem(Path.Combine(tempPath, "b.cs")),
new TaskItem(Path.Combine(tempPath, "a.cs")),
new TaskItem(Path.Combine(tempPath, "a.cs")),
new TaskItem(Path.Combine(tempPath, "a.cs")),
};
foreach (ITaskItem item in sourceFiles)
{
using (StreamWriter sw = FileUtilities.OpenWrite(item.ItemSpec, false)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
}
ITaskItem[] destFiles =
{
new TaskItem(Path.Combine(tempPath, @"xa.cs")), // a.cs -> xa.cs
new TaskItem(Path.Combine(tempPath, @"xa.cs")), // b.cs -> xa.cs should copy because it's a different source
new TaskItem(Path.Combine(tempPath, @"xb.cs")), // a.cs -> xb.cs should copy because it's a different destination
new TaskItem(Path.Combine(tempPath, @"xa.cs")), // a.cs -> xa.cs should copy because it's a different source from the b.cs copy done previously
new TaskItem(Path.Combine(tempPath, @"xa.cs")), // a.cs -> xa.cs should not copy because it's the same source
};
var filesActuallyCopied = new List<KeyValuePair<FileState, FileState>>();
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute(delegate (FileState source, FileState dest)
{
lock (filesActuallyCopied)
{
filesActuallyCopied.Add(new KeyValuePair<FileState, FileState>(source, dest));
}
return true;
}, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.True(success);
Assert.Equal(4, filesActuallyCopied.Count);
Assert.Equal(5, t.CopiedFiles.Length);
// Copy calls to different destinations can come in any order when running in parallel.
string xaPath = Path.Combine(tempPath, "xa.cs");
var xaCopies = filesActuallyCopied.Where(f => f.Value.Name == xaPath).ToList();
Assert.Equal(3, xaCopies.Count);
Assert.Equal(Path.Combine(tempPath, "a.cs"), xaCopies[0].Key.Name);
Assert.Equal(Path.Combine(tempPath, "b.cs"), xaCopies[1].Key.Name);
Assert.Equal(Path.Combine(tempPath, "a.cs"), xaCopies[2].Key.Name);
string xbPath = Path.Combine(tempPath, "xb.cs");
var xbCopies = filesActuallyCopied.Where(f => f.Value.Name == xbPath).ToList();
Assert.Single(xbCopies);
Assert.Equal(Path.Combine(tempPath, "a.cs"), xbCopies[0].Key.Name);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// DestinationFiles should only include files that were successfully copied
/// (or skipped), not files for which there was an error.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DestinationFilesLengthNotEqualSourceFilesLength(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string temp = Path.GetTempPath();
string inFile1 = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string inFile2 = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A399");
string outFile1 = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A400");
try
{
FileStream fs = null;
FileStream fs2 = null;
try
{
fs = File.Create(inFile1);
fs2 = File.Create(inFile2);
}
finally
{
fs?.Dispose();
fs2?.Dispose();
}
var engine = new MockEngine(_testOutputHelper);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(inFile1), new TaskItem(inFile2) },
DestinationFiles = new ITaskItem[] { new TaskItem(outFile1) },
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool success = t.Execute();
Assert.False(success);
Assert.Single(t.DestinationFiles);
Assert.Null(t.CopiedFiles);
Assert.False(File.Exists(outFile1));
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(inFile1);
File.Delete(inFile2);
File.Delete(outFile1);
}
}
/// <summary>
/// If the destination path is too long, the task should not bubble up
/// the System.IO.PathTooLongException
/// </summary>
[WindowsFullFrameworkOnlyTheory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void Regress451057_ExitGracefullyIfPathNameIsTooLong(bool isUseHardLinks, bool isUseSymbolicLinks)
{
string sourceFile = FileUtilities.GetTemporaryFile();
const string destinationFile = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
try
{
using (StreamWriter sw = FileUtilities.OpenWrite(sourceFile, true)) // HIGHCHAR: Test writes in UTF8 without preamble.
{
sw.Write("This is a source temp file.");
}
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
// Expect for there to have been no copies.
Assert.False(result);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
}
}
/// <summary>
/// If the source path is too long, the task should not bubble up
/// the System.IO.PathTooLongException
/// </summary>
[WindowsFullFrameworkOnlyTheory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void Regress451057_ExitGracefullyIfPathNameIsTooLong2(bool isUseHardLinks, bool isUseSymbolicLinks)
{
const string sourceFile = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
string destinationFile = FileUtilities.GetTemporaryFile();
File.Delete(destinationFile);
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
ITaskItem[] destinationFiles = { new TaskItem(destinationFile) };
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
// Expect for there to have been no copies.
Assert.False(result);
Assert.False(File.Exists(destinationFile));
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// If the SourceFiles parameter is given invalid path characters, make sure the task exits gracefully.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void ExitGracefullyOnInvalidPathCharacters(bool isUseHardLinks, bool isUseSymbolicLinks)
{
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = new ITaskItem[] { new TaskItem("foo | bar") },
DestinationFolder = new TaskItem("dest"),
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
// Expect for there to have been no copies.
Assert.False(result);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// If the DestinationFolder parameter is given invalid path characters, make sure the task exits gracefully.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void ExitGracefullyOnInvalidPathCharactersInDestinationFolder(bool isUseHardLinks, bool isUseSymbolicLinks)
{
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = new MockEngine(_testOutputHelper),
SourceFiles = new ITaskItem[] { new TaskItem("foo") },
DestinationFolder = new TaskItem("here | there"),
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
bool result = t.Execute();
// Expect for there to have been no copies.
Assert.False(result);
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
/// <summary>
/// Verifies that we error for retries less than 0
/// </summary>
[Fact]
public void InvalidRetryCount()
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = -1,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3028");
}
/// <summary>
/// Verifies that we error for retry delay less than 0
/// </summary>
[Fact]
public void InvalidRetryDelayCount()
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = 1,
RetryDelayMilliseconds = -1,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3029");
}
/// <summary>
/// Verifies that we do not log the retrying warning if we didn't request
/// retries.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void FailureWithNoRetries(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = 0,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var copyFunctor = new CopyFunctor(2, false /* do not throw on failure */);
bool result = t.Execute(copyFunctor.Copy, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.False(result);
engine.AssertLogDoesntContain("MSB3026");
engine.AssertLogDoesntContain("MSB3027");
}
/// <summary>
/// Retrying default
/// </summary>
[Fact]
public void DefaultRetriesIs10()
{
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
};
Assert.Equal(10, t.Retries);
}
/// <summary>
/// Delay default
/// </summary>
[Fact]
public void DefaultRetryDelayIs1000()
{
var t = new Copy();
Assert.Equal(1000, t.RetryDelayMilliseconds);
}
/// <summary>
/// Hardlink default
/// </summary>
[Fact]
public void DefaultNoHardlink()
{
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
};
Assert.False(t.UseHardlinksIfPossible);
}
/// <summary>
/// Verifies that we get the one retry we ask for after the first attempt fails,
/// and we get appropriate messages.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void SuccessAfterOneRetry(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 0, // Can't really test the delay, but at least try passing in a value
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = 1,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var copyFunctor = new CopyFunctor(2, false /* do not throw on failure */);
bool result = t.Execute(copyFunctor.Copy, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.True(result);
engine.AssertLogContains("MSB3026");
engine.AssertLogDoesntContain("MSB3027");
}
/// <summary>
/// Verifies that after a successful retry we continue to the next file
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void SuccessAfterOneRetryContinueToNextFile(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 1, // Can't really test the delay, but at least try passing in a value
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source"), new TaskItem("c:\\source2") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination"), new TaskItem("c:\\destination2") },
Retries = 1,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var copyFunctor = new CopyFunctor(2, false /* do not throw on failure */);
bool result = t.Execute(copyFunctor.Copy, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.True(result);
engine.AssertLogContains("MSB3026");
engine.AssertLogDoesntContain("MSB3027");
// Copy calls to different destinations can come in any order when running in parallel.
Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Name == FileUtilities.FixFilePath("c:\\source"));
Assert.Contains(copyFunctor.FilesCopiedSuccessfully, f => f.Name == FileUtilities.FixFilePath("c:\\source2"));
}
/// <summary>
/// The copy delegate can return false, or throw on failure.
/// This test tests returning false.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void TooFewRetriesReturnsFalse(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = 2,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var copyFunctor = new CopyFunctor(4, false /* do not throw */);
bool result = t.Execute(copyFunctor.Copy, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.False(result);
engine.AssertLogContains("MSB3026");
engine.AssertLogContains("MSB3027");
}
/// <summary>
/// The copy delegate can return false, or throw on failure.
/// This test tests the throw case.
/// </summary>
[Theory]
[MemberData(nameof(GetHardLinksSymLinksSingleThreaded))]
public void TooFewRetriesThrows(bool isUseHardLinks, bool isUseSymbolicLinks, bool isUseSingleThreadedCopy)
{
var engine = new MockEngine(true /* log to console */);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
Retries = 1,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
};
var copyFunctor = new CopyFunctor(3, true /* throw */);
bool result = t.Execute(copyFunctor.Copy, GetParallelismThreadCount(isUseSingleThreadedCopy));
Assert.False(result);
engine.AssertLogContains("MSB3026");
engine.AssertLogContains("MSB3027");
}
[WindowsOnlyTheory]
[InlineData(false, true)]
[InlineData(true, false)]
public void ErrorIfLinkFailedCheck(bool isUseHardLinks, bool isUseSymbolicLinks)
{
using (var env = TestEnvironment.Create())
{
var source = env.DefaultTestDirectory.CreateFile("source.txt", "This is a source file").Path;
var existing = env.DefaultTestDirectory.CreateFile("destination.txt", "This is an existing file.").Path;
File.SetAttributes(existing, FileAttributes.ReadOnly);
MockEngine engine = new MockEngine(_testOutputHelper);
Copy t = new Copy
{
RetryDelayMilliseconds = 1,
UseHardlinksIfPossible = isUseHardLinks,
UseSymboliclinksIfPossible = isUseSymbolicLinks,
ErrorIfLinkFails = true,
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem(source) },
DestinationFiles = new ITaskItem[] { new TaskItem(existing) },
};
t.Execute().ShouldBeFalse();
engine.AssertLogContains("MSB3893");
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[Fact]
public void CopyToDestinationFolderWithHardLinkCheck()
{
string sourceFile = FileUtilities.GetTemporaryFile();
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
File.WriteAllText(sourceFile, "This is a source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Don't create the dest folder, let task do that
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
var me = new MockEngine(true);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true,
UseHardlinksIfPossible = true
};
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile)); // "destination exists"
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile, destFile);
string destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is a source temp file.", destinationFileContents);
Assert.Single(t.DestinationFiles);
Assert.Single(t.CopiedFiles);
Assert.Equal(destFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile, t.CopiedFiles[0].ItemSpec);
// Now we will write new content to the source file
// we'll then check that the destination file automatically
// has the same content (i.e. it's been hard linked)
File.WriteAllText(sourceFile, "This is another source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Read the destination file (it should have the same modified content as the source)
destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is another source temp file.", destinationFileContents); // "Expected the destination hard linked file to contain the contents of source file. Even after modification of the source"
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
Helpers.DeleteFiles(sourceFile, destFile);
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[WindowsOnlyFact(additionalMessage: "SMB share paths only work on Windows.")]
public void CopyToDestinationFolderWithHardLinkFallbackNetwork()
{
// Workaround: For some reason when this test runs with all other tests we are getting
// the incorrect result from CreateHardLink error message (a message associated with
// another test). Calling GetHRForLastWin32Error / GetExceptionForHR seems to clear
// out the previous message and allow us to get the right message in the Copy task.
int errorCode = Marshal.GetHRForLastWin32Error();
Marshal.GetExceptionForHR(errorCode);
string sourceFile1 = FileUtilities.GetTemporaryFile();
string sourceFile2 = FileUtilities.GetTemporaryFile();
const string temp = @"\\localhost\c$\temp";
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile1 = Path.Combine(destFolder, Path.GetFileName(sourceFile1));
string destFile2 = Path.Combine(destFolder, Path.GetFileName(sourceFile2));
try
{
Directory.CreateDirectory(destFolder);
string nothingFile = Path.Combine(destFolder, "nothing.txt");
File.WriteAllText(nothingFile, "nothing");
File.Delete(nothingFile);
}
catch (Exception)
{
Console.WriteLine("CopyToDestinationFolderWithHardLinkFallbackNetwork test could not access the network.");
// Something caused us to not be able to access our "network" share, don't fail.
return;
}
try
{
// Create 2 files to ensure we test with parallel copy.
File.WriteAllText(sourceFile1, "This is source temp file 1."); // HIGHCHAR: Test writes in UTF8 without preamble.
File.WriteAllText(sourceFile2, "This is source temp file 2.");
ITaskItem[] sourceFiles =
{
new TaskItem(sourceFile1),
new TaskItem(sourceFile2)
};
var me = new MockEngine(true);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
UseHardlinksIfPossible = true,
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true
};
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile1)); // "destination exists"
Assert.True(File.Exists(destFile2)); // "destination exists"
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile1, destFile1);
// Can't do this below, because the real message doesn't end with String.Empty, it ends with a CLR exception string, and so matching breaks in PLOC.
// Instead look for the HRESULT that CLR unfortunately puts inside its exception string. Something like this:
// The system cannot move the file to a different disk drive. (Exception from HRESULT: 0x80070011)
// me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.RetryingAsFileCopy", sourceFile, destFile, String.Empty);
me.AssertLogContains("0x80070011");
string destinationFileContents = File.ReadAllText(destFile1);
Assert.Equal("This is source temp file 1.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
destinationFileContents = File.ReadAllText(destFile2);
Assert.Equal("This is source temp file 2.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
Assert.Equal(2, t.DestinationFiles.Length);
Assert.Equal(2, t.CopiedFiles.Length);
Assert.Equal(destFile1, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile2, t.DestinationFiles[1].ItemSpec);
Assert.Equal(destFile1, t.CopiedFiles[0].ItemSpec);
Assert.Equal(destFile2, t.CopiedFiles[1].ItemSpec);
// Now we will write new content to a source file
// we'll then check that the destination file automatically
// has the same content (i.e. it's been hard linked)
File.WriteAllText(sourceFile1, "This is another source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Read the destination file (it should have the same modified content as the source)
destinationFileContents = File.ReadAllText(destFile1);
Assert.Equal("This is source temp file 1.", destinationFileContents); // "Expected the destination copied file to contain the contents of original source file only."
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile1);
File.Delete(sourceFile2);
File.Delete(destFile1);
File.Delete(destFile2);
FileUtilities.DeleteWithoutTrailingBackslash(destFolder, true);
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[WindowsOnlyFact(additionalMessage: "Only Windows has a (small) link limit, and this tests for an HRESULT.")]
public void CopyToDestinationFolderWithHardLinkFallbackTooManyLinks()
{
// Workaround: For some reason when this test runs with all other tests we are getting
// the incorrect result from CreateHardLink error message (a message associated with
// another test). Calling GetHRForLastWin32Error / GetExceptionForHR seems to clear
// out the previous message and allow us to get the right message in the Copy task.
int errorCode = Marshal.GetHRForLastWin32Error();
Marshal.GetExceptionForHR(errorCode);
string sourceFile = FileUtilities.GetTemporaryFile();
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
File.WriteAllText(sourceFile, "This is a source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
Directory.CreateDirectory(destFolder);
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
MockEngine me = new MockEngine(true);
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
UseHardlinksIfPossible = true,
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true
};
// Exhaust the number (1024) of directory entries that can be created for a file
// This is 1 + (1 x hard links)
// We need to test the fallback code path when we're out of directory entries for a file..
for (int n = 0; n < 1025 /* make sure */; n++)
{
string destLink = Path.Combine(destFolder, Path.GetFileNameWithoutExtension(sourceFile) + "." + n);
string linkError = String.Empty;
Tasks.NativeMethods.MakeHardLink(destLink, sourceFile, ref linkError, t.Log);
}
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile)); // "destination exists"
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile, destFile);
// Can't do this below, because the real message doesn't end with String.Empty, it ends with a CLR exception string, and so matching breaks in PLOC.
// Instead look for the HRESULT that CLR unfortunately puts inside its exception string. Something like this
// Tried to create more than a few links to a file that is supported by the file system. (! yhMcE! Exception from HRESULT: Table c?! 0x80070476)
// me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.RetryingAsFileCopy", sourceFile, destFile, String.Empty);
me.AssertLogContains("0x80070476");
string destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination file to contain the contents of source file."
Assert.Single(t.DestinationFiles);
Assert.Single(t.CopiedFiles);
Assert.Equal(destFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile, t.CopiedFiles[0].ItemSpec);
// Now we will write new content to the source file
// we'll then check that the destination file automatically
// has the same content (i.e. it's been hard linked)
File.WriteAllText(sourceFile, "This is another source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Read the destination file (it should have the same modified content as the source)
destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination copied file to contain the contents of original source file only."
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
File.Delete(destFile);
FileUtilities.DeleteWithoutTrailingBackslash(destFolder, true);
}
}
/// <summary>
/// DestinationFolder should work.
/// </summary>
[RequiresSymbolicLinksFact]
public void CopyToDestinationFolderWithSymbolicLinkCheck()
{
string sourceFile = FileUtilities.GetTemporaryFile();
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
File.WriteAllText(sourceFile, "This is a source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Don't create the dest folder, let task do that
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
var me = new MockEngine(true);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true,
UseSymboliclinksIfPossible = true
};
bool success = t.Execute();
Assert.True(success); // "success"
Assert.True(File.Exists(destFile)); // "destination exists"
Assert.True((File.GetAttributes(destFile) & FileAttributes.ReparsePoint) != 0, "File was copied but is not a symlink");
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.SymbolicLinkComment", sourceFile, destFile);
string destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is a source temp file.", destinationFileContents); // "Expected the destination symbolic linked file to contain the contents of source file."
Assert.Single(t.DestinationFiles);
Assert.Single(t.CopiedFiles);
Assert.Equal(destFile, t.DestinationFiles[0].ItemSpec);
Assert.Equal(destFile, t.CopiedFiles[0].ItemSpec);
// Now we will write new content to the source file
// we'll then check that the destination file automatically
// has the same content (i.e. it's been hard linked)
File.WriteAllText(sourceFile, "This is another source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
// Read the destination file (it should have the same modified content as the source)
destinationFileContents = File.ReadAllText(destFile);
Assert.Equal("This is another source temp file.", destinationFileContents); // "Expected the destination hard linked file to contain the contents of source file. Even after modification of the source"
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3891"); // Didn't do retries
}
finally
{
File.Delete(sourceFile);
File.Delete(destFile);
FileUtilities.DeleteWithoutTrailingBackslash(destFolder, true);
}
}
/// <summary>
/// Verify build successful when UseHardlinksIfPossible and UseSymboliclinksIfPossible are true
/// </summary>
[Fact]
public void CopyWithHardAndSymbolicLinks()
{
string sourceFile = FileUtilities.GetTemporaryFile();
string temp = Path.GetTempPath();
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
string destFile = Path.Combine(destFolder, Path.GetFileName(sourceFile));
try
{
ITaskItem[] sourceFiles = { new TaskItem(sourceFile) };
MockEngine me = new MockEngine(true);
Copy t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
UseHardlinksIfPossible = true,
UseSymboliclinksIfPossible = true,
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFolder = new TaskItem(destFolder),
SkipUnchangedFiles = true
};
bool success = t.Execute();
Assert.True(success);
MockEngine.GetStringDelegate resourceDelegate = AssemblyResources.GetString;
me.AssertLogContainsMessageFromResource(resourceDelegate, "Copy.HardLinkComment", sourceFile, destFile);
}
finally
{
Helpers.DeleteFiles(sourceFile, destFile);
}
}
/// <summary>
/// Verifies that we error when ErrorIfLinkFailed is true when UseHardlinksIfPossible
/// and UseSymboliclinksIfPossible are false.
/// </summary>
[Fact]
public void InvalidErrorIfLinkFailed()
{
var engine = new MockEngine(true);
var t = new Copy
{
BuildEngine = engine,
SourceFiles = new ITaskItem[] { new TaskItem("c:\\source") },
DestinationFiles = new ITaskItem[] { new TaskItem("c:\\destination") },
UseHardlinksIfPossible = false,
UseSymboliclinksIfPossible = false,
ErrorIfLinkFails = true,
};
bool result = t.Execute();
Assert.False(result);
engine.AssertLogContains("MSB3892");
}
/// <summary>
/// An existing link source should not be modified.
/// </summary>
/// <remarks>
/// Related to issue [#8273](https://github.com/dotnet/msbuild/issues/8273)
/// </remarks>
[Theory]
[MemberData(nameof(GetHardLinksSymLinks))]
public void DoNotCorruptSourceOfLink(bool useHardLink, bool useSymbolicLink)
{
using TestEnvironment env = TestEnvironment.Create();
TransientTestFile sourceFile1 = env.CreateFile("source1.tmp", "This is the first source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
TransientTestFile sourceFile2 = env.CreateFile("source2.tmp", "This is the second source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
TransientTestFolder destFolder = env.CreateFolder(createFolder: false);
string destFile = Path.Combine(destFolder.Path, "The Destination");
// Don't create the dest folder, let task do that
ITaskItem[] sourceFiles = { new TaskItem(sourceFile1.Path) };
ITaskItem[] destinationFiles = { new TaskItem(destFile) };
var me = new MockEngine(true);
var t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = useHardLink,
UseSymboliclinksIfPossible = useSymbolicLink,
};
t.Execute().ShouldBeTrue();
File.Exists(destFile).ShouldBeTrue();
File.ReadAllText(destFile).ShouldBe("This is the first source temp file.");
sourceFiles = new TaskItem[] { new TaskItem(sourceFile2.Path) };
t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
UseHardlinksIfPossible = false,
UseSymboliclinksIfPossible = false,
};
t.Execute().ShouldBeTrue();
File.Exists(destFile).ShouldBeTrue();
File.ReadAllText(destFile).ShouldBe("This is the second source temp file.");
// Read the source file (it should not have been overwritten)
File.ReadAllText(sourceFile1.Path).ShouldBe("This is the first source temp file.");
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
destinationFiles = new TaskItem[] { new TaskItem(
Path.Combine(Path.GetDirectoryName(sourceFile2.Path), ".", Path.GetFileName(sourceFile2.Path))) // sourceFile2.Path with a "." inserted before the file name
};
t = new Copy
{
RetryDelayMilliseconds = 1, // speed up tests!
BuildEngine = me,
SourceFiles = sourceFiles,
DestinationFiles = destinationFiles,
SkipUnchangedFiles = true,
};
t.Execute().ShouldBeTrue();
File.Exists(sourceFile2.Path).ShouldBeTrue();
}
internal sealed class CopyMonitor
{
internal int copyCount;
/*
* Method: CopyFile
*
* Don't really copy the file, just count how many times this was called.
*/
internal bool? CopyFile(FileState source, FileState destination)
{
Interlocked.Increment(ref copyCount);
return true;
}
}
/// <summary>
/// Helper functor for retry tests.
/// Simulates the File.Copy method without touching the disk.
/// First copy fails as requested, subsequent copies succeed.
/// </summary>
private sealed class CopyFunctor
{
/// <summary>
/// Protects the counts and lists below.
/// </summary>
private readonly object _lockObj = new object();
/// <summary>
/// On what attempt count should we stop failing?
/// </summary>
private readonly int _countOfSuccess;
/// <summary>
/// Should we throw when we fail, instead of just returning false?
/// </summary>
private readonly bool _throwOnFailure;
/// <summary>
/// How many tries have we done so far
/// </summary>
private int _tries;
/// <summary>
/// Which files we actually copied
/// </summary>
internal List<FileState> FilesCopiedSuccessfully { get; } = new List<FileState>();
/// <summary>
/// Constructor
/// </summary>
internal CopyFunctor(int countOfSuccess, bool throwOnFailure)
{
_countOfSuccess = countOfSuccess;
_throwOnFailure = throwOnFailure;
}
/// <summary>
/// Pretend to be File.Copy.
/// </summary>
internal bool? Copy(FileState source, FileState destination)
{
lock (_lockObj)
{
_tries++;
// 2nd and subsequent copies always succeed
if (FilesCopiedSuccessfully.Count > 0 || _countOfSuccess == _tries)
{
Console.WriteLine("Copied {0} to {1} OK", source, destination);
FilesCopiedSuccessfully.Add(source);
return true;
}
}
if (_throwOnFailure)
{
throw new IOException("oops");
}
return null;
}
}
}
}
|