Merge pull request #930 from cdmihai/cdmihai/buildCommandTestingReleasePR

Release PR: Incremental build tests and improvements
This commit is contained in:
Piotr Puszkiewicz 2016-01-21 12:31:25 -08:00
commit 14f665eb30
39 changed files with 782 additions and 113 deletions

View file

@ -87,6 +87,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Publ
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestLibrary", "test\TestProjects\TestLibrary\TestLibrary.xproj", "{947DD232-8D9B-4B78-9C6A-94F807D2DD58}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Builder.Tests", "test\Microsoft.DotNet.Tools.Builder.Tests\Microsoft.DotNet.Tools.Builder.Tests.xproj", "{833FFEE1-7EED-4F51-8DFD-946D48833333}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestProjectToProjectDependencies", "test\TestProjects\TestProjectToProjectDependencies\TestProjectToProjectDependencies.xproj", "{947DD232-8D9B-4B78-9C6A-94F807D22222}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -659,6 +663,38 @@ Global
{947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Debug|Any CPU.Build.0 = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Debug|x64.ActiveCfg = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Debug|x64.Build.0 = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Release|Any CPU.ActiveCfg = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Release|Any CPU.Build.0 = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Release|x64.ActiveCfg = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.Release|x64.Build.0 = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{833FFEE1-7EED-4F51-8DFD-946D48833333}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|Any CPU.Build.0 = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|x64.ActiveCfg = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|x64.Build.0 = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Release|Any CPU.ActiveCfg = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Release|Any CPU.Build.0 = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Release|x64.ActiveCfg = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.Release|x64.Build.0 = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{947DD232-8D9B-4B78-9C6A-94F807D22222}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -700,5 +736,7 @@ Global
{833FFEE1-7EED-4F51-8DFD-946D48893D6E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{386D412C-003C-47B1-8258-0E35865CB7C4} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{947DD232-8D9B-4B78-9C6A-94F807D2DD58} = {713CBFBB-5392-438D-B766-A9A585EF1BB8}
{833FFEE1-7EED-4F51-8DFD-946D48833333} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{947DD232-8D9B-4B78-9C6A-94F807D22222} = {713CBFBB-5392-438D-B766-A9A585EF1BB8}
EndGlobalSection
EndGlobal

View file

@ -11,9 +11,10 @@ $TestBinRoot = "$RepoRoot\artifacts\tests"
$TestProjects = @(
"E2E",
"StreamForwarderTests"
"Microsoft.DotNet.Tools.Publish.Tests"
"Microsoft.DotNet.Tools.Compiler.Tests"
"StreamForwarderTests",
"Microsoft.DotNet.Tools.Publish.Tests",
"Microsoft.DotNet.Tools.Compiler.Tests",
"Microsoft.DotNet.Tools.Builder.Tests"
)
# Publish each test project

View file

@ -24,6 +24,7 @@ TestProjects=( \
StreamForwarderTests \
Microsoft.DotNet.Tools.Publish.Tests \
Microsoft.DotNet.Tools.Compiler.Tests \
Microsoft.DotNet.Tools.Builder.Tests \
)
for project in ${TestProjects[@]}

View file

@ -15,12 +15,19 @@ namespace Microsoft.DotNet.ProjectModel.Graph
public static readonly int CurrentVersion = 2;
public static readonly string FileName = "project.lock.json";
public string LockFilePath { get; }
public int Version { get; set; }
public IList<ProjectFileDependencyGroup> ProjectFileDependencyGroups { get; set; } = new List<ProjectFileDependencyGroup>();
public IList<LockFilePackageLibrary> PackageLibraries { get; set; } = new List<LockFilePackageLibrary>();
public IList<LockFileProjectLibrary> ProjectLibraries { get; set; } = new List<LockFileProjectLibrary>();
public IList<LockFileTarget> Targets { get; set; } = new List<LockFileTarget>();
public LockFile(string lockFilePath)
{
LockFilePath = lockFilePath;
}
public bool IsValidForProject(Project project)
{
string message;

View file

@ -15,26 +15,26 @@ namespace Microsoft.DotNet.ProjectModel.Graph
{
public static class LockFileReader
{
public static LockFile Read(string filePath)
public static LockFile Read(string lockFilePath)
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var stream = new FileStream(lockFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
try
{
return Read(stream);
return Read(lockFilePath, stream);
}
catch (FileFormatException ex)
{
throw ex.WithFilePath(filePath);
throw ex.WithFilePath(lockFilePath);
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, filePath);
throw FileFormatException.Create(ex, lockFilePath);
}
}
}
internal static LockFile Read(Stream stream)
internal static LockFile Read(string lockFilePath, Stream stream)
{
try
{
@ -43,7 +43,7 @@ namespace Microsoft.DotNet.ProjectModel.Graph
if (jobject != null)
{
return ReadLockFile(jobject);
return ReadLockFile(lockFilePath, jobject);
}
else
{
@ -53,16 +53,16 @@ namespace Microsoft.DotNet.ProjectModel.Graph
catch
{
// Ran into parsing errors, mark it as unlocked and out-of-date
return new LockFile
return new LockFile(lockFilePath)
{
Version = int.MinValue
};
}
}
private static LockFile ReadLockFile(JsonObject cursor)
private static LockFile ReadLockFile(string lockFilePath, JsonObject cursor)
{
var lockFile = new LockFile();
var lockFile = new LockFile(lockFilePath);
lockFile.Version = ReadInt(cursor, "version", defaultValue: int.MinValue);
lockFile.Targets = ReadObject(cursor.ValueAsJsonObject("targets"), ReadTarget);
lockFile.ProjectFileDependencyGroups = ReadObject(cursor.ValueAsJsonObject("projectFileDependencyGroups"), ReadProjectFileDependencyGroup);

View file

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Resolution;
using NuGet.Frameworks;
@ -22,6 +23,8 @@ namespace Microsoft.DotNet.ProjectModel
public Project ProjectFile => RootProject.Project;
public LockFile LockFile { get; }
public string RootDirectory => GlobalSettings.DirectoryPath;
public string ProjectDirectory => ProjectFile.ProjectDirectory;
@ -36,7 +39,8 @@ namespace Microsoft.DotNet.ProjectModel
NuGetFramework targetFramework,
string runtimeIdentifier,
string packagesDirectory,
LibraryManager libraryManager)
LibraryManager libraryManager,
LockFile lockfile)
{
GlobalSettings = globalSettings;
RootProject = rootProject;
@ -44,6 +48,7 @@ namespace Microsoft.DotNet.ProjectModel
RuntimeIdentifier = runtimeIdentifier;
PackagesDirectory = packagesDirectory;
LibraryManager = libraryManager;
LockFile = lockfile;
}
public LibraryExporter CreateExporter(string configuration)

View file

@ -259,7 +259,8 @@ namespace Microsoft.DotNet.ProjectModel
TargetFramework,
target?.RuntimeIdentifier,
PackagesDirectory,
libraryManager);
libraryManager,
LockFile);
}
private void ResolveDependencies(Dictionary<LibraryKey, LibraryDescription> libraries,

View file

@ -58,9 +58,9 @@ namespace Microsoft.DotNet.Tools.Build
{
if (incremental)
{
var dependencyContext = ProjectContext.Create(dependency.Path, dependency.Framework);
var dependencyProjectContext = ProjectContext.Create(dependency.Path, dependency.Framework);
if (!NeedsRebuilding(dependencyContext, new ProjectDependenciesFacade(dependencyContext, _args.ConfigValue)))
if (!NeedsRebuilding(dependencyProjectContext, new ProjectDependenciesFacade(dependencyProjectContext, _args.ConfigValue)))
{
continue;
}
@ -93,7 +93,7 @@ namespace Microsoft.DotNet.Tools.Build
// rebuild if empty inputs / outputs
if (!(compilerIO.Outputs.Any() && compilerIO.Inputs.Any()))
{
Reporter.Verbose.WriteLine($"\nProject {project.ProjectName()} will be compiled because it either has empty inputs or outputs");
Reporter.Output.WriteLine($"\nProject {project.ProjectName()} will be compiled because it either has empty inputs or outputs");
return true;
}
@ -123,11 +123,11 @@ namespace Microsoft.DotNet.Tools.Build
if (!newInputs.Any())
{
Reporter.Verbose.WriteLine($"\nSkipped compilation for project {project.ProjectName()}. All the input files were older than the output files.");
Reporter.Output.WriteLine($"\nProject {project.ProjectName()} was previoulsy compiled. Skipping compilation.");
return false;
}
Reporter.Verbose.WriteLine($"\nProject {project.ProjectName()} was compiled because some of its inputs were newer than its oldest output:");
Reporter.Output.WriteLine($"\nProject {project.ProjectName()} will be compiled because some of its inputs were newer than its oldest output.");
Reporter.Verbose.WriteLine($"Oldest output item was written at {minDate} : {minOutputPath}");
Reporter.Verbose.WriteLine($"Inputs newer than the oldest output item:");
@ -148,14 +148,14 @@ namespace Microsoft.DotNet.Tools.Build
return false;
}
Reporter.Verbose.WriteLine($"\nProject {project.ProjectName()} will be compiled because expected {itemsType} are missing: ");
Reporter.Output.WriteLine($"\nProject {project.ProjectName()} will be compiled because expected {itemsType} are missing. ");
foreach (var missing in missingItems)
{
Reporter.Verbose.WriteLine($"\t {missing}");
}
Reporter.Verbose.WriteLine();
Reporter.Output.WriteLine();
return true;
}
@ -338,23 +338,26 @@ namespace Microsoft.DotNet.Tools.Build
public static CompilerIO GetCompileIO(ProjectContext project, string config, string outputPath, string intermediaryOutputPath, ProjectDependenciesFacade dependencies)
{
var compilerIO = new CompilerIO(new List<string>(), new List<string>());
var compilationOutput = CompilerUtil.GetCompilationOutput(project.ProjectFile, project.TargetFramework, config, outputPath);
// input: project.json
compilerIO.Inputs.Add(project.ProjectFile.ProjectFilePath);
// input: lock file; find when dependencies change
AddLockFile(project, compilerIO);
// input: source files
compilerIO.Inputs.AddRange(CompilerUtil.GetCompilationSources(project));
// todo: Factor out dependency resolution between Build and Compile. Ideally Build injects the dependencies into Compile
// todo: use lock file insteaf of dependencies. One file vs many
// input: dependencies
AddDependencies(dependencies, compilerIO);
// input: key file
AddKeyFile(project, config, compilerIO);
// output: compiler output
compilerIO.Outputs.Add(CompilerUtil.GetCompilationOutput(project.ProjectFile, project.TargetFramework, config, outputPath));
compilerIO.Outputs.Add(compilationOutput);
// input / output: compilation options files
AddFilesFromCompilationOptions(project, config, compilationOutput, compilerIO);
// input / output: resources without culture
AddCultureResources(project, intermediaryOutputPath, compilerIO);
@ -365,22 +368,43 @@ namespace Microsoft.DotNet.Tools.Build
return compilerIO;
}
private static void AddLockFile(ProjectContext project, CompilerIO compilerIO)
{
if(project.LockFile == null)
{
var errorMessage = $"Project {project.ProjectName()} does not have a lock file.";
Reporter.Error.WriteLine(errorMessage);
throw new InvalidOperationException(errorMessage);
}
compilerIO.Inputs.Add(project.LockFile.LockFilePath);
}
private static void AddDependencies(ProjectDependenciesFacade dependencies, CompilerIO compilerIO)
{
// add dependency sources that need compilation
compilerIO.Inputs.AddRange(dependencies.ProjectDependenciesWithSources.Values.SelectMany(p => p.Project.Files.SourceFiles));
// add compilation binaries
compilerIO.Inputs.AddRange(dependencies.Dependencies.SelectMany(d => d.CompilationAssemblies.Select(ca => ca.ResolvedPath)));
// non project dependencies get captured by changes in the lock file
}
private static void AddKeyFile(ProjectContext project, string config, CompilerIO compilerIO)
private static void AddFilesFromCompilationOptions(ProjectContext project, string config, string compilationOutput, CompilerIO compilerIO)
{
var keyFile = CompilerUtil.ResolveCompilationOptions(project, config).KeyFile;
var compilerOptions = CompilerUtil.ResolveCompilationOptions(project, config);
if (keyFile != null)
// output: pdb file. They are always emitted (see compiler.csc)
compilerIO.Outputs.Add(Path.ChangeExtension(compilationOutput, "pdb"));
// output: documentation file
if (compilerOptions.GenerateXmlDocumentation == true)
{
compilerIO.Inputs.Add(keyFile);
compilerIO.Outputs.Add(Path.ChangeExtension(compilationOutput, "xml"));
}
// input: key file
if (compilerOptions.KeyFile != null)
{
compilerIO.Inputs.Add(compilerOptions.KeyFile);
}
}

View file

@ -152,9 +152,10 @@ namespace Microsoft.DotNet.Tools.Compiler
return compilationOptions;
}
//used in incremental precondition checks
public static IEnumerable<string> GetCommandsInvokedByCompile(ProjectContext project)
{
return new List<string> {ResolveCompilerName(project)};
return new List<string> {ResolveCompilerName(project), "dotnet-compile"};
}
}
}

