Skip to content
Closed
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
34 changes: 25 additions & 9 deletions MoreLinq.Test/TestingSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace MoreLinq.Test
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using static TestingSequence;

static class TestingSequence
{
Expand All @@ -33,14 +34,16 @@ internal static TestingSequence<T> Of<T>(Options options, params T[] elements) =
internal static TestingSequence<T> AsTestingSequence<T>(this IEnumerable<T> source,
Options options = Options.None) =>
source != null
? new TestingSequence<T>(source) { IsReiterationAllowed = options.HasFlag(Options.AllowMultipleEnumerations) }
? new TestingSequence<T>(source, options)
: throw new ArgumentNullException(nameof(source));

[Flags]
public enum Options
{
None,
AllowMultipleEnumerations
AllowMultipleEnumerations = 0x1,
AllowRepeatedDisposals = 0x2,
AllowRepeatedMoveNexts = 0x4,
}
}

Expand All @@ -51,14 +54,17 @@ public enum Options
/// </summary>
sealed class TestingSequence<T> : IEnumerable<T>, IDisposable
{
Options _options;
bool? _disposed;
IEnumerable<T>? _sequence;

internal TestingSequence(IEnumerable<T> sequence) =>
internal TestingSequence(IEnumerable<T> sequence, Options options)
{
_sequence = sequence;
_options = options;
}

public bool IsDisposed => _disposed ?? false;
public bool IsReiterationAllowed { get; init; }
public int MoveNextCallCount { get; private set; }

void IDisposable.Dispose() =>
Expand All @@ -77,27 +83,37 @@ void AssertDisposed()

public IEnumerator<T> GetEnumerator()
{
if (!IsReiterationAllowed)
Assert.That(_sequence, Is.Not.Null, "LINQ operators should not enumerate a sequence more than once.");
Assert.That(_sequence, Is.Not.Null, "LINQ operators should not enumerate a sequence more than once.");

Debug.Assert(_sequence is not null);

var enumerator = _sequence.GetEnumerator().AsWatchable();
_disposed = false;
enumerator.Disposed += delegate
{
Assert.That(_disposed, Is.False, "LINQ operators should not dispose a sequence more than once.");
if (!_options.HasFlag(Options.AllowRepeatedDisposals))
Assert.That(_disposed, Is.False, "LINQ operators should not dispose a sequence more than once.");

_disposed = true;
};
var ended = false;
enumerator.MoveNextCalled += (_, moved) =>
{
Assert.That(ended, Is.False, "LINQ operators should not continue iterating a sequence that has terminated.");
if (!_options.HasFlag(Options.AllowRepeatedMoveNexts))
Assert.That(ended, Is.False, "LINQ operators should not continue iterating a sequence that has terminated.");

Assert.That(_disposed, Is.False, "LINQ operators should not call MoveNext() on a disposed sequence.");
ended = !moved;
MoveNextCallCount++;
};

if (!IsReiterationAllowed)
enumerator.GetCurrentCalled += delegate
{
Assert.That(_disposed, Is.False, "LINQ operators should not attempt to get the Current value on a disposed sequence.");
Assert.That(ended, Is.False, "LINQ operators should not attempt to get the Current value on a completed sequence.");
};

if (!_options.HasFlag(Options.AllowMultipleEnumerations))
_sequence = null;

return enumerator;
Expand Down
14 changes: 12 additions & 2 deletions MoreLinq.Test/WatchableEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ sealed class WatchableEnumerator<T> : IEnumerator<T>
readonly IEnumerator<T> _source;

public event EventHandler? Disposed;
public event EventHandler? GetCurrentCalled;
public event EventHandler<bool>? MoveNextCalled;

public WatchableEnumerator(IEnumerator<T> source) =>
_source = source ?? throw new ArgumentNullException(nameof(source));

public T Current => _source.Current;
object? IEnumerator.Current => Current;
public T Current
{
get
{
GetCurrentCalled?.Invoke(this, EventArgs.Empty);
return _source.Current;
}
}

object? IEnumerator.Current => this.Current;

public void Reset() => _source.Reset();

public bool MoveNext()
Expand Down