/// <summary> /// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified /// <see cref="CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/> </param> public CompilerCacheResult(CompilationResult compilationResult) { if (compilationResult == null) { throw new ArgumentNullException(nameof(compilationResult)); } CompilationResult = compilationResult; }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified /// <see cref="Compilation.CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param> /// <param name="expirationTokens">One or more <see cref="IChangeToken"/> instances that indicate when /// this result has expired.</param> public CompilerCacheResult(CompilationResult compilationResult, IList<IChangeToken> expirationTokens) { if (expirationTokens == null) { throw new ArgumentNullException(nameof(expirationTokens)); } CompilationResult = compilationResult; Success = true; ExpirationTokens = expirationTokens; }
public void GetOrAdd_ReturnsCompilationResultFromFactory() { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider); var type = typeof(TestView); var expected = new CompilationResult(type); // Act var result = cache.GetOrAdd(ViewPath, _ => expected); // Assert Assert.True(result.Success); Assert.Same(type, result.CompilationResult.CompiledType); }
public void EnsureSuccessful_ThrowsIfCompilationFailed() { // Arrange var compilationFailure = new CompilationFailure( "test", sourceFileContent: string.Empty, compiledContent: string.Empty, messages: Enumerable.Empty<AspNet.Diagnostics.DiagnosticMessage>()); var failures = new[] { compilationFailure }; var result = new CompilationResult(failures); // Act and Assert Assert.Null(result.CompiledType); Assert.Same(failures, result.CompilationFailures); var exception = Assert.Throws<CompilationFailedException>(() => result.EnsureSuccessful()); var failure = Assert.Single(exception.CompilationFailures); Assert.Same(compilationFailure, failure); }
public void GetOrAdd_NormalizesPathSepartorForPaths(string relativePath) { // Arrange var viewPath = "/Areas/Finances/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(viewPath, "some content"); var cache = new CompilerCache(fileProvider); var type = typeof(TestView); var expected = new CompilationResult(type); // Act - 1 var result1 = cache.GetOrAdd(@"Areas\Finances\Views\Home\Index.cshtml", _ => expected); // Assert - 1 Assert.Same(type, result1.CompilationResult.CompiledType); // Act - 2 var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled); // Assert - 2 Assert.Same(type, result2.CompilationResult.CompiledType); }
// Internal for unit testing internal CompilationResult GetCompilationFailedResult( string relativePath, string compilationContent, string assemblyName, IEnumerable <Diagnostic> diagnostics) { var diagnosticGroups = diagnostics .Where(IsError) .GroupBy(diagnostic => GetFilePath(relativePath, diagnostic), StringComparer.Ordinal); var failures = new List <ICompilationFailure>(); foreach (var group in diagnosticGroups) { var sourceFilePath = group.Key; string sourceFileContent; if (string.Equals(assemblyName, sourceFilePath, StringComparison.Ordinal)) { // The error is in the generated code and does not have a mapping line pragma sourceFileContent = compilationContent; sourceFilePath = Resources.GeneratedCodeFileName; } else { sourceFileContent = ReadFileContentsSafely(_fileProvider, sourceFilePath); } var compilationFailure = new RoslynCompilationFailure(group) { CompiledContent = compilationContent, SourceFileContent = sourceFileContent, SourceFilePath = sourceFilePath }; failures.Add(compilationFailure); } return(CompilationResult.Failed(failures)); }
public void Compile_ReturnsResultFromCompilationServiceIfParseSucceeds() { // Arrange var code = "compiled-content"; var generatorResult = new GeneratorResults( new Block(new BlockBuilder { Type = BlockType.Comment }), Enumerable.Empty <TagHelperDescriptor>(), new ErrorSink(), new CodeGeneratorResult(code, new LineMapping[0]), new ChunkTree()); var host = new Mock <IMvcRazorHost>(); host.Setup(h => h.GenerateCode(It.IsAny <string>(), It.IsAny <Stream>())) .Returns(generatorResult); var fileInfo = new Mock <IFileInfo>(); fileInfo.Setup(f => f.CreateReadStream()) .Returns(Stream.Null); var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); var compilationResult = CompilationResult.Successful(typeof(object)); var compiler = new Mock <ICompilationService>(); compiler.Setup(c => c.Compile(relativeFileInfo, code)) .Returns(compilationResult) .Verifiable(); var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions()); // Act var result = razorService.Compile(relativeFileInfo); // Assert Assert.Same(compilationResult, result); compiler.Verify(); }
public void GetOrAdd_NormalizesPathSepartorForPaths(string relativePath) { // Arrange var viewPath = "/Areas/Finances/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(viewPath, "some content"); var cache = new CompilerCache(fileProvider); var type = typeof(TestView); var expected = new CompilationResult(type); // Act - 1 var result1 = cache.GetOrAdd(@"Areas\Finances\Views\Home\Index.cshtml", _ => expected); // Assert - 1 Assert.Same(type, result1.CompilationResult.CompiledType); // Act - 2 var result2 = cache.GetOrAdd(relativePath, ThrowsIfCalled); // Assert - 2 Assert.Same(type, result2.CompilationResult.CompiledType); }
// Internal for unit testing internal CompilationResult GetCompilationFailedResult(RelativeFileInfo file, IEnumerable <RazorError> errors) { // If a SourceLocation does not specify a file path, assume it is produced // from parsing the current file. var messageGroups = errors .GroupBy(razorError => razorError.Location.FilePath ?? file.RelativePath, StringComparer.Ordinal); var failures = new List <RazorCompilationFailure>(); foreach (var group in messageGroups) { var filePath = group.Key; var fileContent = ReadFileContentsSafely(filePath); var compilationFailure = new RazorCompilationFailure( filePath, fileContent, group.Select(parserError => new RazorCompilationMessage(parserError, filePath))); failures.Add(compilationFailure); } return(CompilationResult.Failed(failures)); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified /// <see cref="Compilation.CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param> public CompilerCacheResult(CompilationResult compilationResult) : this(compilationResult, new IChangeToken[0]) { }
/// <inheritdoc /> public CompilationResult Compile(IFileInfo fileInfo, string compilationContent) { var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, fileInfo.PhysicalPath) }; var references = _applicationReferences.Value; var assemblyName = Path.GetRandomFileName(); var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: syntaxTrees, 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)); } } }
/// <inheritdoc /> public CompilationResult Compile(IFileInfo fileInfo, string compilationContent) { var sourceText = SourceText.From(compilationContent, Encoding.UTF8); var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) }; var references = _applicationReferences.Value; var assemblyName = Path.GetRandomFileName(); var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: syntaxTrees, references: references); using (var ms = new MemoryStream()) { using (var pdb = new MemoryStream()) { EmitResult result; if (PlatformHelper.IsMono) { result = compilation.Emit(ms, pdbStream: null); } else { result = compilation.Emit(ms, pdbStream: pdb); } 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 (PlatformHelper.IsMono) { assembly = _loader.LoadStream(ms, pdbStream: null); } else { pdb.Seek(0, SeekOrigin.Begin); assembly = _loader.LoadStream(ms, pdb); } var type = assembly.GetExportedTypes() .First(); return(UncachedCompilationResult.Successful(type, compilationContent)); } } }
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 _ViewImports 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 _ViewImports 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 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\\_ViewImports.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="CompilerCacheResult"/> with the specified /// <see cref="CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/> </param> public CompilerCacheResult([NotNull] CompilationResult compilationResult) { CompilationResult = compilationResult; }
public void GetOrAdd_ReturnsNewResult_IfAncestorViewImportsWereModified(string globalImportPath) { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider); var expected1 = new CompilationResult(typeof(TestView)); var expected2 = new CompilationResult(typeof(DifferentView)); // Act 1 var result1 = cache.GetOrAdd(ViewPath, _ => expected1); // Assert 1 Assert.True(result1.Success); Assert.Same(expected1.CompiledType, result1.CompilationResult.CompiledType); // Act 2 // Verify we're getting cached results. var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled); // Assert 2 Assert.True(result2.Success); Assert.Same(expected1.CompiledType, result2.CompilationResult.CompiledType); // Act 3 fileProvider.GetChangeToken(globalImportPath).HasChanged = true; var result3 = cache.GetOrAdd(ViewPath, _ => expected2); // Assert 2 Assert.True(result3.Success); Assert.Same(expected2.CompiledType, result3.CompilationResult.CompiledType); }
/// <summary> /// Initializes a new instance of <see cref="CompilerCacheResult"/> with the specified /// <see cref="Compilation.CompilationResult"/>. /// </summary> /// <param name="compilationResult">The <see cref="Compilation.CompilationResult"/>.</param> public CompilerCacheResult(CompilationResult compilationResult) : this(compilationResult, new IChangeToken[0]) { }
public CompilationResult Compile(string content) { var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) }; var targetFramework = _environment.TargetFramework; var references = GetApplicationReferences(); var assemblyName = Path.GetRandomFileName(); var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: syntaxTrees, references: references); using (var ms = new MemoryStream()) { using (var pdb = new MemoryStream()) { EmitResult result; if (PlatformHelper.IsMono) { result = compilation.Emit(ms, pdbStream: null); } else { result = compilation.Emit(ms, pdbStream: pdb); } if (!result.Success) { var formatter = new DiagnosticFormatter(); var messages = result.Diagnostics .Where(IsError) .Select(d => GetCompilationMessage(formatter, d)) .ToList(); return(CompilationResult.Failed(content, messages)); } Assembly assembly; ms.Seek(0, SeekOrigin.Begin); if (PlatformHelper.IsMono) { assembly = _loader.LoadStream(ms, pdbStream: null); } else { pdb.Seek(0, SeekOrigin.Begin); assembly = _loader.LoadStream(ms, pdb); } var type = assembly.GetExportedTypes() .First(); return(CompilationResult.Successful(string.Empty, type)); } } }
public void GetOrAdd_DoesNotQueryFileSystem_IfCachedFileTriggerWasNotSet() { // Arrange var mockFileProvider = new Mock<TestFileProvider> { CallBase = true }; var fileProvider = mockFileProvider.Object; fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider); var type = typeof(TestView); var expected = new CompilationResult(type); // Act 1 var result1 = cache.GetOrAdd(ViewPath, _ => expected); // Assert 1 Assert.True(result1.Success); Assert.Same(type, result1.CompilationResult.CompiledType); // Act 2 var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled); // Assert 2 Assert.True(result2.Success); Assert.Same(type, result2.CompilationResult.CompiledType); mockFileProvider.Verify(v => v.GetFileInfo(ViewPath), Times.Once()); }
public void Compile_ReturnsResultFromCompilationServiceIfParseSucceeds() { // Arrange var code = "compiled-content"; var generatorResult = new GeneratorResults( new Block(new BlockBuilder { Type = BlockType.Comment }), Enumerable.Empty<TagHelperDescriptor>(), new ErrorSink(), new CodeGeneratorResult(code, new LineMapping[0]), new ChunkTree()); var host = new Mock<IMvcRazorHost>(); host.Setup(h => h.GenerateCode(It.IsAny<string>(), It.IsAny<Stream>())) .Returns(generatorResult); var fileInfo = new Mock<IFileInfo>(); fileInfo.Setup(f => f.CreateReadStream()) .Returns(Stream.Null); var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml"); var compilationResult = new CompilationResult(typeof(object)); var compiler = new Mock<ICompilationService>(); compiler.Setup(c => c.Compile(relativeFileInfo, code)) .Returns(compilationResult) .Verifiable(); var razorService = new RazorCompilationService(compiler.Object, host.Object, GetFileProviderAccessor(), NullLoggerFactory.Instance); // Act var result = razorService.Compile(relativeFileInfo); // Assert Assert.Same(compilationResult.CompiledType, result.CompiledType); compiler.Verify(); }
public void GetOrAdd_ReturnsFailedCompilationResult_IfFileWasRemovedFromFileSystem() { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider); var type = typeof(TestView); var expected = new CompilationResult(type); // Act 1 var result1 = cache.GetOrAdd(ViewPath, _ => expected); // Assert 1 Assert.True(result1.Success); Assert.Same(expected.CompiledType, result1.CompilationResult.CompiledType); // Act 2 // Delete the file from the file system and set it's expiration token. fileProvider.DeleteFile(ViewPath); fileProvider.GetChangeToken(ViewPath).HasChanged = true; var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled); // Assert 2 Assert.False(result2.Success); }
public void GetOrAdd_ReturnsRuntimeCompiledAndPrecompiledViews() { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider, _precompiledViews); var expected = new CompilationResult(typeof(TestView)); // Act 1 var result1 = cache.GetOrAdd(ViewPath, _ => expected); // Assert 1 Assert.Same(typeof(TestView), result1.CompilationResult.CompiledType); // Act 2 var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled); // Assert 2 Assert.True(result2.Success); Assert.Same(typeof(TestView), result2.CompilationResult.CompiledType); // Act 3 var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled); // Assert 3 Assert.True(result2.Success); Assert.Same(typeof(PreCompile), result3.CompilationResult.CompiledType); }
public void GetOrAdd_CachesExceptionsInCompilationResult() { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(ViewPath, "some content"); var cache = new CompilerCache(fileProvider); var diagnosticMessages = new[] { new AspNet.Diagnostics.DiagnosticMessage("message", "message", ViewPath, 1, 1, 1, 1) }; var compilationResult = new CompilationResult(new[] { new CompilationFailure(ViewPath, "some content", "compiled content", diagnosticMessages) }); // Act and Assert - 1 var ex = Assert.Throws<CompilationFailedException>(() => cache.GetOrAdd(ViewPath, _ => compilationResult)); Assert.Same(compilationResult.CompilationFailures, ex.CompilationFailures); // Act and Assert - 2 ex = Assert.Throws<CompilationFailedException>(() => cache.GetOrAdd(ViewPath, ThrowsIfCalled)); Assert.Same(compilationResult.CompilationFailures, ex.CompilationFailures); }