View file

@ -45,7 +45,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput);
}
[Fact]
@ -56,13 +56,13 @@ namespace Microsoft.DotNet.Tests.EndToEnd
// first build
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput);
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
// second build; should get skipped (incremental because no inputs changed)
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput);
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Assert.Equal(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
@ -71,68 +71,12 @@ namespace Microsoft.DotNet.Tests.EndToEnd
// third build; should get compiled because the source file got touched
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput);
var latestWriteTimeThirdBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Assert.NotEqual(latestWriteTimeSecondBuild, latestWriteTimeThirdBuild);
}
[Fact]
public void TestDotnetForceIncrementalUnsafe()
{
TestSetup();
// first build
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
// second build; will get recompiled due to force unsafe flag
buildCommand = new BuildCommand(TestProject, output: OutputDirectory, forceIncrementalUnsafe:true);
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Assert.NotEqual(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
}
[Fact]
public void TestDotnetIncrementalBuildDeleteOutputFile()
{
TestSetup();
// first build
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory);
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Reporter.Verbose.WriteLine($"Files in {OutputDirectory}");
foreach (var file in Directory.EnumerateFiles(OutputDirectory))
{
Reporter.Verbose.Write($"\t {file}");
}
// delete output files
foreach (var outputFile in Directory.EnumerateFiles(OutputDirectory).Where(f => Path.GetFileName(f).StartsWith(s_testdirName, StringComparison.OrdinalIgnoreCase)))
{
Reporter.Verbose.WriteLine($"Delete {outputFile}");
File.Delete(outputFile);
Assert.False(File.Exists(outputFile));
}
// second build; should get rebuilt since we deleted output items
buildCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName());
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Assert.NotEqual(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
}
[Fact]
[ActiveIssue(712, PlatformID.Windows | PlatformID.OSX | PlatformID.Linux)]
public void TestDotnetBuildNativeRyuJit()
@ -148,7 +92,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd
buildCommand.Execute().Should().Pass();
var nativeOut = Path.Combine(OutputDirectory, "native");
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName(), s_expectedOutput);
}
[Fact]
@ -165,7 +109,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd
buildCommand.Execute().Should().Pass();
var nativeOut = Path.Combine(OutputDirectory, "native");
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName(), s_expectedOutput);
}
[Fact]
@ -182,13 +126,13 @@ namespace Microsoft.DotNet.Tests.EndToEnd
// first build
var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true);
buildCommand.Execute().Should().Pass();
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName(), s_expectedOutput);
var latestWriteTimeFirstBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
// second build; should be skipped because nothing changed
buildCommand.Execute().Should().Pass();
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName());
TestOutputExecutable(nativeOut, buildCommand.GetOutputExecutableName(), s_expectedOutput);
var latestWriteTimeSecondBuild = GetLastWriteTimeOfDirectoryFiles(OutputDirectory);
Assert.Equal(latestWriteTimeFirstBuild, latestWriteTimeSecondBuild);
@ -220,7 +164,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd
var publishCommand = new PublishCommand(TestProject, output: OutputDirectory);
publishCommand.Execute().Should().Pass();
TestOutputExecutable(OutputDirectory, publishCommand.GetOutputExecutable());
TestOutputExecutable(OutputDirectory, publishCommand.GetOutputExecutable(), s_expectedOutput);
}
private void TestSetup()
@ -245,19 +189,6 @@ namespace Microsoft.DotNet.Tests.EndToEnd
Directory.SetCurrentDirectory(currentDirectory);
}
private void TestOutputExecutable(string outputDir, string executableName)
{
var executablePath = Path.Combine(outputDir, executableName);
var executableCommand = new TestCommand(executablePath);
var result = executableCommand.ExecuteWithCapturedOutput("");
result.Should().HaveStdOut(s_expectedOutput);
result.Should().NotHaveStdErr();
result.Should().Pass();
}
private bool IsCentOS()
{
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))

View file

@ -0,0 +1,81 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// 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;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
namespace Microsoft.DotNet.Tools.Builder.Tests
{
public class ProjectToProjectDependenciesIncrementalTest : IncrementalTestBase
{
private string[] _projects = new[] { "L0", "L11", "L12", "L21", "L22" };
private string _testProjectsRoot = @"TestProjects";
private string _testProject = "TestProjectToProjectDependencies";
private TempDirectory tempProjectRoot;
public ProjectToProjectDependenciesIncrementalTest() : base(
Path.Combine("TestProjects", "TestProjectToProjectDependencies"),
"L0",
"L0 L11 L12 L22 L21 L12 L22 " + Environment.NewLine)
{
}
[Theory,
InlineData("L0", new[] { "L0" }),
InlineData("L11", new[] { "L0", "L11" }),
InlineData("L12", new[] { "L0", "L11", "L12" }),
InlineData("L22", new[] { "L0", "L11", "L12", "L22" }),
InlineData("L21", new[] { "L0", "L11", "L21" })
]
public void TestIncrementalBuildOfDependencyGraph(string projectToTouch, string[] expectedRebuiltProjects)
{
// first clean build; all projects required compilation
var result1 = BuildProject();
AssertRebuilt(result1, _projects);
// second build; nothing changed; no project required compilation
var result2 = BuildProject();
AssertRebuilt(result2, Array.Empty<string>());
//modify the source code of a project
TouchSourcesOfProject(projectToTouch);
// third build; all projects on the paths from touched project to root project need to be rebuilt
var result3 = BuildProject();
AssertRebuilt(result3, expectedRebuiltProjects);
}
// compute A - B
private T[] SetDifference<T>(T[] A, T[] B)
{
var setA = new HashSet<T>(A);
setA.ExceptWith(B);
return setA.ToArray();
}
private void AssertRebuilt(CommandResult buildResult, string[] expectedRebuilt)
{
foreach (var rebuiltProject in expectedRebuilt)
{
AssertProjectCompiled(rebuiltProject, buildResult);
}
foreach (var skippedProject in SetDifference(_projects, expectedRebuilt))
{
AssertProjectSkipped(skippedProject, buildResult);
}
}
protected override string GetProjectDirectory(string projectName)
{
return Path.Combine(_tempProjectRoot.Path, "src", projectName);
}
}
}

View file

@ -0,0 +1,118 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// 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;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
namespace Microsoft.DotNet.Tools.Builder.Tests
{
public class IncrementalTestBase : TestBase
{
protected readonly TempDirectory _tempProjectRoot;
private readonly string _testProjectsRoot;
protected readonly string _mainProject;
protected readonly string _expectedOutput;
public IncrementalTestBase(string testProjectsRoot, string mainProject, string expectedOutput)
{
_testProjectsRoot = testProjectsRoot;
_mainProject = mainProject;
_expectedOutput = expectedOutput;
// create unique directories in the 'temp' folder
var root = Temp.CreateDirectory();
// recursively copy projects to the temp dir and restore them
_tempProjectRoot = root.CopyDirectory(testProjectsRoot);
RunRestore(_tempProjectRoot.Path);
}
protected void TouchSourcesOfProject()
{
TouchSourcesOfProject(_mainProject);
}
protected void TouchSourcesOfProject(string projectToTouch)
{
foreach (var sourceFile in GetSourceFilesForProject(projectToTouch))
{
TouchFile(sourceFile);
}
}
protected static void TouchFile(string file)
{
File.SetLastWriteTimeUtc(file, DateTime.UtcNow);
}
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 buildCommand = new BuildCommand(mainProjectFile, output: outputDir, tempOutput: intermediateOutputDir ,forceIncrementalUnsafe : forceIncrementalUnsafe);
var result = buildCommand.ExecuteWithCapturedOutput();
if (!expectBuildFailure)
{
result.Should().Pass();
TestOutputExecutable(outputDir, buildCommand.GetOutputExecutableName(), _expectedOutput);
}
else
{
result.Should().Fail();
}
return result;
}
protected static void AssertProjectSkipped(string skippedProject, CommandResult buildResult)
{
Assert.Contains($"Project {skippedProject} was previoulsy compiled. Skipping compilation.", buildResult.StdOut);
}
protected static void AssertProjectCompiled(string rebuiltProject, CommandResult buildResult)
{
Assert.Contains($"Project {rebuiltProject} will be compiled", buildResult.StdOut, StringComparison.OrdinalIgnoreCase);
}
protected string GetBinDirectory()
{
return Path.Combine(_tempProjectRoot.Path, "bin");
}
protected virtual string GetProjectDirectory(string projectName)
{
return Path.Combine(_tempProjectRoot.Path);
}
protected string GetProjectFile(string projectName)
{
return Path.Combine(GetProjectDirectory(projectName), "project.json");
}
private string GetOutputFileForProject(string projectName)
{
return Path.Combine(GetBinDirectory(), projectName + ".dll");
}
private IEnumerable<string> GetSourceFilesForProject(string projectName)
{
return Directory.EnumerateFiles(GetProjectDirectory(projectName)).
Where(f => f.EndsWith(".cs"));
}
private void RunRestore(string args)
{
var restoreCommand = new RestoreCommand();
restoreCommand.Execute($"--quiet {args}").Should().Pass();
}
}
}

