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 GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewStartWasAdedSinceTheCacheWasCreated() { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; var fileSystem = new TestFileSystem(); var collection = new ViewCollection(); var precompiledFile = collection.FileInfos[0]; precompiledFile.RelativePath = "Views\\home\\index.cshtml"; var cache = new CompilerCache(new[] { collection }, fileSystem); var testFile = new TestFileInfo { Content = new PreCompile().Content, LastModified = precompiledFile.LastModified, PhysicalPath = precompiledFile.RelativePath }; fileSystem.AddFile(precompiledFile.RelativePath, testFile); var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath); // Act 1 var actual1 = cache.GetOrAdd(relativeFile, compile: _ => { throw new Exception("should not be called"); }); // Assert 1 Assert.Equal(typeof(PreCompile), actual1.CompiledType); // Act 2 fileSystem.AddFile("Views\\_ViewStart.cshtml", ""); var actual2 = cache.GetOrAdd(relativeFile, compile: _ => CompilationResult.Successful(expectedType)); // Assert 2 Assert.Equal(expectedType, actual2.CompiledType); }
/// <inheritdoc /> public CompilationResult Compile([NotNull] RelativeFileInfo file) { GeneratorResults results; using (var inputStream = file.FileInfo.CreateReadStream()) { results = _razorHost.GenerateCode( file.RelativePath, inputStream); } if (!results.Success) { var messages = results.ParserErrors .Select(parseError => new CompilationMessage(parseError.Message, parseError.Location.CharacterIndex, parseError.Location.LineIndex, parseError.Location.CharacterIndex + parseError.Length, parseError.Location.LineIndex)); return(CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages)); } return(_compilationService.Compile(file.FileInfo, results.GeneratedCode)); }
/// <inheritdoc /> public CompilationResult Compile([NotNull] RelativeFileInfo file) { GeneratorResults results; using (var inputStream = file.FileInfo.CreateReadStream()) { results = _razorHost.GenerateCode( file.RelativePath, inputStream); } if (!results.Success) { var messages = results.ParserErrors .Select(parseError => new RazorCompilationMessage(parseError, file.RelativePath)); var failure = new RazorCompilationFailure( file.RelativePath, ReadFileContentsSafely(file.FileInfo), messages); return(CompilationResult.Failed(failure)); } return(_compilationService.Compile(file, results.GeneratedCode)); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified /// <see cref="CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Razor.CompilationResult"/> </param> public CompilerCacheResult([NotNull] CompilationResult compilationResult) { CompilationResult = compilationResult; }
private GetOrAddResult GetOrAddCore(string relativePath, Func <RelativeFileInfo, CompilationResult> compile) { var normalizedPath = NormalizePath(relativePath); var cacheEntry = _cache.Get <CompilerCacheEntry>(normalizedPath); if (cacheEntry == null) { var fileInfo = _fileProvider.GetFileInfo(relativePath); if (!fileInfo.Exists) { return(null); } var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath); return(OnCacheMiss(relativeFileInfo, normalizedPath, compile)); } else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled) { // For precompiled views, the first time the entry is read, we need to ensure that no changes were made // either to the file associated with this entry, or any _GlobalImport associated with it between the time // the View was precompiled and the time EnsureInitialized was called. For later iterations, we can // rely on expiration triggers ensuring the validity of the entry. var fileInfo = _fileProvider.GetFileInfo(relativePath); if (!fileInfo.Exists) { return(null); } var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath); if (cacheEntry.Length != fileInfo.Length) { // Recompile if the file lengths differ return(OnCacheMiss(relativeFileInfo, normalizedPath, compile)); } if (AssociatedGlobalFilesChanged(cacheEntry, compile)) { // Recompile if _GlobalImports have changed since the entry was created. return(OnCacheMiss(relativeFileInfo, normalizedPath, compile)); } if (cacheEntry.LastModified == fileInfo.LastModified) { // Assigning to IsValidatedPreCompiled is an atomic operation and will result in a safe race // if it is being concurrently updated and read. cacheEntry.IsValidatedPreCompiled = true; return(new GetOrAddResult { CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType), CompilerCacheEntry = cacheEntry }); } // Timestamp doesn't match but it might be because of deployment, compare the hash. if (cacheEntry.IsPreCompiled && string.Equals(cacheEntry.Hash, RazorFileHash.GetHash(fileInfo, cacheEntry.HashAlgorithmVersion), StringComparison.Ordinal)) { // Cache hit, but we need to update the entry. // Assigning to LastModified and IsValidatedPreCompiled are atomic operations and will result in safe race // if the entry is being concurrently read or updated. cacheEntry.LastModified = fileInfo.LastModified; cacheEntry.IsValidatedPreCompiled = true; return(new GetOrAddResult { CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType), CompilerCacheEntry = cacheEntry }); } // it's not a match, recompile return(OnCacheMiss(relativeFileInfo, normalizedPath, compile)); } return(new GetOrAddResult { CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType), CompilerCacheEntry = cacheEntry }); }
/// <inheritdoc /> public CompilationResult Compile([NotNull] IFileInfo fileInfo, [NotNull] string compilationContent) { // The path passed to SyntaxTreeGenerator.Generate is used by the compiler to generate symbols (pdb) that // map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null. // This prevents files that exist in a non-physical file system from being debugged. var path = fileInfo.PhysicalPath ?? fileInfo.Name; var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment); var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent, path, compilationSettings); var references = _applicationReferences.Value; var assemblyName = Path.GetRandomFileName(); var compilationOptions = compilationSettings.CompilationOptions .WithOutputKind(OutputKind.DynamicallyLinkedLibrary); var compilation = CSharpCompilation.Create(assemblyName, options: compilationOptions, syntaxTrees: new[] { syntaxTree }, references: references); using (var ms = new MemoryStream()) { using (var pdb = new MemoryStream()) { EmitResult result; if (_supportsPdbGeneration.Value) { result = compilation.Emit(ms, pdbStream: pdb); } else { result = compilation.Emit(ms); } if (!result.Success) { var formatter = new DiagnosticFormatter(); var messages = result.Diagnostics .Where(IsError) .Select(d => GetCompilationMessage(formatter, d)) .ToList(); return(CompilationResult.Failed(fileInfo, compilationContent, messages)); } Assembly assembly; ms.Seek(0, SeekOrigin.Begin); if (_supportsPdbGeneration.Value) { pdb.Seek(0, SeekOrigin.Begin); assembly = _loader.LoadStream(ms, pdb); } else { assembly = _loader.LoadStream(ms, assemblySymbols: null); } var type = assembly.GetExportedTypes() .First(t => t.Name.StartsWith(_classPrefix, StringComparison.Ordinal)); return(UncachedCompilationResult.Successful(type, compilationContent)); } } }
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); }