Skip to content

Commit

Permalink
Add MemoryMappedFile tests involving AcquirePointer (dotnet#42800)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Oct 6, 2020
1 parent bb3cf17 commit 1be8907
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Win32.SafeHandles;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Xunit;
Expand Down Expand Up @@ -69,6 +70,19 @@ protected IEnumerable<MemoryMappedFile> CreateSampleMaps(
}
}

protected static void PopulateWithRandomData(MemoryMappedFile mmf)
{
var rand = new Random(42);
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor())
{
int length = (int)acc.SafeMemoryMappedViewHandle.ByteLength;
for (int i = 0; i < length; i++)
{
acc.Write(i, (byte)rand.Next());
}
}
}

/// <summary>Performs basic verification on a map.</summary>
/// <param name="mmf">The map.</param>
/// <param name="expectedCapacity">The capacity that was specified to create the map.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,18 @@ public void InvalidAccessLevels_ReadWrite_NonUwp(MemoryMappedFileAccess mapAcces
}

/// <summary>
/// Test to verify the accessor's PointerOffset.
/// Test to verify the accessor's PointerOffset at the beginning of a view.
/// </summary>
[Fact]
public void PointerOffsetMatchesViewStart()
[Theory]
[InlineData(1)] // smallest possible non-zero size
[InlineData(4095)] // just under typical page size
[InlineData(4096)] // typical page size
[InlineData(4099)] // just larger than typical page size
[InlineData(12290)] // just larger than a few typical page sizes
[InlineData(70_000)] // larger than typical Windows allocation granularity (64K)
public void PointerOffsetMatchesViewStart(int mapLength)
{
const int MapLength = 4096;
foreach (MemoryMappedFile mmf in CreateSampleMaps(MapLength))
foreach (MemoryMappedFile mmf in CreateSampleMaps(mapLength))
{
using (mmf)
{
Expand All @@ -150,28 +155,10 @@ public void PointerOffsetMatchesViewStart()
Assert.Equal(0, acc.PointerOffset);
}

using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(0, MapLength))
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(0, mapLength))
{
Assert.Equal(0, acc.PointerOffset);
}
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(1, MapLength - 1))
{
Assert.Equal(1, acc.PointerOffset);
}
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(MapLength - 1, 1))
{
Assert.Equal(MapLength - 1, acc.PointerOffset);
}

// On Unix creating a view of size zero will result in an offset and capacity
// of 0 due to mmap behavior, whereas on Windows it's possible to create a
// zero-size view anywhere in the created file mapping.
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(MapLength, 0))
{
Assert.Equal(
OperatingSystem.IsWindows() ? MapLength : 0,
acc.PointerOffset);
}
}
}
}
Expand All @@ -180,26 +167,52 @@ public void PointerOffsetMatchesViewStart()
/// Test all of the Read/Write accessor methods against a variety of maps and accessors.
/// </summary>
[Theory]
[InlineData(0, 8192)]
[InlineData(8100, 92)]
[InlineData(0, 20)]
[InlineData(1, 8191)]
[InlineData(17, 8175)]
[InlineData(17, 20)]
public void AllReadWriteMethods(long offset, long size)
[InlineData(8192, 0, 8192)]
[InlineData(8192, 8100, 92)]
[InlineData(8192, 0, 20)]
[InlineData(8192, 1, 8191)]
[InlineData(8192, 17, 8175)]
[InlineData(8192, 17, 20)]
[InlineData(70_000, 69_900, 16)]
public void AllReadWriteMethods(int mapSize, int offset, int size)
{
foreach (MemoryMappedFile mmf in CreateSampleMaps(8192))
foreach (MemoryMappedFile mmf in CreateSampleMaps(mapSize))
{
using (mmf)
using (MemoryMappedViewAccessor acc = mmf.CreateViewAccessor(offset, size))
{
// Validate all of the read/write methods
AssertWritesReads(acc);

// Make sure that the internal offsets used for reading/writing are correct.
// This is the only meaningful way to validate PointerOffset, as its value's
// correctness is based entirely on the address selected by the OS to start
// the view and the distance from there to the user-supplied offset.
PopulateWithRandomData(mmf);
unsafe
{
byte* ptr = null;
try
{
acc.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
for (int i = 0; i < size; i++)
{
Assert.Equal(acc.ReadByte(i), *(ptr + acc.PointerOffset + i));
acc.Write(i, (byte)i);
Assert.Equal((byte)i, acc.ReadByte(i));
}
}
finally
{
acc.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
}
}

/// <summary>Performs many reads and writes of various data types against the accessor.</summary>
private static unsafe void AssertWritesReads(MemoryMappedViewAccessor acc) // TODO: unsafe can be removed once using C# 6 compiler
private static void AssertWritesReads(MemoryMappedViewAccessor acc)
{
// Successful reads and writes at the beginning for each data type
AssertWriteRead<bool>(false, 0, (pos, value) => acc.Write(pos, value), pos => acc.ReadBoolean(pos));
Expand Down

0 comments on commit 1be8907

Please sign in to comment.