Merge pull request #1052 from cdmihai/cdmihai/HFSTimeStampBug
Allow false positive rebuilds when timestamp collision occurs
This commit is contained in:
commit
65c026afbf
6 changed files with 76 additions and 51 deletions
|
@ -130,7 +130,7 @@ namespace Microsoft.DotNet.Tools.Build
|
||||||
}
|
}
|
||||||
|
|
||||||
// find inputs that are older than the earliest output
|
// find inputs that are older than the earliest output
|
||||||
var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTimeUtc(p) > minDateUtc);
|
var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTimeUtc(p) >= minDateUtc);
|
||||||
|
|
||||||
if (!newInputs.Any())
|
if (!newInputs.Any())
|
||||||
{
|
{
|
||||||
|
|
|
@ -89,6 +89,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
$"Exit Code: {_commandResult.ExitCode}{Environment.NewLine}" +
|
$"Exit Code: {_commandResult.ExitCode}{Environment.NewLine}" +
|
||||||
$"StdOut:{Environment.NewLine}{_commandResult.StdOut}{Environment.NewLine}" +
|
$"StdOut:{Environment.NewLine}{_commandResult.StdOut}{Environment.NewLine}" +
|
||||||
$"StdErr:{Environment.NewLine}{_commandResult.StdErr}{Environment.NewLine}"; ;
|
$"StdErr:{Environment.NewLine}{_commandResult.StdErr}{Environment.NewLine}"; ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AndConstraint<CommandResultAssertions> HaveSkippedProjectCompilation(string skippedProject)
|
||||||
|
{
|
||||||
|
_commandResult.StdOut.Should().Contain($"Project {skippedProject} (DNXCore,Version=v5.0) was previously compiled. Skipping compilation.");
|
||||||
|
|
||||||
|
return new AndConstraint<CommandResultAssertions>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AndConstraint<CommandResultAssertions> HaveCompiledProject(string compiledProject)
|
||||||
|
{
|
||||||
|
_commandResult.StdOut.Should().Contain($"Project {compiledProject} (DNXCore,Version=v5.0) will be compiled");
|
||||||
|
|
||||||
|
return new AndConstraint<CommandResultAssertions>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,6 @@
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FluentAssertions;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Builder.Tests
|
namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
|
@ -59,18 +60,18 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
{
|
{
|
||||||
foreach (var rebuiltProject in expectedRebuilt)
|
foreach (var rebuiltProject in expectedRebuilt)
|
||||||
{
|
{
|
||||||
AssertProjectCompiled(rebuiltProject, buildResult);
|
buildResult.Should().HaveCompiledProject(rebuiltProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var skippedProject in SetDifference(_projects, expectedRebuilt))
|
foreach (var skippedProject in SetDifference(_projects, expectedRebuilt))
|
||||||
{
|
{
|
||||||
AssertProjectSkipped(skippedProject, buildResult);
|
buildResult.Should().HaveSkippedProjectCompilation(skippedProject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetProjectDirectory(string projectName)
|
protected override string GetProjectDirectory(string projectName)
|
||||||
{
|
{
|
||||||
return Path.Combine(_tempProjectRoot.Path, "src", projectName);
|
return Path.Combine(TempProjectRoot.Path, "src", projectName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,26 +13,24 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
{
|
{
|
||||||
public class IncrementalTestBase : TestBase
|
public class IncrementalTestBase : TestBase
|
||||||
{
|
{
|
||||||
protected readonly TempDirectory _tempProjectRoot;
|
protected readonly TempDirectory TempProjectRoot;
|
||||||
|
|
||||||
private readonly string _testProjectsRoot;
|
protected readonly string MainProject;
|
||||||
protected readonly string _mainProject;
|
protected readonly string ExpectedOutput;
|
||||||
protected readonly string _expectedOutput;
|
|
||||||
|
|
||||||
public IncrementalTestBase(string testProjectsRoot, string mainProject, string expectedOutput)
|
public IncrementalTestBase(string testProjectsRoot, string mainProject, string expectedOutput)
|
||||||
{
|
{
|
||||||
_testProjectsRoot = testProjectsRoot;
|
MainProject = mainProject;
|
||||||
_mainProject = mainProject;
|
ExpectedOutput = expectedOutput;
|
||||||
_expectedOutput = expectedOutput;
|
|
||||||
|
|
||||||
var root = Temp.CreateDirectory();
|
var root = Temp.CreateDirectory();
|
||||||
|
|
||||||
_tempProjectRoot = root.CopyDirectory(testProjectsRoot);
|
TempProjectRoot = root.CopyDirectory(testProjectsRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void TouchSourcesOfProject()
|
protected void TouchSourcesOfProject()
|
||||||
{
|
{
|
||||||
TouchSourcesOfProject(_mainProject);
|
TouchSourcesOfProject(MainProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void TouchSourcesOfProject(string projectToTouch)
|
protected void TouchSourcesOfProject(string projectToTouch)
|
||||||
|
@ -50,9 +48,9 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
|
|
||||||
protected CommandResult BuildProject(bool noIncremental = false, bool expectBuildFailure = false)
|
protected CommandResult BuildProject(bool noIncremental = false, bool expectBuildFailure = false)
|
||||||
{
|
{
|
||||||
var outputDir = GetBinDirectory();
|
var outputDir = GetBinRoot();
|
||||||
var intermediateOutputDir = Path.Combine(Directory.GetParent(outputDir).FullName, "obj", _mainProject);
|
var intermediateOutputDir = Path.Combine(Directory.GetParent(outputDir).FullName, "obj", MainProject);
|
||||||
var mainProjectFile = GetProjectFile(_mainProject);
|
var mainProjectFile = GetProjectFile(MainProject);
|
||||||
|
|
||||||
var buildCommand = new BuildCommand(mainProjectFile, output: outputDir, tempOutput: intermediateOutputDir ,noIncremental : noIncremental);
|
var buildCommand = new BuildCommand(mainProjectFile, output: outputDir, tempOutput: intermediateOutputDir ,noIncremental : noIncremental);
|
||||||
var result = buildCommand.ExecuteWithCapturedOutput();
|
var result = buildCommand.ExecuteWithCapturedOutput();
|
||||||
|
@ -60,7 +58,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
if (!expectBuildFailure)
|
if (!expectBuildFailure)
|
||||||
{
|
{
|
||||||
result.Should().Pass();
|
result.Should().Pass();
|
||||||
TestOutputExecutable(outputDir, buildCommand.GetOutputExecutableName(), _expectedOutput);
|
TestOutputExecutable(outputDir, buildCommand.GetOutputExecutableName(), ExpectedOutput);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -70,24 +68,14 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void AssertProjectSkipped(string skippedProject, CommandResult buildResult)
|
protected string GetBinRoot()
|
||||||
{
|
{
|
||||||
Assert.Contains($"Project {skippedProject} (DNXCore,Version=v5.0) was previously compiled. Skipping compilation.", buildResult.StdOut, StringComparison.OrdinalIgnoreCase);
|
return Path.Combine(TempProjectRoot.Path, "bin");
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AssertProjectCompiled(string rebuiltProject, CommandResult buildResult)
|
|
||||||
{
|
|
||||||
Assert.Contains($"Project {rebuiltProject} (DNXCore,Version=v5.0) will be compiled", buildResult.StdOut, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected string GetBinDirectory()
|
|
||||||
{
|
|
||||||
return Path.Combine(_tempProjectRoot.Path, "bin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string GetProjectDirectory(string projectName)
|
protected virtual string GetProjectDirectory(string projectName)
|
||||||
{
|
{
|
||||||
return Path.Combine(_tempProjectRoot.Path);
|
return Path.Combine(TempProjectRoot.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetProjectFile(string projectName)
|
protected string GetProjectFile(string projectName)
|
||||||
|
@ -108,7 +96,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
|
|
||||||
protected string GetCompilationOutputPath()
|
protected string GetCompilationOutputPath()
|
||||||
{
|
{
|
||||||
var executablePath = Path.Combine(GetBinDirectory(), "Debug", "dnxcore50");
|
var executablePath = Path.Combine(GetBinRoot(), "Debug", "dnxcore50");
|
||||||
|
|
||||||
return executablePath;
|
return executablePath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
@ -25,7 +24,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
public void TestNoIncrementalFlag()
|
public void TestNoIncrementalFlag()
|
||||||
{
|
{
|
||||||
var buildResult = BuildProject();
|
var buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
buildResult = BuildProject(noIncremental: true);
|
buildResult = BuildProject(noIncremental: true);
|
||||||
Assert.Contains("[Forced Unsafe]", buildResult.StdOut);
|
Assert.Contains("[Forced Unsafe]", buildResult.StdOut);
|
||||||
|
@ -54,9 +53,9 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
{
|
{
|
||||||
|
|
||||||
var buildResult = BuildProject();
|
var buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
var lockFile = Path.Combine(_tempProjectRoot.Path, "project.lock.json");
|
var lockFile = Path.Combine(TempProjectRoot.Path, "project.lock.json");
|
||||||
Assert.True(File.Exists(lockFile));
|
Assert.True(File.Exists(lockFile));
|
||||||
|
|
||||||
File.Delete(lockFile);
|
File.Delete(lockFile);
|
||||||
|
@ -66,38 +65,66 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
Assert.Contains("does not have a lock file", buildResult.StdOut);
|
Assert.Contains("does not have a lock file", buildResult.StdOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip="https://github.com/dotnet/cli/issues/980")]
|
[Fact]
|
||||||
public void TestRebuildChangedLockFile()
|
public void TestRebuildChangedLockFile()
|
||||||
{
|
{
|
||||||
|
|
||||||
var buildResult = BuildProject();
|
var buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
var lockFile = Path.Combine(_tempProjectRoot.Path, "project.lock.json");
|
var lockFile = Path.Combine(TempProjectRoot.Path, "project.lock.json");
|
||||||
TouchFile(lockFile);
|
TouchFile(lockFile);
|
||||||
|
|
||||||
buildResult = BuildProject();
|
buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip="https://github.com/dotnet/cli/issues/980")]
|
[Fact]
|
||||||
public void TestRebuildChangedProjectFile()
|
public void TestRebuildChangedProjectFile()
|
||||||
{
|
{
|
||||||
|
|
||||||
var buildResult = BuildProject();
|
var buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
TouchFile(GetProjectFile(_mainProject));
|
TouchFile(GetProjectFile(MainProject));
|
||||||
|
|
||||||
buildResult = BuildProject();
|
buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// regression for https://github.com/dotnet/cli/issues/965
|
||||||
|
[Fact]
|
||||||
|
public void TestInputWithSameTimeAsOutputCausesProjectToCompile()
|
||||||
|
{
|
||||||
|
var buildResult = BuildProject();
|
||||||
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
|
var outputTimestamp = SetAllOutputItemsToSameTime();
|
||||||
|
|
||||||
|
// set an input to have the same last write time as an output item
|
||||||
|
// this should trigger recompilation to account for file systems with second timestamp granularity
|
||||||
|
// (an input file that changed within the same second as the previous outputs should trigger a rebuild)
|
||||||
|
File.SetLastWriteTime(GetProjectFile(MainProject), outputTimestamp);
|
||||||
|
|
||||||
|
buildResult = BuildProject();
|
||||||
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime SetAllOutputItemsToSameTime()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
foreach (var f in Directory.EnumerateFiles(GetCompilationOutputPath()))
|
||||||
|
{
|
||||||
|
File.SetLastWriteTime(f, now);
|
||||||
|
}
|
||||||
|
return now;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TestDeleteOutputWithExtension(string extension)
|
private void TestDeleteOutputWithExtension(string extension)
|
||||||
{
|
{
|
||||||
|
|
||||||
var buildResult = BuildProject();
|
var buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
|
|
||||||
Reporter.Verbose.WriteLine($"Files in {GetCompilationOutputPath()}");
|
Reporter.Verbose.WriteLine($"Files in {GetCompilationOutputPath()}");
|
||||||
foreach (var file in Directory.EnumerateFiles(GetCompilationOutputPath()))
|
foreach (var file in Directory.EnumerateFiles(GetCompilationOutputPath()))
|
||||||
|
@ -109,7 +136,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
foreach (var outputFile in Directory.EnumerateFiles(GetCompilationOutputPath()).Where(f =>
|
foreach (var outputFile in Directory.EnumerateFiles(GetCompilationOutputPath()).Where(f =>
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName(f);
|
var fileName = Path.GetFileName(f);
|
||||||
return fileName.StartsWith(_mainProject, StringComparison.OrdinalIgnoreCase) &&
|
return fileName.StartsWith(MainProject, StringComparison.OrdinalIgnoreCase) &&
|
||||||
fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
|
fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
|
@ -121,7 +148,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||||
|
|
||||||
// second build; should get rebuilt since we deleted an output item
|
// second build; should get rebuilt since we deleted an output item
|
||||||
buildResult = BuildProject();
|
buildResult = BuildProject();
|
||||||
AssertProjectCompiled(_mainProject, buildResult);
|
buildResult.Should().HaveCompiledProject(MainProject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue