parent
3f948ad3c5
commit
14fc427068
|
|
@ -91,10 +91,34 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="_RazorCreateSourceHashFiles" DependsOnTargets="_RazorResolveSourceFiles">
|
||||||
|
<PropertyGroup>
|
||||||
|
<_RazorSourcesHashFile>$(IntermediateOutputPath)$(MSBuildProjectName).RazorSourceHash.cache</_RazorSourcesHashFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Hash ItemsToHash="@(RazorCompile)">
|
||||||
|
<Output TaskParameter="HashResult" PropertyName="_RazorSourcesHash" />
|
||||||
|
</Hash>
|
||||||
|
|
||||||
|
<MakeDir
|
||||||
|
Directories="$(IntermediateOutputPath)"
|
||||||
|
Condition="!Exists('$(IntermediateOutputPath)')" />
|
||||||
|
|
||||||
|
<WriteLinesToFile
|
||||||
|
Lines="$(_RazorSourcesHash)"
|
||||||
|
File="$(_RazorSourcesHashFile)"
|
||||||
|
Overwrite="True"
|
||||||
|
WriteOnlyWhenDifferent="True" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FileWrites Include="$(_RazorSourcesHashFile)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
<Target
|
<Target
|
||||||
Name="RazorCoreGenerate"
|
Name="RazorCoreGenerate"
|
||||||
DependsOnTargets="_RazorResolveSourceFiles;_RazorResolveTagHelpers"
|
DependsOnTargets="_RazorResolveSourceFiles;_RazorCreateSourceHashFiles;_RazorResolveTagHelpers"
|
||||||
Inputs="$(MSBuildAllProjects);@(RazorCompile);$(_RazorTagHelperOutputCache)"
|
Inputs="$(MSBuildAllProjects);$(_RazorSourcesHashFile);@(RazorCompile);$(_RazorTagHelperOutputCache)"
|
||||||
Outputs="@(_RazorGenerated)">
|
Outputs="@(_RazorGenerated)">
|
||||||
|
|
||||||
<RemoveDir
|
<RemoveDir
|
||||||
|
|
@ -235,4 +259,4 @@
|
||||||
DestinationFolder="$(OutDir)"
|
DestinationFolder="$(OutDir)"
|
||||||
SkipUnchangedFiles="true"/>
|
SkipUnchangedFiles="true"/>
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(result));
|
throw new ArgumentNullException(nameof(result));
|
||||||
};
|
}
|
||||||
|
|
||||||
if (result.ExitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
{
|
{
|
||||||
|
|
@ -259,4 +259,4 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
protected override string Heading => $"File: '{FilePath}' was found, but should not exist.";
|
protected override string Heading => $"File: '{FilePath}' was found, but should not exist.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
|
{
|
||||||
|
public class FileThumbPrint : IEquatable<FileThumbPrint>
|
||||||
|
{
|
||||||
|
private FileThumbPrint(DateTime lastWriteTimeUtc, string hash)
|
||||||
|
{
|
||||||
|
LastWriteTimeUtc = lastWriteTimeUtc;
|
||||||
|
Hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime LastWriteTimeUtc { get; }
|
||||||
|
|
||||||
|
public string Hash { get; }
|
||||||
|
|
||||||
|
public static FileThumbPrint Create(string path)
|
||||||
|
{
|
||||||
|
byte[] hashBytes;
|
||||||
|
using (var sha1 = SHA1.Create())
|
||||||
|
using (var fileStream = File.OpenRead(path))
|
||||||
|
{
|
||||||
|
hashBytes = sha1.ComputeHash(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = Convert.ToBase64String(hashBytes);
|
||||||
|
var lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
|
||||||
|
return new FileThumbPrint(lastWriteTimeUtc, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(FileThumbPrint other)
|
||||||
|
{
|
||||||
|
return LastWriteTimeUtc == other.LastWriteTimeUtc &&
|
||||||
|
string.Equals(Hash, other.Hash, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => LastWriteTimeUtc.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
{
|
{
|
||||||
|
|
@ -68,5 +70,37 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
|
|
||||||
File.WriteAllText(filePath, content, Encoding.UTF8);
|
File.WriteAllText(filePath, content, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locks all files, discovered at the time of method invocation, under the
|
||||||
|
/// specified <paramref name="directory" /> from reads or writes.
|
||||||
|
/// </summary>
|
||||||
|
public IDisposable LockDirectory(string directory)
|
||||||
|
{
|
||||||
|
directory = Path.Combine(Project.DirectoryPath, directory);
|
||||||
|
var disposables = new List<IDisposable>();
|
||||||
|
foreach (var file in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
disposables.Add(LockFile(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
var disposable = new Mock<IDisposable>();
|
||||||
|
disposable.Setup(d => d.Dispose())
|
||||||
|
.Callback(() => disposables.ForEach(d => d.Dispose()));
|
||||||
|
|
||||||
|
return disposable.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable LockFile(string path)
|
||||||
|
{
|
||||||
|
path = Path.Combine(Project.DirectoryPath, path);
|
||||||
|
return File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileThumbPrint GetThumbPrint(string path)
|
||||||
|
{
|
||||||
|
path = Path.Combine(Project.DirectoryPath, path);
|
||||||
|
return FileThumbPrint.Create(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,27 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
{
|
{
|
||||||
internal class ProjectDirectory : IDisposable
|
internal class ProjectDirectory : IDisposable
|
||||||
{
|
{
|
||||||
|
#if PRESERVE_WORKING_DIRECTORY
|
||||||
|
public bool PreserveWorkingDirectory { get; set; } = true;
|
||||||
|
#else
|
||||||
|
public bool PreserveWorkingDirectory { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
public static ProjectDirectory Create(string projectName)
|
public static ProjectDirectory Create(string projectName)
|
||||||
{
|
{
|
||||||
var destinationPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
var destinationPath = Path.Combine(Path.GetTempPath(), "Razor", Path.GetRandomFileName());
|
||||||
Directory.CreateDirectory(destinationPath);
|
Directory.CreateDirectory(destinationPath);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -80,6 +89,7 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
#else
|
#else
|
||||||
#error Unknown Configuration
|
#error Unknown Configuration
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var text = $@"
|
var text = $@"
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
@ -113,7 +123,14 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
CleanupDirectory(DirectoryPath);
|
if (PreserveWorkingDirectory)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Skipping deletion of working directory {DirectoryPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CleanupDirectory(DirectoryPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CleanupDirectory(string filePath)
|
private static void CleanupDirectory(string filePath)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -8,11 +11,13 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
{
|
{
|
||||||
public class RazorGenerateIntegrationTest : MSBuildIntegrationTestBase
|
public class RazorGenerateIntegrationTest : MSBuildIntegrationTestBase
|
||||||
{
|
{
|
||||||
|
private const string RazorGenerateTarget = "RazorGenerate";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[InitializeTestProject("SimpleMvc")]
|
[InitializeTestProject("SimpleMvc")]
|
||||||
public async Task RazorGenerate_Success_GeneratesFilesOnDisk()
|
public async Task RazorGenerate_Success_GeneratesFilesOnDisk()
|
||||||
{
|
{
|
||||||
var result = await DotnetMSBuild("RazorGenerate");
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
|
||||||
Assert.BuildPassed(result);
|
Assert.BuildPassed(result);
|
||||||
|
|
||||||
|
|
@ -52,5 +57,126 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
||||||
// The file should still be generated even if we had a Razor syntax error.
|
// The file should still be generated even if we had a Razor syntax error.
|
||||||
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cs");
|
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "Index.cs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[InitializeTestProject("SimpleMvc")]
|
||||||
|
public async Task RazorGenerate_BuildsIncrementally()
|
||||||
|
{
|
||||||
|
// Act - 1
|
||||||
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
var generatedFile = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.cs");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, generatedFile);
|
||||||
|
var thumbPrint = GetThumbPrint(generatedFile);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
using (var razorGenDirectoryLock = LockDirectory(RazorIntermediateOutputPath))
|
||||||
|
{
|
||||||
|
result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, generatedFile);
|
||||||
|
var currentThumbPrint = GetThumbPrint(generatedFile);
|
||||||
|
Assert.Equal(thumbPrint, currentThumbPrint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[InitializeTestProject("SimpleMvc")]
|
||||||
|
public async Task RazorGenerate_Rebuilds_IfSourcesAreUpdated()
|
||||||
|
{
|
||||||
|
// Act - 1
|
||||||
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Contact.cshtml");
|
||||||
|
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Contact.cs");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
var fileThumbPrint = GetThumbPrint(generatedFile);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
// Update the source content and build. We should expect the outputs to be regenerated.
|
||||||
|
// Timestamps on xplat are precise only to a second. Add a delay so we can ensure that MSBuild recognizes the
|
||||||
|
// file change. See https://github.com/dotnet/corefx/issues/26024
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
ReplaceContent("Uodated content", file);
|
||||||
|
result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
var newThumbPrint = GetThumbPrint(generatedFile);
|
||||||
|
Assert.NotEqual(fileThumbPrint, newThumbPrint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[InitializeTestProject("SimpleMvc")]
|
||||||
|
public async Task RazorGenerate_Rebuilds_IfOutputFilesAreMissing()
|
||||||
|
{
|
||||||
|
// Act - 1
|
||||||
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
var file = Path.Combine(Project.DirectoryPath, RazorIntermediateOutputPath, "Views", "Home", "About.cs");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, file);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
File.Delete(file);
|
||||||
|
result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[InitializeTestProject("SimpleMvc")]
|
||||||
|
public async Task RazorGenerate_Rebuilds_IfInputFilesAreRenamed()
|
||||||
|
{
|
||||||
|
// Act - 1
|
||||||
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Index.cshtml");
|
||||||
|
var renamed = Path.Combine(Project.DirectoryPath, "Views", "Home", "NewIndex.cshtml");
|
||||||
|
var generated = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cs");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, file);
|
||||||
|
Assert.FileExists(result, generated);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
File.Move(file, renamed);
|
||||||
|
result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, RazorIntermediateOutputPath, "Views", "Home", "NewIndex.cs");
|
||||||
|
Assert.FileDoesNotExist(result, generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[InitializeTestProject("SimpleMvc")]
|
||||||
|
public async Task RazorGenerate_Rebuilds_IfInputFilesAreDeleted()
|
||||||
|
{
|
||||||
|
// Act - 1
|
||||||
|
var result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
var file = Path.Combine(Project.DirectoryPath, "Views", "Home", "Index.cshtml");
|
||||||
|
var generatedFile = Path.Combine(RazorIntermediateOutputPath, "Views", "Home", "Index.cs");
|
||||||
|
|
||||||
|
// Assert - 1
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileExists(result, generatedFile);
|
||||||
|
|
||||||
|
// Act - 2
|
||||||
|
File.Delete(file);
|
||||||
|
result = await DotnetMSBuild(RazorGenerateTarget);
|
||||||
|
|
||||||
|
// Assert - 2
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
Assert.FileDoesNotExist(result, generatedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
-->
|
-->
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
|
<DefineConstants Condition="'$(PreserveWorkingDirectory)'=='true'">$(DefineConstants);PRESERVE_WORKING_DIRECTORY</DefineConstants>
|
||||||
<!-- Copy references locally so that we can use them in the test. -->
|
<!-- Copy references locally so that we can use them in the test. -->
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms);netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue