Allow false positive rebuilds when timestamp collision occurs

Fixes #965
This commit is contained in:
Mihai Codoban 2016-01-26 14:53:56 -08:00
parent be4629a6a6
commit 6d1ff3af8c
6 changed files with 76 additions and 51 deletions

View file

@ -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())
{ {

View file

@ -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);
} }
} }
} }

View file

@ -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
{ {

View file

@ -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);
} }
} }
} }

View file

@ -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 forceIncrementalUnsafe = false, bool expectBuildFailure = false) protected CommandResult BuildProject(bool forceIncrementalUnsafe = 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 ,forceIncrementalUnsafe : forceIncrementalUnsafe); var buildCommand = new BuildCommand(mainProjectFile, output: outputDir, tempOutput: intermediateOutputDir ,forceIncrementalUnsafe : forceIncrementalUnsafe);
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;
} }

View file

@ -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 TestForceIncrementalUnsafe() public void TestForceIncrementalUnsafe()
{ {
var buildResult = BuildProject(); var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult); buildResult.Should().HaveCompiledProject(MainProject);
buildResult = BuildProject(forceIncrementalUnsafe: true); buildResult = BuildProject(forceIncrementalUnsafe: 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);
} }
} }
} }