View file

@ -0,0 +1,130 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// 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;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
namespace Microsoft.DotNet.Tools.Builder.Tests
{
public class IncrementalTests : IncrementalTestBase
{
private string _testProjectsRoot = @"TestProjects";
private string _testProject = "TestProjectToProjectDependencies";
private TempDirectory tempProjectRoot;
public IncrementalTests() : base(
Path.Combine("TestProjects", "TestSimpleIncrementalApp"),
"TestSimpleIncrementalApp",
"Hello World!" + Environment.NewLine)
{
}
[Fact]
public void TestForceIncrementalUnsafe()
{
var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
buildResult = BuildProject(forceIncrementalUnsafe: true);
Assert.Contains("[Forced Unsafe]", buildResult.StdOut);
}
[Fact]
public void TestRebuildMissingPdb()
{
TestDeleteOutputWithExtension("pdb");
}
[Fact]
public void TestRebuildMissingDll()
{
TestDeleteOutputWithExtension("dll");
}
[Fact]
public void TestRebuildMissingXml()
{
TestDeleteOutputWithExtension("xml");
}
[Fact]
public void TestNoLockFile()
{
var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
var lockFile = Path.Combine(_tempProjectRoot.Path, "project.lock.json");
Assert.True(File.Exists(lockFile));
File.Delete(lockFile);
Assert.False(File.Exists(lockFile));
buildResult = BuildProject(expectBuildFailure : true);
Assert.Contains("does not have a lock file", buildResult.StdErr);
}
[Fact]
public void TestRebuildChangedLockFile()
{
var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
var lockFile = Path.Combine(_tempProjectRoot.Path, "project.lock.json");
TouchFile(lockFile);
buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
}
[Fact]
public void TestRebuildChangedProjectFile()
{
var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
TouchFile(GetProjectFile(_mainProject));
buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
}
private void TestDeleteOutputWithExtension(string extension)
{
var buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
Reporter.Verbose.WriteLine($"Files in {GetBinDirectory()}");
foreach (var file in Directory.EnumerateFiles(GetBinDirectory()))
{
Reporter.Verbose.Write($"\t {file}");
}
// delete output files with extensions
foreach (var outputFile in Directory.EnumerateFiles(GetBinDirectory()).Where(f =>
{
var fileName = Path.GetFileName(f);
return fileName.StartsWith(_mainProject, StringComparison.OrdinalIgnoreCase) &&
fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
}))
{
Reporter.Output.WriteLine($"Deleted {outputFile}");
File.Delete(outputFile);
Assert.False(File.Exists(outputFile));
}
// second build; should get rebuilt since we deleted an output item
buildResult = BuildProject();
AssertProjectCompiled(_mainProject, buildResult);
}
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>833ffee1-7eed-4f51-8dfd-946d48833333</ProjectGuid>
<RootNamespace>Microsoft.DotNet.Tools.Builder.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,23 @@
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23704",
"Microsoft.NETCore.TestHost" : "1.0.0-*",
"xunit": "2.1.0",
"xunit.console.netcore": "1.0.2-prerelease-00101",
"xunit.netcore.extensions": "1.0.0-prerelease-*",
"xunit.runner.utility": "2.1.0",
"Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" },
"Microsoft.DotNet.Cli.Utils": {
"target": "project",
"type": "build"
}
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -201,7 +201,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
public override CommandResult ExecuteWithCapturedOutput(string args = "")
{
args = $"build {BuildArgs()} {args}";
args = $"--verbose build {BuildArgs()} {args}";
return base.ExecuteWithCapturedOutput(args);
}

View file

@ -69,6 +69,31 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
File.Copy(originalPath, filePath);
return _root.AddFile(new DisposableFile(filePath));
}
/// <summary>
/// Recursively copy the provided directory into this TempDirectory.
/// Does not handle links.
/// </summary>
/// <param name="sourceDirectory"></param>
/// <returns></returns>
public TempDirectory CopyDirectory(string sourceDirectory)
{
Debug.Assert(Directory.Exists(sourceDirectory));
var tempCopy = CreateDirectory(new DirectoryInfo(sourceDirectory).Name);
foreach(var file in Directory.EnumerateFiles(sourceDirectory))
{
tempCopy.CopyFile(file);
}
foreach(var directory in Directory.EnumerateDirectories(sourceDirectory))
{
tempCopy.CopyDirectory(directory);
}
return tempCopy;
}
/// <summary>
/// Creates a subdirectory in this directory.

