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
2 changes: 1 addition & 1 deletion src/ZLinq/ZLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Bcl.Memory" Version="9.0.9" />
<PackageReference Include="Microsoft.Bcl.Memory" Version="10.0.5" />
<PackageReference Include="System.Buffers" Version="4.6.1" />
<PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
Expand Down
15 changes: 14 additions & 1 deletion tests/System.Linq.Tests/SkipReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public static class SkipReason
/// </summary>
public const string EnumeratorReset = "ZLinq don't support Enumerator.Reset() API.";

/// <summary>
/// Custom enumerator is not used by ZLinq optimization.
/// If target collection implements collection interfaces other than IEnumerable.
/// </summary>
public const string CustomEnumerator = "Custom enumerator is not used by ZLinq optimization.";

/// <summary>
/// ZLinq don't support CreateOrderedEnumerable API
/// </summary>
Expand Down Expand Up @@ -73,7 +79,14 @@ public static class SkipReason
/// ZLinq's query can't re-assign to variable when ValueEnumerable type is different.
/// </summary>
public const string Issue0096 = "ZLinq don't support reuse variables if ValueEnumerable type is different. " +
"See: https://github.com/Cysharp/ZLinq/issues/0096";
"See: https://github.com/Cysharp/ZLinq/issues/96";

/// <summary>
/// When using Count() with ICollection based collection.
/// OverflowException is not thrown as expected, and return wrong value.
/// </summary>
public const string Issue0242 = "Count() return wrong value when using ZLinq with ICollection<T> based collection. " +
"See: https://github.com/Cysharp/ZLinq/issues/242";

// Dummy code.
public const string ZLinq_IssueNNNN = "See: https://github.com/Cysharp/ZLinq/issues/{placeholder}";
Expand Down
125 changes: 122 additions & 3 deletions tests/System.Linq.Tests/Stubs/System.Linq/EnumerableTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Original code: https://github.com/dotnet/runtime/blob/v9.0.3/src/libraries/System.Linq/tests/EnumerableTests.cs
// Original code: https://github.com/dotnet/runtime/blob/v11.0.100-preview.2.26159.112/src/libraries/System.Linq/tests/EnumerableTests.cs

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xunit;

#nullable disable

Expand Down Expand Up @@ -445,5 +443,126 @@ public DelegateIterator(

void IDisposable.Dispose() => _dispose();
}

#nullable enable
protected sealed class DisposeTrackingList<T> : IList<T>, IReadOnlyList<T>
{
private readonly List<T> _list;
private int _disposeCalls;

public DisposeTrackingList(T[] items)
{
_list = [.. items];
}

public int DisposeCalls => _disposeCalls;

public T this[int index]
{
get => _list[index];
set => _list[index] = value;
}

public int Count => _list.Count;

public bool IsReadOnly => false;

public void Add(T item) => _list.Add(item);
public void Clear() => _list.Clear();
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public int IndexOf(T item) => _list.IndexOf(item);
public void Insert(int index, T item) => _list.Insert(index, item);
public bool Remove(T item) => _list.Remove(item);
public void RemoveAt(int index) => _list.RemoveAt(index);

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public IEnumerator<T> GetEnumerator() => new DisposeTrackingEnumerator(this);

private sealed class DisposeTrackingEnumerator : IEnumerator<T>
{
private readonly DisposeTrackingList<T> _parent;
private readonly IEnumerator<T> _enumerator;

public DisposeTrackingEnumerator(DisposeTrackingList<T> parent)
{
_parent = parent;
_enumerator = parent._list.GetEnumerator();
}

public T Current => _enumerator.Current;
object? IEnumerator.Current => Current;
public bool MoveNext() => _enumerator.MoveNext();
public void Reset() => throw new NotSupportedException();

public void Dispose()
{
_parent._disposeCalls++;
_enumerator.Dispose();
}
}
}

