Allow false positive rebuilds when timestamp collision occurs
Fixes #965
This commit is contained in:
parent
be4629a6a6
commit
6d1ff3af8c
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
|
||||
var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTimeUtc(p) > minDateUtc);
|
||||
var newInputs = compilerIO.Inputs.FindAll(p => File.GetLastWriteTimeUtc(p) >= minDateUtc);
|
||||
|
||||
if (!newInputs.Any())
|
||||
{
|
||||
|
|
|
@ -89,6 +89,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
$"Exit Code: {_commandResult.ExitCode}{Environment.NewLine}" +
|
||||
$"StdOut:{Environment.NewLine}{_commandResult.StdOut}{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.
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Builder.Tests
|
||||
|
@ -59,18 +60,18 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
{
|
||||
foreach (var rebuiltProject in expectedRebuilt)
|
||||
{
|
||||
AssertProjectCompiled(rebuiltProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(rebuiltProject);
|
||||
}
|
||||
|
||||
foreach (var skippedProject in SetDifference(_projects, expectedRebuilt))
|
||||
{
|
||||
AssertProjectSkipped(skippedProject, buildResult);
|
||||
buildResult.Should().HaveSkippedProjectCompilation(skippedProject);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
protected readonly TempDirectory _tempProjectRoot;
|
||||
protected readonly TempDirectory TempProjectRoot;
|
||||
|
||||
private readonly string _testProjectsRoot;
|
||||
protected readonly string _mainProject;
|
||||
protected readonly string _expectedOutput;
|
||||
protected readonly string MainProject;
|
||||
protected readonly string ExpectedOutput;
|
||||
|
||||
public IncrementalTestBase(string testProjectsRoot, string mainProject, string expectedOutput)
|
||||
{
|
||||
_testProjectsRoot = testProjectsRoot;
|
||||
_mainProject = mainProject;
|
||||
_expectedOutput = expectedOutput;
|
||||
MainProject = mainProject;
|
||||
ExpectedOutput = expectedOutput;
|
||||
|
||||
var root = Temp.CreateDirectory();
|
||||
|
||||
_tempProjectRoot = root.CopyDirectory(testProjectsRoot);
|
||||
TempProjectRoot = root.CopyDirectory(testProjectsRoot);
|
||||
}
|
||||
|
||||
protected void TouchSourcesOfProject()
|
||||
{
|
||||
TouchSourcesOfProject(_mainProject);
|
||||
TouchSourcesOfProject(MainProject);
|
||||
}
|
||||
|
||||
protected void TouchSourcesOfProject(string projectToTouch)
|
||||
|
@ -50,9 +48,9 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
|
||||
protected CommandResult BuildProject(bool forceIncrementalUnsafe = false, bool expectBuildFailure = false)
|
||||
{
|
||||
var outputDir = GetBinDirectory();
|
||||
var intermediateOutputDir = Path.Combine(Directory.GetParent(outputDir).FullName, "obj", _mainProject);
|
||||
var mainProjectFile = GetProjectFile(_mainProject);
|
||||
var outputDir = GetBinRoot();
|
||||
var intermediateOutputDir = Path.Combine(Directory.GetParent(outputDir).FullName, "obj", MainProject);
|
||||
var mainProjectFile = GetProjectFile(MainProject);
|
||||
|
||||
var buildCommand = new BuildCommand(mainProjectFile, output: outputDir, tempOutput: intermediateOutputDir ,forceIncrementalUnsafe : forceIncrementalUnsafe);
|
||||
var result = buildCommand.ExecuteWithCapturedOutput();
|
||||
|
@ -60,7 +58,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
if (!expectBuildFailure)
|
||||
{
|
||||
result.Should().Pass();
|
||||
TestOutputExecutable(outputDir, buildCommand.GetOutputExecutableName(), _expectedOutput);
|
||||
TestOutputExecutable(outputDir, buildCommand.GetOutputExecutableName(), ExpectedOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -70,24 +68,14 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
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);
|
||||
}
|
||||
|
||||
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");
|
||||
return Path.Combine(TempProjectRoot.Path, "bin");
|
||||
}
|
||||
|
||||
protected virtual string GetProjectDirectory(string projectName)
|
||||
{
|
||||
return Path.Combine(_tempProjectRoot.Path);
|
||||
return Path.Combine(TempProjectRoot.Path);
|
||||
}
|
||||
|
||||
protected string GetProjectFile(string projectName)
|
||||
|
@ -108,7 +96,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
|
||||
protected string GetCompilationOutputPath()
|
||||
{
|
||||
var executablePath = Path.Combine(GetBinDirectory(), "Debug", "dnxcore50");
|
||||
var executablePath = Path.Combine(GetBinRoot(), "Debug", "dnxcore50");
|
||||
|
||||
return executablePath;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
|
@ -25,7 +24,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
public void TestForceIncrementalUnsafe()
|
||||
{
|
||||
var buildResult = BuildProject();
|
||||
AssertProjectCompiled(_mainProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(MainProject);
|
||||
|
||||
buildResult = BuildProject(forceIncrementalUnsafe: true);
|
||||
Assert.Contains("[Forced Unsafe]", buildResult.StdOut);
|
||||
|
@ -54,9 +53,9 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
{
|
||||
|
||||
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));
|
||||
|
||||
File.Delete(lockFile);
|
||||
|
@ -66,38 +65,66 @@ namespace Microsoft.DotNet.Tools.Builder.Tests
|
|||
Assert.Contains("does not have a lock file", buildResult.StdOut);
|
||||
}
|
||||
|
||||
[Fact(Skip="https://github.com/dotnet/cli/issues/980")]
|
||||
[Fact]
|
||||
public void TestRebuildChangedLockFile()
|
||||
{
|
||||
|
||||
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);
|
||||
|
||||
buildResult = BuildProject();
|
||||
AssertProjectCompiled(_mainProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(MainProject);
|
||||
}
|
||||
|
||||
[Fact(Skip="https://github.com/dotnet/cli/issues/980")]
|
||||
[Fact]
|
||||
public void TestRebuildChangedProjectFile()
|
||||
{
|
||||
|
||||
var buildResult = BuildProject();
|
||||
AssertProjectCompiled(_mainProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(MainProject);
|
||||
|
||||
TouchFile(GetProjectFile(_mainProject));
|
||||
TouchFile(GetProjectFile(MainProject));
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
var buildResult = BuildProject();
|
||||
AssertProjectCompiled(_mainProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(MainProject);
|
||||
|
||||
Reporter.Verbose.WriteLine($"Files in {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 =>
|
||||
{
|
||||
var fileName = Path.GetFileName(f);
|
||||
return fileName.StartsWith(_mainProject, StringComparison.OrdinalIgnoreCase) &&
|
||||
return fileName.StartsWith(MainProject, 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
|
||||
buildResult = BuildProject();
|
||||
AssertProjectCompiled(_mainProject, buildResult);
|
||||
buildResult.Should().HaveCompiledProject(MainProject);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue