Add Checksum computation to RazorSourceDocument.

- Renamed many of our `RazorSourceDocument` abstractions to not include the word `Razor`.
- Added a `GetChecksum()` method to `RazorSourceDocument` to allow source documents to compute their own checksums.
- Re-generated codegen tests that did not normalize new lines. Ones that did re-generate newlines converted from stream => string => normalized string and then ran the Razor parser.
- Added tests to validate `GetChecksum` for all source document types.
- Removed unused `LegacySourceDocument`.
This commit is contained in:
N. Taylor Mullen 2017-05-24 17:30:19 -07:00
parent d917311883
commit dcccea3004
17 changed files with 497 additions and 282 deletions

View File

@ -39,26 +39,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
Guid = Sha1AlgorithmId
};
var charBuffer = new char[sourceDocument.Length];
sourceDocument.CopyTo(0, charBuffer, 0, sourceDocument.Length);
var encoder = sourceDocument.Encoding.GetEncoder();
var byteCount = encoder.GetByteCount(charBuffer, 0, charBuffer.Length, flush: true);
var checksumBytes = new byte[byteCount];
encoder.GetBytes(charBuffer, 0, charBuffer.Length, checksumBytes, 0, flush: true);
using (var hashAlgorithm = SHA1.Create())
var checksum = sourceDocument.GetChecksum();
var fileHashBuilder = new StringBuilder(checksum.Length * 2);
foreach (var value in checksum)
{
var hashedBytes = hashAlgorithm.ComputeHash(checksumBytes);
var fileHashBuilder = new StringBuilder(hashedBytes.Length * 2);
foreach (var value in hashedBytes)
{
fileHashBuilder.Append(value.ToString("x2"));
}
node.Bytes = fileHashBuilder.ToString();
fileHashBuilder.Append(value.ToString("x2"));
}
node.Bytes = fileHashBuilder.ToString();
return node;
}
}

View File

@ -4,11 +4,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class LargeTextRazorSourceDocument : RazorSourceDocument
internal class LargeTextSourceDocument : RazorSourceDocument
{
private readonly List<char[]> _chunks;
@ -17,8 +18,9 @@ namespace Microsoft.AspNetCore.Razor.Language
private readonly RazorSourceLineCollection _lines;
private readonly int _length;
private byte[] _checksum;
public LargeTextRazorSourceDocument(StreamReader reader, int chunkMaxLength, Encoding encoding, string fileName)
public LargeTextSourceDocument(StreamReader reader, int chunkMaxLength, Encoding encoding, string fileName)
{
if (reader == null)
{
@ -101,6 +103,30 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
public override byte[] GetChecksum()
{
if (_checksum == null)
{
var charBuffer = new char[Length];
CopyTo(0, charBuffer, 0, Length);
var encoder = Encoding.GetEncoder();
var byteCount = encoder.GetByteCount(charBuffer, 0, charBuffer.Length, flush: true);
var bytes = new byte[byteCount];
encoder.GetBytes(charBuffer, 0, charBuffer.Length, bytes, 0, flush: true);
using (var hashAlgorithm = SHA1.Create())
{
_checksum = hashAlgorithm.ComputeHash(bytes);
}
}
var copiedChecksum = new byte[_checksum.Length];
_checksum.CopyTo(copiedChecksum, 0);
return copiedChecksum;
}
private static void ReadChunks(StreamReader reader, int chunkMaxLength, out int length, out List<char[]> chunks)
{
length = 0;

View File

@ -1,79 +0,0 @@
using System;
using System.Text;
// 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 Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class LegacySourceDocument : RazorSourceDocument
{
private readonly ITextBuffer _buffer;
private readonly string _filename;
private readonly RazorSourceLineCollection _lines;
public static RazorSourceDocument Create(ITextBuffer buffer, string filename)
{
return new LegacySourceDocument(buffer, filename);
}
private LegacySourceDocument(ITextBuffer buffer, string filename)
{
_buffer = buffer;
_filename = filename;
_lines = new DefaultRazorSourceLineCollection(this);
}
public override char this[int position]
{
get
{
_buffer.Position = position;
return (char)_buffer.Read();
}
}
public override Encoding Encoding => Encoding.UTF8;
public override string FileName => _filename;
public override int Length => _buffer.Length;
public override RazorSourceLineCollection Lines => _lines;
public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
if (sourceIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(sourceIndex));
}
if (destinationIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(destinationIndex));
}
if (count < 0 || count > Length - sourceIndex || count > destination.Length - destinationIndex)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (count == 0)
{
return;
}
for (var i = 0; i < count; i++)
{
destination[destinationIndex + i] = this[sourceIndex + i];
}
}
}
}

View File

@ -51,6 +51,12 @@ namespace Microsoft.AspNetCore.Razor.Language
/// <param name="count">The number of characters in this instance to copy to destination.</param>
public abstract void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count);
/// <summary>
/// Calculates the checksum for the <see cref="RazorSourceDocument"/>.
/// </summary>
/// <returns>The checksum.</returns>
public abstract byte[] GetChecksum();
/// <summary>
/// Reads the <see cref="RazorSourceDocument"/> from the specified <paramref name="stream"/>.
/// </summary>
@ -64,7 +70,7 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(stream));
}
return ReadFromInternal(stream, fileName, encoding: null);
return new StreamSourceDocument(stream, encoding: null, fileName: fileName);
}
/// <summary>
@ -86,7 +92,7 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(encoding));
}
return ReadFromInternal(stream, fileName, encoding);
return new StreamSourceDocument(stream, encoding, fileName);
}
/// <summary>
@ -142,57 +148,7 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(encoding));
}
return new DefaultRazorSourceDocument(content, encoding, fileName);
}
private static RazorSourceDocument ReadFromInternal(Stream stream, string fileName, Encoding encoding)
{
var streamLength = (int)stream.Length;
var content = string.Empty;
var contentEncoding = encoding ?? Encoding.UTF8;
if (streamLength > 0)
{
var bufferSize = Math.Min(streamLength, LargeObjectHeapLimitInChars);
var reader = new StreamReader(
stream,
contentEncoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: bufferSize,
leaveOpen: true);
using (reader)
{
reader.Peek(); // Just to populate the encoding
if (encoding == null)
{
contentEncoding = reader.CurrentEncoding;
}
else if (encoding != reader.CurrentEncoding)
{
throw new InvalidOperationException(
Resources.FormatMismatchedContentEncoding(
encoding.EncodingName,
reader.CurrentEncoding.EncodingName));
}
if (streamLength > LargeObjectHeapLimitInChars)
{
// If the resulting string would end up on the large object heap, then use LargeTextRazorSourceDocument.
return new LargeTextRazorSourceDocument(
reader,
LargeObjectHeapLimitInChars,
contentEncoding,
fileName);
}
content = reader.ReadToEnd();
}
}
return new DefaultRazorSourceDocument(content, contentEncoding, fileName);
return new StringSourceDocument(content, encoding, fileName);
}
}
}

View File

@ -0,0 +1,111 @@
// 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;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class StreamSourceDocument : RazorSourceDocument
{
// Internal for testing
internal readonly RazorSourceDocument _innerSourceDocument;
private readonly byte[] _checksum;
public StreamSourceDocument(Stream stream, Encoding encoding, string fileName)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
_checksum = ComputeChecksum(stream);
_innerSourceDocument = CreateInnerSourceDocument(stream, encoding, fileName);
}
public override char this[int position] => _innerSourceDocument[position];
public override Encoding Encoding => _innerSourceDocument.Encoding;
public override string FileName => _innerSourceDocument.FileName;
public override int Length => _innerSourceDocument.Length;
public override RazorSourceLineCollection Lines => _innerSourceDocument.Lines;
public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
=> _innerSourceDocument.CopyTo(sourceIndex, destination, destinationIndex, count);
public override byte[] GetChecksum()
{
var copiedChecksum = new byte[_checksum.Length];
_checksum.CopyTo(copiedChecksum, 0);
return copiedChecksum;
}
private static byte[] ComputeChecksum(Stream stream)
{
using (var hashAlgorithm = SHA1.Create())
{
var checksum = hashAlgorithm.ComputeHash(stream);
stream.Position = 0;
return checksum;
}
}
private static RazorSourceDocument CreateInnerSourceDocument(Stream stream, Encoding encoding, string fileName)
{
var streamLength = (int)stream.Length;
var content = string.Empty;
var contentEncoding = encoding ?? Encoding.UTF8;
if (streamLength > 0)
{
var bufferSize = Math.Min(streamLength, LargeObjectHeapLimitInChars);
var reader = new StreamReader(
stream,
contentEncoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: bufferSize,
leaveOpen: true);
using (reader)
{
reader.Peek(); // Just to populate the encoding
if (encoding == null)
{
contentEncoding = reader.CurrentEncoding;
}
else if (encoding != reader.CurrentEncoding)
{
throw new InvalidOperationException(
Resources.FormatMismatchedContentEncoding(
encoding.EncodingName,
reader.CurrentEncoding.EncodingName));
}
if (streamLength > LargeObjectHeapLimitInChars)
{
// If the resulting string would end up on the large object heap, then use LargeTextSourceDocument.
return new LargeTextSourceDocument(
reader,
LargeObjectHeapLimitInChars,
contentEncoding,
fileName);
}
content = reader.ReadToEnd();
}
}
return new StringSourceDocument(content, contentEncoding, fileName);
}
}
}

View File

@ -2,16 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorSourceDocument : RazorSourceDocument
internal class StringSourceDocument : RazorSourceDocument
{
private readonly string _content;
private readonly RazorSourceLineCollection _lines;
private byte[] _checksum;
public DefaultRazorSourceDocument(string content, Encoding encoding, string fileName)
public StringSourceDocument(string content, Encoding encoding, string fileName)
{
if (content == null)
{
@ -69,5 +71,27 @@ namespace Microsoft.AspNetCore.Razor.Language
_content.CopyTo(sourceIndex, destination, destinationIndex, count);
}
public override byte[] GetChecksum()
{
if (_checksum == null)
{
var charBuffer = _content.ToCharArray();
var encoder = Encoding.GetEncoder();
var byteCount = encoder.GetByteCount(charBuffer, 0, charBuffer.Length, flush: true);
var bytes = new byte[byteCount];
encoder.GetBytes(charBuffer, 0, charBuffer.Length, bytes, 0, flush: true);
using (var hashAlgorithm = SHA1.Create())
{
_checksum = hashAlgorithm.ComputeHash(bytes);
}
}
var copiedChecksum = new byte[_checksum.Length];
_checksum.CopyTo(copiedChecksum, 0);
return copiedChecksum;
}
}
}

View File

@ -838,7 +838,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var errorSink = new ErrorSink();
var phase = new DefaultRazorTagHelperBinderPhase();
var document = RazorCodeDocument.Create(new DefaultRazorSourceDocument("Test content", encoding: Encoding.UTF8, fileName: "TestFile"));
var document = RazorCodeDocument.Create(new StringSourceDocument("Test content", encoding: Encoding.UTF8, fileName: "TestFile"));
// Act
var prefix = phase.ProcessTagHelperPrefix(((IEnumerable<TagHelperDirectiveDescriptor>)directiveDescriptors).ToList(), document, errorSink);

View File

@ -1,15 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test
{
public class LargeTextRazorSourceDocumentTest
public class LargeTextSourceDocumentTest
{
private const int ChunkTestLength = 10;
[Fact]
public void GetChecksum_ReturnsCopiedChecksum()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF8, "file.cshtml");
// Act
var firstChecksum = document.GetChecksum();
var secondChecksum = document.GetChecksum();
// Assert
Assert.Equal(firstChecksum, secondChecksum);
Assert.NotSame(firstChecksum, secondChecksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF8()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF8, "file.cshtml");
var expectedChecksum = new byte[] { 10, 77, 85, 168, 215, 120, 229, 2, 47, 171, 112, 25, 119, 197, 216, 64, 187, 196, 134, 208 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF32()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString, Encoding.UTF32);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF32, "file.cshtml");
var expectedChecksum = new byte[] { 108, 172, 130, 171, 42, 19, 155, 176, 211, 80, 224, 121, 169, 133, 25, 134, 48, 228, 199, 141 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Theory]
[InlineData(ChunkTestLength - 1)]
[InlineData(ChunkTestLength)]
@ -31,7 +81,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var stream = TestRazorSourceDocument.CreateStreamContent(new string(content));
var reader = new StreamReader(stream, true);
var document = new LargeTextRazorSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
// Act
var output = new char[contentLength];
@ -56,7 +106,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var reader = new StreamReader(stream, true);
// Act
var document = new LargeTextRazorSourceDocument(reader, ChunkTestLength, Encoding.UTF8, fileName);
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, fileName);
// Assert
Assert.Equal(fileName, document.FileName);
@ -70,7 +120,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var reader = new StreamReader(stream, true);
// Act
var document = new LargeTextRazorSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
// Assert
Assert.Equal(3, document.Lines.Count);
@ -92,7 +142,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var stream = TestRazorSourceDocument.CreateStreamContent("abcdefghijklmnopqrstuvwxyz");
var reader = new StreamReader(stream, true);
var document = new LargeTextRazorSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, "file.cshtml");
// Act
var destination = new char[1000];

View File

@ -1,7 +1,6 @@
// 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.Text;
using Xunit;
@ -19,7 +18,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var document = RazorSourceDocument.ReadFrom(content, "file.cshtml");
// Assert
Assert.IsType<DefaultRazorSourceDocument>(document);
Assert.IsType<StreamSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Same(Encoding.UTF8, document.Encoding);
}
@ -35,7 +34,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert
Assert.Equal("file.cshtml", document.FileName);
Assert.Same(Encoding.UTF32, Assert.IsType<DefaultRazorSourceDocument>(document).Encoding);
Assert.Same(Encoding.UTF32, Assert.IsType<StreamSourceDocument>(document).Encoding);
}
[Fact]
@ -49,86 +48,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert
Assert.Equal("file.cshtml", document.FileName);
Assert.Same(Encoding.UTF32, Assert.IsType<DefaultRazorSourceDocument>(document).Encoding);
}
[Fact]
public void ReadFrom_DetectsEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(encoding: Encoding.UTF32);
// Act
var document = RazorSourceDocument.ReadFrom(content, "file.cshtml");
// Assert
Assert.IsType<DefaultRazorSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Equal(Encoding.UTF32, document.Encoding);
}
[Fact]
public void ReadFrom_EmptyStream_DetectsEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(content: string.Empty, encoding: Encoding.UTF32);
// Act
var document = RazorSourceDocument.ReadFrom(content, "file.cshtml");
// Assert
Assert.IsType<DefaultRazorSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Equal(Encoding.UTF32, document.Encoding);
}
[Fact]
public void ReadFrom_FailsOnMismatchedEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(encoding: Encoding.UTF32);
var expectedMessage = Resources.FormatMismatchedContentEncoding(Encoding.UTF8.EncodingName, Encoding.UTF32.EncodingName);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => RazorSourceDocument.ReadFrom(content, "file.cshtml", Encoding.UTF8));
Assert.Equal(expectedMessage, exception.Message);
}
[Theory]
[InlineData(100000)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars + 2)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars * 2 - 1)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars * 2)]
public void ReadFrom_LargeContent(int contentLength)
{
// Arrange
var content = new string('a', contentLength);
var stream = TestRazorSourceDocument.CreateStreamContent(content);
// Act
var document = RazorSourceDocument.ReadFrom(stream, "file.cshtml");
// Assert
Assert.IsType<LargeTextRazorSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Same(Encoding.UTF8, document.Encoding);
Assert.Equal(content, ReadContent(document));
}
[Fact]
public void ReadFrom_ProjectItem()
{
// Arrange
var projectItem = new TestRazorProjectItem("/test-path");
// Act
var document = RazorSourceDocument.ReadFrom(projectItem);
// Assert
Assert.Equal(projectItem.Path, document.FileName);
Assert.Equal(projectItem.Content, ReadContent(document));
Assert.Same(Encoding.UTF32, Assert.IsType<StreamSourceDocument>(document).Encoding);
}
[Fact]
@ -145,6 +65,20 @@ namespace Microsoft.AspNetCore.Razor.Language
Assert.Equal(projectItem.Content, ReadContent(document));
}
[Fact]
public void ReadFrom_ProjectItem()
{
// Arrange
var projectItem = new TestRazorProjectItem("/test-path");
// Act
var document = RazorSourceDocument.ReadFrom(projectItem);
// Assert
Assert.Equal(projectItem.Path, document.FileName);
Assert.Equal(projectItem.Content, ReadContent(document));
}
[Fact]
public void Create_WithoutEncoding()
{

View File

@ -0,0 +1,147 @@
// 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.Text;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class StreamSourceDocumentTest
{
[Fact]
public void GetChecksum_ReturnsCopiedChecksum()
{
// Arrange
var content = "Hello World!";
var stream = CreateBOMStream(content, Encoding.UTF8);
var document = new StreamSourceDocument(stream, Encoding.UTF8, "file.cshtml");
// Act
var firstChecksum = document.GetChecksum();
var secondChecksum = document.GetChecksum();
// Assert
Assert.Equal(firstChecksum, secondChecksum);
Assert.NotSame(firstChecksum, secondChecksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF8()
{
// Arrange
var content = "Hello World!";
var stream = CreateBOMStream(content, Encoding.UTF8);
var document = new StreamSourceDocument(stream, Encoding.UTF8, "file.cshtml");
var expectedChecksum = new byte[] { 70, 180, 84, 105, 70, 79, 152, 31, 71, 157, 46, 159, 50, 83, 1, 243, 222, 48, 90, 18 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF32AutoDetect()
{
// Arrange
var content = "Hello World!";
var stream = CreateBOMStream(content, Encoding.UTF32);
var document = new StreamSourceDocument(stream, encoding: null, fileName: "file.cshtml");
var expectedChecksum = new byte[] { 159, 154, 109, 89, 250, 163, 165, 108, 2, 112, 34, 4, 247, 161, 82, 168, 77, 213, 107, 71 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void ConstructedWithoutEncoding_DetectsEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(encoding: Encoding.UTF32);
// Act
var document = new StreamSourceDocument(content, encoding: null, fileName: "file.cshtml");
// Assert
Assert.IsType<StreamSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Equal(Encoding.UTF32, document.Encoding);
}
[Fact]
public void ConstructedWithoutEncoding_EmptyStream_DetectsEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(content: string.Empty, encoding: Encoding.UTF32);
// Act
var document = new StreamSourceDocument(content, encoding: null, fileName: "file.cshtml");
// Assert
Assert.IsType<StreamSourceDocument>(document);
Assert.Equal("file.cshtml", document.FileName);
Assert.Equal(Encoding.UTF32, document.Encoding);
}
[Fact]
public void FailsOnMismatchedEncoding()
{
// Arrange
var content = TestRazorSourceDocument.CreateStreamContent(encoding: Encoding.UTF32);
var expectedMessage = Resources.FormatMismatchedContentEncoding(Encoding.UTF8.EncodingName, Encoding.UTF32.EncodingName);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(
() => new StreamSourceDocument(content, Encoding.UTF8, "file.cshtml"));
Assert.Equal(expectedMessage, exception.Message);
}
[Theory]
[InlineData(100000)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars + 2)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars * 2 - 1)]
[InlineData(RazorSourceDocument.LargeObjectHeapLimitInChars * 2)]
public void DetectsSizeOfStreamForLargeContent(int contentLength)
{
// Arrange
var content = new string('a', contentLength);
var stream = TestRazorSourceDocument.CreateStreamContent(content);
// Act
var document = new StreamSourceDocument(stream, encoding: null, fileName: "file.cshtml");
// Assert
var streamDocument = Assert.IsType<StreamSourceDocument>(document);
Assert.IsType<LargeTextSourceDocument>(streamDocument._innerSourceDocument);
Assert.Equal("file.cshtml", document.FileName);
Assert.Same(Encoding.UTF8, document.Encoding);
Assert.Equal(content, ReadContent(document));
}
private static MemoryStream CreateBOMStream(string content, Encoding encoding)
{
var preamble = encoding.GetPreamble();
var contentBytes = encoding.GetBytes(content);
var buffer = new byte[preamble.Length + contentBytes.Length];
preamble.CopyTo(buffer, 0);
contentBytes.CopyTo(buffer, preamble.Length);
var stream = new MemoryStream(buffer);
return stream;
}
private static string ReadContent(RazorSourceDocument razorSourceDocument)
{
var buffer = new char[razorSourceDocument.Length];
razorSourceDocument.CopyTo(0, buffer, 0, buffer.Length);
return new string(buffer);
}
}
}

View File

@ -1,21 +1,66 @@
// 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.IO;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorSourceDocumentTest
public class StringSourceDocumentTest
{
[Fact]
public void GetChecksum_ReturnsCopiedChecksum()
{
// Arrange
var content = "Hello World!";
var document = new StringSourceDocument(content, Encoding.UTF8, "file.cshtml");
// Act
var firstChecksum = document.GetChecksum();
var secondChecksum = document.GetChecksum();
// Assert
Assert.Equal(firstChecksum, secondChecksum);
Assert.NotSame(firstChecksum, secondChecksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF8()
{
// Arrange
var content = "Hello World!";
var document = new StringSourceDocument(content, Encoding.UTF8, "file.cshtml");
var expectedChecksum = new byte[] { 46, 247, 189, 230, 8, 206, 84, 4, 233, 125, 95, 4, 47, 149, 248, 159, 28, 35, 40, 113 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF32()
{
// Arrange
var content = "Hello World!";
var document = new StringSourceDocument(content, Encoding.UTF32, "file.cshtml");
var expectedChecksum = new byte[] { 8, 149, 159, 15, 242, 255, 115, 227, 219, 78, 61, 53, 127, 239, 77, 239, 215, 140, 248, 44 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void Indexer_ProvidesCharacterAccessToContent()
{
// Arrange
var expectedContent = "Hello, World!";
var indexerBuffer = new char[expectedContent.Length];
var document = new DefaultRazorSourceDocument(expectedContent, Encoding.UTF8, fileName: "file.cshtml");
var document = new StringSourceDocument(expectedContent, Encoding.UTF8, fileName: "file.cshtml");
// Act
for (var i = 0; i < document.Length; i++)
@ -33,7 +78,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var expectedContent = "Hello, World!";
var document = new DefaultRazorSourceDocument(expectedContent, Encoding.UTF8, fileName: "file.cshtml");
var document = new StringSourceDocument(expectedContent, Encoding.UTF8, fileName: "file.cshtml");
// Act & Assert
Assert.Equal(expectedContent.Length, document.Length);
@ -46,7 +91,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var content = "Hello, World!";
// Act
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: "file.cshtml");
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: "file.cshtml");
// Assert
Assert.Equal("file.cshtml", document.FileName);
@ -59,7 +104,7 @@ namespace Microsoft.AspNetCore.Razor.Language
var content = "Hello, World!";
// Act
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Assert
Assert.Null(document.FileName);
@ -70,7 +115,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "Hello, World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
var expectedContent = "Hello";
var charBuffer = new char[expectedContent.Length];
@ -87,7 +132,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "Hello, World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
var expectedContent = "$Hello";
var charBuffer = new char[expectedContent.Length];
charBuffer[0] = '$';
@ -105,7 +150,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "Hello, World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
var expectedContent = "World";
var charBuffer = new char[expectedContent.Length];
@ -122,7 +167,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "Hi";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
var charBuffer = new char[2];
// Act
@ -138,7 +183,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "Hi";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act & Assert
//
@ -157,7 +202,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = string.Empty;
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.Count;
@ -171,7 +216,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = string.Empty;
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLineLength(0);
@ -185,7 +230,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "hello\n";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLineLength(0);
@ -199,7 +244,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
// Arrange
var content = "hello\r\n";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLineLength(0);
@ -218,13 +263,13 @@ namespace Microsoft.AspNetCore.Razor.Language
.Append("jumps over the lazy dog.")
.ToString();
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
// Assert
Assert.Equal(new int[]{ 16, 5, 24 }, actual);
Assert.Equal(new int[] { 16, 5, 24 }, actual);
}
[Fact]
@ -233,7 +278,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -248,7 +293,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\rWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -264,7 +309,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\rBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -279,7 +324,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -294,7 +339,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\u0085World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -309,7 +354,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\u2028World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -324,7 +369,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\u2029World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = GetAllLineMappings(document);
@ -339,7 +384,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello, World!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: "Hi.cshtml");
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: "Hi.cshtml");
// Act
var actual = document.Lines.GetLocation(1);
@ -357,7 +402,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLocation(0);
@ -373,7 +418,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLocation(5);
@ -389,7 +434,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLocation(7);
@ -405,7 +450,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLocation(11);
@ -421,7 +466,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Arrange
var content = "Hello\nBig\r\nWorld!";
var document = new DefaultRazorSourceDocument(content, Encoding.UTF8, fileName: null);
var document = new StringSourceDocument(content, Encoding.UTF8, fileName: null);
// Act
var actual = document.Lines.GetLocation(16);

View File

@ -1,4 +1,4 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithBaseType.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithBaseType.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "38aa8e26c5d2a85c61d8e93fe69dd326fe82671b"
namespace Razor
{
#line hidden

View File

@ -1,4 +1,4 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "38aa8e26c5d2a85c61d8e93fe69dd326fe82671b"
namespace Razor
{
#line hidden

View File

@ -1,4 +1,4 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithDefaults.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithDefaults.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "38aa8e26c5d2a85c61d8e93fe69dd326fe82671b"
namespace Razor
{
#line hidden

View File

@ -1,4 +1,4 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithSetNamespace.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithSetNamespace.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "38aa8e26c5d2a85c61d8e93fe69dd326fe82671b"
namespace MyApp.Razor.Views
{
#line hidden

View File

@ -28,6 +28,18 @@ namespace Microsoft.AspNetCore.Razor.Language
public string Content { get; set; } = "Default content";
public override Stream Read() => new MemoryStream(Encoding.UTF8.GetBytes(Content));
public override Stream Read()
{
// Act like a file and have a UTF8 BOM.
var preamble = Encoding.UTF8.GetPreamble();
var contentBytes = Encoding.UTF8.GetBytes(Content);
var buffer = new byte[preamble.Length + contentBytes.Length];
preamble.CopyTo(buffer, 0);
contentBytes.CopyTo(buffer, preamble.Length);
var stream = new MemoryStream(buffer);
return stream;
}
}
}

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Language
content = NormalizeNewLines(content);
}
return new DefaultRazorSourceDocument(content, encoding ?? Encoding.UTF8, path);
return new StringSourceDocument(content, encoding ?? Encoding.UTF8, path);
}
}
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Language
content = NormalizeNewLines(content);
}
return new DefaultRazorSourceDocument(content, encoding ?? Encoding.UTF8, fileName);
return new StringSourceDocument(content, encoding ?? Encoding.UTF8, fileName);
}
private static string NormalizeNewLines(string content)