|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Xunit;
using System.Text;
using System.IO.Compression;
using Roslyn.Test.Utilities;
using System.Linq;
namespace Microsoft.CodeAnalysis.UnitTests
{
public class EmbeddedTextTests
{
[Fact]
public void FromBytes_ArgumentErrors()
{
Assert.Throws<ArgumentNullException>("filePath", () => EmbeddedText.FromBytes(null, default(ArraySegment<byte>)));
Assert.Throws<ArgumentException>("filePath", () => EmbeddedText.FromBytes("", default(ArraySegment<byte>)));
Assert.Throws<ArgumentNullException>("bytes", () => EmbeddedText.FromBytes("path", default(ArraySegment<byte>)));
Assert.Throws<ArgumentException>("checksumAlgorithm", () => EmbeddedText.FromBytes("path", new ArraySegment<byte>(new byte[0], 0, 0), SourceHashAlgorithm.None));
}
[Fact]
public void FromSource_ArgumentErrors()
{
Assert.Throws<ArgumentNullException>("filePath", () => EmbeddedText.FromSource(null, null));
Assert.Throws<ArgumentException>("filePath", () => EmbeddedText.FromSource("", null));
Assert.Throws<ArgumentNullException>("text", () => EmbeddedText.FromSource("path", null));
// no encoding
Assert.Throws<ArgumentException>("text", () => EmbeddedText.FromSource("path", SourceText.From("source")));
// embedding not allowed
Assert.Throws<ArgumentException>("text", () => EmbeddedText.FromSource("path", SourceText.From(new byte[0], 0, Encoding.UTF8, canBeEmbedded: false)));
Assert.Throws<ArgumentException>("text", () => EmbeddedText.FromSource("path", SourceText.From(new MemoryStream(new byte[0]), Encoding.UTF8, canBeEmbedded: false)));
}
[Fact]
public void FromStream_ArgumentErrors()
{
Assert.Throws<ArgumentNullException>("filePath", () => EmbeddedText.FromStream(null, null));
Assert.Throws<ArgumentException>("filePath", () => EmbeddedText.FromStream("", null));
Assert.Throws<ArgumentNullException>("stream", () => EmbeddedText.FromStream("path", null));
Assert.Throws<ArgumentException>("stream", () => EmbeddedText.FromStream("path", new CannotReadStream()));
Assert.Throws<ArgumentException>("stream", () => EmbeddedText.FromStream("path", new CannotSeekStream()));
Assert.Throws<ArgumentException>("checksumAlgorithm", () => EmbeddedText.FromStream("path", new MemoryStream(), SourceHashAlgorithm.None));
}
[Fact]
public void FromStream_IOErrors()
{
Assert.Throws<IOException>(() => EmbeddedText.FromStream("path", new HugeStream()));
Assert.Throws<EndOfStreamException>(() => EmbeddedText.FromStream("path", new TruncatingStream(10)));
Assert.Throws<EndOfStreamException>(() => EmbeddedText.FromStream("path", new TruncatingStream(1000)));
Assert.Throws<IOException>(() => EmbeddedText.FromStream("path", new ReadFailsStream()));
}
private const string SmallSource = @"class P {}";
private const string LargeSource = @"
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
class Program
{
static void Main() {}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
";
[Fact]
public void FromBytes_Empty()
{
var text = EmbeddedText.FromBytes("pathToEmpty", new ArraySegment<byte>(new byte[0], 0, 0), SourceHashAlgorithm.Sha1);
Assert.Equal("pathToEmpty", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(SourceText.CalculateChecksum(new byte[0], 0, 0, SourceHashAlgorithm.Sha1), text.Checksum);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob);
}
[Fact]
public void FromStream_Empty()
{
var text = EmbeddedText.FromStream("pathToEmpty", new MemoryStream(new byte[0]), SourceHashAlgorithm.Sha1);
var checksum = SourceText.CalculateChecksum(new byte[0], 0, 0, SourceHashAlgorithm.Sha1);
Assert.Equal("pathToEmpty", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(checksum, text.Checksum);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob);
}
[Fact]
public void FromSource_Empty()
{
var source = SourceText.From("", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), SourceHashAlgorithm.Sha1);
var text = EmbeddedText.FromSource("pathToEmpty", source);
var checksum = SourceText.CalculateChecksum(new byte[0], 0, 0, SourceHashAlgorithm.Sha1);
Assert.Equal("pathToEmpty", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(checksum, text.Checksum);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob);
}
[Fact]
public void FromBytes_Small()
{
var bytes = Encoding.UTF8.GetBytes(SmallSource);
var checksum = SourceText.CalculateChecksum(bytes, 0, bytes.Length, SourceHashAlgorithm.Sha1);
var text = EmbeddedText.FromBytes("pathToSmall", new ArraySegment<byte>(bytes, 0, bytes.Length));
Assert.Equal("pathToSmall", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(checksum, text.Checksum);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob.Take(4));
AssertEx.Equal(bytes, text.Blob.Skip(4));
}
[Fact]
public void FromBytes_SmallSpan()
{
var bytes = Encoding.UTF8.GetBytes(SmallSource);
var paddedBytes = new byte[] { 0 }.Concat(bytes).Concat(new byte[] { 0 }).ToArray();
var checksum = SourceText.CalculateChecksum(bytes, 0, bytes.Length, SourceHashAlgorithm.Sha1);
var text = EmbeddedText.FromBytes("pathToSmall", new ArraySegment<byte>(paddedBytes, 1, bytes.Length));
Assert.Equal("pathToSmall", text.FilePath);
AssertEx.Equal(checksum, text.Checksum);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob.Take(4));
AssertEx.Equal(bytes, text.Blob.Skip(4));
}
[Fact]
public void FromSource_Small()
{
var source = SourceText.From(SmallSource, Encoding.UTF8, SourceHashAlgorithm.Sha1);
var text = EmbeddedText.FromSource("pathToSmall", source);
Assert.Equal("pathToSmall", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(source.GetChecksum(), text.Checksum);
AssertEx.Equal(new byte[] { 0, 0, 0, 0 }, text.Blob.Take(4));
AssertEx.Equal(Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(SmallSource)), text.Blob.Skip(4));
}
[Fact]
public void FromBytes_Large()
{
var bytes = Encoding.Unicode.GetBytes(LargeSource);
var checksum = SourceText.CalculateChecksum(bytes, 0, bytes.Length, SourceHashAlgorithms.Default);
var text = EmbeddedText.FromBytes("pathToLarge", new ArraySegment<byte>(bytes, 0, bytes.Length), SourceHashAlgorithms.Default);
Assert.Equal("pathToLarge", text.FilePath);
Assert.Equal(SourceHashAlgorithms.Default, text.ChecksumAlgorithm);
AssertEx.Equal(checksum, text.Checksum);
AssertEx.Equal(BitConverter.GetBytes(bytes.Length), text.Blob.Take(4));
AssertEx.Equal(bytes, Decompress(text.Blob.Skip(4)));
}
[Fact]
public void FromBytes_LargeSpan()
{
var bytes = Encoding.Unicode.GetBytes(LargeSource);
var paddedBytes = new byte[] { 0 }.Concat(bytes).Concat(new byte[] { 0 }).ToArray();
var checksum = SourceText.CalculateChecksum(bytes, 0, bytes.Length, SourceHashAlgorithms.Default);
var text = EmbeddedText.FromBytes("pathToLarge", new ArraySegment<byte>(paddedBytes, 1, bytes.Length), SourceHashAlgorithms.Default);
Assert.Equal("pathToLarge", text.FilePath);
AssertEx.Equal(checksum, text.Checksum);
Assert.Equal(SourceHashAlgorithms.Default, text.ChecksumAlgorithm);
AssertEx.Equal(BitConverter.GetBytes(bytes.Length), text.Blob.Take(4));
AssertEx.Equal(bytes, Decompress(text.Blob.Skip(4)));
}
[Fact]
public void FromSource_Large()
{
var source = SourceText.From(LargeSource, Encoding.Unicode, SourceHashAlgorithms.Default);
var text = EmbeddedText.FromSource("pathToLarge", source);
Assert.Equal("pathToLarge", text.FilePath);
Assert.Equal(SourceHashAlgorithms.Default, text.ChecksumAlgorithm);
AssertEx.Equal(source.GetChecksum(), text.Checksum);
AssertEx.Equal(BitConverter.GetBytes(Encoding.Unicode.GetPreamble().Length + LargeSource.Length * sizeof(char)), text.Blob.Take(4));
AssertEx.Equal(Encoding.Unicode.GetPreamble().Concat(Encoding.Unicode.GetBytes(LargeSource)), Decompress(text.Blob.Skip(4)));
}
[Fact]
public void FromTextReader_Small()
{
var expected = SourceText.From(SmallSource, Encoding.UTF8, SourceHashAlgorithm.Sha1);
var expectedEmbedded = EmbeddedText.FromSource("pathToSmall", expected);
var actual = SourceText.From(new StringReader(SmallSource), SmallSource.Length, Encoding.UTF8, SourceHashAlgorithm.Sha1);
var actualEmbedded = EmbeddedText.FromSource(expectedEmbedded.FilePath, actual);
Assert.Equal(expectedEmbedded.FilePath, actualEmbedded.FilePath);
Assert.Equal(expectedEmbedded.ChecksumAlgorithm, actualEmbedded.ChecksumAlgorithm);
AssertEx.Equal(expectedEmbedded.Checksum, actualEmbedded.Checksum);
AssertEx.Equal(expectedEmbedded.Blob, actualEmbedded.Blob);
}
[Fact]
public void FromTextReader_Large()
{
var expected = SourceText.From(LargeSource, Encoding.UTF8, SourceHashAlgorithm.Sha1);
var expectedEmbedded = EmbeddedText.FromSource("pathToSmall", expected);
var actual = SourceText.From(new StringReader(LargeSource), LargeSource.Length, Encoding.UTF8, SourceHashAlgorithm.Sha1);
var actualEmbedded = EmbeddedText.FromSource(expectedEmbedded.FilePath, actual);
Assert.Equal(expectedEmbedded.FilePath, actualEmbedded.FilePath);
Assert.Equal(expectedEmbedded.ChecksumAlgorithm, actualEmbedded.ChecksumAlgorithm);
AssertEx.Equal(expectedEmbedded.Checksum, actualEmbedded.Checksum);
AssertEx.Equal(expectedEmbedded.Blob, actualEmbedded.Blob);
}
[Fact]
public void FromSource_Precomputed()
{
byte[] bytes = Encoding.ASCII.GetBytes(LargeSource);
bytes[0] = 0xFF; // invalid ASCII, should be reflected in checksum, blob.
foreach (bool useStream in new[] { true, false })
{
var source = useStream ?
SourceText.From(new MemoryStream(bytes), Encoding.ASCII, SourceHashAlgorithm.Sha1, canBeEmbedded: true) :
SourceText.From(bytes, bytes.Length, Encoding.ASCII, SourceHashAlgorithm.Sha1, canBeEmbedded: true);
var text = EmbeddedText.FromSource("pathToPrecomputed", source);
Assert.Equal("pathToPrecomputed", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(SourceText.CalculateChecksum(bytes, 0, bytes.Length, SourceHashAlgorithm.Sha1), source.GetChecksum());
AssertEx.Equal(source.GetChecksum(), text.Checksum);
AssertEx.Equal(BitConverter.GetBytes(bytes.Length), text.Blob.Take(4));
AssertEx.Equal(bytes, Decompress(text.Blob.Skip(4)));
}
}
[ConditionalFact(typeof(ClrOnly), Reason = "https://github.com/mono/mono/issues/12603")]
public void FromBytes_EncodingFallbackCase()
{
var source = EncodedStringText.Create(new MemoryStream(new byte[] { 0xA9, 0x0D, 0x0A }), canBeEmbedded: true);
var text = EmbeddedText.FromSource("pathToLarge", source);
Assert.Equal("pathToLarge", text.FilePath);
Assert.Equal(SourceHashAlgorithm.Sha1, text.ChecksumAlgorithm);
AssertEx.Equal(source.GetChecksum(), text.Checksum);
}
private byte[] Decompress(IEnumerable<byte> bytes)
{
var destination = new MemoryStream();
using (var source = new DeflateStream(new MemoryStream(bytes.ToArray()), CompressionMode.Decompress))
{
source.CopyTo(destination);
}
return destination.ToArray();
}
private sealed class CannotReadStream : MemoryStream
{
public override bool CanRead => false;
}
private sealed class CannotSeekStream : MemoryStream
{
public override bool CanSeek => false;
}
private sealed class HugeStream : MemoryStream
{
public override long Length => (long)int.MaxValue + 1;
}
private sealed class TruncatingStream : MemoryStream
{
public TruncatingStream(long length)
{
Length = length;
}
public override long Length { get; }
public override int Read(byte[] buffer, int offset, int count) => 0;
}
private sealed class ReadFailsStream : MemoryStream
{
public override int Read(byte[] buffer, int offset, int count)
{
throw new IOException();
}
}
}
}
|