Add tests for WritableBuffer extensions and rename WriteAscii => WriteAsciiNoValidation

This commit is contained in:
Nate McMaster 2017-03-29 16:10:31 -07:00
parent 7f785588ef
commit 686829d226
No known key found for this signature in database
GPG Key ID: BD729980AA6A21BD
9 changed files with 250 additions and 40 deletions

View File

@ -7772,7 +7772,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 17, 14);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7798,7 +7798,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 31, 8);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7819,7 +7819,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 133, 16);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7845,7 +7845,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 350, 10);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7877,7 +7877,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 0, 17);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7898,7 +7898,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 39, 14);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7919,7 +7919,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 53, 10);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7940,7 +7940,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 63, 11);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7966,7 +7966,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 74, 21);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -7987,7 +7987,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 95, 11);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8008,7 +8008,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 106, 7);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8029,7 +8029,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 113, 11);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8050,7 +8050,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 124, 9);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8071,7 +8071,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 149, 20);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8092,7 +8092,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 169, 20);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8113,7 +8113,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 189, 20);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8134,7 +8134,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 209, 15);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8155,7 +8155,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 224, 17);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8176,7 +8176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 241, 11);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8197,7 +8197,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 252, 17);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8218,7 +8218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 269, 17);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8239,7 +8239,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 286, 7);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8260,7 +8260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 293, 8);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8281,7 +8281,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 301, 12);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8302,7 +8302,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 313, 22);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8323,7 +8323,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 335, 15);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8344,7 +8344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 360, 14);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8365,7 +8365,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 374, 8);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8386,7 +8386,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 382, 20);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8407,7 +8407,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 402, 36);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8428,7 +8428,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 438, 32);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8449,7 +8449,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 470, 32);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8470,7 +8470,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 502, 31);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8491,7 +8491,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 533, 33);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}
@ -8512,7 +8512,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_headerBytes, 566, 26);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}

View File

@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{
output.WriteFast(_CrLf);
output.WriteAscii(kv.Key);
output.WriteAsciiNoValidation(kv.Key);
output.WriteFast(_colonSpace);
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}
}
}

View File

@ -158,7 +158,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
}
}
public unsafe static void WriteAscii(this WritableBuffer buffer, string data)
/// <summary>
/// Write string characters as ASCII without validating that characters fall in the ASCII range
/// </summary>
/// <remarks>
/// ASCII character validation is done by <see cref="FrameHeaders.ValidateHeaderCharacters(string)"/>
/// </remarks>
/// <param name="buffer">the buffer</param>
/// <param name="data">The string to write</param>
public unsafe static void WriteAsciiNoValidation(this WritableBuffer buffer, string data)
{
if (string.IsNullOrEmpty(data))
{

View File

@ -13,6 +13,7 @@
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Content Include="..\shared\TestResources\testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,5 @@
{
"$schema": "http://json.schemastore.org/xunit.runner.schema",
"methodDisplay": "method",
"longRunningTestSeconds": 60
}

View File

@ -0,0 +1,167 @@
// 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.Pipelines;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests
{
public class PipelineExtensionTests : IDisposable
{
// ulong.MaxValue.ToString().Length
private const int _ulongMaxValueLength = 20;
private readonly IPipe _pipe;
private readonly PipeFactory _pipeFactory = new PipeFactory();
public PipelineExtensionTests()
{
_pipe = _pipeFactory.Create();
}
public void Dispose()
{
_pipeFactory.Dispose();
}
[Theory]
[InlineData(ulong.MinValue)]
[InlineData(ulong.MaxValue)]
[InlineData(4_8_15_16_23_42)]
public async Task WritesNumericToAscii(ulong number)
{
var writer = _pipe.Writer.Alloc();
writer.WriteNumeric(number);
await writer.FlushAsync();
var reader = await _pipe.Reader.ReadAsync();
var numAsStr = number.ToString();
var expected = Encoding.ASCII.GetBytes(numAsStr);
AssertExtensions.Equal(expected, reader.Buffer.Slice(0, numAsStr.Length).ToArray());
}
[Theory]
[InlineData(1)]
[InlineData(_ulongMaxValueLength / 2)]
[InlineData(_ulongMaxValueLength - 1)]
public void WritesNumericAcrossSpanBoundaries(int gapSize)
{
var writer = _pipe.Writer.Alloc(100);
// almost fill up the first block
var spacer = new Span<byte>(new byte[writer.Buffer.Length - gapSize]);
writer.Write(spacer);
var bufferLength = writer.Buffer.Length;
writer.WriteNumeric(ulong.MaxValue);
Assert.NotEqual(bufferLength, writer.Buffer.Length);
writer.FlushAsync().GetAwaiter().GetResult();
var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
var numAsString = ulong.MaxValue.ToString();
var written = reader.Buffer.Slice(spacer.Length, numAsString.Length);
Assert.False(written.IsSingleSpan, "The buffer should cross spans");
AssertExtensions.Equal(Encoding.ASCII.GetBytes(numAsString), written.ToArray());
}
[Theory]
[InlineData("\0abcxyz", new byte[] { 0, 97, 98, 99, 120, 121, 122 })]
[InlineData("!#$%i", new byte[] { 33, 35, 36, 37, 105 })]
[InlineData("!#$%", new byte[] { 33, 35, 36, 37 })]
[InlineData("!#$", new byte[] { 33, 35, 36 })]
[InlineData("!#", new byte[] { 33, 35 })]
[InlineData("!", new byte[] { 33 })]
// null or empty
[InlineData("", new byte[0])]
[InlineData(null, new byte[0])]
public async Task EncodesAsAscii(string input, byte[] expected)
{
var writer = _pipe.Writer.Alloc();
writer.WriteAsciiNoValidation(input);
await writer.FlushAsync();
var reader = await _pipe.Reader.ReadAsync();
if (expected.Length > 0)
{
AssertExtensions.Equal(
expected,
reader.Buffer.ToArray());
}
else
{
Assert.Equal(0, reader.Buffer.Length);
}
}
[Theory]
// non-ascii characters stored in 32 bits
[InlineData("𤭢𐐝")]
// non-ascii characters stored in 16 bits
[InlineData("ñ٢⛄⛵")]
public async Task WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input)
{
// WriteAscii doesn't validate if characters are in the ASCII range
// but it shouldn't produce more than one byte per character
var writer = _pipe.Writer.Alloc();
writer.WriteAsciiNoValidation(input);
await writer.FlushAsync();
var reader = await _pipe.Reader.ReadAsync();
Assert.Equal(input.Length, reader.Buffer.Length);
}
[Fact]
public async Task WriteAsciiNoValidation()
{
const byte maxAscii = 0x7f;
var writer = _pipe.Writer.Alloc();
for (var i = 0; i < maxAscii; i++)
{
writer.WriteAsciiNoValidation(new string((char)i, 1));
}
await writer.FlushAsync();
var reader = await _pipe.Reader.ReadAsync();
var data = reader.Buffer.Slice(0, maxAscii).ToArray();
for (var i = 0; i < maxAscii; i++)
{
Assert.Equal(i, data[i]);
}
}
[Theory]
[InlineData(2, 1)]
[InlineData(3, 1)]
[InlineData(4, 2)]
[InlineData(5, 3)]
[InlineData(7, 4)]
[InlineData(8, 3)]
[InlineData(8, 4)]
[InlineData(8, 5)]
[InlineData(100, 48)]
public void WritesAsciiAcrossBlockBoundaries(int stringLength, int gapSize)
{
var testString = new string(' ', stringLength);
var writer = _pipe.Writer.Alloc(100);
// almost fill up the first block
var spacer = new Span<byte>(new byte[writer.Buffer.Length - gapSize]);
writer.Write(spacer);
Assert.Equal(gapSize, writer.Buffer.Span.Length);
var bufferLength = writer.Buffer.Length;
writer.WriteAsciiNoValidation(testString);
Assert.NotEqual(bufferLength, writer.Buffer.Length);
writer.FlushAsync().GetAwaiter().GetResult();
var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
var written = reader.Buffer.Slice(spacer.Length, stringLength);
Assert.False(written.IsSingleSpan, "The buffer should cross spans");
AssertExtensions.Equal(Encoding.ASCII.GetBytes(testString), written.ToArray());
}
}
}

View File

@ -0,0 +1,27 @@
// 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 Xunit.Sdk;
namespace Xunit
{
public static class AssertExtensions
{
public static void Equal(byte[] expected, Span<byte> actual)
{
if (expected.Length != actual.Length)
{
throw new XunitException($"Expected length to be {expected.Length} but was {actual.Length}");
}
for (var i = 0; i < expected.Length; i++)
{
if (expected[i] != actual[i])
{
throw new XunitException($@"Expected byte at index {i} to be '{expected[i]}' but was '{actual[i]}'");
}
}
}
}
}

View File

@ -1,4 +1,6 @@
{
"$schema": "http://json.schemastore.org/xunit.runner.schema",
"appDomain": "denied"
"appDomain": "denied",
"methodDisplay": "method",
"longRunningTestSeconds": 60
}

View File

@ -541,7 +541,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
if (value != null)
{{
output.WriteFast(_headerBytes, {header.BytesOffset}, {header.BytesCount});
output.WriteAscii(value);
output.WriteAsciiNoValidation(value);
}}
}}
}}