View file

@ -3,8 +3,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Test.Utilities
{
@ -55,5 +57,18 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
string.Equals("1", val, StringComparison.OrdinalIgnoreCase) ||
string.Equals("on", val, StringComparison.OrdinalIgnoreCase));
}
protected void TestOutputExecutable(string outputDir, string executableName, string expectedOutput)
{
var executablePath = Path.Combine(outputDir, executableName);
var executableCommand = new TestCommand(executablePath);
var result = executableCommand.ExecuteWithCapturedOutput("");
result.Should().HaveStdOut(expectedOutput);
result.Should().NotHaveStdErr();
result.Should().Pass();
}
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>947dd232-8d9b-4b78-9c6a-94f807d22222</ProjectGuid>
<RootNamespace>TestProjectToProjectDependencies</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,3 @@
{
"projects": [ "src" ]
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class L0
{
public static void Main(string[] args)
{
Console.WriteLine("L0 " + L11.Value() + L12.Value());
}
}
}

View file

@ -0,0 +1,17 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"L11": "1.0.0-*",
"L12": "1.0.0-*",
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class L11
{
public static string Value()
{
return "L11 " + L12.Value() + L21.Value();
}
}
}

View file

@ -0,0 +1,14 @@
{
"version": "1.0.0-*",
"dependencies": {
"L12": "1.0.0-*",
"L21": "1.0.0-*",
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class L12
{
public static string Value()
{
return "L12 " + L22.Value();
}
}
}

View file

@ -0,0 +1,13 @@
{
"version": "1.0.0-*",
"dependencies": {
"L22": "1.0.0-*",
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class L21
{
public static string Value()
{
return "L21 ";
}
}
}

View file

@ -0,0 +1,11 @@
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class L22
{
public static string Value()
{
return "L22 ";
}
}
}

View file

@ -0,0 +1,11 @@
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>58808bbc-371e-47d6-a3d0-4902145edaaa</ProjectGuid>
<RootNamespace>TestSimpleIncrementalApp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,15 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true,
"xmlDoc": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23704"
},
"frameworks": {
"dnxcore50": { }
}
}