maui-linux/Xamarin.Forms.Core.UnitTests/ListProxyTests.cs

524 строки
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
namespace Xamarin.Forms.Core.UnitTests
{
[TestFixture]
public class ListProxyTests : BaseTestFixture
{
[SetUp]
public override void Setup()
{
base.Setup ();
Device.PlatformServices = new MockPlatformServices ();
}
[TearDown]
public override void TearDown()
{
base.TearDown ();
Device.PlatformServices = null;
}
[Test]
public void ListCount()
{
var list = new List<string> { "foo", "bar" };
var proxy = new ListProxy (list);
Assert.AreEqual (list.Count, proxy.Count);
list.Add ("baz");
Assert.AreEqual (list.Count, proxy.Count);
}
[Test]
public void CollectionCount()
{
var list = new Collection<string> { "foo", "bar" };
var proxy = new ListProxy (list);
Assert.AreEqual (list.Count, proxy.Count);
list.Add ("baz");
Assert.AreEqual (list.Count, proxy.Count);
}
[Test]
[Description ("Count should ensure that the window is created if neccessary")]
public void EnumerableInitialCount()
{
var enumerable = Enumerable.Range (0, 100);
var proxy = new ListProxy (enumerable, 10);
Assert.AreEqual (10, proxy.Count);
}
[Test]
public void EnumerableCount()
{
var enumerable = Enumerable.Range (0, 100);
var proxy = new ListProxy (enumerable, 10);
int changed = 0;
proxy.CountChanged += (o, e) => changed++;
var enumerator = proxy.GetEnumerator();
enumerator.MoveNext();
Assert.AreEqual (10, proxy.Count);
Assert.AreEqual (1, changed);
enumerator.MoveNext();
Assert.AreEqual (10, proxy.Count);
Assert.AreEqual (1, changed);
while (enumerator.MoveNext()) {
}
enumerator.Dispose();
Assert.AreEqual (100, proxy.Count);
Assert.AreEqual (19, changed);
using (enumerator = proxy.GetEnumerator()) {
Assert.AreEqual (100, proxy.Count);
while (enumerator.MoveNext())
Assert.AreEqual (100, proxy.Count);
Assert.AreEqual (100, proxy.Count);
}
Assert.AreEqual (19, changed);
}
[Test]
public void InsideWindowSize()
{
var numbers = Enumerable.Range (0, 100);
var proxy = new ListProxy (numbers, 10);
int i = (int)proxy[5];
Assert.That (i, Is.EqualTo (5));
}
[Test]
public void IndexOutsideWindowSize()
{
var numbers = Enumerable.Range (0, 100);
var proxy = new ListProxy (numbers, 10);
int i = (int)proxy[50];
Assert.That (i, Is.EqualTo (50));
}
[Test]
public void IndexInsideToOutsideWindowSize()
{
var numbers = Enumerable.Range (0, 100);
var proxy = new ListProxy (numbers, 10);
int i = (int)proxy[5];
Assert.That (i, Is.EqualTo (5));
i = (int)proxy[50];
Assert.That (i, Is.EqualTo (50));
}
[Test]
public void IndexOutsideToPreWindowSize()
{
var numbers = Enumerable.Range (0, 100);
var proxy = new ListProxy (numbers, 10);
int i = (int)proxy[50];
Assert.That (i, Is.EqualTo (50));
i = (int)proxy[5];
Assert.That (i, Is.EqualTo (5));
}
[Test]
public void EnumerableIndexOutOfRange()
{
var numbers = Enumerable.Range (0, 100);
var proxy = new ListProxy (numbers);
Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
}
class IntCollection
: ICollection
{
readonly List<int> ints;
public IntCollection (IEnumerable<int> ints)
{
this.ints = ints.ToList();
}
public IEnumerator GetEnumerator()
{
return ints.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void CopyTo (Array array, int index)
{
throw new NotImplementedException();
}
public int Count { get { return ints.Count; }}
public object SyncRoot
{
get { throw new NotImplementedException(); }
}
public bool IsSynchronized
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly { get { return true; } }
}
[Test]
public void CollectionIndexOutOfRange()
{
var numbers = new IntCollection (Enumerable.Range (0, 100));
var proxy = new ListProxy (numbers);
Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
}
[Test]
public void ListIndexOutOfRange()
{
var numbers = Enumerable.Range (0, 100).ToList();
var proxy = new ListProxy (numbers);
Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
}
[Test]
public void CollectionChangedWhileEnumerating()
{
var c = new ObservableCollection<string> { "foo", "bar" };
var p = new ListProxy (c);
IEnumerator<object> e = p.GetEnumerator();
Assert.IsTrue (e.MoveNext(), "Initial MoveNext() failed, test can't continue");
c.Add ("baz");
Assert.That (() => e.MoveNext(), Throws.InvalidOperationException,
"MoveNext did not throw an exception when the underlying collection had changed");
}
[Test]
public void SynchronizedCollectionAccess()
{
var collection = new ObservableCollection<string> { "foo" };
var context = new object();
var list = new ListProxy (collection);
bool executed = false;
BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => {
executed = true;
Assert.AreSame (collection, enumerable);
Assert.AreSame (context, o);
Assert.IsNotNull (method);
Assert.IsFalse (access);
lock (enumerable)
method();
});
object value = list[0];
Assert.IsTrue (executed, "Callback was not executed");
}
[Test]
public void SynchronizedCollectionAdd()
{
bool invoked = false;
Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: action => {
invoked = true;
action();
});
var collection = new ObservableCollection<string> { "foo" };
var context = new object();
var list = new ListProxy (collection);
Assert.IsFalse (invoked, "An invoke shouldn't be executed just setting up ListProxy");
bool executed = false;
BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => {
executed = true;
Assert.AreSame (collection, enumerable);
Assert.AreSame (context, o);
Assert.IsNotNull (method);
Assert.IsFalse (access);
lock (enumerable)
method();
});
var mre = new ManualResetEvent (false);
Task.Factory.StartNew (() => {
lock (collection)
collection.Add ("foo");
mre.Set();
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
mre.WaitOne (5000);
Assert.IsTrue (executed, "Callback was not executed");
Assert.IsTrue (invoked, "Callback was not executed on the UI thread");
}
[Test]
public void ClearEnumerable()
{
var proxy = new ListProxy (Enumerable.Range (0, 100));
var enumerator = proxy.GetEnumerator();
enumerator.MoveNext();
enumerator.MoveNext();
proxy.Clear();
Assert.AreEqual (100, proxy.Count);
Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
}
[Test]
public void ClearCollection()
{
var proxy = new ListProxy (new IntCollection (Enumerable.Range (0, 100)));
var enumerator = proxy.GetEnumerator();
enumerator.MoveNext();
enumerator.MoveNext();
proxy.Clear();
Assert.AreEqual (100, proxy.Count);
Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
}
[Test]
public void ClearList()
{
var proxy = new ListProxy (Enumerable.Range (0, 100).ToList());
var enumerator = proxy.GetEnumerator();
enumerator.MoveNext();
enumerator.MoveNext();
proxy.Clear();
Assert.AreEqual (100, proxy.Count);
Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
}
[Test]
public void IndexOfValueTypeNonList()
{
var proxy = new ListProxy (Enumerable.Range (0, 100));
Assert.AreEqual (1, proxy.IndexOf (1));
}
[Test]
public void EnumeratorForEnumerable()
{
var proxy = new ListProxy (Enumerable.Range (0, 2));
var enumerator = proxy.GetEnumerator();
Assert.That (enumerator.Current, Is.Null);
Assert.That (enumerator.MoveNext(), Is.True);
Assert.That (enumerator.Current, Is.EqualTo (0));
Assert.That (enumerator.MoveNext(), Is.True);
Assert.That (enumerator.Current, Is.EqualTo (1));
Assert.That (enumerator.MoveNext(), Is.False);
}
[Test]
public void ProxyIsWeaklyHeldByINotifyCollectionChanged()
{
ObservableCollection<string> collection = new ObservableCollection<string>();
WeakReference weakProxy = null;
int i = 0;
Action create = null;
create = () => {
if (i++ < 1024) {
create();
return;
}
var proxy = new ListProxy (collection);
weakProxy = new WeakReference (proxy);
};
create();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.That (weakProxy.IsAlive, Is.False);
}
[Test]
public void IEnumerableAddDoesNotReport0()
{
var custom = new CustomINCC();
custom.Add ("test");
custom.Add ("test2");
var proxy = new ListProxy (custom);
Assert.That (proxy.Count, Is.EqualTo (2));
custom.Add ("testing");
Assert.That (proxy.Count, Is.EqualTo (3));
}
class CustomINCC : IEnumerable<string>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
List<string> Items = new List<string> ();
public void Add (string s)
{
Items.Add(s);
if (CollectionChanged != null)
CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, s));
}
public IEnumerator<string> GetEnumerator ()
{
return Items.GetEnumerator ();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return Items.GetEnumerator ();
}
}
// Need a member to keep this reference around, otherwise it gets optimized
// out early in Release mode during the WeakToWeak test
#pragma warning disable 0414 // Never accessed, it's just here to prevent GC
ListProxy _proxyForWeakToWeakTest;
#pragma warning restore 0414
[Test]
public void WeakToWeak()
{
WeakCollectionChangedList list = new WeakCollectionChangedList();
_proxyForWeakToWeakTest = new ListProxy(list);
Assert.True(list.AddObject(), "GC hasn't run");
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsTrue(list.AddObject(), "GC run, but proxy should still hold a reference");
_proxyForWeakToWeakTest = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(list.AddObject(), "Proxy is gone and GC has run");
}
public class WeakCollectionChangedList : List<object>, INotifyCollectionChanged
{
List<WeakHandler> handlers = new List<WeakHandler>();
public WeakCollectionChangedList()
{
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { handlers.Add(new WeakHandler(this, value)); }
remove { throw new NotImplementedException(); }
}
public bool AddObject()
{
bool invoked = false;
var me = new object();
Console.WriteLine($"Handler count is {handlers.Count}");
foreach (var handler in handlers.ToList())
{
if (handler.IsActive)
{
invoked = true;
handler.Handler.DynamicInvoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, me));
}
else
{
Console.WriteLine($"Handler is inactive");
handlers.Remove(handler);
}
}
return invoked;
}
class WeakHandler
{
WeakReference source;
WeakReference originalHandler;
public bool IsActive
{
get { return this.source != null && this.source.IsAlive && this.originalHandler != null && this.originalHandler.IsAlive; }
}
public NotifyCollectionChangedEventHandler Handler
{
get
{
if (this.originalHandler == null)
{
return default(NotifyCollectionChangedEventHandler);
}
else
{
return (NotifyCollectionChangedEventHandler)this.originalHandler.Target;
}
}
}
public WeakHandler(object source, NotifyCollectionChangedEventHandler originalHandler)
{
this.source = new WeakReference(source);
this.originalHandler = new WeakReference(originalHandler);
}
}
}
}
}