[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 Compile_ReturnsUncachedCompilationResultWithCompiledContent() { // Arrange var content = @" public class MyTestType {}"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = new Mock<IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns(string.Empty); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert var uncachedResult = Assert.IsType<UncachedCompilationResult>(result); Assert.Equal("MyTestType", result.CompiledType.Name); Assert.Equal(content, uncachedResult.CompiledContent); }
private IEnumerable <RelativeFileInfo> GetFileInfosRecursive(string currentPath) { IEnumerable <IFileInfo> fileInfos; string path = currentPath; if (!_fileSystem.TryGetDirectoryContents(path, out fileInfos)) { yield break; } foreach (var fileInfo in fileInfos) { if (fileInfo.IsDirectory) { var subPath = Path.Combine(path, fileInfo.Name); foreach (var info in GetFileInfosRecursive(subPath)) { yield return(info); } } else if (Path.GetExtension(fileInfo.Name) .Equals(FileExtension, StringComparison.OrdinalIgnoreCase)) { var info = new RelativeFileInfo() { FileInfo = fileInfo, RelativePath = Path.Combine(currentPath, fileInfo.Name), }; yield return(info); } } }
public void GetOrAdd_DoesNotCacheCompiledContent_OnCallsAfterInitial() { // Arrange var lastModified = DateTime.UtcNow; var cache = new CompilerCache(Enumerable.Empty <RazorFileInfoCollection>(), new TestFileSystem()); var fileInfo = new TestFileInfo { PhysicalPath = "test", LastModified = lastModified }; var type = GetType(); var uncachedResult = UncachedCompilationResult.Successful(type, "hello world"); var runtimeFileInfo = new RelativeFileInfo(fileInfo, "test"); // Act cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult); var actual1 = cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult); var actual2 = cache.GetOrAdd(runtimeFileInfo, _ => uncachedResult); // Assert Assert.NotSame(uncachedResult, actual1); Assert.NotSame(uncachedResult, actual2); var result = Assert.IsType <CompilationResult>(actual1); Assert.Null(actual1.CompiledContent); Assert.Same(type, actual1.CompiledType); result = Assert.IsType <CompilationResult>(actual2); Assert.Null(actual2.CompiledContent); Assert.Same(type, actual2.CompiledType); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheEntry"/> for a file that was dynamically compiled. /// </summary> /// <param name="info">Metadata about the file that was compiled.</param> /// <param name="compiledType">The compiled <see cref="Type"/>.</param> public CompilerCacheEntry([NotNull] RelativeFileInfo info, [NotNull] Type compiledType) { CompiledType = compiledType; RelativePath = info.RelativePath; Length = info.FileInfo.Length; LastModified = info.FileInfo.LastModified; }
/// <inheritdoc /> public IRazorPage CreateInstance([NotNull] string relativePath) { if (relativePath.StartsWith("~/", StringComparison.Ordinal)) { // For tilde slash paths, drop the leading ~ to make it work with the underlying IFileSystem. relativePath = relativePath.Substring(1); } var fileInfo = _fileSystemCache.GetFileInfo(relativePath); if (fileInfo.Exists) { var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath); var result = _compilerCache.GetOrAdd( relativeFileInfo, RazorCompilationService.Compile); var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType); page.Path = relativePath; return(page); } return(null); }
/// <inheritdoc /> public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo, [NotNull] Func <RelativeFileInfo, CompilationResult> compile) { CompilationResult result; var entry = GetOrAdd(fileInfo, compile, out result); return(result); }
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); }
private CompilationResult OnCacheMiss(RelativeFileInfo file, bool isInstrumented, Func <CompilationResult> compile) { var result = compile(); var cacheEntry = new CompilerCacheEntry(file, result.CompiledType, isInstrumented); _cache[NormalizePath(file.RelativePath)] = cacheEntry; return(result); }
private CompilerCacheEntry GetOrAdd(RelativeFileInfo relativeFileInfo, Func <RelativeFileInfo, CompilationResult> compile, out CompilationResult result) { CompilerCacheEntry cacheEntry; var normalizedPath = NormalizePath(relativeFileInfo.RelativePath); if (!_cache.TryGetValue(normalizedPath, out cacheEntry)) { return(OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result)); } else { var fileInfo = relativeFileInfo.FileInfo; if (cacheEntry.Length != fileInfo.Length) { // Recompile if the file lengths differ return(OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result)); } if (AssociatedViewStartsChanged(cacheEntry, compile)) { // Recompile if the view starts have changed since the entry was created. return(OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result)); } if (cacheEntry.LastModified == fileInfo.LastModified) { result = CompilationResult.Successful(cacheEntry.CompiledType); return(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), StringComparison.Ordinal)) { // Cache hit, but we need to update the entry. // Assigning to LastModified is an atomic operation and will result in a safe race if it is // being concurrently read and written or updated concurrently. cacheEntry.LastModified = fileInfo.LastModified; result = CompilationResult.Successful(cacheEntry.CompiledType); return(cacheEntry); } // it's not a match, recompile return(OnCacheMiss(relativeFileInfo, normalizedPath, compile, out result)); } }
private GetOrAddResult OnCacheMiss(RelativeFileInfo file, string normalizedPath, Func <RelativeFileInfo, CompilationResult> compile) { var compilationResult = compile(file).EnsureSuccessful(); // Concurrent addition to MemoryCache with the same key result in safe race. var cacheEntry = _cache.Set(normalizedPath, new CompilerCacheEntry(file, compilationResult.CompiledType), PopulateCacheSetContext); return(new GetOrAddResult { CompilationResult = compilationResult, CompilerCacheEntry = cacheEntry }); }
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); }
private CompilerCacheEntry OnCacheMiss(RelativeFileInfo file, string normalizedPath, Func <RelativeFileInfo, CompilationResult> compile, out CompilationResult result) { result = compile(file); var cacheEntry = new CompilerCacheEntry(file, result.CompiledType) { AssociatedViewStartEntry = GetCompositeViewStartEntry(normalizedPath, compile) }; // The cache is a concurrent dictionary, so concurrent addition to it with the same key would result in a // safe race. _cache[normalizedPath] = cacheEntry; return(cacheEntry); }
public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalImportWasAdedSinceTheCacheWasCreated() { // Arrange var expectedType = typeof(RuntimeCompileDifferent); var fileProvider = new TestFileProvider(); var collection = new ViewCollection(); var precompiledFile = collection.FileInfos[0]; precompiledFile.RelativePath = "Views\\home\\index.cshtml"; var cache = new CompilerCache(new[] { collection }, TestLoadContext, fileProvider); var testFile = new TestFileInfo { Content = new PreCompile().Content, LastModified = precompiledFile.LastModified, PhysicalPath = precompiledFile.RelativePath }; fileProvider.AddFile(precompiledFile.RelativePath, testFile); var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath); // Act 1 var result1 = cache.GetOrAdd(testFile.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 globalTrigger = fileProvider.GetTrigger("Views\\_GlobalImport.cshtml"); globalTrigger.IsExpired = true; var result2 = cache.GetOrAdd(testFile.PhysicalPath, compile: _ => CompilationResult.Successful(expectedType)); // Assert 2 Assert.NotSame(CompilerCacheResult.FileNotFound, result2); var actual2 = result2.CompilationResult; Assert.NotNull(actual2); 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(e => new CompilationMessage(e.Message)); return(CompilationResult.Failed(file.FileInfo, results.GeneratedCode, messages)); } return(_compilationService.Compile(file.FileInfo, results.GeneratedCode)); }
public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() { // Arrange var content = @" #if MY_CUSTOM_DEFINE public class MyCustomDefinedClass {} #else public class MyNonCustomDefinedClass {} #endif "; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock <ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } }); var mvcRazorHost = new Mock <IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns("My"); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType); Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); }
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); }
// Returns the entry for the nearest _ViewStart that the file inherits directives from. Since _ViewStart // entries are affected by other _ViewStart entries that are in the path hierarchy, the returned value // represents the composite result of performing a cache check on individual _ViewStart entries. private CompilerCacheEntry GetCompositeViewStartEntry(string relativePath, Func <RelativeFileInfo, CompilationResult> compile) { var viewStartLocations = ViewStartUtility.GetViewStartLocations(relativePath); foreach (var viewStartLocation in viewStartLocations) { var viewStartFileInfo = _fileProvider.GetFileInfo(viewStartLocation); if (viewStartFileInfo.Exists) { var relativeFileInfo = new RelativeFileInfo(viewStartFileInfo, viewStartLocation); CompilationResult result; return(GetOrAdd(relativeFileInfo, compile, out result)); } } // No _ViewStarts discovered. return(null); }
/// <inheritdoc /> public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo, bool enableInstrumentation, [NotNull] Func <CompilationResult> compile) { CompilerCacheEntry cacheEntry; if (!_cache.TryGetValue(NormalizePath(fileInfo.RelativePath), out cacheEntry)) { return(OnCacheMiss(fileInfo, enableInstrumentation, compile)); } else { if ((cacheEntry.Length != fileInfo.FileInfo.Length) || (enableInstrumentation && !cacheEntry.IsInstrumented)) { // Recompile if // (a) If the file lengths differ // (b) If the compiled type is not instrumented but we require it to be instrumented. return(OnCacheMiss(fileInfo, enableInstrumentation, compile)); } if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified) { // Match, not update needed return(CompilationResult.Successful(cacheEntry.CompiledType)); } var hash = RazorFileHash.GetHash(fileInfo.FileInfo); // Timestamp doesn't match but it might be because of deployment, compare the hash. if (cacheEntry.IsPreCompiled && string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal)) { // Cache hit, but we need to update the entry return(OnCacheMiss(fileInfo, enableInstrumentation, () => CompilationResult.Successful(cacheEntry.CompiledType))); } // it's not a match, recompile return(OnCacheMiss(fileInfo, enableInstrumentation, compile)); } }
private void GetFileInfosRecursive(string root, List <RelativeFileInfo> razorFiles) { var fileInfos = _fileProvider.GetDirectoryContents(root); foreach (var fileInfo in fileInfos) { if (fileInfo.IsDirectory) { var subPath = Path.Combine(root, fileInfo.Name); GetFileInfosRecursive(subPath, razorFiles); } else if (Path.GetExtension(fileInfo.Name) .Equals(FileExtension, StringComparison.OrdinalIgnoreCase)) { var relativePath = Path.Combine(root, fileInfo.Name); var info = new RelativeFileInfo(fileInfo, relativePath); razorFiles.Add(info); } } }
protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo, [NotNull] IBeforeCompileContext context, [NotNull] CSharpParseOptions options) { using (var stream = fileInfo.FileInfo.CreateReadStream()) { var results = _host.GenerateCode(fileInfo.RelativePath, stream); foreach (var parserError in results.ParserErrors) { var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath); context.Diagnostics.Add(diagnostic); } var generatedCode = results.GeneratedCode; if (generatedCode != null) { var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode, fileInfo.FileInfo.PhysicalPath, options); var fullTypeName = results.GetMainClassName(_host, syntaxTree); if (fullTypeName != null) { context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree); var hash = RazorFileHash.GetHash(fileInfo.FileInfo); return(new RazorFileInfo() { FullTypeName = fullTypeName, RelativePath = fileInfo.RelativePath, LastModified = fileInfo.FileInfo.LastModified, Length = fileInfo.FileInfo.Length, Hash = hash, }); } } } 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 Compile_ReturnsUncachedCompilationResultWithCompiledContent() { // Arrange var content = @" public class MyTestType {}"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock <ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = new Mock <IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns(string.Empty); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert var uncachedResult = Assert.IsType <UncachedCompilationResult>(result); Assert.Equal("MyTestType", result.CompiledType.Name); Assert.Equal(content, uncachedResult.CompiledContent); }
public void Compile_ReturnsSingleTypeThatStartsWithMainClassNamePrefix() { // Arrange var content = @" public class RazorPrefixType {} public class NotRazorPrefixType {}"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock <ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = new Mock <IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns("RazorPrefix"); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType); Assert.Equal("RazorPrefixType", result.CompiledType.Name); }
public void Compile_ReturnsCompilationFailureWithRelativePath() { // Arrange var fileContent = "test file content"; var content = @"this should fail"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock <ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = Mock.Of <IMvcRazorHost>(); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost); var fileInfo = new TestFileInfo { Content = fileContent, PhysicalPath = "physical path" }; var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.IsType <CompilationResult>(result); Assert.Null(result.CompiledType); Assert.Equal(relativeFileInfo.RelativePath, result.CompilationFailure.SourceFilePath); Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent); }
public void GetOrAdd_ReturnsCompilationResultFromFactory() { // Arrange var fileSystem = new TestFileSystem(); var cache = new CompilerCache(Enumerable.Empty <RazorFileInfoCollection>(), fileSystem); var fileInfo = new TestFileInfo { LastModified = DateTime.FromFileTimeUtc(10000) }; var type = GetType(); var expected = UncachedCompilationResult.Successful(type, "hello world"); var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab"); // Act var actual = cache.GetOrAdd(runtimeFileInfo, _ => expected); // Assert Assert.Same(expected, actual); Assert.Equal("hello world", actual.CompiledContent); Assert.Same(type, 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 Compile_ReturnsCompilationFailureWithRelativePath() { // Arrange var fileContent = "test file content"; var content = @"this should fail"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = Mock.Of<IMvcRazorHost>(); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost); var fileInfo = new TestFileInfo { Content = fileContent, PhysicalPath = "physical path" }; var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.IsType<CompilationResult>(result); Assert.Null(result.CompiledType); Assert.Equal(relativeFileInfo.RelativePath, result.CompilationFailure.SourceFilePath); Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent); }
/// <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)); }
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); }
public void Compile_DoesNotThrow_IfFileCannotBeRead() { // Arrange var content = @"this should fail"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock <ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = Mock.Of <IMvcRazorHost>(); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost); var mockFileInfo = new Mock <IFileInfo>(); mockFileInfo.Setup(f => f.CreateReadStream()) .Throws(new Exception()); var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.IsType <CompilationResult>(result); Assert.Null(result.CompiledType); Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath); Assert.Null(result.CompilationFailure.SourceFileContent); }
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 }); }
public void Compile_DoesNotThrow_IfFileCannotBeRead() { // Arrange var content = @"this should fail"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = Mock.Of<IMvcRazorHost>(); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost); var mockFileInfo = new Mock<IFileInfo>(); mockFileInfo.Setup(f => f.CreateReadStream()) .Throws(new Exception()); var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.IsType<CompilationResult>(result); Assert.Null(result.CompiledType); Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath); Assert.Null(result.CompilationFailure.SourceFileContent); }
public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() { // Arrange var content = @" #if MY_CUSTOM_DEFINE public class MyCustomDefinedClass {} #else public class MyNonCustomDefinedClass {} #endif "; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } }); var mvcRazorHost = new Mock<IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns("My"); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType); Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); }
public void Compile_ReturnsSingleTypeThatStartsWithMainClassNamePrefix() { // Arrange var content = @" public class RazorPrefixType {} public class NotRazorPrefixType {}"; var applicationEnvironment = GetApplicationEnvironment(); var accessor = GetLoadContextAccessor(); var libraryManager = GetLibraryManager(); var compilerOptionsProvider = new Mock<ICompilerOptionsProvider>(); compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationName, applicationEnvironment.RuntimeFramework, applicationEnvironment.Configuration)) .Returns(new CompilerOptions()); var mvcRazorHost = new Mock<IMvcRazorHost>(); mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) .Returns("RazorPrefix"); var compilationService = new RoslynCompilationService(applicationEnvironment, accessor, libraryManager, compilerOptionsProvider.Object, mvcRazorHost.Object); var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" }, "some-relative-path"); // Act var result = compilationService.Compile(relativeFileInfo, content); // Assert Assert.NotNull(result.CompiledType); Assert.Equal("RazorPrefixType", result.CompiledType.Name); }