// Custom DisposeTrackingList for ZLinq tests.
// Implement IEnumerable<T> only to skip ZLinq optimization path.
protected sealed class DisposeTrackingListForZLinq<T> : IEnumerable<T>
{
private readonly List<T> _list;
private int _disposeCalls;

public DisposeTrackingListForZLinq(T[] items)
{
_list = [.. items];
}

public int DisposeCalls => _disposeCalls;

public T this[int index]
{
get => _list[index];
set => _list[index] = value;
}

public int Count => _list.Count;

public bool IsReadOnly => false;

public void Add(T item) => _list.Add(item);
public void Clear() => _list.Clear();
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public int IndexOf(T item) => _list.IndexOf(item);
public void Insert(int index, T item) => _list.Insert(index, item);
public bool Remove(T item) => _list.Remove(item);
public void RemoveAt(int index) => _list.RemoveAt(index);

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public IEnumerator<T> GetEnumerator() => new DisposeTrackingEnumerator(this);

private sealed class DisposeTrackingEnumerator : IEnumerator<T>
{
private readonly DisposeTrackingListForZLinq<T> _parent;
private readonly IEnumerator<T> _enumerator;

public DisposeTrackingEnumerator(DisposeTrackingListForZLinq<T> parent)
{
_parent = parent;
_enumerator = parent._list.GetEnumerator();
}

public T Current => _enumerator.Current;
object? IEnumerator.Current => Current;
public bool MoveNext() => _enumerator.MoveNext();
public void Reset() => throw new NotSupportedException();

public void Dispose()
{
_parent._disposeCalls++;
_enumerator.Dispose();
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private static bool ComputeIsLinqSizeOptimized()
public static bool IsNotBrowser => !IsBrowser;
public static bool IsWasi => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI"));
public static bool IsWasmThreadingSupported => IsBrowser && IsEnvironmentVariableTrue("IsBrowserThreadingSupported");
public static bool IsThreadingSupported => (!IsWasi && !IsBrowser) || IsWasmThreadingSupported;
public static bool IsMultithreadingSupported => (!IsWasi && !IsBrowser) || IsWasmThreadingSupported;

public static bool IsBuiltWithAggressiveTrimming => IsNativeAot || IsAppleMobile;
public static bool IsNotBuiltWithAggressiveTrimming => !IsBuiltWithAggressiveTrimming;
Expand Down
2 changes: 1 addition & 1 deletion tests/System.Linq.Tests/Stubs/xUnit/ConditionalUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static bool IsEnable(Type type, string key)
{
switch (key)
{
case "IsThreadingSupported" when PlatformDetection.IsThreadingSupported:
case "IsMultithreadingSupported" when PlatformDetection.IsMultithreadingSupported:
case "IsDebuggerTypeProxyAttributeSupported" when PlatformDetection.IsDebuggerTypeProxyAttributeSupported:
case "IsNotBuiltWithAggressiveTrimming" when PlatformDetection.IsNotBuiltWithAggressiveTrimming:

Expand Down
4 changes: 1 addition & 3 deletions tests/System.Linq.Tests/System.Linq.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,17 @@
</PropertyGroup>

<!-- Add .NET 11 support if .NET 11 or later version of MSBuild is used. -->
<!--
<PropertyGroup Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreAppMaximumVersion)','11.0'))">
<TargetFrameworks>$(TargetFrameworks);net11.0</TargetFrameworks>
</PropertyGroup>
-->

<ItemGroup>
<None Remove="$(MSBuildThisFileName).slnx" />
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="xunit.v3" Version="3.0.1" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="Shouldly" Version="4.3.0" />
</ItemGroup>

Expand Down
81 changes: 78 additions & 3 deletions tests/System.Linq.Tests/Tests/System.Linq/AppendPrependTests.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Xunit;
using System.Collections;

namespace System.Linq.Tests
{
public class AppendPrependTests : EnumerableTests
{
// Mock collection for testing overflow without allocating memory
private sealed class MockCollection<T> : ICollection<T>
{
private readonly int _count;

public MockCollection(int count) => _count = count;

public int Count => _count;
public bool IsReadOnly => true;

public void Add(T item) => throw new NotSupportedException();
public void Clear() => throw new NotSupportedException();
public bool Contains(T item) => false;
public void CopyTo(T[] array, int arrayIndex) { }
public bool Remove(T item) => throw new NotSupportedException();
public IEnumerator<T> GetEnumerator() => Enumerable.Empty<T>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

[Fact]
public void SameResultsRepeatCallsIntQueryAppend()
{
Expand Down Expand Up @@ -263,5 +280,63 @@ public void AppendPrepend_First_Last_ElementAt()
Assert.Equal(84, NumberRangeGuaranteedNotCollectionType(42, 1).Append(84).ElementAt(1));
Assert.Equal(84, NumberRangeGuaranteedNotCollectionType(84, 1).Prepend(42).ElementAt(1));
}

// Currently bugfix for Append/Prepend GetCount overflow is not backported to .NET 10.
// https://github.com/dotnet/runtime/pull/123821
#if NET11_0_OR_GREATER
[Fact]
public void AppendOverflowCount()
{
// AppendPrepend1Iterator overflow when source has GetCount optimization
var source = Enumerable.Repeat(0, int.MaxValue);
var appended = source.Append(1);
Assert.Throws<OverflowException>(() => appended.Count());
}

[Fact]
public void PrependOverflowCount()
{
// AppendPrepend1Iterator overflow when source has GetCount optimization
var source = Enumerable.Repeat(0, int.MaxValue);
var prepended = source.Prepend(1);
Assert.Throws<OverflowException>(() => prepended.Count());
}

[Fact]
public void AppendPrependNOverflowCount()
{
// AppendPrependN overflow when source has GetCount optimization
var source = Enumerable.Repeat(0, int.MaxValue);
var result = source.Append(1).Prepend(2);
Assert.Throws<OverflowException>(() => result.Count());
}

[Fact]
public void AppendOverflowCountWithICollection()
{
// AppendPrepend1Iterator overflow when source is ICollection
var source = new MockCollection<int>(int.MaxValue);
var appended = source.Append(1);
Assert.Throws<OverflowException>(() => appended.Count());
}

[Fact]
public void PrependOverflowCountWithICollection()
{
// AppendPrepend1Iterator overflow when source is ICollection
var source = new MockCollection<int>(int.MaxValue);
var prepended = source.Prepend(1);
Assert.Throws<OverflowException>(() => prepended.Count());
}

[Fact]
public void AppendPrependNOverflowCountWithICollection()
{
// AppendPrependN overflow when source is ICollection
var source = new MockCollection<int>(int.MaxValue);
var result = source.Append(1).Prepend(2);
Assert.Throws<OverflowException>(() => result.Count());
}
#endif
}
}
27 changes: 26 additions & 1 deletion tests/System.Linq.Tests/Tests/System.Linq/SelectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void EmptyWithIndexedSelector()
Assert.Equal([], Enumerable.Empty<string>().Select((s, i) => s.Length + i));
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))]
public void EnumerateFromDifferentThread()
{
var selected = Enumerable.Range(0, 100).Where(i => i > 3).Select(i => i.ToString());
Expand Down Expand Up @@ -1261,5 +1261,30 @@ public static IEnumerable<object[]> RunSelectorDuringCountData()
}
}
}

