[InlineData(10000)] // expected failure: same time and length public void GetOrAdd_UsesFilesFromCache_IfTimestampDiffers_ButContentAndLengthAreTheSame(long fileTimeUTC) { // Arrange var instance = new RuntimeCompileIdentical(); var length = Encoding.UTF8.GetByteCount(instance.Content); var collection = new ViewCollection(); var fileSystem = new TestFileSystem(); var cache = new CompilerCache(new[] { new ViewCollection() }, fileSystem); var fileInfo = new TestFileInfo { Length = length, LastModified = DateTime.FromFileTimeUtc(fileTimeUTC), Content = instance.Content }; var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab"); var precompiledContent = new PreCompile().Content; var razorFileInfo = new RazorFileInfo { FullTypeName = typeof(PreCompile).FullName, Hash = RazorFileHash.GetHash(GetMemoryStream(precompiledContent)), LastModified = DateTime.FromFileTimeUtc(10000), Length = Encoding.UTF8.GetByteCount(precompiledContent), RelativePath = "ab", }; // Act var actual = cache.GetOrAdd(runtimeFileInfo, compile: _ => { throw new Exception("Shouldn't be called."); }); // Assert Assert.Equal(typeof(PreCompile), actual.CompiledType); }
public void GetOrAdd_IgnoresCachedValue_IfGlobalFileWasChangedSinceCacheWasCreated( RazorFileInfo viewStartRazorFileInfo, IFileInfo globalFileInfo) { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; var viewStartLastModified = DateTime.UtcNow; var content = "some content"; var fileInfo = new TestFileInfo { Length = 1020, Content = content, LastModified = lastModified, PhysicalPath = "Views\\home\\index.cshtml" }; var fileProvider = new TestFileProvider(); fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo); fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo); var viewCollection = new ViewCollection(); var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider); // Act var result = cache.GetOrAdd(fileInfo.PhysicalPath, compile: _ => CompilationResult.Successful(expectedType)); // Assert Assert.NotSame(CompilerCacheResult.FileNotFound, result); var actual = result.CompilationResult; Assert.NotNull(actual); Assert.Equal(expectedType, actual.CompiledType); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was precompiled. /// </summary> /// <param name="info">Metadata about the precompiled file.</param> /// <param name="compiledType">The compiled <see cref="Type"/>.</param> public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type compiledType) { CompiledType = compiledType; RelativePath = info.RelativePath; Length = info.Length; LastModified = info.LastModified; Hash = info.Hash; }
protected virtual string GenerateFile([NotNull] RazorFileInfo fileInfo) { return(string.Format(FileFormat, fileInfo.LastModified.ToFileTimeUtc(), fileInfo.Length, fileInfo.RelativePath, fileInfo.FullTypeName, fileInfo.Hash)); }
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasDeletedSinceCacheWasCreated() { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; var fileSystem = new TestFileSystem(); var viewCollection = new ViewCollection(); var precompiledView = viewCollection.FileInfos[0]; precompiledView.RelativePath = "Views\\Index.cshtml"; var viewFileInfo = new TestFileInfo { Content = new PreCompile().Content, LastModified = precompiledView.LastModified, PhysicalPath = precompiledView.RelativePath }; fileSystem.AddFile(viewFileInfo.PhysicalPath, viewFileInfo); var viewStartFileInfo = new TestFileInfo { PhysicalPath = "Views\\_ViewStart.cshtml", Content = "viewstart-content", LastModified = lastModified }; var viewStart = new RazorFileInfo { FullTypeName = typeof(RuntimeCompileIdentical).FullName, RelativePath = viewStartFileInfo.PhysicalPath, LastModified = viewStartFileInfo.LastModified, Hash = RazorFileHash.GetHash(viewStartFileInfo), Length = viewStartFileInfo.Length }; fileSystem.AddFile(viewStartFileInfo.PhysicalPath, viewStartFileInfo); viewCollection.Add(viewStart); var cache = new CompilerCache(new[] { viewCollection }, fileSystem); var fileInfo = new RelativeFileInfo(viewFileInfo, viewFileInfo.PhysicalPath); // Act 1 var actual1 = cache.GetOrAdd(fileInfo, compile: _ => { throw new Exception("should not be called"); }); // Assert 1 Assert.Equal(typeof(PreCompile), actual1.CompiledType); // Act 2 fileSystem.DeleteFile(viewStartFileInfo.PhysicalPath); var actual2 = cache.GetOrAdd(fileInfo, compile: _ => CompilationResult.Successful(expectedType)); // Assert 2 Assert.Equal(expectedType, actual2.CompiledType); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was precompiled. /// </summary> /// <param name="info">Metadata about the precompiled file.</param> /// <param name="compiledType">The compiled <see cref="Type"/>.</param> public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type compiledType) { CompiledType = compiledType; RelativePath = info.RelativePath; Length = info.Length; LastModified = info.LastModified; Hash = info.Hash; HashAlgorithmVersion = info.HashAlgorithmVersion; IsPreCompiled = true; }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was precompiled. /// </summary> /// <param name="info">Metadata about the precompiled file.</param> /// <param name="compiledType">The compiled <see cref="Type"/>.</param> public CompilerCacheEntry([NotNull] RazorFileInfo info, [NotNull] Type compiledType) { CompiledType = compiledType; RelativePath = info.RelativePath; Length = info.Length; LastModified = info.LastModified; Hash = info.Hash; // Precompiled views are always instrumented. IsInstrumented = true; }
protected virtual RazorFileInfoCollection CreateFileInfoCollection() { var filesToProcess = new List <RelativeFileInfo>(); GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess); if (filesToProcess.Count == 0) { return(null); } var razorFiles = new RazorFileInfo[filesToProcess.Count]; var syntaxTrees = new SyntaxTree[filesToProcess.Count]; var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism }; var diagnosticsLock = new object(); var hasErrors = false; Parallel.For(0, filesToProcess.Count, parallelOptions, index => { var file = filesToProcess[index]; var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath, file, OnCacheMiss); if (cacheEntry != null) { if (cacheEntry.Success) { syntaxTrees[index] = cacheEntry.SyntaxTree; razorFiles[index] = cacheEntry.FileInfo; } else { hasErrors = true; lock (diagnosticsLock) { AddRange(CompileContext.Diagnostics, cacheEntry.Diagnostics); } } } }); if (hasErrors) { // If any of the Razor files had syntax errors, don't emit the precompiled views assembly. return(null); } return(GeneratePrecompiledAssembly(syntaxTrees.Where(tree => tree != null), razorFiles.Where(file => file != null))); }
public void GetOrAdd_UsesValueFromCache_IfGlobalHasNotChanged() { // Arrange var instance = new PreCompile(); var length = Encoding.UTF8.GetByteCount(instance.Content); var fileProvider = new TestFileProvider(); var lastModified = DateTime.UtcNow; var fileInfo = new TestFileInfo { Length = length, LastModified = lastModified, Content = instance.Content }; fileProvider.AddFile(ViewPath, fileInfo); var globalContent = "global-content"; var globalFileInfo = new TestFileInfo { Content = globalContent, LastModified = DateTime.UtcNow }; fileProvider.AddFile("_GlobalImport.cshtml", globalFileInfo); var globalRazorFileInfo = new RazorFileInfo { Hash = Crc32.Calculate(GetMemoryStream(globalContent)).ToString(CultureInfo.InvariantCulture), HashAlgorithmVersion = 1, LastModified = globalFileInfo.LastModified, Length = globalFileInfo.Length, RelativePath = "_GlobalImport.cshtml", FullTypeName = typeof(RuntimeCompileIdentical).FullName }; var precompiledViews = new ViewCollection(); precompiledViews.Add(globalRazorFileInfo); var cache = new CompilerCache(new[] { precompiledViews }, TestLoadContext, fileProvider); // Act var result = cache.GetOrAdd(ViewPath, compile: _ => { throw new Exception("shouldn't be invoked"); }); // Assert Assert.NotSame(CompilerCacheResult.FileNotFound, result); var actual = result.CompilationResult; Assert.NotNull(actual); Assert.Equal(typeof(PreCompile), actual.CompiledType); }
public void GetOrAdd_UsesValueFromCache_IfViewStartHasNotChanged() { // Arrange var instance = (View)Activator.CreateInstance(typeof(PreCompile)); var length = Encoding.UTF8.GetByteCount(instance.Content); var fileSystem = new TestFileSystem(); var lastModified = DateTime.UtcNow; var fileInfo = new TestFileInfo { Length = length, LastModified = lastModified, Content = instance.Content }; var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab"); var viewStartContent = "viewstart-content"; var viewStartFileInfo = new TestFileInfo { Content = viewStartContent, LastModified = DateTime.UtcNow }; fileSystem.AddFile("_ViewStart.cshtml", viewStartFileInfo); var viewStartRazorFileInfo = new RazorFileInfo { Hash = RazorFileHash.GetHash(GetMemoryStream(viewStartContent)), LastModified = viewStartFileInfo.LastModified, Length = viewStartFileInfo.Length, RelativePath = "_ViewStart.cshtml", FullTypeName = typeof(RuntimeCompileIdentical).FullName }; var precompiledViews = new ViewCollection(); precompiledViews.Add(viewStartRazorFileInfo); var cache = new CompilerCache(new[] { precompiledViews }, fileSystem); // Act var actual = cache.GetOrAdd(runtimeFileInfo, compile: _ => { throw new Exception("shouldn't be invoked"); }); // Assert Assert.Equal(typeof(PreCompile), actual.CompiledType); }
protected virtual IEnumerable <RazorFileInfo> CreateCompilationDescriptors( [NotNull] IBeforeCompileContext context) { var filesToProcess = new List <RelativeFileInfo>(); GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess); var razorFiles = new RazorFileInfo[filesToProcess.Count]; var syntaxTrees = new SyntaxTree[filesToProcess.Count]; var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism }; var diagnosticsLock = new object(); Parallel.For(0, filesToProcess.Count, parallelOptions, index => { var file = filesToProcess[index]; var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath, file, OnCacheMiss); if (cacheEntry != null) { if (cacheEntry.Success) { syntaxTrees[index] = cacheEntry.SyntaxTree; razorFiles[index] = cacheEntry.FileInfo; } else { lock (diagnosticsLock) { foreach (var diagnostic in cacheEntry.Diagnostics) { context.Diagnostics.Add(diagnostic); } } } } }); context.CSharpCompilation = context.CSharpCompilation .AddSyntaxTrees(syntaxTrees.Where(tree => tree != null)); return(razorFiles.Where(file => file != null)); }
protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo) { using (var stream = fileInfo.FileInfo.CreateReadStream()) { var host = GetRazorHost(); var results = host.GenerateCode(fileInfo.RelativePath, stream); if (results.Success) { var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode, fileInfo.FileInfo.PhysicalPath, CompilationSettings); var fullTypeName = results.GetMainClassName(host, syntaxTree); if (fullTypeName != null) { var hashAlgorithmVersion = RazorFileHash.HashAlgorithmVersion1; var hash = RazorFileHash.GetHash(fileInfo.FileInfo, hashAlgorithmVersion); var razorFileInfo = new RazorFileInfo { RelativePath = fileInfo.RelativePath, LastModified = fileInfo.FileInfo.LastModified, Length = fileInfo.FileInfo.Length, FullTypeName = fullTypeName, Hash = hash, HashAlgorithmVersion = hashAlgorithmVersion }; return(new PrecompilationCacheEntry(razorFileInfo, syntaxTree)); } } else { var diagnostics = results.ParserErrors .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath)) .ToList(); return(new PrecompilationCacheEntry(diagnostics)); } } return(null); }
public void GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreated( RazorFileInfo viewStartRazorFileInfo, TestFileInfo viewStartFileInfo) { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; var viewStartLastModified = DateTime.UtcNow; var content = "some content"; var fileInfo = new TestFileInfo { Length = 1020, Content = content, LastModified = lastModified, PhysicalPath = "Views\\home\\index.cshtml" }; var runtimeFileInfo = new RelativeFileInfo(fileInfo, fileInfo.PhysicalPath); var razorFileInfo = new RazorFileInfo { FullTypeName = typeof(PreCompile).FullName, Hash = RazorFileHash.GetHash(fileInfo), LastModified = lastModified, Length = Encoding.UTF8.GetByteCount(content), RelativePath = fileInfo.PhysicalPath, }; var fileSystem = new TestFileSystem(); fileSystem.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo); var viewCollection = new ViewCollection(); var cache = new CompilerCache(new[] { viewCollection }, fileSystem); // Act var actual = cache.GetOrAdd(runtimeFileInfo, compile: _ => CompilationResult.Successful(expectedType)); // Assert Assert.Equal(expectedType, actual.CompiledType); }
public void GetOrAdd_RecompilesFile_IfContentAndLengthAreChanged( Type resultViewType, long fileTimeUTC) { // Arrange var instance = (View)Activator.CreateInstance(resultViewType); var length = Encoding.UTF8.GetByteCount(instance.Content); var collection = new ViewCollection(); var fileSystem = new TestFileSystem(); var cache = new CompilerCache(new[] { new ViewCollection() }, fileSystem); var fileInfo = new TestFileInfo { Length = length, LastModified = DateTime.FromFileTimeUtc(fileTimeUTC), Content = instance.Content }; var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab"); var precompiledContent = new PreCompile().Content; var razorFileInfo = new RazorFileInfo { FullTypeName = typeof(PreCompile).FullName, Hash = RazorFileHash.GetHash(GetMemoryStream(precompiledContent)), LastModified = DateTime.FromFileTimeUtc(10000), Length = Encoding.UTF8.GetByteCount(precompiledContent), RelativePath = "ab", }; // Act var actual = cache.GetOrAdd(runtimeFileInfo, compile: _ => CompilationResult.Successful(resultViewType)); // Assert Assert.Equal(resultViewType, actual.CompiledType); }
public void Add(RazorFileInfo fileInfo) { _fileInfos.Add(fileInfo); }
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalWasDeletedSinceCacheWasCreated() { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; var fileProvider = new TestFileProvider(); var viewCollection = new ViewCollection(); var precompiledView = viewCollection.FileInfos[0]; precompiledView.RelativePath = "Views\\Index.cshtml"; var viewFileInfo = new TestFileInfo { Content = new PreCompile().Content, LastModified = precompiledView.LastModified, PhysicalPath = precompiledView.RelativePath }; fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo); var globalFileInfo = new TestFileInfo { PhysicalPath = "Views\\_GlobalImport.cshtml", Content = "viewstart-content", LastModified = lastModified }; var globalFile = new RazorFileInfo { FullTypeName = typeof(RuntimeCompileIdentical).FullName, RelativePath = globalFileInfo.PhysicalPath, LastModified = globalFileInfo.LastModified, Hash = RazorFileHash.GetHash(globalFileInfo, hashAlgorithmVersion: 1), HashAlgorithmVersion = 1, Length = globalFileInfo.Length }; fileProvider.AddFile(globalFileInfo.PhysicalPath, globalFileInfo); viewCollection.Add(globalFile); var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider); // Act 1 var result1 = cache.GetOrAdd(viewFileInfo.PhysicalPath, compile: _ => { throw new Exception("should not be called"); }); // Assert 1 Assert.NotSame(CompilerCacheResult.FileNotFound, result1); var actual1 = result1.CompilationResult; Assert.NotNull(actual1); Assert.Equal(typeof(PreCompile), actual1.CompiledType); // Act 2 var trigger = fileProvider.GetTrigger(globalFileInfo.PhysicalPath); trigger.IsExpired = true; var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath, compile: _ => CompilationResult.Successful(expectedType)); // Assert 2 Assert.NotSame(CompilerCacheResult.FileNotFound, result2); var actual2 = result2.CompilationResult; Assert.NotNull(actual2); Assert.Equal(expectedType, actual2.CompiledType); }
/// <summary> /// Initializes a new instance of <see cref="PrecompilationCacheEntry"/> for a successful parse. /// </summary> /// <param name="fileInfo">The <see cref="RazorFileInfo"/> of the file being cached.</param> /// <param name="syntaxTree">The <see cref="CodeAnalysis.SyntaxTree"/> to cache.</param> public PrecompilationCacheEntry([NotNull] RazorFileInfo fileInfo, [NotNull] SyntaxTree syntaxTree) { FileInfo = fileInfo; SyntaxTree = syntaxTree; }