Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Testably.Abstractions.Testing.Helpers;
using Testably.Abstractions.Testing.Storage;

Expand All @@ -26,6 +27,12 @@ internal sealed class MockSafeFileHandleRegistry
/// </summary>
private const long FirstHandleValue = 0x4000_0000L;

/// <summary>
/// Shared by all <see cref="MockFileSystem" />s, so that a handle passed to another instance is foreign there
/// instead of resolving to an unrelated file that happens to have the same value.
/// </summary>
private static long _lastHandleValue = FirstHandleValue - 1;

private const FileOptions ValidFileOptions = FileOptions.WriteThrough |
FileOptions.Asynchronous |
FileOptions.RandomAccess |
Expand All @@ -47,7 +54,6 @@ internal sealed class MockSafeFileHandleRegistry
private readonly List<Entry> _pendingDeletes = [];

private volatile bool _hasWork;
private long _nextHandleValue = FirstHandleValue;
private bool _sweeping;

internal MockSafeFileHandleRegistry(MockFileSystem fileSystem)
Expand Down Expand Up @@ -79,7 +85,7 @@ internal SafeFileHandle Open(string path,

lock (_lock)
{
IntPtr value = new(_nextHandleValue++);
IntPtr value = new(Interlocked.Increment(ref _lastHandleValue));
SafeFileHandle handle = new(value, ownsHandle: false);
_entries[value] = new Entry(
new WeakReference<SafeFileHandle>(handle),
Expand Down Expand Up @@ -250,14 +256,6 @@ private SafeFileHandleMock MapForeign(SafeFileHandle handle)
return entry;
}

// Handle values are issued sequentially and never reused, so a value within the issued range that is no
// longer registered belonged to a handle this registry created and the caller has since closed.
long candidate = value.ToInt64();
if (candidate >= FirstHandleValue && candidate < _nextHandleValue)
{
throw ExceptionFactory.HandleIsClosed();
}

return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,46 @@ await That(Act).DoesNotThrow()
.Because("a handle that is no longer referenced is closed, as a real one would be when finalized");
}

[Test]
public async Task RandomAccess_WithHandleFromAnotherFileSystem_ShouldNotBeReportedAsClosed()
{
MockFileSystem fileSystemA = new();
MockFileSystem fileSystemB = new();
fileSystemA.File.WriteAllText("a.txt", "a");
fileSystemB.File.WriteAllText("b.txt", "b");
fileSystemB.File.OpenHandle("b.txt").Dispose();
fileSystemB.File.WriteAllText("c.txt", "cc");
fileSystemB.WithSafeFileHandleStrategy(
new DefaultSafeFileHandleStrategy(_ => new SafeFileHandleMock("c.txt")));

using SafeFileHandle handleA = fileSystemA.File.OpenHandle("a.txt");

void Act() => fileSystemB.RandomAccess.GetLength(handleA);

await That(Act).DoesNotThrow()
.Because("a live handle from another file system was never closed");
}

[Test]
public async Task RandomAccess_WithHandleFromAnotherFileSystem_ShouldUseTheSafeFileHandleStrategy()
{
MockFileSystem fileSystemA = new();
MockFileSystem fileSystemB = new();
fileSystemA.File.WriteAllText("a.txt", "a");
fileSystemB.File.WriteAllText("b.txt", "b");
fileSystemB.File.WriteAllText("c.txt", "cc");
fileSystemB.WithSafeFileHandleStrategy(
new DefaultSafeFileHandleStrategy(_ => new SafeFileHandleMock("c.txt")));

using SafeFileHandle handleA = fileSystemA.File.OpenHandle("a.txt");
using SafeFileHandle handleB = fileSystemB.File.OpenHandle("b.txt");

long result = fileSystemB.RandomAccess.GetLength(handleA);

await That(result).IsEqualTo(2)
.Because("a handle from another file system is foreign and must be mapped by the strategy");
}

[Test]
public async Task Write_AtAnOffsetThatOverflows_ShouldThrowIOException()
{
Expand Down
Loading