[Fact]
public void Select_SourceIsIList_EnumeratorDisposedOnComplete()
{
var source = new DisposeTrackingList<int>([1, 2, 3, 4, 5]);

foreach (int item in source.Select(i => i * 2))
{
}

Assert.Equal(1, source.DisposeCalls);
}

[Fact]
public void Select_SourceIsIList_EnumeratorDisposedOnExplicitDispose()
{
var source = new DisposeTrackingList<int>([1, 2, 3, 4, 5]);

using (var enumerator = source.Select(i => i * 2).GetEnumerator())
{
enumerator.MoveNext();
}

Assert.Equal(1, source.DisposeCalls);
}
}
}
34 changes: 34 additions & 0 deletions tests/System.Linq.Tests/Tests/System.Linq/SkipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,5 +387,39 @@ public void DisposeSource(int sourceCount, int count)
Assert.False(iterator.MoveNext());
Assert.Equal(-1, state);
}

// Currently bugfix is not backported to .NET 10.
// https://github.com/dotnet/runtime/pull/123295
#if NET11_0_OR_GREATER
[Fact]
public void SkipMoreThanCountFollowedByOperators()
{
int[] items = [2, 3];

foreach (IEnumerable<int> source in CreateSources([1]))
{
Assert.Equal(items, source.Skip(2).Concat(items).ToArray());
Assert.Equal(items, source.Skip(2).Concat(items).ToList());
Assert.Equal(items, source.Skip(2).Append(2).Append(3).ToArray());
Assert.Equal(items, source.Skip(2).Append(2).Append(3).ToList());
Assert.Equal(items, items.Concat(source.Skip(2)).ToArray());
Assert.Equal(items, items.Concat(source.Skip(2)).ToList());
Assert.Empty(source.Skip(2).Select(x => x * 2).ToArray());
Assert.Empty(source.Skip(2).Select(x => x * 2).ToList());
Assert.Empty(source.Skip(2).Where(x => x > 0).ToArray());
Assert.Empty(source.Skip(2).Where(x => x > 0).ToList());
Assert.Empty(source.Skip(2).Take(10).ToArray());
Assert.Empty(source.Skip(2).Take(10).ToList());
Assert.Empty(source.Skip(2).Skip(1).ToArray());
Assert.Empty(source.Skip(2).Skip(1).ToList());
Assert.Empty(source.Skip(2).Distinct().ToArray());
Assert.Empty(source.Skip(2).Distinct().ToList());
Assert.Empty(source.Skip(2).OrderBy(x => x).ToArray());
Assert.Empty(source.Skip(2).OrderBy(x => x).ToList());
Assert.DoesNotContain(1, source.Skip(2));
Assert.DoesNotContain(2, source.Skip(2));
}
}
#endif
}
}
Loading
Loading