Moved all unit test files into a separate directory in preparation for splitting the project

This commit is contained in:
Markus Ewald 2024-07-24 13:27:29 +02:00 committed by cygon
parent 28b96fd557
commit ba5234f701
58 changed files with 0 additions and 853 deletions

View file

@ -0,0 +1,225 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_SETS
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the expression tree-based cloner</summary>
[TestFixture]
internal class ExpressionTreeClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
public ExpressionTreeClonerTest() {
this.cloneFactory = new ExpressionTreeCloner();
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
[Test]
public void CloningNullYieldsNull() {
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
}
/// <summary>
/// Verifies that clones of objects whose class doesn't possess a default constructor
/// can be made
/// </summary>
[Test]
public void ClassWithoutDefaultConstructorCanBeCloned() {
var original = new ClassWithoutDefaultConstructor(1234);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreEqual(original.Dummy, clone.Dummy);
}
/// <summary>Verifies that clones of primitive types can be created</summary>
[Test]
public void PrimitiveTypesCanBeCloned() {
int original = 12345;
int clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual(original, clone);
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test]
public void ReferenceTypesCanBeCloned() {
var original = new TestReferenceType() { TestField = 123, TestProperty = 456 };
TestReferenceType clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreEqual(original.TestField, clone.TestField);
Assert.AreEqual(original.TestProperty, clone.TestProperty);
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test]
public void PrimitiveArraysCanBeCloned() {
var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
CollectionAssert.AreEqual(original, clone);
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test]
public void ShallowClonesOfArraysCanBeMade() {
var original = new TestReferenceType[] {
new TestReferenceType() { TestField = 123, TestProperty = 456 }
};
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
Assert.AreSame(original[0], clone[0]);
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
[Test]
public void DeepClonesOfArraysCanBeMade() {
var original = new TestReferenceType[,] {
{
new TestReferenceType() { TestField = 123, TestProperty = 456 }
}
};
TestReferenceType[,] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0, 0], clone[0, 0]);
Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField);
Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty);
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
[Test]
public void GenericListsCanBeCloned() {
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
[Test]
public void GenericDictionariesCanBeCloned() {
var original = new Dictionary<int, string>();
original.Add(1, "one");
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
}
/// <summary>
/// Verifies that a field-based shallow clone of a value type can be performed
/// </summary>
[Test]
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based shallow clone of a reference type can be performed
/// </summary>
[Test]
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a property-based shallow clone of a value type can be performed
/// </summary>
[Test]
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based shallow clone of a reference type can be performed
/// </summary>
[Test]
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>Clone factory being tested</summary>
private ICloneFactory cloneFactory;
}
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST
#endif // !NO_SETS

View file

@ -0,0 +1,198 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the reflection-based cloner</summary>
[TestFixture]
internal class ReflectionClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
public ReflectionClonerTest() {
this.cloneFactory = new ReflectionCloner();
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
[Test]
public void CloningNullYieldsNull() {
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
}
/// <summary>
/// Verifies that clones of objects whose class doesn't possess a default constructor
/// can be made
/// </summary>
[Test]
public void ClassWithoutDefaultConstructorCanBeCloned() {
var original = new ClassWithoutDefaultConstructor(1234);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreEqual(original.Dummy, clone.Dummy);
}
/// <summary>Verifies that clones of primitive types can be created</summary>
[Test]
public void PrimitiveTypesCanBeCloned() {
int original = 12345;
int clone = this.cloneFactory.ShallowFieldClone(original);
Assert.AreEqual(original, clone);
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
[Test]
public void ShallowClonesOfArraysCanBeMade() {
var original = new TestReferenceType[] {
new TestReferenceType() { TestField = 123, TestProperty = 456 }
};
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
Assert.AreSame(original[0], clone[0]);
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
[Test]
public void DeepClonesOfArraysCanBeMade() {
var original = new TestReferenceType[] {
new TestReferenceType() { TestField = 123, TestProperty = 456 }
};
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0], clone[0]);
Assert.AreEqual(original[0].TestField, clone[0].TestField);
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
[Test]
public void GenericListsCanBeCloned() {
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
[Test]
public void GenericDictionariesCanBeCloned() {
var original = new Dictionary<int, string>();
original.Add(1, "one");
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
}
/// <summary>
/// Verifies that a field-based shallow clone of a value type can be performed
/// </summary>
[Test]
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based shallow clone of a reference type can be performed
/// </summary>
[Test]
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a property-based shallow clone of a value type can be performed
/// </summary>
[Test]
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based shallow clone of a reference type can be performed
/// </summary>
[Test]
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>Clone factory being tested</summary>
private ICloneFactory cloneFactory;
}
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST

View file

@ -0,0 +1,145 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the binary serializer-based cloner</summary>
[TestFixture]
internal class SerializationClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
public SerializationClonerTest() {
this.cloneFactory = new SerializationCloner();
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
[Test]
public void CloningNullYieldsNull() {
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
}
/// <summary>
/// Verifies that clones of objects whose class doesn't possess a default constructor
/// can be made
/// </summary>
[Test]
public void ClassWithoutDefaultConstructorCanBeCloned() {
var original = new ClassWithoutDefaultConstructor(1234);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreEqual(original.Dummy, clone.Dummy);
}
/// <summary>Verifies that clones of primitive types can be created</summary>
[Test]
public void PrimitiveTypesCanBeCloned() {
int original = 12345;
int clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual(original, clone);
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
[Test]
public void DeepClonesOfArraysCanBeMade() {
var original = new TestReferenceType[] {
new TestReferenceType() { TestField = 123, TestProperty = 456 }
};
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0], clone[0]);
Assert.AreEqual(original[0].TestField, clone[0].TestField);
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
[Test]
public void GenericListsCanBeCloned() {
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
[Test]
public void GenericDictionariesCanBeCloned() {
var original = new Dictionary<int, string>();
original.Add(1, "one");
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
}
/// <summary>
/// Verifies that a field-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a field-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
}
/// <summary>
/// Verifies that a property-based deep clone of a value type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
HierarchicalValueType original = CreateValueType();
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>
/// Verifies that a property-based deep clone of a reference type can be performed
/// </summary>
[Test]
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
}
/// <summary>Clone factory being tested</summary>
private ICloneFactory cloneFactory;
}
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST

View file

@ -0,0 +1,52 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Specialized;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the collection constants</summary>
[TestFixture]
internal class ConstantsTest {
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>
/// Verifies that the collection reset event arguments have 'reset' specified as
/// their action
/// </summary>
[Test]
public void CollectionResetEventArgsHaveResetActionSet() {
Assert.AreEqual(
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
);
}
#endif // !NO_SPECIALIZED_COLLECTIONS
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,710 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the double ended queue</summary>
[TestFixture]
internal class DequeTest {
/// <summary>Verifies that the AddLast() method of the deque is working</summary>
[Test]
public void TestAddLast() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 48; ++item) {
intDeque.AddLast(item);
}
for(int item = 0; item < 48; ++item) {
Assert.AreEqual(item, intDeque[item]);
}
}
/// <summary>Verifies that the AddFirst() method of the deque is working</summary>
[Test]
public void TestAddFirst() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 48; ++item) {
intDeque.AddFirst(item);
}
for(int item = 0; item < 48; ++item) {
Assert.AreEqual(47 - item, intDeque[item]);
}
}
/// <summary>
/// Verifies that the RemoveFirst() method of the deque is working
/// </summary>
[Test]
public void TestRemoveFirst() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 48; ++item) {
intDeque.AddLast(item);
}
for(int item = 0; item < 48; ++item) {
Assert.AreEqual(item, intDeque.First);
Assert.AreEqual(48 - item, intDeque.Count);
intDeque.RemoveFirst();
}
}
/// <summary>
/// Verifies that the RemoveLast() method of the deque is working
/// </summary>
[Test]
public void TestRemoveLast() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 48; ++item) {
intDeque.AddLast(item);
}
for(int item = 0; item < 48; ++item) {
Assert.AreEqual(47 - item, intDeque.Last);
Assert.AreEqual(48 - item, intDeque.Count);
intDeque.RemoveLast();
}
}
/// <summary>Verifies that the Insert() method works in all cases</summary>
/// <remarks>
/// We have several different cases here that will be tested. The deque can
/// shift items to the left or right (depending on which end is closer to
/// the insertion point) and the insertion point may fall in an only partially
/// occupied block, requiring elaborate index calculations
/// </remarks>
[Test]
public void TestInsert() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> intDeque = createDeque(96);
intDeque.Insert(testedIndex, 12345);
Assert.AreEqual(97, intDeque.Count);
for(int index = 0; index < testedIndex; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
Assert.AreEqual(12345, intDeque[testedIndex]);
for(int index = testedIndex + 1; index < 97; ++index) {
Assert.AreEqual(index - 1, intDeque[index]);
}
}
}
/// <summary>
/// Verifies that the Insert() method works in all cases when the deque doesn't
/// start at a block boundary
/// </summary>
[Test]
public void TestInsertNonNormalized() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> intDeque = createNonNormalizedDeque(96);
intDeque.Insert(testedIndex, 12345);
Assert.AreEqual(97, intDeque.Count);
for(int index = 0; index < testedIndex; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
Assert.AreEqual(12345, intDeque[testedIndex]);
for(int index = testedIndex + 1; index < 97; ++index) {
Assert.AreEqual(index - 1, intDeque[index]);
}
}
}
/// <summary>Verifies the the RemoveAt() method works in all cases</summary>
[Test]
public void TestRemoveAt() {
for(int testedIndex = 0; testedIndex < 96; ++testedIndex) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 96; ++item) {
intDeque.AddLast(item);
}
intDeque.RemoveAt(testedIndex);
Assert.AreEqual(95, intDeque.Count);
for(int index = 0; index < testedIndex; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
for(int index = testedIndex; index < 95; ++index) {
Assert.AreEqual(index + 1, intDeque[index]);
}
}
}
/// <summary>
/// Verifies the the RemoveAt() method works in all cases when the deque doesn't
/// start at a block boundary
/// </summary>
[Test]
public void TestRemoveAtNonNormalized() {
for(int testedIndex = 0; testedIndex < 96; ++testedIndex) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 4; item < 96; ++item) {
intDeque.AddLast(item);
}
intDeque.AddFirst(3);
intDeque.AddFirst(2);
intDeque.AddFirst(1);
intDeque.AddFirst(0);
intDeque.RemoveAt(testedIndex);
Assert.AreEqual(95, intDeque.Count);
for(int index = 0; index < testedIndex; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
for(int index = testedIndex; index < 95; ++index) {
Assert.AreEqual(index + 1, intDeque[index]);
}
}
}
/// <summary>
/// Tests whether the RemoveAt() method keeps the state of the deque intact when
/// it has to remove a block from the left end of the deque
/// </summary>
[Test]
public void TestRemoveAtEmptiesLeftBlock() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 1; item <= 16; ++item) {
intDeque.AddLast(item);
}
intDeque.AddFirst(0);
intDeque.RemoveAt(3);
Assert.AreEqual(16, intDeque.Count);
for(int index = 0; index < 3; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
for(int index = 3; index < 16; ++index) {
Assert.AreEqual(index + 1, intDeque[index]);
}
}
/// <summary>
/// Tests whether the RemoveAt() method keeps the state of the deque intact when
/// it has to remove a block from the right end of the deque
/// </summary>
[Test]
public void TestRemoveAtEmptiesRightBlock() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item <= 16; ++item) {
intDeque.AddLast(item);
}
intDeque.RemoveAt(13);
Assert.AreEqual(16, intDeque.Count);
for(int index = 0; index < 13; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
for(int index = 13; index < 16; ++index) {
Assert.AreEqual(index + 1, intDeque[index]);
}
}
/// <summary>
/// Validates that an exception is thrown if the 'First' property is accessed
/// in an empty deque
/// </summary>
[Test]
public void TestThrowOnAccessFirstInEmptyDeque() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(intDeque.First); }
);
}
/// <summary>
/// Validates that an exception is thrown if the 'Last' property is accessed
/// in an empty deque
/// </summary>
[Test]
public void TestThrowOnAccessLastInEmptyDeque() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(intDeque.Last); }
);
}
/// <summary>
/// Validates that an exception is thrown if the first item is attempted to be
/// removed from an empty deque
/// </summary>
[Test]
public void TestThrowOnRemoveFirstFromEmptyDeque() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<InvalidOperationException>(
delegate() { intDeque.RemoveFirst(); }
);
}
/// <summary>
/// Validates that an exception is thrown if the last item is attempted to be
/// removed from an empty deque
/// </summary>
[Test]
public void TestThrowOnRemoveLastFromEmptyDeque() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<InvalidOperationException>(
delegate() { intDeque.RemoveLast(); }
);
}
/// <summary>
/// Verifies that items can be assigned by their index
/// </summary>
[Test]
public void TestIndexAssignment() {
Deque<int> intDeque = createDeque(32);
intDeque[16] = 12345;
intDeque[17] = 54321;
for(int index = 0; index < 16; ++index) {
intDeque.RemoveFirst();
}
Assert.AreEqual(12345, intDeque.First);
intDeque.RemoveFirst();
Assert.AreEqual(54321, intDeque.First);
}
/// <summary>
/// Verifies that an exception is thrown if an invalid index is accessed
/// </summary>
[Test]
public void TestThrowOnInvalidIndex() {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < 32; ++item) {
intDeque.AddLast(item);
}
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { Console.WriteLine(intDeque[32]); }
);
}
/// <summary>Tests the IndexOf() method</summary>
[Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)]
public void TestIndexOf(int count) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < count; ++item) {
intDeque.AddLast(item);
}
for(int item = 0; item < count; ++item) {
Assert.AreEqual(item, intDeque.IndexOf(item));
}
Assert.AreEqual(-1, intDeque.IndexOf(count));
}
/// <summary>
/// Tests the IndexOf() method with the deque not starting at a block boundary
/// </summary>
[Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)]
public void TestIndexOfNonNormalized(int count) {
Deque<int> intDeque = createNonNormalizedDeque(count);
for(int item = 0; item < count; ++item) {
Assert.AreEqual(item, intDeque.IndexOf(item));
}
Assert.AreEqual(-1, intDeque.IndexOf(count));
}
/// <summary>Verifies that the deque's enumerator works</summary>
[Test]
public void TestEnumerator() {
Deque<int> intDeque = createNonNormalizedDeque(40);
for(int testRun = 0; testRun < 2; ++testRun) {
using(IEnumerator<int> enumerator = intDeque.GetEnumerator()) {
for(int index = 0; index < intDeque.Count; ++index) {
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(index, enumerator.Current);
}
Assert.IsFalse(enumerator.MoveNext());
enumerator.Reset();
}
}
}
/// <summary>Verifies that the deque's enumerator works</summary>
[Test]
public void TestObjectEnumerator() {
Deque<int> intDeque = createNonNormalizedDeque(40);
for(int testRun = 0; testRun < 2; ++testRun) {
IEnumerator enumerator = ((IEnumerable)intDeque).GetEnumerator();
for(int index = 0; index < intDeque.Count; ++index) {
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(index, enumerator.Current);
}
Assert.IsFalse(enumerator.MoveNext());
enumerator.Reset();
}
}
/// <summary>
/// Verifies that an exception is thrown if the enumerator is accessed in
/// an invalid position
/// </summary>
[Test]
public void TestThrowOnInvalidEnumeratorPosition() {
Deque<int> intDeque = createNonNormalizedDeque(40);
using(IEnumerator<int> enumerator = intDeque.GetEnumerator()) {
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(enumerator.Current); }
);
while(enumerator.MoveNext()) { }
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(enumerator.Current); }
);
}
}
/// <summary>Tests whether a small deque can be cleared</summary>
[Test]
public void TestClearSmallDeque() {
Deque<int> intDeque = createDeque(12);
intDeque.Clear();
Assert.AreEqual(0, intDeque.Count);
}
/// <summary>Tests whether a large deque can be cleared</summary>
[Test]
public void TestClearLargeDeque() {
Deque<int> intDeque = createDeque(40);
intDeque.Clear();
Assert.AreEqual(0, intDeque.Count);
}
/// <summary>Verifies that the non-typesafe Add() method is working</summary>
[Test]
public void TestAddObject() {
Deque<int> intDeque = new Deque<int>();
Assert.AreEqual(0, ((IList)intDeque).Add(123));
Assert.AreEqual(1, intDeque.Count);
}
/// <summary>
/// Tests whether an exception is thrown if the non-typesafe Add() method is
/// used to add an incompatible object into the deque
/// </summary>
[Test]
public void TestThrowOnAddIncompatibleObject() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque).Add("Hello World"); }
);
}
/// <summary>Verifies that the Add() method is working</summary>
[Test]
public void TestAdd() {
Deque<int> intDeque = new Deque<int>();
((IList<int>)intDeque).Add(123);
Assert.AreEqual(1, intDeque.Count);
}
/// <summary>Tests whether the Contains() method is working</summary>
[Test]
public void TestContains() {
Deque<int> intDeque = createDeque(16);
Assert.IsTrue(intDeque.Contains(14));
Assert.IsFalse(intDeque.Contains(16));
}
/// <summary>Tests the non-typesafe Contains() method</summary>
[Test]
public void TestContainsObject() {
Deque<int> intDeque = createDeque(16);
Assert.IsTrue(((IList)intDeque).Contains(14));
Assert.IsFalse(((IList)intDeque).Contains(16));
Assert.IsFalse(((IList)intDeque).Contains("Hello World"));
}
/// <summary>Tests the non-typesafe Contains() method</summary>
[Test]
public void TestIndexOfObject() {
Deque<int> intDeque = createDeque(16);
Assert.AreEqual(14, ((IList)intDeque).IndexOf(14));
Assert.AreEqual(-1, ((IList)intDeque).IndexOf(16));
Assert.AreEqual(-1, ((IList)intDeque).IndexOf("Hello World"));
}
/// <summary>Tests wether the non-typesafe Insert() method is working</summary>
[Test]
public void TestInsertObject() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> intDeque = createDeque(96);
((IList)intDeque).Insert(testedIndex, 12345);
Assert.AreEqual(97, intDeque.Count);
for(int index = 0; index < testedIndex; ++index) {
Assert.AreEqual(index, intDeque[index]);
}
Assert.AreEqual(12345, intDeque[testedIndex]);
for(int index = testedIndex + 1; index < 97; ++index) {
Assert.AreEqual(index - 1, intDeque[index]);
}
}
}
/// <summary>
/// Verifies that an exception is thrown if an incompatible object is inserted
/// into the deque
/// </summary>
[Test]
public void TestThrowOnInsertIncompatibleObject() {
Deque<int> intDeque = createDeque(12);
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque).Insert(8, "Hello World"); }
);
}
/// <summary>Validates that the IsFixedObject property is set to false</summary>
[Test]
public void TestIsFixedObject() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsFixedSize);
}
/// <summary>Validates that the IsSynchronized property is set to false</summary>
[Test]
public void TestIsSynchronized() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsSynchronized);
}
/// <summary>
/// Verifies that items can be assigned by their index using the non-typesafe
/// IList interface
/// </summary>
[Test]
public void TestObjectIndexAssignment() {
Deque<int> intDeque = createDeque(32);
((IList)intDeque)[16] = 12345;
((IList)intDeque)[17] = 54321;
Assert.AreEqual(12345, ((IList)intDeque)[16]);
Assert.AreEqual(54321, ((IList)intDeque)[17]);
}
/// <summary>
/// Tests whether an exception is thrown if an incompatible object is assigned
/// to the deque
/// </summary>
[Test]
public void TestIncompatibleObjectIndexAssignment() {
Deque<int> intDeque = createDeque(2);
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque)[0] = "Hello World"; }
);
}
/// <summary>Verifies that the Remove() method is working correctly</summary>
[Test]
public void TestRemove() {
Deque<int> intDeque = createDeque(16);
Assert.AreEqual(16, intDeque.Count);
Assert.IsTrue(intDeque.Remove(13));
Assert.IsFalse(intDeque.Remove(13));
Assert.AreEqual(15, intDeque.Count);
}
/// <summary>Tests the non-typesafe remove method</summary>
[Test]
public void TestRemoveObject() {
Deque<int> intDeque = createDeque(10);
Assert.IsTrue(intDeque.Contains(8));
Assert.AreEqual(10, intDeque.Count);
((IList)intDeque).Remove(8);
Assert.IsFalse(intDeque.Contains(8));
Assert.AreEqual(9, intDeque.Count);
}
/// <summary>
/// Tests the non-typesafe remove method used to remove an incompatible object
/// </summary>
[Test]
public void TestRemoveIncompatibleObject() {
Deque<int> intDeque = createDeque(10);
((IList)intDeque).Remove("Hello World"); // should simply do nothing
Assert.AreEqual(10, intDeque.Count);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
Deque<int> intDeque = new Deque<int>();
if(!(intDeque as ICollection).IsSynchronized) {
lock((intDeque as ICollection).SyncRoot) {
Assert.AreEqual(0, intDeque.Count);
}
}
}
/// <summary>
/// Validates that the IsReadOnly property of the deque returns false
/// </summary>
[Test]
public void TestIsReadOnly() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsReadOnly);
Assert.IsFalse(((ICollection<int>)intDeque).IsReadOnly);
}
/// <summary>Tests the non-typesafe CopyTo() method</summary>
[Test]
public void TestCopyToObjectArray() {
Deque<int> intDeque = createNonNormalizedDeque(40);
int[] array = new int[40];
((ICollection)intDeque).CopyTo(array, 0);
Assert.AreEqual(intDeque, array);
}
/// <summary>Tests the CopyTo() method</summary>
[Test]
public void TestCopyToArray() {
Deque<int> intDeque = createDeque(12);
intDeque.RemoveFirst();
intDeque.RemoveFirst();
int[] array = new int[14];
intDeque.CopyTo(array, 4);
Assert.AreEqual(
new int[] { 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 },
array
);
}
/// <summary>
/// Verifies that the non-typesafe CopyTo() method throws an exception if
/// the array is of an incompatible type
/// </summary>
[Test]
public void TestThrowOnCopyToIncompatibleObjectArray() {
Deque<int> intDeque = createDeque(4);
short[] array = new short[4];
Assert.Throws<ArgumentException>(
delegate() { ((ICollection)intDeque).CopyTo(array, 4); }
);
}
/// <summary>
/// Verifies that the CopyTo() method throws an exception if the target array
/// is too small
/// </summary>
[Test]
public void TestThrowOnCopyToTooSmallArray() {
Deque<int> intDeque = createDeque(8);
Assert.Throws<ArgumentException>(
delegate() { intDeque.CopyTo(new int[7], 0); }
);
}
#if DEBUG
/// <summary>
/// Tests whether the deque enumerator detects when it runs out of sync
/// </summary>
[Test]
public void TestInvalidatedEnumeratorDetection() {
Deque<int> intDeque = createDeque(8);
using(IEnumerator<int> enumerator = intDeque.GetEnumerator()) {
Assert.IsTrue(enumerator.MoveNext());
intDeque.AddFirst(12345);
Assert.Throws<InvalidOperationException>(
delegate() { enumerator.MoveNext(); }
);
}
}
#endif
/// <summary>
/// Creates a deque whose first element does not coincide with a block boundary
/// </summary>
/// <param name="count">Number of items the deque will be filled with</param>
/// <returns>The newly created deque</returns>
private static Deque<int> createNonNormalizedDeque(int count) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 4; item < count; ++item) {
intDeque.AddLast(item);
}
if(count > 3) { intDeque.AddFirst(3); }
if(count > 2) { intDeque.AddFirst(2); }
if(count > 1) { intDeque.AddFirst(1); }
if(count > 0) { intDeque.AddFirst(0); }
return intDeque;
}
/// <summary>Creates a deque filled with the specified number of items
/// </summary>
/// <param name="count">Number of items the deque will be filled with</param>
/// <returns>The newly created deque</returns>
private static Deque<int> createDeque(int count) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < count; ++item) {
intDeque.AddLast(item);
}
return intDeque;
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,136 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the IList extension methods</summary>
[TestFixture]
internal class IListExtensionsTest {
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
[Test]
public void InsertionSortCanSortWholeList() {
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
var testListAsIList = (IList<int>)testList;
testListAsIList.InsertionSort();
CollectionAssert.AreEqual(
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
testList
);
}
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
[Test]
public void InsertionSortCanSortBigList() {
const int ListSize = 16384;
var testList = new List<int>(capacity: ListSize);
{
var random = new Random();
for(int index = 0; index < ListSize; ++index) {
testList.Add(random.Next());
}
}
var testListAsIList = (IList<int>)testList;
testListAsIList.InsertionSort();
for(int index = 1; index < ListSize; ++index) {
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
}
}
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
[Test]
public void InsertionSortCanSortListSegment() {
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testListAsIList = (IList<int>)testList;
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
CollectionAssert.AreEqual(
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
testList
);
}
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
[Test]
public void QuickSortCanSortWholeList() {
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
var testListAsIList = (IList<int>)testList;
testListAsIList.QuickSort();
CollectionAssert.AreEqual(
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
testList
);
}
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
[Test]
public void QuickSortCanSortBigList() {
const int ListSize = 16384;
var testList = new List<int>(capacity: ListSize);
{
var random = new Random();
for(int index = 0; index < ListSize; ++index) {
testList.Add(random.Next());
}
}
var testListAsIList = (IList<int>)testList;
testListAsIList.QuickSort();
for(int index = 1; index < ListSize; ++index) {
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
}
}
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
[Test]
public void QuickSortCanSortListSegment() {
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testListAsIList = (IList<int>)testList;
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
CollectionAssert.AreEqual(
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
testList
);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,55 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the item event argument container</summary>
[TestFixture]
internal class ItemEventArgsTest {
/// <summary>
/// Tests whether an integer argument can be stored in the argument container
/// </summary>
[Test]
public void IntegersCanBeCarried() {
var test = new ItemEventArgs<int>(12345);
Assert.AreEqual(12345, test.Item);
}
/// <summary>
/// Tests whether a string argument can be stored in the argument container
/// </summary>
[Test]
public void StringsCanBeCarried() {
var test = new ItemEventArgs<string>("hello world");
Assert.AreEqual("hello world", test.Item);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,57 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the item event argument container</summary>
[TestFixture]
internal class ItemReplaceEventArgsTest {
/// <summary>
/// Tests whether an integer argument can be stored in the argument container
/// </summary>
[Test]
public void IntegersCanBeCarried() {
var test = new ItemReplaceEventArgs<int>(12345, 54321);
Assert.AreEqual(12345, test.OldItem);
Assert.AreEqual(54321, test.NewItem);
}
/// <summary>
/// Tests whether a string argument can be stored in the argument container
/// </summary>
[Test]
public void StringsCanBeCarried() {
var test = new ItemReplaceEventArgs<string>("hello", "world");
Assert.AreEqual("hello", test.OldItem);
Assert.AreEqual("world", test.NewItem);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,256 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the list segment class</summary>
[TestFixture]
internal class ListSegmentTest {
/// <summary>
/// Tests whether the default constructor of the ListSegment class throws the
/// right exception when being passed 'null' instead of a list
/// </summary>
[Test]
public void SimpleConstructorThrowsWhenListIsNull() {
Assert.Throws<ArgumentNullException>(
delegate() { new ListSegment<int>(null); }
);
}
/// <summary>
/// Tests whether the simple constructor of the ListSegment class accepts
/// an empty list
/// </summary>
[Test]
public void SimpleConstructorAcceptsEmptyList() {
new ListSegment<int>(new List<int>());
}
/// <summary>
/// Tests whether the full constructor of the ListSegment class throws the
/// right exception when being passed 'null' instead of a string
/// </summary>
[Test]
public void ConstructorThrowsWhenListIsNull() {
Assert.Throws<ArgumentNullException>(
delegate() { new ListSegment<int>(null, 0, 0); }
);
}
/// <summary>
/// Tests whether the full constructor of the ListSegment class accepts
/// an empty string
/// </summary>
[Test]
public void ConstructorAcceptsEmptyList() {
new ListSegment<int>(new List<int>(), 0, 0);
}
/// <summary>
/// Tests whether the full constructor of the ListSegment class throws the
/// right exception when being passed an invalid start offset
/// </summary>
[Test]
public void ConstructorThrowsOnInvalidOffset() {
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
);
}
/// <summary>
/// Tests whether the full constructor of the ListSegment class throws the
/// right exception when being passed an invalid element count
/// </summary>
[Test]
public void ConstructorThrowsOnInvalidCount() {
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
);
}
/// <summary>
/// Tests whether the full constructor of the ListSegment class throws the
/// right exception when being passed a string length that's too large
/// </summary>
[Test]
public void ConstructorThrowsOnListOverrun() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
Assert.Throws<ArgumentException>(
delegate() { new ListSegment<int>(testList, 3, 3); }
);
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
[Test]
public void ListPropertyStoresOriginalList() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
Assert.AreSame(testList, testSegment.List);
}
/// <summary>Tests whether the 'Offset' property works as expected</summary>
[Test]
public void OffsetPropertyIsStored() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
Assert.AreEqual(1, testSegment.Offset);
}
/// <summary>Tests whether the 'Count' property works as expected</summary>
[Test]
public void CountPropertyIsStored() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
Assert.AreEqual(3, testSegment.Count);
}
/// <summary>
/// Tests whether two differing instances produce different hash codes
/// </summary>
[Test]
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
var forwardCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var reverseCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
);
Assert.AreNotEqual(
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
);
}
/// <summary>
/// Tests whether two equivalent instances produce an identical hash code
/// </summary>
[Test]
public void EquivalentInstancesHaveSameHashcode() {
var testSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var identicalSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
Assert.AreEqual(
testSegment.GetHashCode(), identicalSegment.GetHashCode()
);
}
/// <summary>Tests the equals method performing a comparison against null</summary>
[Test]
public void EqualsAgainstNullIsAlwaysFalse() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
Assert.IsFalse(
testSegment.Equals(null)
);
}
/// <summary>Tests the equality operator with differing instances</summary>
[Test]
public void DifferingInstancesAreNotEqual() {
var forwardCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var reverseCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
);
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
}
/// <summary>Tests the equality operator with equivalent instances</summary>
[Test]
public void EquivalentInstancesAreEqual() {
var testSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var identicalSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
Assert.IsTrue(testSegment == identicalSegment);
}
/// <summary>Tests the inequality operator with differing instances</summary>
[Test]
public void DifferingInstancesAreUnequal() {
var forwardCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var reverseCountSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
);
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
}
/// <summary>Tests the inequality operator with equivalent instances</summary>
[Test]
public void EquivalentInstancesAreNotUnequal() {
var testSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
var identicalSegment = new ListSegment<int>(
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
);
Assert.IsFalse(testSegment != identicalSegment);
}
/// <summary>Tests the ToString() method of the string segment</summary>
[Test]
public void TestToString() {
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
string stringRepresentation = testSegment.ToString();
StringAssert.Contains("3, 4", stringRepresentation);
StringAssert.DoesNotContain("2", stringRepresentation);
StringAssert.DoesNotContain("5", stringRepresentation);
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
[Test]
public void ToListReturnsSubset() {
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
CollectionAssert.AreEqual(
new List<int>(capacity: 3) { 2, 3, 4 },
testSegment.ToList()
);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,390 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit tests for the multi dictionary</summary>
[TestFixture]
internal class MultiDictionaryTest {
/// <summary>
/// Verifies that new instances of the multi dictionary can be created
/// </summary>
[Test]
public void CanConstructNewDictionary() {
var dictionary = new MultiDictionary<int, string>();
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
}
/// <summary>
/// Verifies that the count is initialized correctly when building
/// a multi dictionary from a dictionary of value collections.
/// </summary>
[Test]
public void CountIsCalculatedIfInitializedFromDictionary() {
var contents = new Dictionary<int, ICollection<string>>();
contents.Add(1, new List<string>(new string[] { "one", "eins" }));
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
var multiDictionary = new MultiDictionary<int, string>(contents);
Assert.AreEqual(4, multiDictionary.Count);
}
/// <summary>
/// Verifies that a new multi dictionary based on a read-only dictionary is
/// also read-only
/// </summary>
[Test]
public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
new Dictionary<int, ICollection<string>>()
);
var dictionary = new MultiDictionary<int, string>(readOnly);
Assert.IsTrue(dictionary.IsReadOnly);
}
/// <summary>
/// Ensures that the multi dictionary can contain the same key multiple times
/// (or in other words, multiple values on the same key)
/// </summary>
[Test]
public void CanContainKeyMultipleTimes() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(123, "one two three");
dictionary.Add(123, "eins zwei drei");
Assert.AreEqual(2, dictionary.Count);
CollectionAssert.AreEquivalent(
new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>(123, "one two three"),
new KeyValuePair<int, string>(123, "eins zwei drei")
},
dictionary
);
}
/// <summary>
/// Verifies that adding values through the indexer still updates the item count
/// </summary>
[Test]
public void AddingValuesFromIndexerUpdatesCount() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(42, "the answer to everything");
dictionary[42].Add("21x2");
Assert.AreEqual(2, dictionary.Count);
CollectionAssert.AreEquivalent(
new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>(42, "the answer to everything"),
new KeyValuePair<int, string>(42, "21x2")
},
dictionary
);
}
/// <summary>
/// Tests whether the collection can count the number of values stored
/// under a key
/// </summary>
[Test]
public void ValuesWithSameKeyCanBeCounted() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(20, "twenty");
dictionary.Add(30, "thirty");
dictionary.Add(10, "zehn");
dictionary.Add(20, "zwanzig");
dictionary.Add(10, "dix");
Assert.AreEqual(6, dictionary.Count);
Assert.AreEqual(3, dictionary.CountValues(10));
Assert.AreEqual(2, dictionary.CountValues(20));
Assert.AreEqual(1, dictionary.CountValues(30));
}
/// <summary>
/// Verifies that counting the values of a non-existing key returns 0
/// </summary>
[Test]
public void CountingValuesOfNonExistentKeyReturnsNull() {
var dictionary = new MultiDictionary<int, string>();
Assert.AreEqual(0, dictionary.CountValues(1));
}
/// <summary>
/// Ensures that its possible to remove values individually without affecting
/// other values stored under the same key
/// </summary>
[Test]
public void ValuesCanBeRemovedIndividually() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
dictionary.Add(10, "dix");
dictionary.Remove(10, "zehn");
Assert.AreEqual(2, dictionary.Count);
CollectionAssert.AreEquivalent(
new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>(10, "ten"),
new KeyValuePair<int, string>(10, "dix")
},
dictionary
);
}
/// <summary>
/// Verifies that the Count property returns the number of unique keys if it is called
/// on the collection-of-collections interface implemented by the multi dictionary
/// </summary>
[Test]
public void CollectionOfCollectionCountIsUniqueKeyCount() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
Assert.AreEqual(2, dictionary.Count);
var collectionOfCollections =
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
Assert.AreEqual(1, collectionOfCollections.Count);
}
/// <summary>
/// Verifies that the multi dictionary can be tested for containment of a specific value
/// </summary>
[Test]
public void ContainmentCanBeTested() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
}
/// <summary>
/// Verifies that the multi dictionary can be tested for containment of a specific key
/// </summary>
[Test]
public void KeyContainmentCanBeTested() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
Assert.IsTrue(dictionary.ContainsKey(10));
Assert.IsFalse(dictionary.ContainsKey(20));
}
/// <summary>
/// Verifies that the key collection can be retrieved from the dictionary
/// </summary>
[Test]
public void KeyCollectionCanBeRetrieved() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
ICollection<int> keys = dictionary.Keys;
Assert.IsNotNull(keys);
Assert.AreEqual(1, keys.Count);
}
/// <summary>
/// Verifies that the key collection can be retrieved from the dictionary
/// </summary>
[Test]
public void ValueCollectionCanBeRetrieved() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
dictionary.Add(20, "twenty");
ICollection<string> values = dictionary.Values;
Assert.IsNotNull(values);
Assert.AreEqual(3, values.Count);
}
/// <summary>
/// Verifies that TryGetValue() returns false and doesn't throw if a key
/// is not found in the collection
/// </summary>
[Test]
public void TryGetValueReturnsFalseOnMissingKey() {
var dictionary = new MultiDictionary<int, string>();
ICollection<string> values;
Assert.IsFalse(dictionary.TryGetValue(123, out values));
}
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
[Test]
public void TryGetValueCanLookUpValues() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
ICollection<string> values;
Assert.IsTrue(dictionary.TryGetValue(10, out values));
Assert.AreEqual(2, values.Count);
}
/// <summary>
/// Verifies that assigning null to a key deletes all the values stored
/// under it
/// </summary>
[Test]
public void AssigningNullToKeyRemovesAllValues() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
dictionary.Add(20, "twenty");
Assert.AreEqual(3, dictionary.Count);
dictionary[10] = null;
Assert.AreEqual(1, dictionary.Count);
Assert.IsFalse(dictionary.ContainsKey(10));
}
/// <summary>
/// Verifies that assigning null to a key deletes all the values stored
/// under it
/// </summary>
[Test]
public void ValueListCanBeAssignedToNewKey() {
var dictionary = new MultiDictionary<int, string>();
dictionary[3] = new List<string>() { "three", "drei" };
Assert.AreEqual(2, dictionary.Count);
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
}
/// <summary>
/// Verifies that assigning null to a key deletes all the values stored
/// under it
/// </summary>
[Test]
public void ValueListCanOverwriteExistingKey() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "dix");
Assert.AreEqual(1, dictionary.Count);
dictionary[10] = new List<string>() { "ten", "zehn" };
Assert.AreEqual(2, dictionary.Count);
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
}
/// <summary>
/// Verifies that nothing bad happens when a key is removed from the dictionary
/// that it doesn't contain
/// </summary>
[Test]
public void NonExistingKeyCanBeRemoved() {
var dictionary = new MultiDictionary<int, string>();
Assert.AreEqual(0, dictionary.RemoveKey(123));
}
/// <summary>
/// Verifies that the remove method returns the number of values that have
/// been removed from the dictionary
/// </summary>
[Test]
public void RemoveReturnsNumberOfValuesRemoved() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
Assert.AreEqual(2, dictionary.RemoveKey(10));
}
/// <summary>
/// Verifies that the dictionary becomes empty after clearing it
/// </summary>
[Test]
public void DictionaryIsEmptyAfterClear() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(10, "ten");
dictionary.Add(10, "zehn");
dictionary.Add(20, "twenty");
Assert.AreEqual(3, dictionary.Count);
dictionary.Clear();
Assert.AreEqual(0, dictionary.Count);
}
/// <summary>
/// Verifies that non-existing values can be removed from the dictionary
/// </summary>
[Test]
public void NonExistingValueCanBeRemoved() {
var dictionary = new MultiDictionary<int, string>();
Assert.IsFalse(dictionary.Remove(123, "test"));
}
/// <summary>
/// Verifies that nothing bad happens when the last value under a key is removed
/// </summary>
[Test]
public void LastValueOfKeyCanBeRemoved() {
var dictionary = new MultiDictionary<int, string>();
dictionary.Add(123, "test");
dictionary.Remove(123, "test");
Assert.AreEqual(0, dictionary.CountValues(123));
}
/// <summary>
/// Verifies that the dictionary can be copied into an array
/// </summary>
[Test]
public void DictionaryCanBeCopiedIntoArray() {
var expected = new List<KeyValuePair<int, string>>() {
new KeyValuePair<int, string>(1, "one"),
new KeyValuePair<int, string>(1, "eins"),
new KeyValuePair<int, string>(2, "two"),
new KeyValuePair<int, string>(2, "zwei")
};
var dictionary = new MultiDictionary<int, string>();
foreach(KeyValuePair<int, string> entry in expected) {
dictionary.Add(entry.Key, entry.Value);
}
var actual = new KeyValuePair<int, string>[4];
dictionary.CopyTo(actual, 0);
CollectionAssert.AreEquivalent(expected, actual);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,144 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_NMOCK
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable collection class</summary>
[TestFixture]
internal class ObservableCollectionTest {
#region interface IObservableCollectionSubscriber
/// <summary>Interface used to test the observable collection</summary>
public interface IObservableCollectionSubscriber {
/// <summary>Called when the collection is about to clear its contents</summary>
/// <param name="sender">Collection that is clearing its contents</param>
/// <param name="arguments">Not used</param>
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the collection has been cleared of its contents</summary>
/// <param name="sender">Collection that was cleared of its contents</param>
/// <param name="arguments">Not used</param>
void Cleared(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the collection</summary>
/// <param name="sender">Collection to which an item is being added</param>
/// <param name="arguments">Contains the item that is being added</param>
void ItemAdded(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is removed from the collection</summary>
/// <param name="sender">Collection from which an item is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param>
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
}
#endregion // interface IObservableCollectionSubscriber
/// <summary>Initialization routine executed before each test is run</summary>
[SetUp]
public void Setup() {
this.mockery = new MockFactory();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.observedCollection = new ObservableCollection<int>();
this.observedCollection.Clearing += new EventHandler(
this.mockedSubscriber.MockObject.Clearing
);
this.observedCollection.Cleared += new EventHandler(
this.mockedSubscriber.MockObject.Cleared
);
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
this.mockedSubscriber.MockObject.ItemAdded
);
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
this.mockedSubscriber.MockObject.ItemRemoved
);
}
/// <summary>Tests whether the Clearing event is fired</summary>
[Test]
public void TestClearingEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
this.observedCollection.Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether the ItemAdded event is fired</summary>
[Test]
public void TestItemAddedEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedCollection.Add(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether the ItemRemoved event is fired</summary>
[Test]
public void TestItemRemovedEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedCollection.Add(123);
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.observedCollection.Remove(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether a the list constructor is working</summary>
[Test]
public void TestListConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
var testCollection = new ObservableCollection<int>(integers);
CollectionAssert.AreEqual(integers, testCollection);
}
/// <summary>Mock object factory</summary>
private MockFactory mockery;
/// <summary>The mocked observable collection subscriber</summary>
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
/// <summary>An observable collection to which a mock will be subscribed</summary>
private ObservableCollection<int> observedCollection;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_NMOCK

View file

@ -0,0 +1,589 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_NMOCK
#if UNITTEST
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable dictionary wrapper</summary>
[TestFixture]
internal class ObservableDictionaryTest {
#region interface IObservableDictionarySubscriber
/// <summary>Interface used to test the observable dictionary</summary>
public interface IObservableDictionarySubscriber {
/// <summary>Called when the dictionary is about to clear its contents</summary>
/// <param name="sender">Dictionary that is clearing its contents</param>
/// <param name="arguments">Not used</param>
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the dictionary has been clear of its contents</summary>
/// <param name="sender">Dictionary that was cleared of its contents</param>
/// <param name="arguments">Not used</param>
void Cleared(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the dictionary</summary>
/// <param name="sender">Dictionary to which an item is being added</param>
/// <param name="arguments">Contains the item that is being added</param>
void ItemAdded(object sender, ItemEventArgs<KeyValuePair<int, string>> arguments);
/// <summary>Called when an item is removed from the dictionary</summary>
/// <param name="sender">Dictionary from which an item is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param>
void ItemRemoved(object sender, ItemEventArgs<KeyValuePair<int, string>> arguments);
/// <summary>Called when an item is replaced in the dictionary</summary>
/// <param name="sender">Dictionary in which an item is being replaced</param>
/// <param name="arguments">Contains the replaced item and its replacement</param>
void ItemReplaced(
object sender, ItemReplaceEventArgs<KeyValuePair<int, string>> arguments
);
}
#endregion // interface IObservableDictionarySubscriber
/// <summary>Initialization routine executed before each test is run</summary>
[SetUp]
public void Setup() {
this.mockery = new MockFactory();
this.mockedSubscriber = this.mockery.CreateMock<IObservableDictionarySubscriber>();
this.observedDictionary = new ObservableDictionary<int, string>();
this.observedDictionary.Add(1, "one");
this.observedDictionary.Add(2, "two");
this.observedDictionary.Add(3, "three");
this.observedDictionary.Add(42, "forty-two");
this.observedDictionary.Clearing +=
new EventHandler(this.mockedSubscriber.MockObject.Clearing);
this.observedDictionary.Cleared +=
new EventHandler(this.mockedSubscriber.MockObject.Cleared);
this.observedDictionary.ItemAdded +=
new EventHandler<ItemEventArgs<KeyValuePair<int, string>>>(
this.mockedSubscriber.MockObject.ItemAdded
);
this.observedDictionary.ItemRemoved +=
new EventHandler<ItemEventArgs<KeyValuePair<int, string>>>(
this.mockedSubscriber.MockObject.ItemRemoved
);
this.observedDictionary.ItemReplaced +=
new EventHandler<ItemReplaceEventArgs<KeyValuePair<int, string>>>(
this.mockedSubscriber.MockObject.ItemReplaced
);
}
/// <summary>
/// Verifies that the default constructor of the observable dictionary works
/// </summary>
[Test]
public void TestDefaultConstructor() {
ObservableDictionary<int, string> testDictionary =
new ObservableDictionary<int, string>();
Assert.AreEqual(0, testDictionary.Count);
}
/// <summary>
/// Verifies that the copy constructor of the observable dictionary works
/// </summary>
[Test]
public void TestCopyConstructor() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
CollectionAssert.AreEqual(numbers, testDictionary);
}
/// <summary>Verifies that the IsReadOnly property is working</summary>
[Test]
public void TestIsReadOnly() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsFalse(testDictionary.IsReadOnly);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains an item
/// </summary>
[Test]
public void TestContains() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsTrue(
testDictionary.Contains(new KeyValuePair<int, string>(42, "forty-two"))
);
Assert.IsFalse(
testDictionary.Contains(new KeyValuePair<int, string>(24, "twenty-four"))
);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains a key
/// </summary>
[Test]
public void TestContainsKey() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsTrue(testDictionary.ContainsKey(42));
Assert.IsFalse(testDictionary.ContainsKey(24));
}
/// <summary>
/// Verifies that the CopyTo() of the observable dictionary works
/// </summary>
[Test]
public void TestCopyToArray() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
testDictionary.CopyTo(items, 0);
CollectionAssert.AreEqual(numbers, items);
}
/// <summary>
/// Tests whether the typesafe enumerator of the observable dictionary is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
List<KeyValuePair<int, string>> outputItems = new List<KeyValuePair<int, string>>();
foreach(KeyValuePair<int, string> item in testDictionary) {
outputItems.Add(item);
}
CollectionAssert.AreEqual(numbers, outputItems);
}
/// <summary>
/// Tests whether the keys collection of the observable dictionary can be queried
/// </summary>
[Test]
public void TestGetKeysCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
ICollection<int> inputKeys = numbers.Keys;
ICollection<int> keys = testDictionary.Keys;
CollectionAssert.AreEquivalent(inputKeys, keys);
}
/// <summary>
/// Tests whether the values collection of the observable dictionary can be queried
/// </summary>
[Test]
public void TestGetValuesCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
ICollection<string> inputValues = numbers.Values;
ICollection<string> values = testDictionary.Values;
CollectionAssert.AreEquivalent(inputValues, values);
}
/// <summary>
/// Tests whether the TryGetValue() method of the observable dictionary is working
/// </summary>
[Test]
public void TestTryGetValue() {
string value;
Assert.IsTrue(this.observedDictionary.TryGetValue(42, out value));
Assert.AreEqual("forty-two", value);
Assert.IsFalse(this.observedDictionary.TryGetValue(24, out value));
Assert.AreEqual(null, value);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the observable
/// dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexer() {
Assert.AreEqual("forty-two", this.observedDictionary[42]);
}
/// <summary>
/// Tests whether an exception is thrown if the indexer of the observable dictionary
/// is used to attempt to retrieve a non-existing value
/// </summary>
[Test]
public void TestRetrieveNonExistingValueByIndexer() {
Assert.Throws<KeyNotFoundException>(
delegate() { Console.WriteLine(this.observedDictionary[24]); }
);
}
/// <summary>
/// Checks whether the Add() methods works via the generic
/// IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestAddViaGenericIDictionary() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>).Add(10, "ten");
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.Contains(
this.observedDictionary, new KeyValuePair<int, string>(10, "ten")
);
}
/// <summary>
/// Checks whether the Remove() method works via the generic
/// IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestRemoveViaGenericIDictionary() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>).Remove(3);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.DoesNotContain(this.observedDictionary.Keys, 3);
}
/// <summary>
/// Tests whether the TryGetValue() method of the observable dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaGenericIDictionary() {
Assert.AreEqual(
"forty-two", (this.observedDictionary as IDictionary<int, string>)[42]
);
}
/// <summary>
/// Verifies that the indexer can be used to insert an item via the generic
/// IDictionar&lt;&gt; interface
/// </summary>
[Test]
public void TestReplaceByIndexerViaGenericIDictionary() {
this.mockedSubscriber.Expects.One.Method(
m => m.ItemReplaced(null, null)
).WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>)[42] = "two and fourty";
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual("two and fourty", this.observedDictionary[42]);
}
/// <summary>
/// Checks whether the Clear() method of observable dictionary is working
/// </summary>
[Test]
public void TestClearViaIDictionary() {
this.mockedSubscriber.Expects.One.Method(
m => m.Clearing(null, null)
).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(
m => m.Cleared(null, null)
).WithAnyArguments();
(this.observedDictionary as IDictionary).Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual(0, this.observedDictionary.Count);
}
/// <summary>
/// Checks whether the Add() method works via the IDictionary interface
/// </summary>
[Test]
public void TestAddViaIDictionary() {
this.mockedSubscriber.Expects.One.Method(
m => m.ItemAdded(null, null)
).WithAnyArguments();
(this.observedDictionary as IDictionary).Add(24, "twenty-four");
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.Contains(
this.observedDictionary, new KeyValuePair<int, string>(24, "twenty-four")
);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains an item via the IDictionary interface
/// </summary>
[Test]
public void TestContainsViaIDictionary() {
Assert.IsTrue((this.observedDictionary as IDictionary).Contains(42));
Assert.IsFalse((this.observedDictionary as IDictionary).Contains(24));
}
/// <summary>
/// Checks whether the GetEnumerator() method of the observable dictionary
/// returns a working enumerator if accessed via the IDictionary interface
/// </summary>
[Test]
public void TestEnumeratorViaIDictionary() {
Dictionary<int, string> outputNumbers = new Dictionary<int, string>();
foreach(DictionaryEntry entry in (this.observedDictionary as IDictionary)) {
(outputNumbers as IDictionary).Add(entry.Key, entry.Value);
}
CollectionAssert.AreEquivalent(this.observedDictionary, outputNumbers);
}
/// <summary>
/// Checks whether the IsFixedSize property of the observable dictionary returns
/// the expected result for a read only dictionary based on a dynamic dictionary
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
Assert.IsFalse((this.observedDictionary as IDictionary).IsFixedSize);
}
/// <summary>
/// Tests whether the keys collection of the observable dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetKeysCollectionViaIDictionary() {
ICollection keys = (this.observedDictionary as IDictionary).Keys;
Assert.AreEqual(this.observedDictionary.Count, keys.Count);
}
/// <summary>
/// Tests whether the values collection of the observable dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetValuesCollectionViaIDictionary() {
ICollection values = (this.observedDictionary as IDictionary).Values;
Assert.AreEqual(this.observedDictionary.Count, values.Count);
}
/// <summary>
/// Checks whether Remove() method works via the IDictionary interface
/// </summary>
[Test]
public void TestRemoveViaIDictionary() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
(this.observedDictionary as IDictionary).Remove(3);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.DoesNotContain(this.observedDictionary.Keys, 3);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the observable
/// dictionary is working via the IDictionary interface
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaIDictionary() {
Assert.AreEqual("forty-two", (this.observedDictionary as IDictionary)[42]);
}
/// <summary>
/// Verifies the indexer can be used to insert an item via the IDictionary interface
/// </summary>
[Test]
public void TestReplaceByIndexerViaIDictionary() {
this.mockedSubscriber.Expects.One.Method(
m => m.ItemRemoved(null, null)
).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(
m => m.ItemAdded(null, null)
).WithAnyArguments();
(this.observedDictionary as IDictionary)[42] = "two and fourty";
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual("two and fourty", this.observedDictionary[42]);
}
/// <summary>
/// Checks whether Add() method is working via the generic
/// ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestAddViaGenericICollection() {
this.mockedSubscriber.Expects.One.Method(
m => m.ItemAdded(null, null)
).WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Add(
new KeyValuePair<int, string>(24, "twenty-four")
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.Contains(
this.observedDictionary, new KeyValuePair<int, string>(24, "twenty-four")
);
}
/// <summary>
/// Checks whether the Clear() method is working via the generic
/// ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestClearViaGenericICollection() {
this.mockedSubscriber.Expects.One.Method(
m => m.Clearing(null, null)
).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(
m => m.Cleared(null, null)
).WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual(0, this.observedDictionary.Count);
}
/// <summary>
/// Checks whether the Remove() method is working via the
/// generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestRemoveViaGenericICollection() {
IEnumerator<KeyValuePair<int, string>> enumerator =
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).GetEnumerator();
enumerator.MoveNext();
this.mockedSubscriber.Expects.One.Method(
m => m.ItemRemoved(null, null)
).WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Remove(
enumerator.Current
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.DoesNotContain(this.observedDictionary, enumerator.Current);
}
/// <summary>
/// Verifies that the CopyTo() of the observable dictionary works when called
/// via the the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
DictionaryEntry[] entries = new DictionaryEntry[numbers.Count];
(testDictionary as ICollection).CopyTo(entries, 0);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
for(int index = 0; index < entries.Length; ++index) {
items[index] = new KeyValuePair<int, string>(
(int)entries[index].Key, (string)entries[index].Value
);
}
CollectionAssert.AreEquivalent(numbers, items);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
if(!(testDictionary as ICollection).IsSynchronized) {
lock((testDictionary as ICollection).SyncRoot) {
Assert.AreEqual(numbers.Count, testDictionary.Count);
}
}
}
/// <summary>
/// Test whether the observable dictionary can be serialized
/// </summary>
[Test]
public void TestSerialization() {
BinaryFormatter formatter = new BinaryFormatter();
using(MemoryStream memory = new MemoryStream()) {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary1 = makeObservable(numbers);
formatter.Serialize(memory, testDictionary1);
memory.Position = 0;
object testDictionary2 = formatter.Deserialize(memory);
CollectionAssert.AreEquivalent(testDictionary1, (IEnumerable)testDictionary2);
}
}
/// <summary>
/// Creates a new observable dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created observable dictionary</returns>
private static Dictionary<int, string> createTestDictionary() {
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
numbers.Add(3, "three");
numbers.Add(42, "forty-two");
return new Dictionary<int, string>(numbers);
}
/// <summary>
/// Creates a new observable dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created observable dictionary</returns>
private static ObservableDictionary<int, string> makeObservable(
IDictionary<int, string> dictionary
) {
return new ObservableDictionary<int, string>(dictionary);
}
/// <summary>Mock object factory</summary>
private MockFactory mockery;
/// <summary>The mocked observable collection subscriber</summary>
private Mock<IObservableDictionarySubscriber> mockedSubscriber;
/// <summary>An observable dictionary to which a mock will be subscribed</summary>
private ObservableDictionary<int, string> observedDictionary;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_NMOCK

View file

@ -0,0 +1,175 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_NMOCK
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable list class</summary>
[TestFixture]
internal class ObservableListTest {
#region interface IObservableCollectionSubscriber
/// <summary>Interface used to test the observable collection</summary>
public interface IObservableCollectionSubscriber {
/// <summary>Called when the collection is about to clear its contents</summary>
/// <param name="sender">Collection that is clearing its contents</param>
/// <param name="arguments">Not used</param>
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the collection has been cleared of its contents</summary>
/// <param name="sender">Collection that was cleared of its contents</param>
/// <param name="arguments">Not used</param>
void Cleared(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the collection</summary>
/// <param name="sender">Collection to which an item is being added</param>
/// <param name="arguments">Contains the item that is being added</param>
void ItemAdded(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is removed from the collection</summary>
/// <param name="sender">Collection from which an item is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param>
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is replaced in the dictionary</summary>
/// <param name="sender">Dictionary in which an item is being replaced</param>
/// <param name="arguments">Contains the replaced item and its replacement</param>
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
}
#endregion // interface IObservableCollectionSubscriber
/// <summary>Initialization routine executed before each test is run</summary>
[SetUp]
public void Setup() {
this.mockery = new MockFactory();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.observedList = new ObservableList<int>();
this.observedList.Clearing += new EventHandler(
this.mockedSubscriber.MockObject.Clearing
);
this.observedList.Cleared += new EventHandler(
this.mockedSubscriber.MockObject.Cleared
);
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
this.mockedSubscriber.MockObject.ItemAdded
);
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
this.mockedSubscriber.MockObject.ItemRemoved
);
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
this.mockedSubscriber.MockObject.ItemReplaced
);
}
/// <summary>Tests whether the Clearing event is fired</summary>
[Test]
public void TestClearingEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
this.observedList.Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether the ItemAdded event is fired</summary>
[Test]
public void TestItemAddedEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedList.Add(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether the ItemRemoved event is fired</summary>
[Test]
public void TestItemRemovedEvent() {
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedList.Add(123);
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.observedList.Remove(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether items in the collection can be replaced</summary>
[Test]
public void TestItemReplacement() {
this.mockedSubscriber.Expects.Exactly(3).Method(
m => m.ItemAdded(null, null)
).WithAnyArguments();
this.observedList.Add(1);
this.observedList.Add(2);
this.observedList.Add(3);
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
// Replace the middle item with something else
this.observedList[1] = 4;
Assert.AreEqual(
1, this.observedList.IndexOf(4)
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
}
/// <summary>Tests whether a the list constructor is working</summary>
[Test]
public void TestListConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
var testList = new ObservableList<int>(integers);
CollectionAssert.AreEqual(integers, testList);
}
/// <summary>Mock object factory</summary>
private MockFactory mockery;
/// <summary>The mocked observable collection subscriber</summary>
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
/// <summary>An observable collection to which a mock will be subscribed</summary>
private ObservableList<int> observedList;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_NMOCK

View file

@ -0,0 +1,300 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_SETS
#if UNITTEST
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable set wrapper</summary>
[TestFixture]
internal class ObservableSetTest {
#region interface IObservableCollectionSubscriber<TItem>
public interface IObservableCollectionSubscriber<TItem> {
/// <summary>Called when an item has been added to the collection</summary>
void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
/// <summary>Called when an item is removed from the collection</summary>
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
/// <summary>Called when an item is replaced in the collection</summary>
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
/// <summary>Called when the collection is about to be cleared</summary>
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the collection has been cleared</summary>
void Cleared(object sender, EventArgs arguments);
}
#endregion // interface IObservableCollectionSubscriber<TItem>
/// <summary>Called before each test is run</summary>
[SetUp]
public void Setup() {
this.mockFactory = new MockFactory();
this.observableSet = new ObservableSet<int>();
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
}
/// <summary>Called after each test has run</summary>
[TearDown]
public void Teardown() {
if(this.mockFactory != null) {
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
this.subscriber = null;
this.mockFactory.Dispose();
this.mockFactory = null;
}
}
/// <summary>
/// Verifies that the observable set has a default constructor
/// </summary>
[Test]
public void HasDefaultConstructor() {
Assert.IsNotNull(new ObservableSet<int>());
}
/// <summary>
/// Verifies that adding items to the set triggers the 'ItemAdded' event
/// </summary>
[Test]
public void AddingItemsTriggersEvent() {
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
}
/// <summary>
/// Verifies that removing items from the set triggers the 'ItemRemoved' event
/// </summary>
[Test]
public void RemovingItemsTriggersEvent() {
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.observableSet.Remove(123);
}
/// <summary>
/// Verifies that adding items to the set triggers the 'ItemAdded' event
/// </summary>
[Test]
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
}
/// <summary>
/// Verifies that excepting the set with itself empties the set
/// </summary>
[Test]
public void ExceptWithSelfEmptiesSet() {
this.subscriber.Expects.Exactly(3).Method(
(s) => s.ItemAdded(null, null)
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(2);
this.observableSet.Add(3);
Assert.AreEqual(3, this.observableSet.Count);
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
this.observableSet.ExceptWith(this.observableSet);
Assert.AreEqual(0, this.observableSet.Count);
}
/// <summary>
/// Verifies that a set can be excepted with a collection
/// </summary>
[Test]
public void SetCanBeExceptedWithCollection() {
this.subscriber.Expects.Exactly(2).Method(
(s) => s.ItemAdded(null, null)
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(2);
var collection = new List<int>() { 1 };
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.observableSet.ExceptWith(collection);
Assert.AreEqual(1, this.observableSet.Count);
Assert.IsTrue(this.observableSet.Contains(2));
}
/// <summary>
/// Verifies that a set can be intersected with a collection
/// </summary>
[Test]
public void SetCanBeIntersectedWithCollection() {
this.subscriber.Expects.Exactly(2).Method(
(s) => s.ItemAdded(null, null)
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(2);
var collection = new List<int>() { 1 };
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.observableSet.IntersectWith(collection);
Assert.AreEqual(1, this.observableSet.Count);
Assert.IsTrue(this.observableSet.Contains(1));
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a proper subset
/// or superset of another set
/// </summary>
[Test]
public void CanDetermineProperSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new HashSet<int>() { 1, 3 };
Assert.IsTrue(set1.IsProperSupersetOf(set2));
Assert.IsTrue(set2.IsProperSubsetOf(set1));
set2.Add(2);
Assert.IsFalse(set1.IsProperSupersetOf(set2));
Assert.IsFalse(set2.IsProperSubsetOf(set1));
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a subset
/// or a superset of another set
/// </summary>
[Test]
public void CanDetermineSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new HashSet<int>() { 1, 2, 3 };
Assert.IsTrue(set1.IsSupersetOf(set2));
Assert.IsTrue(set2.IsSubsetOf(set1));
set2.Add(4);
Assert.IsFalse(set1.IsSupersetOf(set2));
Assert.IsFalse(set2.IsSubsetOf(set1));
}
/// <summary>
/// Verifies that a set can determine if another set overlaps with it
/// </summary>
[Test]
public void CanDetermineOverlap() {
var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set2 = new HashSet<int>() { 3 };
Assert.IsTrue(set1.Overlaps(set2));
Assert.IsTrue(set2.Overlaps(set1));
}
/// <summary>
/// Verifies that a set can determine if another set contains the same elements
/// </summary>
[Test]
public void CanDetermineSetEquality() {
var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set2 = new HashSet<int>() { 3, 1, 5 };
Assert.IsTrue(set1.SetEquals(set2));
Assert.IsTrue(set2.SetEquals(set1));
set1.Add(7);
Assert.IsFalse(set1.SetEquals(set2));
Assert.IsFalse(set2.SetEquals(set1));
}
/// <summary>
/// Verifies that a set can be symmetrically excepted with another set
/// </summary>
[Test]
public void CanBeSymmetricallyExcepted() {
var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new HashSet<int>() { 3, 4, 5 };
set1.SymmetricExceptWith(set2);
Assert.AreEqual(4, set1.Count);
}
/// <summary>
/// Verifies that a union of two sets can be built
/// </summary>
[Test]
public void CanBeUnioned() {
this.subscriber.Expects.Exactly(3).Method(
(s) => s.ItemAdded(null, null)
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(2);
this.observableSet.Add(3);
var set2 = new ObservableSet<int>() { 3, 4, 5 };
this.subscriber.Expects.Exactly(2).Method(
(s) => s.ItemAdded(null, null)
).WithAnyArguments();
this.observableSet.UnionWith(set2);
Assert.AreEqual(5, this.observableSet.Count);
}
/// <summary>Creates mock object for the test</summary>
private MockFactory mockFactory;
/// <summary>Observable set being tested</summary>
private ObservableSet<int> observableSet;
/// <summary>Subscriber for the observable set's events</summary>
private Mock<IObservableCollectionSubscriber<int>> subscriber;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_SETS

View file

@ -0,0 +1,150 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the priority queue class</summary>
[TestFixture]
internal class PairPriorityQueueTest {
/// <summary>Tests to ensure the count property is properly updated</summary>
[Test]
public void TestCount() {
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
Assert.AreEqual(0, testQueue.Count);
testQueue.Enqueue(12.34f, "a");
Assert.AreEqual(1, testQueue.Count);
testQueue.Enqueue(56.78f, "b");
Assert.AreEqual(2, testQueue.Count);
testQueue.Dequeue();
Assert.AreEqual(1, testQueue.Count);
testQueue.Enqueue(9.0f, "c");
Assert.AreEqual(2, testQueue.Count);
testQueue.Clear();
Assert.AreEqual(0, testQueue.Count);
}
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
[Test]
public void TestOrdering() {
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(9.0f, "i");
testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(8.0f, "h");
testQueue.Enqueue(3.0f, "c");
testQueue.Enqueue(7.0f, "g");
testQueue.Enqueue(4.0f, "d");
testQueue.Enqueue(6.0f, "f");
testQueue.Enqueue(5.0f, "e");
Assert.AreEqual("i", testQueue.Dequeue().Item);
Assert.AreEqual("h", testQueue.Dequeue().Item);
Assert.AreEqual("g", testQueue.Dequeue().Item);
Assert.AreEqual("f", testQueue.Dequeue().Item);
Assert.AreEqual("e", testQueue.Dequeue().Item);
Assert.AreEqual("d", testQueue.Dequeue().Item);
Assert.AreEqual("c", testQueue.Dequeue().Item);
Assert.AreEqual("b", testQueue.Dequeue().Item);
Assert.AreEqual("a", testQueue.Dequeue().Item);
}
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
[Test]
public void TestPeek() {
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(0.0f, "c");
Assert.AreEqual("b", testQueue.Peek().Item);
}
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
[Test]
public void TestCopyTo() {
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(9.0f, "i");
testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(8.0f, "h");
testQueue.Enqueue(3.0f, "c");
testQueue.Enqueue(7.0f, "g");
testQueue.Enqueue(4.0f, "d");
testQueue.Enqueue(6.0f, "f");
testQueue.Enqueue(5.0f, "e");
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
testQueue.CopyTo(itemArray, 0);
CollectionAssert.AreEquivalent(testQueue, itemArray);
}
/// <summary>
/// Tests whether the priority collection provides a synchronization root
/// </summary>
[Test]
public void TestSyncRoot() {
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
// If IsSynchronized returns true, SyncRoot is allowed to be null
if(!testQueue.IsSynchronized) {
lock(testQueue.SyncRoot) {
testQueue.Clear();
}
}
}
/// <summary>
/// Tests whether the priority collection provides a working type-safe enumerator
/// </summary>
[Test]
public void TestEnumerator() {
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(0.0f, "c");
List<PriorityItemPair<float, string>> testList =
new List<PriorityItemPair<float,string>>();
foreach(PriorityItemPair<float, string> entry in testQueue) {
testList.Add(entry);
}
CollectionAssert.AreEquivalent(testQueue, testList);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,100 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Parentable class</summary>
[TestFixture]
internal class ParentableTest {
#region class TestParentable
/// <summary>Parentable object that can be the child of an int</summary>
private class TestParentable : Parentable<int> {
/// <summary>Initializes a new instance of the parentable test class</summary>
public TestParentable() { }
/// <summary>The parent object that owns this instance</summary>
public int GetParent() {
return base.Parent;
}
/// <summary>Invoked whenever the instance's owner changes</summary>
/// <remarks>
/// When items are parented for the first time, the oldParent argument will
/// be null. Also, if the element is removed from the collection, the
/// current parent will be null.
/// </remarks>
/// <param name="oldParent">Previous owner of the instance</param>
protected override void OnParentChanged(int oldParent) {
this.parentChangedCalled = true;
base.OnParentChanged(oldParent); // to satisfy NCover :-/
}
/// <summary>Whether the OnParentChanged method has been called</summary>
public bool ParentChangedCalled {
get { return this.parentChangedCalled; }
}
/// <summary>Whether the OnParentChanged method has been called</summary>
private bool parentChangedCalled;
}
#endregion // class TestParentable
/// <summary>
/// Tests whether a parent can be assigned and then retrieved from
/// the parentable object
/// </summary>
[Test]
public void TestParentAssignment() {
TestParentable testParentable = new TestParentable();
testParentable.SetParent(12345);
Assert.AreEqual(12345, testParentable.GetParent());
}
/// <summary>
/// Tests whether a parent can be assigned and then retrieved from
/// the parentable object
/// </summary>
[Test]
public void TestParentChangedNotification() {
TestParentable testParentable = new TestParentable();
testParentable.SetParent(12345);
Assert.IsTrue(testParentable.ParentChangedCalled);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,189 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Parenting Collection class</summary>
[TestFixture]
internal class ParentingCollectionTest {
#region class TestParentable
/// <summary>Parentable object that can be the child of an int</summary>
private class TestParentable : Parentable<int>, IDisposable {
/// <summary>Initializes a new instance of the parentable test class</summary>
public TestParentable() { }
/// <summary>The parent object that owns this instance</summary>
public int GetParent() {
return base.Parent;
}
/// <summary>Immediately releases all resources owned by the item</summary>
public void Dispose() {
this.disposeCalled = true;
}
/// <summary>Whether Dispose() has been called on this item</summary>
public bool DisposeCalled {
get { return this.disposeCalled; }
}
/// <summary>Whether Dispose() has been called on this item</summary>
private bool disposeCalled;
}
#endregion // class TestParentable
#region class TestParentingCollection
/// <summary>Parentable object that can be the child of an int</summary>
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
/// <summary>Changes the parent of the collection</summary>
/// <param name="parent">New parent to assign to the collection</param>
public void SetParent(int parent) {
base.Reparent(parent);
}
/// <summary>Disposes all items contained in the collection</summary>
public new void DisposeItems() {
base.DisposeItems();
}
}
#endregion // class TestParentingCollection
/// <summary>
/// Tests whether the parenting collection propagates its parent to an item that
/// is added to the collection after the collection's aprent is already assigned
/// </summary>
[Test]
public void TestPropagatePreassignedParent() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable = new TestParentable();
testCollection.SetParent(54321);
testCollection.Add(testParentable);
Assert.AreEqual(54321, testParentable.GetParent());
}
/// <summary>
/// Tests whether the parenting collection propagates a new parent to all items
/// contained in it when its parent is changed
/// </summary>
[Test]
public void TestPropagateParentChange() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
}
/// <summary>
/// Tests whether the parenting collection propagates its parent to an item that
/// is added to the collection after the collection's aprent is already assigned
/// </summary>
[Test]
public void TestPropagateParentOnReplace() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable1 = new TestParentable();
TestParentable testParentable2 = new TestParentable();
testCollection.SetParent(54321);
testCollection.Add(testParentable1);
testCollection[0] = testParentable2;
Assert.AreEqual(0, testParentable1.GetParent());
Assert.AreEqual(54321, testParentable2.GetParent());
}
/// <summary>
/// Tests whether the parenting collection unsets the parent when an item is removed
/// from the collection
/// </summary>
[Test]
public void TestUnsetParentOnRemoveItem() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
testCollection.RemoveAt(0);
Assert.AreEqual(0, testParentable.GetParent());
}
/// <summary>
/// Tests whether the parenting collection unsets the parent when all item are
/// removed from the collection by clearing it
/// </summary>
[Test]
public void TestUnsetParentOnClear() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
testCollection.Clear();
Assert.AreEqual(0, testParentable.GetParent());
}
/// <summary>
/// Tests whether the parenting collection calls Dispose() on all contained items
/// that implement IDisposable when its DisposeItems() method is called
/// </summary>
[Test]
public void TestDisposeItems() {
TestParentingCollection testCollection = new TestParentingCollection();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.DisposeItems();
Assert.IsTrue(testParentable.DisposeCalled);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,117 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit tests for the Pool class</summary>
[TestFixture]
internal class PoolTest {
#region class TestClass
/// <summary>Used to test the pool</summary>
private class TestClass : IRecyclable {
/// <summary>Returns the object to its initial state</summary>
public void Recycle() {
this.Recycled = true;
}
/// <summary>Whether the instance has been recycled</summary>
public bool Recycled;
}
#endregion // class TestClass
#region class NoDefaultConstructor
/// <summary>Used to test the pool</summary>
private class NoDefaultConstructor {
/// <summary>Private constructor so no instances can be created</summary>
private NoDefaultConstructor() { }
}
#endregion // class NoDefaultConstructor
/// <summary>
/// Verifies that the pool can return newly constructed objects
/// </summary>
[Test]
public void NewInstancesCanBeObtained() {
Pool<TestClass> pool = new Pool<TestClass>();
Assert.IsNotNull(pool.Get());
}
/// <summary>
/// Verifies that an exception is thrown if the pool's default instance creator is used
/// on a type that doesn't have a default constructor
/// </summary>
[Test]
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
Assert.Throws<ArgumentException>(
delegate() { new Pool<NoDefaultConstructor>(); }
);
}
/// <summary>
/// Tests whether the pool can redeem objects that are no longer used
/// </summary>
[Test]
public void InstancesCanBeRedeemed() {
Pool<TestClass> pool = new Pool<TestClass>();
pool.Redeem(new TestClass());
}
/// <summary>
/// Tests whether the Recycle() method is called at the appropriate time
/// </summary>
[Test]
public void RedeemedItemsWillBeRecycled() {
Pool<TestClass> pool = new Pool<TestClass>();
TestClass x = new TestClass();
Assert.IsFalse(x.Recycled);
pool.Redeem(x);
Assert.IsTrue(x.Recycled);
}
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
[Test]
public void PoolCapacityCanBeAdjusted() {
Pool<TestClass> pool = new Pool<TestClass>(123);
Assert.AreEqual(123, pool.Capacity);
pool.Capacity = 321;
Assert.AreEqual(321, pool.Capacity);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,99 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Priority/Item pair class</summary>
[TestFixture]
internal class PriorityItemPairTest {
#region class ToStringNullReturner
/// <summary>Test class in which ToString() can return null</summary>
private class ToStringNullReturner {
/// <summary>
/// Returns a System.String that represents the current System.Object
/// </summary>
/// <returns>A System.String that represents the current System.Object</returns>
public override string ToString() { return null; }
}
#endregion // class ToStringNullReturner
/// <summary>Tests whether the pair's default constructor works</summary>
[Test]
public void TestDefaultConstructor() {
new PriorityItemPair<int, string>();
}
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
[Test]
public void TestPriorityRetrieval() {
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
12345, "hello world"
);
Assert.AreEqual(12345, testPair.Priority);
}
/// <summary>Tests whether the item can be retrieved from the pair</summary>
[Test]
public void TestItemRetrieval() {
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
12345, "hello world"
);
Assert.AreEqual("hello world", testPair.Item);
}
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
[Test]
public void TestToStringWithValidStrings() {
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
"hello", "world"
);
Assert.AreEqual("[hello, world]", testPair.ToString());
}
/// <summary>Tests whether the ToString() methods works with null strings</summary>
[Test]
public void TestToStringWithNullStrings() {
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
new ToStringNullReturner(), new ToStringNullReturner()
);
Assert.AreEqual("[, ]", testPair.ToString());
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,157 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the priority queue class</summary>
[TestFixture]
internal class PriorityQueueTest {
#region class FloatComparer
/// <summary>Comparer for two floating point values</summary>
private class FloatComparer : IComparer<float> {
/// <summary>The default instance of this comparer</summary>
public static readonly FloatComparer Default = new FloatComparer();
/// <summary>Compares two floating points against each other</summary>
/// <param name="left">First float to compare</param>
/// <param name="right">Second float to compare</param>
/// <returns>The relationship of the two floats to each other</returns>
public int Compare(float left, float right) {
return Math.Sign(left - right);
}
}
#endregion // class FloatComparer
/// <summary>Tests to ensure the count property is properly updated</summary>
[Test]
public void TestCount() {
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
Assert.AreEqual(0, testQueue.Count);
testQueue.Enqueue(12.34f);
Assert.AreEqual(1, testQueue.Count);
testQueue.Enqueue(56.78f);
Assert.AreEqual(2, testQueue.Count);
testQueue.Dequeue();
Assert.AreEqual(1, testQueue.Count);
testQueue.Enqueue(9.0f);
Assert.AreEqual(2, testQueue.Count);
testQueue.Clear();
Assert.AreEqual(0, testQueue.Count);
}
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
[Test]
public void TestOrdering() {
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
testQueue.Enqueue(1.0f);
testQueue.Enqueue(9.0f);
testQueue.Enqueue(2.0f);
testQueue.Enqueue(8.0f);
testQueue.Enqueue(3.0f);
testQueue.Enqueue(7.0f);
testQueue.Enqueue(4.0f);
testQueue.Enqueue(6.0f);
testQueue.Enqueue(5.0f);
Assert.AreEqual(9.0f, testQueue.Dequeue());
Assert.AreEqual(8.0f, testQueue.Dequeue());
Assert.AreEqual(7.0f, testQueue.Dequeue());
Assert.AreEqual(6.0f, testQueue.Dequeue());
Assert.AreEqual(5.0f, testQueue.Dequeue());
Assert.AreEqual(4.0f, testQueue.Dequeue());
Assert.AreEqual(3.0f, testQueue.Dequeue());
Assert.AreEqual(2.0f, testQueue.Dequeue());
Assert.AreEqual(1.0f, testQueue.Dequeue());
}
#if DEBUG
/// <summary>
/// Tests whether the priority queue's enumerators are invalidated when the queue's
/// contents are modified
/// </summary>
[Test]
public void TestEnumeratorInvalidationOnModify() {
PriorityQueue<int> testQueue = new PriorityQueue<int>();
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
testQueue.Enqueue(123);
Assert.Throws<InvalidOperationException>(
delegate() { testQueueEnumerator.MoveNext(); }
);
}
#endif
/// <summary>
/// Verifies that an exception is thrown when Peek() is called on an empty queue
/// </summary>
[Test]
public void TestPeekEmptyQueue() {
PriorityQueue<int> testQueue = new PriorityQueue<int>();
Assert.Throws<InvalidOperationException>(
delegate() { testQueue.Peek(); }
);
}
/// <summary>
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue
/// </summary>
[Test]
public void TestDequeueEmptyQueue() {
PriorityQueue<int> testQueue = new PriorityQueue<int>();
Assert.Throws<InvalidOperationException>(
delegate() { testQueue.Dequeue(); }
);
}
/// <summary>
/// Verifies that the priority queue can handle large amounts of data
/// </summary>
[Test]
public void TestLargeQueue() {
PriorityQueue<int> testQueue = new PriorityQueue<int>();
List<int> testList = new List<int>();
for(int index = 0; index < 1000; ++index) {
testQueue.Enqueue(index * 2);
testList.Add(index * 2);
}
CollectionAssert.AreEquivalent(testList, testQueue);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,162 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the read only collection wrapper</summary>
[TestFixture]
internal class ReadOnlyCollectionTest {
/// <summary>
/// Verifies that the copy constructor of the read only collection works
/// </summary>
[Test]
public void TestCopyConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
CollectionAssert.AreEqual(integers, testCollection);
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
[Test]
public void TestIsReadOnly() {
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
Assert.IsTrue(testCollection.IsReadOnly);
}
/// <summary>
/// Verifies that the CopyTo() of the read only collection works
/// </summary>
[Test]
public void TestCopyToArray() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
int[] outputIntegers = new int[testCollection.Count];
testCollection.CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Checks whether the Contains() method of the read only collection is able to
/// determine if the collection contains an item
/// </summary>
[Test]
public void TestContains() {
int[] integers = new int[] { 1234, 6789 };
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
Assert.IsTrue(testCollection.Contains(1234));
Assert.IsFalse(testCollection.Contains(4321));
}
/// <summary>
/// Ensures that the Add() method of the read only collection throws an exception
/// </summary>
[Test]
public void TestThrowOnAdd() {
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<int>).Add(123); }
);
}
/// <summary>
/// Ensures that the Remove() method of the read only collection throws an exception
/// </summary>
[Test]
public void TestThrowOnRemove() {
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<int>).Remove(123); }
);
}
/// <summary>
/// Ensures that the Clear() method of the read only collection throws an exception
/// </summary>
[Test]
public void TestThrowOnClear() {
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<int>).Clear(); }
);
}
/// <summary>
/// Tests whether the typesafe enumerator of the read only collection is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
List<int> outputIntegers = new List<int>();
foreach(int value in testCollection) {
outputIntegers.Add(value);
}
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Verifies that the CopyTo() of the read only collection works if invoked via
/// the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
int[] outputIntegers = new int[testCollection.Count];
(testCollection as ICollection).CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
if(!(testCollection as ICollection).IsSynchronized) {
lock((testCollection as ICollection).SyncRoot) {
Assert.AreEqual(0, testCollection.Count);
}
}
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,509 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the read only dictionary wrapper</summary>
[TestFixture]
internal class ReadOnlyDictionaryTest {
/// <summary>
/// Verifies that the copy constructor of the read only dictionary works
/// </summary>
[Test]
public void TestCopyConstructor() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
CollectionAssert.AreEqual(numbers, testDictionary);
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
[Test]
public void TestIsReadOnly() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.IsTrue(testDictionary.IsReadOnly);
}
/// <summary>
/// Checks whether the Contains() method of the read only dictionary is able to
/// determine if the dictionary contains an item
/// </summary>
[Test]
public void TestContains() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.IsTrue(
testDictionary.Contains(new KeyValuePair<int, string>(42, "forty-two"))
);
Assert.IsFalse(
testDictionary.Contains(new KeyValuePair<int, string>(24, "twenty-four"))
);
}
/// <summary>
/// Checks whether the Contains() method of the read only dictionary is able to
/// determine if the dictionary contains a key
/// </summary>
[Test]
public void TestContainsKey() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.IsTrue(testDictionary.ContainsKey(42));
Assert.IsFalse(testDictionary.ContainsKey(24));
}
/// <summary>
/// Verifies that the CopyTo() of the read only dictionary works
/// </summary>
[Test]
public void TestCopyToArray() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
testDictionary.CopyTo(items, 0);
CollectionAssert.AreEqual(numbers, items);
}
/// <summary>
/// Tests whether the typesafe enumerator of the read only dictionary is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
List<KeyValuePair<int, string>> outputItems = new List<KeyValuePair<int, string>>();
foreach(KeyValuePair<int, string> item in testDictionary) {
outputItems.Add(item);
}
CollectionAssert.AreEqual(numbers, outputItems);
}
/// <summary>
/// Tests whether the keys collection of the read only dictionary can be queried
/// </summary>
[Test]
public void TestGetKeysCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
ICollection<int> inputKeys = numbers.Keys;
ICollection<int> keys = testDictionary.Keys;
CollectionAssert.AreEqual(inputKeys, keys);
}
/// <summary>
/// Tests whether the values collection of the read only dictionary can be queried
/// </summary>
[Test]
public void TestGetValuesCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
ICollection<string> inputValues = numbers.Values;
ICollection<string> values = testDictionary.Values;
CollectionAssert.AreEqual(inputValues, values);
}
/// <summary>
/// Tests whether the TryGetValue() method of the read only dictionary is working
/// </summary>
[Test]
public void TestTryGetValue() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
string value;
Assert.IsTrue(testDictionary.TryGetValue(42, out value));
Assert.AreEqual("forty-two", value);
Assert.IsFalse(testDictionary.TryGetValue(24, out value));
Assert.AreEqual(null, value);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the read only
/// dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexer() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.AreEqual("forty-two", testDictionary[42]);
}
/// <summary>
/// Tests whether an exception is thrown if the indexer of the read only dictionary
/// is used to attempt to retrieve a non-existing value
/// </summary>
[Test]
public void TestThrowOnRetrieveNonExistingValueByIndexer() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<KeyNotFoundException>(
delegate() { Console.WriteLine(testDictionary[24]); }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Add() method is called via the generic IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnAddViaGenericIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary<int, string>).Add(10, "ten"); }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Remove() method is called via the generic IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary<int, string>).Remove(3); }
);
}
/// <summary>
/// Tests whether the TryGetValue() method of the read only dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaGenericIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.AreEqual("forty-two", (testDictionary as IDictionary<int, string>)[42]);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// indexer is used to insert an item via the generic IDictionar&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaGenericIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary<int, string>)[24] = "twenty-four"; }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Clear() method is called via the IDictionary interface
/// </summary>
[Test]
public void TestThrowOnClearViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary).Clear(); }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Add() method is called via the IDictionary interface
/// </summary>
[Test]
public void TestThrowOnAddViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary).Add(24, "twenty-four"); }
);
}
/// <summary>
/// Checks whether the Contains() method of the read only dictionary is able to
/// determine if the dictionary contains an item via the IDictionary interface
/// </summary>
[Test]
public void TestContainsViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.IsTrue((testDictionary as IDictionary).Contains(42));
Assert.IsFalse((testDictionary as IDictionary).Contains(24));
}
/// <summary>
/// Checks whether the GetEnumerator() method of the read only dictionary returns
/// a working enumerator if accessed via the IDictionary interface
/// </summary>
[Test]
public void TestEnumeratorViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Dictionary<int, string> outputNumbers = new Dictionary<int, string>();
foreach(DictionaryEntry entry in (testDictionary as IDictionary)) {
(outputNumbers as IDictionary).Add(entry.Key, entry.Value);
}
CollectionAssert.AreEquivalent(numbers, outputNumbers);
}
/// <summary>
/// Checks whether the IsFixedSize property of the read only dictionary returns
/// the expected result for a read only dictionary based on a dynamic dictionary
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.IsFalse((testDictionary as IDictionary).IsFixedSize);
}
/// <summary>
/// Tests whether the keys collection of the read only dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetKeysCollectionViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
ICollection inputKeys = (numbers as IDictionary).Keys;
ICollection keys = (testDictionary as IDictionary).Keys;
CollectionAssert.AreEqual(inputKeys, keys);
}
/// <summary>
/// Tests whether the values collection of the read only dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetValuesCollectionViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
ICollection inputValues = (numbers as IDictionary).Values;
ICollection values = (testDictionary as IDictionary).Values;
CollectionAssert.AreEqual(inputValues, values);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Remove() method is called via the IDictionary interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary).Remove(3); }
);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the read only
/// dictionary is working via the IDictionary interface
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.AreEqual("forty-two", (testDictionary as IDictionary)[42]);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// indexer is used to insert an item via the IDictionary interface
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaIDictionary() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as IDictionary)[24] = "twenty-four"; }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Add() method is used via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnAddViaGenericICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() {
(testDictionary as ICollection<KeyValuePair<int, string>>).Add(
new KeyValuePair<int, string>(24, "twenty-four")
);
}
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Clear() method is used via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnClearViaGenericICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() { (testDictionary as ICollection<KeyValuePair<int, string>>).Clear(); }
);
}
/// <summary>
/// Checks whether the read only dictionary will throw an exception if its
/// Remove() method is used via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
Assert.Throws<NotSupportedException>(
delegate() {
(testDictionary as ICollection<KeyValuePair<int, string>>).Remove(
new KeyValuePair<int, string>(42, "fourty-two")
);
}
);
}
/// <summary>
/// Verifies that the CopyTo() of the read only dictionary works when called
/// via the the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
DictionaryEntry[] entries = new DictionaryEntry[numbers.Count];
(testDictionary as ICollection).CopyTo(entries, 0);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
for(int index = 0; index < entries.Length; ++index) {
items[index] = new KeyValuePair<int, string>(
(int)entries[index].Key, (string)entries[index].Value
);
}
CollectionAssert.AreEquivalent(numbers, items);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
if(!(testDictionary as ICollection).IsSynchronized) {
lock((testDictionary as ICollection).SyncRoot) {
Assert.AreEqual(numbers.Count, testDictionary.Count);
}
}
}
/// <summary>
/// Test whether the read only dictionary can be serialized
/// </summary>
[Test]
public void TestSerialization() {
BinaryFormatter formatter = new BinaryFormatter();
using(MemoryStream memory = new MemoryStream()) {
Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary1 = makeReadOnly(numbers);
formatter.Serialize(memory, testDictionary1);
memory.Position = 0;
object testDictionary2 = formatter.Deserialize(memory);
CollectionAssert.AreEquivalent(testDictionary1, (IEnumerable)testDictionary2);
}
}
/// <summary>
/// Creates a new read-only dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created read-only dictionary</returns>
private static Dictionary<int, string> createTestDictionary() {
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
numbers.Add(3, "three");
numbers.Add(42, "forty-two");
return new Dictionary<int, string>(numbers);
}
/// <summary>
/// Creates a new read-only dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created read-only dictionary</returns>
private static ReadOnlyDictionary<int, string> makeReadOnly(
IDictionary<int, string> dictionary
) {
return new ReadOnlyDictionary<int, string>(dictionary);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,380 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the read only list wrapper</summary>
[TestFixture]
internal class ReadOnlyListTest {
/// <summary>
/// Verifies that the copy constructor of the read only list works
/// </summary>
[Test]
public void TestCopyConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
CollectionAssert.AreEqual(integers, testList);
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
[Test]
public void TestIsReadOnly() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.IsTrue(testList.IsReadOnly);
}
/// <summary>
/// Verifies that the CopyTo() of the read only list works
/// </summary>
[Test]
public void TestCopyToArray() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
int[] outputIntegers = new int[testList.Count];
testList.CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Checks whether the Contains() method of the read only list is able to
/// determine if the list contains an item
/// </summary>
[Test]
public void TestContains() {
int[] integers = new int[] { 1234, 6789 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue(testList.Contains(1234));
Assert.IsFalse(testList.Contains(4321));
}
/// <summary>
/// Checks whether the IndexOf() method of the read only list is able to
/// determine if the index of an item in the list
/// </summary>
[Test]
public void TestIndexOf() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(0, testList.IndexOf(12));
Assert.AreEqual(1, testList.IndexOf(34));
Assert.AreEqual(2, testList.IndexOf(67));
Assert.AreEqual(3, testList.IndexOf(89));
}
/// <summary>
/// Checks whether the indexer method of the read only list is able to
/// retrieve items from the list
/// </summary>
[Test]
public void TestRetrieveByIndexer() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, testList[0]);
Assert.AreEqual(34, testList[1]);
Assert.AreEqual(67, testList[2]);
Assert.AreEqual(89, testList[3]);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Insert() method
/// is called via the generic IList&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnInsertViaGenericIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList<int>).Insert(0, 12345); }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its RemoveAt() method
/// is called via the generic IList&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList<int>).RemoveAt(0); }
);
}
/// <summary>
/// Checks whether the indexer method of the read only list will throw an exception
/// if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestRetrieveByIndexerViaGenericIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, (testList as IList<int>)[0]);
Assert.AreEqual(34, (testList as IList<int>)[1]);
Assert.AreEqual(67, (testList as IList<int>)[2]);
Assert.AreEqual(89, (testList as IList<int>)[3]);
}
/// <summary>
/// Checks whether the indexer method of the read only list will throw an exception
/// if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaGenericIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList<int>)[0] = 12345; }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Add() method
/// is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnAddViaGenericICollection() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as ICollection<int>).Add(12345); }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Clear() method
/// is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnClearViaGenericICollection() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as ICollection<int>).Clear(); }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Remove() method
/// is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericICollection() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.Throws<NotSupportedException>(
delegate() { (testList as ICollection<int>).Remove(89); }
);
}
/// <summary>
/// Tests whether the typesafe enumerator of the read only list is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
List<int> outputIntegers = new List<int>();
foreach(int value in testList) {
outputIntegers.Add(value);
}
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Clear() method
/// is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnClearViaIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList).Clear(); }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Add() method
/// is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnAddViaIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList).Add(12345); }
);
}
/// <summary>
/// Checks whether the Contains() method of the read only list is able to
/// determine if the list contains an item
/// </summary>
[Test]
public void TestContainsViaIList() {
int[] integers = new int[] { 1234, 6789 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue((testList as IList).Contains(1234));
Assert.IsFalse((testList as IList).Contains(4321));
}
/// <summary>
/// Checks whether the IndexOf() method of the read only list is able to
/// determine if the index of an item in the list
/// </summary>
[Test]
public void TestIndexOfViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(0, (testList as IList).IndexOf(12));
Assert.AreEqual(1, (testList as IList).IndexOf(34));
Assert.AreEqual(2, (testList as IList).IndexOf(67));
Assert.AreEqual(3, (testList as IList).IndexOf(89));
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Insert() method
/// is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnInsertViaIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList).Insert(0, 12345); }
);
}
/// <summary>
/// Checks whether the IsFixedSize property of the read only list returns the
/// expected result for a read only list based on a fixed array
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue((testList as IList).IsFixedSize);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Remove() method
/// is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaIList() {
int[] integers = new int[] { 1234, 6789 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList).Remove(6789); }
);
}
/// <summary>
/// Checks whether the read only list will throw an exception if its Remove() method
/// is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnRemoveAtViaIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList).RemoveAt(0); }
);
}
/// <summary>
/// Checks whether the indexer method of the read only list will throw an exception
/// if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestRetrieveByIndexerViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, (testList as IList)[0]);
Assert.AreEqual(34, (testList as IList)[1]);
Assert.AreEqual(67, (testList as IList)[2]);
Assert.AreEqual(89, (testList as IList)[3]);
}
/// <summary>
/// Checks whether the indexer method of the read only list will throw an exception
/// if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaIList() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testList as IList)[0] = 12345; }
);
}
/// <summary>
/// Verifies that the CopyTo() of the read only list works if invoked via
/// the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
int[] outputIntegers = new int[testList.Count];
(testList as ICollection).CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
if(!(testList as ICollection).IsSynchronized) {
lock((testList as ICollection).SyncRoot) {
Assert.AreEqual(0, testList.Count);
}
}
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,207 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_SETS
#if UNITTEST
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable set wrapper</summary>
[TestFixture]
internal class ReadOnlySetTest {
/// <summary>Called before each test is run</summary>
[SetUp]
public void Setup() {
this.set = new HashSet<int>();
this.readOnlySet = new ReadOnlySet<int>(this.set);
}
/// <summary>
/// Verifies that the observable set has a default constructor
/// </summary>
[Test]
public void HasDefaultConstructor() {
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
}
/// <summary>
/// Verifies that an exception is thrown upon any attempt to add items
/// to a read-only set
/// </summary>
[Test]
public void AddingThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
);
}
/// <summary>
/// Verifies that an exception is thrown upon any attempt to remove items
/// from a read-only set
/// </summary>
[Test]
public void RemovingThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
);
}
/// <summary>
/// Verifies that an exception is thrown upon any attempt to except
/// the set with another set
/// </summary>
[Test]
public void ExceptingThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
);
}
/// <summary>
/// Verifies that an exception is thrown upon any attempt to intersect
/// the set with another set
/// </summary>
[Test]
public void InsersectThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
);
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a proper subset
/// or superset of another set
/// </summary>
[Test]
public void CanDetermineProperSubsetAndSuperset() {
this.set.Add(1);
this.set.Add(2);
this.set.Add(3);
var set2 = new HashSet<int>() { 1, 3 };
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
set2.Add(2);
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a subset
/// or a superset of another set
/// </summary>
[Test]
public void CanDetermineSubsetAndSuperset() {
this.set.Add(1);
this.set.Add(2);
this.set.Add(3);
var set2 = new HashSet<int>() { 1, 2, 3 };
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
set2.Add(4);
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
}
/// <summary>
/// Verifies that a set can determine if another set overlaps with it
/// </summary>
[Test]
public void CanDetermineOverlap() {
this.set.Add(1);
this.set.Add(3);
this.set.Add(5);
var set2 = new HashSet<int>() { 3 };
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
Assert.IsTrue(set2.Overlaps(this.readOnlySet));
}
/// <summary>
/// Verifies that a set can determine if another set contains the same elements
/// </summary>
[Test]
public void CanDetermineSetEquality() {
this.set.Add(1);
this.set.Add(3);
this.set.Add(5);
var set2 = new HashSet<int>() { 3, 1, 5 };
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
this.set.Add(7);
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
Assert.IsFalse(set2.SetEquals(this.readOnlySet));
}
/// <summary>
/// Verifies that any attempt to symmetrically except a read-only set
/// causes an exception
/// </summary>
[Test]
public void SymmetricallyExceptingThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
);
}
/// <summary>
/// Verifies that any attempt to union a read-only set causes an exception
/// </summary>
[Test]
public void UnioningThrowsException() {
Assert.Throws<NotSupportedException>(
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
);
}
/// <summary>Set being wrapped in a read-only set</summary>
private ISet<int> set;
/// <summary>Read-only wrapper around the set</summary>
private ReadOnlySet<int> readOnlySet;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_SETS

View file

@ -0,0 +1,116 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the ReverseComparer helper class</summary>
[TestFixture]
internal class ReverseComparerTest {
#region class FortyTwoComparer
/// <summary>Special comparer in which 42 is larger than everything</summary>
private class FortyTwoComparer : IComparer<int> {
/// <summary>Compares the left value to the right value</summary>
/// <param name="left">Value on the left side</param>
/// <param name="right">Value on the right side</param>
/// <returns>The relationship of the two values</returns>
public int Compare(int left, int right) {
// Is there a 42 in the arguments?
if(left == 42) {
if(right == 42) {
return 0; // both are equal
} else {
return +1; // left is larger
}
} else if(right == 42) {
return -1; // right is larger
}
// No 42 encountered, proceed as normal
return Math.Sign(left - right);
}
}
#endregion // class FortyTwoComparer
/// <summary>
/// Tests whether the default constructor of the reverse comparer works
/// </summary>
[Test]
public void TestDefaultConstructor() {
new ReverseComparer<int>();
}
/// <summary>
/// Tests whether the full constructor of the reverse comparer works
/// </summary>
[Test]
public void TestFullConstructor() {
new ReverseComparer<int>(new FortyTwoComparer());
}
/// <summary>
/// Tests whether the full constructor of the reverse comparer works
/// </summary>
[Test]
public void TestReversedDefaultComparer() {
Comparer<int> comparer = Comparer<int>.Default;
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
Assert.Greater(0, comparer.Compare(10, 20));
Assert.Less(0, comparer.Compare(20, 10));
Assert.Less(0, reverseComparer.Compare(10, 20));
Assert.Greater(0, reverseComparer.Compare(20, 10));
}
/// <summary>
/// Tests whether the full constructor of the reverse comparer works
/// </summary>
[Test]
public void TestReversedCustomComparer() {
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
fortyTwoComparer
);
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,119 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the SortableBindingList class</summary>
[TestFixture]
internal class SortableBindingListTest {
#region class TestRecord
/// <summary>Dummy record used to test the sortable binding list</summary>
private class TestRecord {
/// <summary>A property of type integer</summary>
public int IntegerValue { get; set; }
/// <summary>A property of type string</summary>
public string StringValue { get; set; }
/// <summary>A property of type float</summary>
public float FloatValue { get; set; }
}
#endregion // class TestRecord
/// <summary>Verifies that the sortable binding list is default constructible</summary>
[Test]
public void HasDefaultConstructor() {
Assert.DoesNotThrow(
delegate () { new SortableBindingList<TestRecord>(); }
);
}
/// <summary>
/// Tests whether the sortable binding list can copy an existing list
/// when being constructed
/// </summary>
[Test]
public void HasEnumerableConstructor() {
var items = new List<TestRecord>() {
new TestRecord() { IntegerValue = 123 },
new TestRecord() { IntegerValue = 456 }
};
var testList = new SortableBindingList<TestRecord>(items);
Assert.AreEqual(2, testList.Count);
Assert.AreSame(items[0], testList[0]);
Assert.AreSame(items[1], testList[1]);
}
/// <summary>Verifies that the sortable binding list supports sorting</summary>
[Test]
public void SupportsSorting() {
var testList = new SortableBindingList<TestRecord>();
IBindingList testListAsBindingList = testList;
Assert.IsTrue(testListAsBindingList.SupportsSorting);
}
/// <summary>
/// Tests whether the sortable binding list can sort its elements by different properties
/// </summary>
[Test]
public void CanSortItems() {
var items = new List<TestRecord>() {
new TestRecord() { IntegerValue = 456 },
new TestRecord() { IntegerValue = 789 },
new TestRecord() { IntegerValue = 123 }
};
var testList = new SortableBindingList<TestRecord>(items);
IBindingList testListAsBindingList = testList;
PropertyDescriptor integerValuePropertyDescriptor = (
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
);
testListAsBindingList.ApplySort(
integerValuePropertyDescriptor, ListSortDirection.Ascending
);
Assert.AreEqual(3, testList.Count);
Assert.AreEqual(123, testList[0].IntegerValue);
Assert.AreEqual(456, testList[1].IntegerValue);
Assert.AreEqual(789, testList[2].IntegerValue);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View file

@ -0,0 +1,500 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_NMOCK
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the transforming read only collection wrapper</summary>
[TestFixture]
internal class TransformingReadOnlyCollectionTest {
#region class StringTransformer
/// <summary>Test implementation of a transforming collection</summary>
private class StringTransformer : TransformingReadOnlyCollection<int, string> {
/// <summary>Initializes a new int-to-string transforming collection</summary>
/// <param name="items">Items the transforming collection will contain</param>
public StringTransformer(IList<int> items) : base(items) { }
/// <summary>Transforms an item into the exposed type</summary>
/// <param name="item">Item to be transformed</param>
/// <returns>The transformed item</returns>
/// <remarks>
/// This method is used to transform an item in the wrapped collection into
/// the exposed item type whenever the user accesses an item. Expect it to
/// be called frequently, because the TransformingReadOnlyCollection does
/// not cache or otherwise store the transformed items.
/// </remarks>
protected override string Transform(int item) {
if(item == 42) {
return null;
}
return item.ToString();
}
}
#endregion // class StringTransformer
/// <summary>
/// Verifies that the copy constructor of the transforming read only collection works
/// </summary>
[Test]
public void TestCopyConstructor() {
int[] integers = new int[] { 12, 34, 56, 78 };
StringTransformer testCollection = new StringTransformer(integers);
string[] strings = new string[] { "12", "34", "56", "78" };
CollectionAssert.AreEqual(strings, testCollection);
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
[Test]
public void TestIsReadOnly() {
StringTransformer testCollection = new StringTransformer(new int[0]);
Assert.IsTrue(testCollection.IsReadOnly);
}
/// <summary>
/// Verifies that the CopyTo() method of the transforming read only collection works
/// </summary>
[Test]
public void TestCopyToArray() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
StringTransformer testCollection = new StringTransformer(inputIntegers);
string[] inputStrings = new string[] { "12", "34", "56", "78" };
string[] outputStrings = new string[testCollection.Count];
testCollection.CopyTo(outputStrings, 0);
CollectionAssert.AreEqual(inputStrings, outputStrings);
}
/// <summary>
/// Verifies that the CopyTo() method of the transforming read only collection throws
/// an exception if the target array is too small to hold the collection's contents
/// </summary>
[Test]
public void TestThrowOnCopyToTooSmallArray() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
StringTransformer testCollection = new StringTransformer(inputIntegers);
string[] outputStrings = new string[testCollection.Count - 1];
Assert.Throws<ArgumentException>(
delegate() { testCollection.CopyTo(outputStrings, 0); }
);
}
/// <summary>
/// Checks whether the Contains() method of the transforming read only collection
/// is able to determine if the collection contains an item
/// </summary>
[Test]
public void TestContains() {
int[] integers = new int[] { 1234, 6789 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.IsTrue(testCollection.Contains("1234"));
Assert.IsFalse(testCollection.Contains("4321"));
}
/// <summary>
/// Checks whether the IndexOf() method of the transforming read only collection
/// is able to determine if the index of an item in the collection
/// </summary>
[Test]
public void TestIndexOf() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual(0, testCollection.IndexOf("12"));
Assert.AreEqual(1, testCollection.IndexOf("34"));
Assert.AreEqual(2, testCollection.IndexOf("67"));
Assert.AreEqual(3, testCollection.IndexOf("89"));
}
/// <summary>
/// Checks whether the IndexOf() method of the transforming read only collection
/// can cope with queries for 'null' when no 'null' item is contained on it
/// </summary>
[Test]
public void TestIndexOfWithNullItemNotContained() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual(-1, testCollection.IndexOf(null));
}
/// <summary>
/// Checks whether the IndexOf() method of the transforming read only collection
/// can cope with queries for 'null' when a 'null' item is contained on it
/// </summary>
[Test]
public void TestIndexOfWithNullItemContained() {
int[] integers = new int[] { 12, 34, 67, 89, 42 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual(4, testCollection.IndexOf(null));
}
/// <summary>
/// Verifies that the Enumerator of the transforming read only collection correctly
/// implements the Reset() method
/// </summary>
[Test]
public void TestEnumeratorReset() {
int[] integers = new int[] { 1234, 6789 };
StringTransformer testCollection = new StringTransformer(integers);
IEnumerator<string> stringEnumerator = testCollection.GetEnumerator();
Assert.IsTrue(stringEnumerator.MoveNext());
Assert.IsTrue(stringEnumerator.MoveNext());
Assert.IsFalse(stringEnumerator.MoveNext());
stringEnumerator.Reset();
Assert.IsTrue(stringEnumerator.MoveNext());
Assert.IsTrue(stringEnumerator.MoveNext());
Assert.IsFalse(stringEnumerator.MoveNext());
}
/// <summary>
/// Checks whether the indexer method of the transforming read only collection
/// is able to retrieve items from the collection
/// </summary>
[Test]
public void TestRetrieveByIndexer() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual("12", testCollection[0]);
Assert.AreEqual("34", testCollection[1]);
Assert.AreEqual("67", testCollection[2]);
Assert.AreEqual("89", testCollection[3]);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Insert() method is called via the generic IList&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnInsertViaGenericIList() {
StringTransformer testCollection = new StringTransformer(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList<string>).Insert(0, "12345"); }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its RemoveAt() method is called via the generic IList&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericIList() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList<string>).RemoveAt(0); }
);
}
/// <summary>
/// Checks whether the indexer method of the transforming read only collection will
/// throw an exception if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestRetrieveByIndexerViaGenericIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual("12", (testCollection as IList<string>)[0]);
Assert.AreEqual("34", (testCollection as IList<string>)[1]);
Assert.AreEqual("67", (testCollection as IList<string>)[2]);
Assert.AreEqual("89", (testCollection as IList<string>)[3]);
}
/// <summary>
/// Checks whether the indexer method of the transforming read only collection
/// will throw an exception if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaGenericIList() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList<string>)[0] = "12345"; }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Add() method is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnAddViaGenericICollection() {
StringTransformer testCollection = new StringTransformer(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<string>).Add("12345"); }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Clear() method is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnClearViaGenericICollection() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<string>).Clear(); }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Remove() method is called via the generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaGenericICollection() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as ICollection<string>).Remove("89"); }
);
}
/// <summary>
/// Tests whether the typesafe enumerator of the read only collection is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
StringTransformer testCollection = new StringTransformer(inputIntegers);
List<string> outputStrings = new List<string>();
foreach(string value in testCollection) {
outputStrings.Add(value);
}
string[] inputStrings = new string[] { "12", "34", "56", "78" };
CollectionAssert.AreEqual(inputStrings, outputStrings);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Clear() method is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnClearViaIList() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList).Clear(); }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Add() method is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnAddViaIList() {
StringTransformer testCollection = new StringTransformer(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList).Add("12345"); }
);
}
/// <summary>
/// Checks whether the Contains() method of the transforming read only collection
/// is able to determine if the collection contains an item
/// </summary>
[Test]
public void TestContainsViaIList() {
int[] integers = new int[] { 1234, 6789 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.IsTrue((testCollection as IList).Contains("1234"));
Assert.IsFalse((testCollection as IList).Contains("4321"));
}
/// <summary>
/// Checks whether the IndexOf() method of the transforming read only collection
/// is able to determine if the index of an item in the collection
/// </summary>
[Test]
public void TestIndexOfViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual(0, (testCollection as IList).IndexOf("12"));
Assert.AreEqual(1, (testCollection as IList).IndexOf("34"));
Assert.AreEqual(2, (testCollection as IList).IndexOf("67"));
Assert.AreEqual(3, (testCollection as IList).IndexOf("89"));
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Insert() method is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnInsertViaIList() {
StringTransformer testCollection = new StringTransformer(new int[0]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList).Insert(0, "12345"); }
);
}
/// <summary>
/// Checks whether the IsFixedSize property of the transforming read only collection
/// returns the expected result for a transforming read only collection based on
/// a fixed array
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.IsTrue((testCollection as IList).IsFixedSize);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Remove() method is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnRemoveViaIList() {
int[] integers = new int[] { 1234, 6789 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList).Remove("6789"); }
);
}
/// <summary>
/// Checks whether the transforming read only collection will throw an exception
/// if its Remove() method is called via the IList interface
/// </summary>
[Test]
public void TestThrowOnRemoveAtViaIList() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList).RemoveAt(0); }
);
}
/// <summary>
/// Checks whether the indexer method of the transforming read only collection
/// will throw an exception if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestRetrieveByIndexerViaIList() {
int[] integers = new int[] { 12, 34, 67, 89 };
StringTransformer testCollection = new StringTransformer(integers);
Assert.AreEqual("12", (testCollection as IList)[0]);
Assert.AreEqual("34", (testCollection as IList)[1]);
Assert.AreEqual("67", (testCollection as IList)[2]);
Assert.AreEqual("89", (testCollection as IList)[3]);
}
/// <summary>
/// Checks whether the indexer method of the transforming read only collection
/// will throw an exception if it is attempted to be used for replacing an item
/// </summary>
[Test]
public void TestThrowOnReplaceByIndexerViaIList() {
StringTransformer testCollection = new StringTransformer(new int[1]);
Assert.Throws<NotSupportedException>(
delegate() { (testCollection as IList)[0] = "12345"; }
);
}
/// <summary>
/// Verifies that the CopyTo() method of the transforming read only collection
/// works if invoked via the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
StringTransformer testCollection = new StringTransformer(inputIntegers);
string[] outputStrings = new string[testCollection.Count];
(testCollection as ICollection).CopyTo(outputStrings, 0);
string[] inputStrings = new string[] { "12", "34", "56", "78" };
CollectionAssert.AreEqual(inputStrings, outputStrings);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
StringTransformer testCollection = new StringTransformer(new int[0]);
if(!(testCollection as ICollection).IsSynchronized) {
lock((testCollection as ICollection).SyncRoot) {
Assert.AreEqual(0, testCollection.Count);
}
}
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// on transforming read only collections based on IList&lt;&gt;s that do not
/// implement the ICollection interface
/// </summary>
[Test]
public void TestSynchronizationOfIListWithoutICollection() {
MockFactory mockery = new MockFactory();
Mock<IList<int>> mockedIList = mockery.CreateMock<IList<int>>();
StringTransformer testCollection = new StringTransformer(mockedIList.MockObject);
if(!(testCollection as ICollection).IsSynchronized) {
lock((testCollection as ICollection).SyncRoot) {
mockedIList.Expects.One.GetProperty(p => p.Count).WillReturn(12345);
int count = testCollection.Count;
Assert.AreEqual(12345, count); // ;-)
}
}
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_NMOCK

View file

@ -0,0 +1,87 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_SETS
using System;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Variegator multi dictionary</summary>
[TestFixture]
internal class VariegatorTest {
/// <summary>
/// Tests whether the default constructor of the reverse comparer works
/// </summary>
[Test]
public void InstancesCanBeCreated() {
new Variegator<int, string>();
}
/// <summary>
/// Verifies that querying for a missing value leads to an exception being thrown
/// </summary>
[Test]
public void QueryingMissingValueThrowsException() {
var variegator = new Variegator<int, string>();
Assert.Throws<KeyNotFoundException>(
() => {
variegator.Get(123);
}
);
}
/// <summary>
/// Verifies that the variegator resolves ambiguous matches according to its design
/// </summary>
[Test]
public void AmbiguityResolvesToLeastRecentValue() {
var variegator = new Variegator<int, string>();
variegator.Add(1, "one");
variegator.Add(1, "eins");
string first = variegator.Get(1);
string second = variegator.Get(1);
// The variegator should have selected the first value by random and then
// returned the other value on the second query
Assert.AreNotEqual(first, second);
// Now the variegator should return the first value again because it is
// the least recently used value
Assert.AreEqual(first, variegator.Get(1));
// Repeating the query, the second should be returned again because now
// it has become the least recently used value
Assert.AreEqual(second, variegator.Get(1));
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // !NO_SETS

View file

@ -0,0 +1,668 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the weak collection wrapper</summary>
[TestFixture]
internal class WeakCollectionTest {
#region class Dummy
/// <summary>Dummy class used to test the weakly referencing collection</summary>
private class Dummy {
/// <summary>Initializes a new dummy</summary>
/// <param name="value">Value that will be stored by the dummy</param>
public Dummy(int value) {
this.Value = value;
}
/// <summary>
/// Determines whether the specified System.Object is equal to
/// the current Dummy object.
/// </summary>
/// <param name="otherAsObject">
/// The System.Object to compare with the current Dummy object
/// </param>
/// <returns>
/// True if the specified System.Object is equal to the current Dummy object;
/// otherwise, false.
/// </returns>
public override bool Equals(object otherAsObject) {
Dummy other = otherAsObject as Dummy;
if(other == null) {
return false;
}
return this.Value.Equals(other.Value);
}
/// <summary>Serves as a hash function for a particular type.</summary>
/// <returns>A hash code for the current System.Object.</returns>
public override int GetHashCode() {
return this.Value.GetHashCode();
}
/// <summary>Some value that can be used for testing</summary>
public int Value;
}
#endregion // class Dummy
#region class ListWithoutICollection
private class ListWithoutICollection : IList<WeakReference<Dummy>> {
public int IndexOf(WeakReference<Dummy> item) { throw new NotImplementedException(); }
public void Insert(int index, WeakReference<Dummy> item) {
throw new NotImplementedException();
}
public void RemoveAt(int index) { throw new NotImplementedException(); }
public WeakReference<Dummy> this[int index] {
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public void Add(WeakReference<Dummy> item) { throw new NotImplementedException(); }
public void Clear() { throw new NotImplementedException(); }
public bool Contains(WeakReference<Dummy> item) { throw new NotImplementedException(); }
public void CopyTo(WeakReference<Dummy>[] array, int arrayIndex) {
throw new NotImplementedException();
}
public int Count { get { return 12345; } }
public bool IsReadOnly { get { throw new NotImplementedException(); } }
public bool Remove(WeakReference<Dummy> item) { throw new NotImplementedException(); }
public IEnumerator<WeakReference<Dummy>> GetEnumerator() {
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}
#endregion // class ListWithoutICollection
/// <summary>Verifies that the constructor of the weak collection is working</summary>
[Test]
public void TestConstructor() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.IsNotNull(dummies);
}
/// <summary>
/// Test whether the non-typesafe Add() method of the weak collection works
/// </summary>
[Test]
public void TestAddAsObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(12345);
(dummies as IList).Add((object)oneTwoThreeDummy);
CollectionAssert.Contains(dummies, oneTwoThreeDummy);
}
/// <summary>
/// Test whether the non-typesafe Add() method throws an exception if an object is
/// added that is not compatible to the collection's item type
/// </summary>
[Test]
public void TestThrowOnAddIncompatibleObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.Throws<ArgumentException>(
delegate() { (dummies as IList).Add(new object()); }
);
}
/// <summary>
/// Test whether the generic Add() method of the weak collection works
/// </summary>
[Test]
public void TestAdd() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(12345);
dummies.Add(oneTwoThreeDummy);
CollectionAssert.Contains(dummies, oneTwoThreeDummy);
}
/// <summary>Tests whether the Clear() method works</summary>
[Test]
public void TestClear() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(12345);
dummies.Add(oneTwoThreeDummy);
Dummy threeTwoOneDummy = new Dummy(54321);
dummies.Add(threeTwoOneDummy);
Assert.AreEqual(2, dummies.Count);
dummies.Clear();
Assert.AreEqual(0, dummies.Count);
}
/// <summary>Tests whether the Contains() method works</summary>
[Test]
public void TestContains() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(12345);
dummies.Add(oneTwoThreeDummy);
Dummy threeTwoOneDummy = new Dummy(54321);
Assert.IsTrue(dummies.Contains(oneTwoThreeDummy));
Assert.IsFalse(dummies.Contains(threeTwoOneDummy));
}
/// <summary>Tests whether the non-typesafe Contains() method works</summary>
[Test]
public void TestContainsWithObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(12345);
dummies.Add(oneTwoThreeDummy);
Dummy threeTwoOneDummy = new Dummy(54321);
Assert.IsTrue((dummies as IList).Contains((object)oneTwoThreeDummy));
Assert.IsFalse((dummies as IList).Contains((object)threeTwoOneDummy));
}
/// <summary>
/// Verifies that the Enumerator of the dummy collection correctly
/// implements the Reset() method
/// </summary>
[Test]
public void TestEnumeratorReset() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
IEnumerator<Dummy> dummyEnumerator = dummies.GetEnumerator();
Assert.IsTrue(dummyEnumerator.MoveNext());
Assert.IsTrue(dummyEnumerator.MoveNext());
Assert.IsFalse(dummyEnumerator.MoveNext());
dummyEnumerator.Reset();
Assert.IsTrue(dummyEnumerator.MoveNext());
Assert.IsTrue(dummyEnumerator.MoveNext());
Assert.IsFalse(dummyEnumerator.MoveNext());
}
/// <summary>Verifies that the IndexOf() method is working as intended</summary>
[Test]
public void TestIndexOf() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Dummy sevenEightNineDummy = new Dummy(789);
Assert.AreEqual(0, dummies.IndexOf(oneTwoThreeDummy));
Assert.AreEqual(1, dummies.IndexOf(fourFiveSixDummy));
Assert.AreEqual(-1, dummies.IndexOf(sevenEightNineDummy));
}
/// <summary>
/// Verifies that the non-typesafe IndexOf() method is working as intended
/// </summary>
[Test]
public void TestIndexOfWithObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Dummy sevenEightNineDummy = new Dummy(789);
Assert.AreEqual(0, (dummies as IList).IndexOf((object)oneTwoThreeDummy));
Assert.AreEqual(1, (dummies as IList).IndexOf((object)fourFiveSixDummy));
Assert.AreEqual(-1, (dummies as IList).IndexOf((object)sevenEightNineDummy));
}
/// <summary>
/// Verifies that an exception is thrown if an incompatible object is passed to
/// the non-typesafe variant of the IndexOf() method
/// </summary>
[Test]
public void TestThrowOnIndexOfWithIncompatibleObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.Throws<ArgumentException>(
delegate() { Assert.IsNull((dummies as IList).IndexOf(new object())); }
);
}
/// <summary>Test whether the IndexOf() method can cope with null references</summary>
[Test]
public void TestIndexOfNull() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.AreEqual(-1, dummies.IndexOf(null));
dummies.Add(null);
Assert.AreEqual(0, dummies.IndexOf(null));
}
/// <summary>
/// Verifies that the CopyTo() method of the weak collection works
/// </summary>
[Test]
public void TestCopyToArray() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Dummy[] inputDummies = new Dummy[] { oneTwoThreeDummy, fourFiveSixDummy };
Dummy[] outputDummies = new Dummy[dummies.Count];
dummies.CopyTo(outputDummies, 0);
CollectionAssert.AreEqual(inputDummies, outputDummies);
}
/// <summary>
/// Verifies that the CopyTo() method of the weak collection throws an exception
/// if the target array is too small to hold the collection's contents
/// </summary>
[Test]
public void TestThrowOnCopyToTooSmallArray() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Dummy[] outputStrings = new Dummy[dummies.Count - 1];
Assert.Throws<ArgumentException>(
delegate() { dummies.CopyTo(outputStrings, 0); }
);
}
/// <summary>
/// Verifies that the CopyTo() method of the transforming read only collection
/// works if invoked via the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Dummy[] inputDummies = new Dummy[] { oneTwoThreeDummy, fourFiveSixDummy };
Dummy[] outputDummies = new Dummy[dummies.Count];
(dummies as ICollection).CopyTo(outputDummies, 0);
CollectionAssert.AreEqual(inputDummies, outputDummies);
}
/// <summary>
/// Verifies that the Insert() method correctly shifts items in the collection
/// </summary>
[Test]
public void TestInsert() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Insert(0, fourFiveSixDummy);
Assert.AreEqual(2, dummies.Count);
Assert.AreSame(fourFiveSixDummy, dummies[0]);
Assert.AreSame(oneTwoThreeDummy, dummies[1]);
}
/// <summary>
/// Verifies that the non-typesafe Insert() method correctly shifts items in
/// the collection
/// </summary>
[Test]
public void TestInsertObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
(dummies as IList).Insert(0, (object)fourFiveSixDummy);
Assert.AreEqual(2, dummies.Count);
Assert.AreSame(fourFiveSixDummy, dummies[0]);
Assert.AreSame(oneTwoThreeDummy, dummies[1]);
}
/// <summary>
/// Verifies that the non-typesafe Insert() method correctly shifts items in
/// the collection
/// </summary>
[Test]
public void TestThrowOnInsertIncompatibleObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Assert.Throws<ArgumentException>(
delegate() { (dummies as IList).Insert(0, new object()); }
);
}
/// <summary>
/// Checks whether the IsFixedSize property of the weak collection returns
/// the expected result for a weak collection based on a fixed array
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
Dummy oneTwoThreeDummy = new Dummy(123);
Dummy fourFiveSixDummy = new Dummy(456);
WeakReference<Dummy>[] dummyReferences = new WeakReference<Dummy>[] {
new WeakReference<Dummy>(oneTwoThreeDummy),
new WeakReference<Dummy>(fourFiveSixDummy)
};
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(dummyReferences);
Assert.IsTrue((dummies as IList).IsFixedSize);
}
/// <summary>
/// Tests whether the IsReadOnly property of the weak collection works
/// </summary>
[Test]
public void TestIsReadOnly() {
Dummy oneTwoThreeDummy = new Dummy(123);
Dummy fourFiveSixDummy = new Dummy(456);
List<WeakReference<Dummy>> dummyReferences = new List<WeakReference<Dummy>>();
dummyReferences.Add(new WeakReference<Dummy>(oneTwoThreeDummy));
dummyReferences.Add(new WeakReference<Dummy>(fourFiveSixDummy));
ReadOnlyList<WeakReference<Dummy>> readOnlyDummyReferences =
new ReadOnlyList<WeakReference<Dummy>>(dummyReferences);
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(dummyReferences);
WeakCollection<Dummy> readOnlydummies = new WeakCollection<Dummy>(
readOnlyDummyReferences
);
Assert.IsFalse(dummies.IsReadOnly);
Assert.IsTrue(readOnlydummies.IsReadOnly);
}
/// <summary>
/// Tests whether the IsSynchronized property of the weak collection works
/// </summary>
[Test]
public void TestIsSynchronized() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.IsFalse((dummies as IList).IsSynchronized);
}
/// <summary>Tests the indexer of the weak collection</summary>
[Test]
public void TestIndexer() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Assert.AreSame(oneTwoThreeDummy, dummies[0]);
Assert.AreSame(fourFiveSixDummy, dummies[1]);
dummies[0] = fourFiveSixDummy;
Assert.AreSame(fourFiveSixDummy, dummies[0]);
}
/// <summary>Tests the non-typesafe indexer of the weak collection</summary>
[Test]
public void TestIndexerWithObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Assert.AreSame((object)oneTwoThreeDummy, (dummies as IList)[0]);
Assert.AreSame((object)fourFiveSixDummy, (dummies as IList)[1]);
(dummies as IList)[0] = (object)fourFiveSixDummy;
Assert.AreSame((object)fourFiveSixDummy, (dummies as IList)[0]);
}
/// <summary>
/// Tests whether the non-typesafe indexer of the weak collection throws
/// the correct exception if an incompatible object is assigned
/// </summary>
[Test]
public void TestThrowOnIndexerWithIncompatibleObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Assert.Throws<ArgumentException>(
delegate() { (dummies as IList)[0] = new object(); }
);
}
/// <summary>Tests the Remove() method of the weak collection</summary>
[Test]
public void TestRemove() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Assert.AreEqual(2, dummies.Count);
Assert.IsTrue(dummies.Remove(oneTwoThreeDummy));
Assert.AreEqual(1, dummies.Count);
Assert.IsFalse(dummies.Remove(oneTwoThreeDummy));
}
/// <summary>Tests the non-typesafe Remove() method of the weak collection</summary>
[Test]
public void TestRemoveObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Assert.AreEqual(2, dummies.Count);
(dummies as IList).Remove((object)oneTwoThreeDummy);
Assert.AreEqual(1, dummies.Count);
}
/// <summary>
/// Tests whether a null object can be managed by and removed from the weak collection
/// </summary>
[Test]
public void TestRemoveNull() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
dummies.Add(null);
Assert.AreEqual(1, dummies.Count);
Assert.IsTrue(dummies.Remove(null));
Assert.AreEqual(0, dummies.Count);
}
/// <summary>
/// Tests whether the non-typesafe Remove() method of the weak collection throws
/// an exception if an object is tried to be removed that is incompatible with
/// the collection's item type
/// </summary>
[Test]
public void TestThrowOnRemoveIncompatibleObject() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Assert.Throws<ArgumentException>(
delegate() { (dummies as IList).Remove(new object()); }
);
}
/// <summary>Tests the RemoveAt() method of the weak collection</summary>
[Test]
public void TestRemoveAt() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
Dummy oneTwoThreeDummy = new Dummy(123);
dummies.Add(oneTwoThreeDummy);
Dummy fourFiveSixDummy = new Dummy(456);
dummies.Add(fourFiveSixDummy);
Assert.AreSame(oneTwoThreeDummy, dummies[0]);
dummies.RemoveAt(0);
Assert.AreSame(fourFiveSixDummy, dummies[0]);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new List<WeakReference<Dummy>>()
);
if(!(dummies as ICollection).IsSynchronized) {
lock((dummies as ICollection).SyncRoot) {
Assert.AreEqual(0, dummies.Count);
}
}
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// on transforming read only collections based on IList&lt;&gt;s that do not
/// implement the ICollection interface
/// </summary>
[Test]
public void TestSynchronizationOfIListWithoutICollection() {
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(
new ListWithoutICollection()
);
if(!(dummies as ICollection).IsSynchronized) {
lock((dummies as ICollection).SyncRoot) {
int count = dummies.Count;
Assert.AreEqual(12345, count); // ;-)
}
}
}
/// <summary>Tests the RemoveDeadItems() method</summary>
[Test]
public void TestRemoveDeadItems() {
List<WeakReference<Dummy>> dummyReferences = new List<WeakReference<Dummy>>();
Dummy oneTwoThreeDummy = new Dummy(123);
dummyReferences.Add(new WeakReference<Dummy>(oneTwoThreeDummy));
dummyReferences.Add(new WeakReference<Dummy>(null));
Dummy fourFiveSixDummy = new Dummy(456);
dummyReferences.Add(new WeakReference<Dummy>(fourFiveSixDummy));
WeakCollection<Dummy> dummies = new WeakCollection<Dummy>(dummyReferences);
Assert.AreEqual(3, dummies.Count);
dummies.RemoveDeadItems();
Assert.AreEqual(2, dummies.Count);
Assert.AreSame(oneTwoThreeDummy, dummies[0]);
Assert.AreSame(fourFiveSixDummy, dummies[1]);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

123
Tests/EnumHelperTest.cs Normal file
View file

@ -0,0 +1,123 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.IO;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the enumeration helper class</summary>
[TestFixture]
internal class EnumHelperTest {
#region enum TestEnumeration
/// <summary>An enumeration used for unit testing</summary>
internal enum TestEnumeration {
/// <summary>First arbitrary enumeration value</summary>
One = -2,
/// <summary>Third arbitrary enumeration value</summary>
Three = 33,
/// <summary>Second arbitrary enumeration value</summary>
Two = 23
}
#endregion // enum TestEnumeration
#region enum EmptyEnumeration
internal enum EmptyEnumeration { }
#endregion // enum EmptyEnumeration
/// <summary>
/// Verifies that the enum helper can list the members of an enumeration
/// </summary>
[Test]
public void TestGetValues() {
CollectionAssert.AreEquivalent(
new TestEnumeration[] {
TestEnumeration.One, TestEnumeration.Two, TestEnumeration.Three
},
EnumHelper.GetValues<TestEnumeration>()
);
}
/// <summary>
/// Verifies that the enum helper can locate the highest value in an enumeration
/// </summary>
[Test]
public void TestGetHighestValue() {
Assert.AreEqual(
TestEnumeration.Three, EnumHelper.GetHighestValue<TestEnumeration>()
);
}
/// <summary>
/// Verifies that the enum helper can locate the lowest value in an enumeration
/// </summary>
[Test]
public void TestGetLowestValue() {
Assert.AreEqual(
TestEnumeration.One, EnumHelper.GetLowestValue<TestEnumeration>()
);
}
/// <summary>
/// Tests whether an exception is thrown if the GetValues() method is used on
/// a non-enumeration type
/// </summary>
[Test]
public void TestThrowOnNonEnumType() {
Assert.Throws<ArgumentException>(
delegate() { EnumHelper.GetValues<int>(); }
);
}
/// <summary>
/// Verifies that the default value for an enumeration is returned if
/// the GetLowestValue() method is used on an empty enumeration
/// </summary>
[Test]
public void TestLowestValueInEmptyEnumeration() {
Assert.AreEqual(
default(EmptyEnumeration), EnumHelper.GetLowestValue<EmptyEnumeration>()
);
}
/// <summary>
/// Verifies that the default value for an enumeration is returned if
/// the GetHighestValue() method is used on an empty enumeration
/// </summary>
[Test]
public void TestHighestValueInEmptyEnumeration() {
Assert.AreEqual(
default(EmptyEnumeration), EnumHelper.GetHighestValue<EmptyEnumeration>()
);
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

265
Tests/FloatHelperTest.cs Normal file
View file

@ -0,0 +1,265 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the FloatHelper class</summary>
[TestFixture]
internal class FloatHelperTest {
/// <summary>Tests the floating point value comparison helper</summary>
[Test]
public void UlpDistancesOnFloatsCompareAsEqual() {
Assert.IsTrue(
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1),
"Minimal difference between very small floating point numbers is considered equal"
);
Assert.IsFalse(
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1),
"Larger difference between very small floating point numbers is not considered equal"
);
Assert.IsTrue(
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1),
"Minimal difference between very large floating point numbers is considered equal"
);
Assert.IsFalse(
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1),
"Larger difference between very large floating point numbers is not considered equal"
);
}
/// <summary>Tests the double precision floating point value comparison helper</summary>
[Test]
public void UlpDistancesOnDoublesCompareAsEqual() {
Assert.IsTrue(
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1),
"Minimal difference between very small double precision floating point " +
"numbers is considered equal"
);
Assert.IsFalse(
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1),
"Larger difference between very small double precision floating point " +
"numbers is not considered equal"
);
Assert.IsTrue(
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1),
"Minimal difference between very large double precision floating point " +
"numbers is considered equal"
);
Assert.IsFalse(
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1),
"Larger difference between very large double precision floating point " +
"numbers is not considered equal"
);
}
/// <summary>Tests the integer reinterpretation functions</summary>
[Test]
public void IntegersCanBeReinterpretedAsFloats() {
Assert.AreEqual(
12345.0f,
FloatHelper.ReinterpretAsFloat(FloatHelper.ReinterpretAsInt(12345.0f)),
"Number hasn't changed after mirrored reinterpretation"
);
}
/// <summary>Tests the long reinterpretation functions</summary>
[Test]
public void LongsCanBeReinterpretedAsDoubles() {
Assert.AreEqual(
12345.67890,
FloatHelper.ReinterpretAsDouble(FloatHelper.ReinterpretAsLong(12345.67890)),
"Number hasn't changed after mirrored reinterpretation"
);
}
/// <summary>Tests the floating point reinterpretation functions</summary>
[Test]
public void FloatsCanBeReinterpretedAsIntegers() {
Assert.AreEqual(
12345,
FloatHelper.ReinterpretAsInt(FloatHelper.ReinterpretAsFloat(12345)),
"Number hasn't changed after mirrored reinterpretation"
);
}
/// <summary>
/// Verifies that the IsZero() method can distinguish zero from very small values
/// </summary>
[Test]
public void CanDetermineIfFloatIsZero() {
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroFloat));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroFloat));
Assert.IsFalse(FloatHelper.IsZero(1.401298E-45f));
Assert.IsFalse(FloatHelper.IsZero(-1.401298E-45f));
}
/// <summary>
/// Verifies that the IsZero() method can distinguish zero from very small values
/// </summary>
[Test]
public void CanDetermineIfDoubleIsZero() {
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroDouble));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroDouble));
Assert.IsFalse(FloatHelper.IsZero(4.94065645841247E-324));
Assert.IsFalse(FloatHelper.IsZero(-4.94065645841247E-324));
}
/// <summary>
/// Tests the double prevision floating point reinterpretation functions
/// </summary>
[Test]
public void DoublesCanBeReinterpretedAsLongs() {
Assert.AreEqual(
1234567890,
FloatHelper.ReinterpretAsLong(FloatHelper.ReinterpretAsDouble(1234567890)),
"Number hasn't changed after mirrored reinterpretation"
);
}
/// <summary>
/// Verifies that two denormalized floats can be compared in ulps
/// </summary>
[Test]
public void DenormalizedFloatsCanBeCompared() {
float zero = 0.0f;
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
FloatHelper.ReinterpretAsInt(zero) + 1
);
float zeroMinusOneUlp = -zeroPlusOneUlp;
// Across zero
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
// Against zero
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
}
/// <summary>
/// Verifies that the negative floating point zero is within one ulp of the positive
/// floating point zero and vice versa
/// </summary>
[Test]
public void NegativeZeroFloatEqualsPositiveZero() {
Assert.IsTrue(
FloatHelper.AreAlmostEqual(
FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
)
);
Assert.IsTrue(
FloatHelper.AreAlmostEqual(
FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
)
);
}
/// <summary>Verifies that floats can be compared across the zero boundary</summary>
[Test]
public void FloatsCanBeComparedAcrossZeroInUlps() {
float tenUlps = float.Epsilon * 10.0f;
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
}
/// <summary>
/// Verifies that two denormalized doubles can be compared in ulps
/// </summary>
[Test]
public void DenormalizedDoublesCanBeCompared() {
double zero = 0.0;
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
FloatHelper.ReinterpretAsLong(zero) + 1
);
double zeroMinusOneUlp = -zeroPlusOneUlp;
// Across zero
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
// Against zero
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
}
/// <summary>
/// Verifies that the negative double precision floating point zero is within one ulp
/// of the positive double precision floating point zero and vice versa
/// </summary>
[Test]
public void NegativeZeroDoubleEqualsPositiveZero() {
Assert.IsTrue(
FloatHelper.AreAlmostEqual(
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
)
);
Assert.IsTrue(
FloatHelper.AreAlmostEqual(
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
)
);
}
/// <summary>Verifies that doubles can be compared across the zero boundary</summary>
[Test]
public void DoublesCanBeComparedAcrossZeroInUlps() {
double tenUlps = double.Epsilon * 10.0;
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

561
Tests/IO/ChainStreamTest.cs Normal file
View file

@ -0,0 +1,561 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.IO;
using NUnit.Framework;
namespace Nuclex.Support.IO {
/// <summary>Unit Test for the stream chainer</summary>
[TestFixture]
internal class ChainStreamTest {
#region class TestStream
/// <summary>Testing stream that allows specific features to be disabled</summary>
private class TestStream : Stream {
/// <summary>Initializes a new test stream</summary>
/// <param name="wrappedStream">Stream that will be wrapped</param>
/// <param name="allowRead">Whether to allow reading from the stream</param>
/// <param name="allowWrite">Whether to allow writing to the stream</param>
/// <param name="allowSeek">Whether to allow seeking within the stream</param>
public TestStream(
Stream wrappedStream, bool allowRead, bool allowWrite, bool allowSeek
) {
this.stream = wrappedStream;
this.readAllowed = allowRead;
this.writeAllowed = allowWrite;
this.seekAllowed = allowSeek;
}
/// <summary>Whether data can be read from the stream</summary>
public override bool CanRead {
get { return this.readAllowed; }
}
/// <summary>Whether the stream supports seeking</summary>
public override bool CanSeek {
get { return this.seekAllowed; }
}
/// <summary>Whether data can be written into the stream</summary>
public override bool CanWrite {
get { return this.writeAllowed; }
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written
/// to the underlying device.
/// </summary>
public override void Flush() {
++this.flushCallCount;
this.stream.Flush();
}
/// <summary>Length of the stream in bytes</summary>
public override long Length {
get {
enforceSeekAllowed();
return this.stream.Length;
}
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Position {
get {
enforceSeekAllowed();
return this.stream.Position;
}
set {
enforceSeekAllowed();
this.stream.Position = value;
}
}
/// <summary>
/// Reads a sequence of bytes from the stream and advances the position of
/// the file pointer by the number of bytes read.
/// </summary>
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="offset">
/// Offset in the buffer at which the stream will place the data read
/// </param>
/// <param name="count">Maximum number of bytes that will be read</param>
/// <returns>
/// The number of bytes that were actually read from the stream and written into
/// the provided buffer
/// </returns>
public override int Read(byte[] buffer, int offset, int count) {
enforceReadAllowed();
return this.stream.Read(buffer, offset, count);
}
/// <summary>Changes the position of the file pointer</summary>
/// <param name="offset">
/// Offset to move the file pointer by, relative to the position indicated by
/// the <paramref name="origin" /> parameter.
/// </param>
/// <param name="origin">
/// Reference point relative to which the file pointer is placed
/// </param>
/// <returns>The new absolute position within the stream</returns>
public override long Seek(long offset, SeekOrigin origin) {
enforceSeekAllowed();
return this.stream.Seek(offset, origin);
}
/// <summary>Changes the length of the stream</summary>
/// <param name="value">New length the stream shall have</param>
public override void SetLength(long value) {
enforceSeekAllowed();
this.stream.SetLength(value);
}
/// <summary>
/// Writes a sequence of bytes to the stream and advances the position of
/// the file pointer by the number of bytes written.
/// </summary>
/// <param name="buffer">
/// Buffer containing the data that will be written to the stream
/// </param>
/// <param name="offset">
/// Offset in the buffer at which the data to be written starts
/// </param>
/// <param name="count">Number of bytes that will be written into the stream</param>
public override void Write(byte[] buffer, int offset, int count) {
enforceWriteAllowed();
this.stream.Write(buffer, offset, count);
}
/// <summary>Number of times the Flush() method has been called</summary>
public int FlushCallCount {
get { return this.flushCallCount; }
}
/// <summary>Throws an exception if reading is not allowed</summary>
private void enforceReadAllowed() {
if(!this.readAllowed) {
throw new NotSupportedException("Reading has been disabled");
}
}
/// <summary>Throws an exception if writing is not allowed</summary>
private void enforceWriteAllowed() {
if(!this.writeAllowed) {
throw new NotSupportedException("Writing has been disabled");
}
}
/// <summary>Throws an exception if seeking is not allowed</summary>
private void enforceSeekAllowed() {
if(!this.seekAllowed) {
throw new NotSupportedException("Seeking has been disabled");
}
}
/// <summary>Stream being wrapped for testing</summary>
private Stream stream;
/// <summary>whether to allow reading from the wrapped stream</summary>
private bool readAllowed;
/// <summary>Whether to allow writing to the wrapped stream</summary>
private bool writeAllowed;
/// <summary>Whether to allow seeking within the wrapped stream</summary>
private bool seekAllowed;
/// <summary>Number of times the Flush() method has been called</summary>
private int flushCallCount;
}
#endregion // class TestStream
/// <summary>
/// Tests whether the stream chainer correctly partitions a long write request
/// over its chained streams and appends any remaining data to the end of
/// the last chained stream.
/// </summary>
[Test]
public void TestPartitionedWrite() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
byte[] testData = new byte[20];
for(int index = 0; index < testData.Length; ++index) {
testData[index] = (byte)(index + 1);
}
chainer.Position = 5;
chainer.Write(testData, 0, testData.Length);
Assert.AreEqual(
new byte[] { 0, 0, 0, 0, 0, 1, 2, 3, 4, 5 },
((MemoryStream)chainer.ChainedStreams[0]).ToArray()
);
Assert.AreEqual(
new byte[] { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 },
((MemoryStream)chainer.ChainedStreams[1]).ToArray()
);
}
/// <summary>
/// Tests whether the stream chainer correctly partitions a long read request
/// over its chained streams.
/// </summary>
[Test]
public void TestPartitionedRead() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
((MemoryStream)chainer.ChainedStreams[0]).Write(
new byte[] { 1, 2, 3, 4, 5 }, 0, 5
);
((MemoryStream)chainer.ChainedStreams[1]).Write(
new byte[] { 6, 7, 8, 9, 10 }, 0, 5
);
chainer.Position = 3;
byte[] buffer = new byte[15];
int bytesRead = chainer.Read(buffer, 0, 14);
Assert.AreEqual(14, bytesRead);
Assert.AreEqual(new byte[] { 4, 5, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 0, 0, 0 }, buffer);
}
/// <summary>
/// Tests whether the stream chainer can handle a stream resize
/// </summary>
[Test]
public void TestWriteAfterResize() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
// The first stream has a size of 10 bytes, so this goes into the second stream
chainer.Position = 11;
chainer.Write(new byte[] { 12, 34 }, 0, 2);
// Now we resize the first stream to 15 bytes, so this goes into the first stream
((MemoryStream)chainer.ChainedStreams[0]).SetLength(15);
chainer.Write(new byte[] { 56, 78, 11, 22 }, 0, 4);
Assert.AreEqual(
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 78 },
((MemoryStream)chainer.ChainedStreams[0]).ToArray()
);
Assert.AreEqual(
new byte[] { 11, 22, 34, 0, 0, 0, 0, 0, 0, 0 },
((MemoryStream)chainer.ChainedStreams[1]).ToArray()
);
}
/// <summary>
/// Tests writing to a stream chainer that contains an unseekable stream
/// </summary>
[Test]
public void TestWriteToUnseekableStream() {
MemoryStream firstStream = new MemoryStream();
// Now the second stream _does_ support seeking. If the stream chainer ignores
// that, it would overwrite data in the second stream.
MemoryStream secondStream = new MemoryStream();
secondStream.Write(new byte[] { 0, 9, 8, 7, 6 }, 0, 5);
secondStream.Position = 0;
TestStream testStream = new TestStream(firstStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream, secondStream });
chainer.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
Assert.IsFalse(chainer.CanSeek);
Assert.AreEqual(0, firstStream.Length);
Assert.AreEqual(new byte[] { 0, 9, 8, 7, 6, 1, 2, 3, 4, 5 }, secondStream.ToArray());
}
/// <summary>
/// Tests reading from a stream chainer that contains an unseekable stream
/// </summary>
[Test]
public void TestReadFromUnseekableStream() {
MemoryStream firstStream = new MemoryStream();
// Now the second stream _does_ support seeking. If the stream chainer ignores
// that, it would overwrite data in the second stream.
MemoryStream secondStream = new MemoryStream();
secondStream.Write(new byte[] { 0, 9, 8, 7, 6 }, 0, 5);
secondStream.Position = 3;
TestStream testStream = new TestStream(firstStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream, secondStream });
Assert.IsFalse(chainer.CanSeek);
byte[] buffer = new byte[5];
int readByteCount = chainer.Read(buffer, 0, 3);
Assert.AreEqual(3, readByteCount);
Assert.AreEqual(new byte[] { 0, 9, 8, 0, 0 }, buffer);
readByteCount = chainer.Read(buffer, 0, 3);
Assert.AreEqual(2, readByteCount);
Assert.AreEqual(new byte[] { 7, 6, 8, 0, 0 }, buffer);
}
/// <summary>
/// Tests reading from a stream chainer that contains an unreadable stream
/// </summary>
[Test]
public void TestThrowOnReadFromUnreadableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, false, true, true);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { chainer.Read(new byte[5], 0, 5); }
);
}
/// <summary>
/// Tests writing to a stream chainer that contains an unwriteable stream
/// </summary>
[Test]
public void TestThrowOnWriteToUnwriteableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, true, false, true);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { chainer.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); }
);
}
/// <summary>
/// Verifies that the stream chainer throws an exception if the attempt is
/// made to change the length of the stream
/// </summary>
[Test]
public void TestThrowOnLengthChange() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
Assert.Throws<NotSupportedException>(
delegate() { chainer.SetLength(123); }
);
}
/// <summary>
/// Verifies that the CanRead property is correctly determined by the stream chainer
/// </summary>
[Test]
public void TestCanRead() {
MemoryStream yesStream = new MemoryStream();
TestStream noStream = new TestStream(yesStream, false, true, true);
Stream[] yesGroup = new Stream[] { yesStream, yesStream, yesStream, yesStream };
Stream[] partialGroup = new Stream[] { yesStream, yesStream, noStream, yesStream };
Stream[] noGroup = new Stream[] { noStream, noStream, noStream, noStream };
Assert.IsTrue(new ChainStream(yesGroup).CanRead);
Assert.IsFalse(new ChainStream(partialGroup).CanRead);
Assert.IsFalse(new ChainStream(noGroup).CanRead);
}
/// <summary>
/// Verifies that the CanRead property is correctly determined by the stream chainer
/// </summary>
[Test]
public void TestCanWrite() {
MemoryStream yesStream = new MemoryStream();
TestStream noStream = new TestStream(yesStream, true, false, true);
Stream[] yesGroup = new Stream[] { yesStream, yesStream, yesStream, yesStream };
Stream[] partialGroup = new Stream[] { yesStream, yesStream, noStream, yesStream };
Stream[] noGroup = new Stream[] { noStream, noStream, noStream, noStream };
Assert.IsTrue(new ChainStream(yesGroup).CanWrite);
Assert.IsFalse(new ChainStream(partialGroup).CanWrite);
Assert.IsFalse(new ChainStream(noGroup).CanWrite);
}
/// <summary>
/// Verifies that the CanSeek property is correctly determined by the stream chainer
/// </summary>
[Test]
public void TestCanSeek() {
MemoryStream yesStream = new MemoryStream();
TestStream noStream = new TestStream(yesStream, true, true, false);
Stream[] yesGroup = new Stream[] { yesStream, yesStream, yesStream, yesStream };
Stream[] partialGroup = new Stream[] { yesStream, yesStream, noStream, yesStream };
Stream[] noGroup = new Stream[] { noStream, noStream, noStream, noStream };
Assert.IsTrue(new ChainStream(yesGroup).CanSeek);
Assert.IsFalse(new ChainStream(partialGroup).CanSeek);
Assert.IsFalse(new ChainStream(noGroup).CanSeek);
}
/// <summary>
/// Tests whether an exception is thrown if the Seek() method is called on
/// a stream chainer with streams that do not support seeking
/// </summary>
[Test]
public void TestThrowOnSeekWithUnseekableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { chainer.Seek(123, SeekOrigin.Begin); }
);
}
/// <summary>
/// Tests whether an exception is thrown if the Position property is retrieved
/// on a stream chainer with streams that do not support seeking
/// </summary>
[Test]
public void TestThrowOnGetPositionWithUnseekableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { Console.WriteLine(chainer.Position); }
);
}
/// <summary>
/// Tests whether an exception is thrown if the Position property is set
/// on a stream chainer with streams that do not support seeking
/// </summary>
[Test]
public void TestThrowOnSetPositionWithUnseekableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { chainer.Position = 123; }
);
}
/// <summary>
/// Tests whether an exception is thrown if the Length property is retrieved
/// on a stream chainer with streams that do not support seeking
/// </summary>
[Test]
public void TestThrowOnGetLengthWithUnseekableStream() {
MemoryStream memoryStream = new MemoryStream();
TestStream testStream = new TestStream(memoryStream, true, true, false);
ChainStream chainer = new ChainStream(new Stream[] { testStream });
Assert.Throws<NotSupportedException>(
delegate() { Assert.IsTrue(chainer.Length != chainer.Length); }
);
}
/// <summary>
/// Tests whether the Seek() method of the stream chainer is working
/// </summary>
[Test]
public void TestSeeking() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
Assert.AreEqual(7, chainer.Seek(-13, SeekOrigin.End));
Assert.AreEqual(14, chainer.Seek(7, SeekOrigin.Current));
Assert.AreEqual(11, chainer.Seek(11, SeekOrigin.Begin));
}
/// <summary>
/// Tests whether the stream behaves correctly if data is read from beyond its end
/// </summary>
[Test]
public void TestReadBeyondEndOfStream() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
chainer.Seek(10, SeekOrigin.End);
// This is how the MemoryStream behaves: it returns 0 bytes.
int readByteCount = chainer.Read(new byte[1], 0, 1);
Assert.AreEqual(0, readByteCount);
}
/// <summary>
/// Tests whether the Seek() method throws an exception if an invalid
/// reference point is provided
/// </summary>
[Test]
public void TestThrowOnInvalidSeekReferencePoint() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
Assert.Throws<ArgumentException>(
delegate() { chainer.Seek(1, (SeekOrigin)12345); }
);
}
/// <summary>Verifies that the position property works correctly</summary>
[Test]
public void TestPositionChange() {
ChainStream chainer = chainTwoStreamsOfTenBytes();
chainer.Position = 7;
Assert.AreEqual(chainer.Position, 7);
chainer.Position = 14;
Assert.AreEqual(chainer.Position, 14);
}
/// <summary>Tests the Flush() method of the stream chainer</summary>
[Test]
public void TestFlush() {
MemoryStream firstStream = new MemoryStream();
TestStream firstTestStream = new TestStream(firstStream, true, true, true);
MemoryStream secondStream = new MemoryStream();
TestStream secondTestStream = new TestStream(secondStream, true, true, true);
ChainStream chainer = new ChainStream(
new Stream[] { firstTestStream, secondTestStream }
);
Assert.AreEqual(0, firstTestStream.FlushCallCount);
Assert.AreEqual(0, secondTestStream.FlushCallCount);
chainer.Flush();
Assert.AreEqual(1, firstTestStream.FlushCallCount);
Assert.AreEqual(1, secondTestStream.FlushCallCount);
}
/// <summary>
/// Creates a stream chainer with two streams that each have a size of 10 bytes
/// </summary>
/// <returns>The new stream chainer with two chained 10-byte streams</returns>
private static ChainStream chainTwoStreamsOfTenBytes() {
MemoryStream firstStream = new MemoryStream(10);
MemoryStream secondStream = new MemoryStream(10);
firstStream.SetLength(10);
secondStream.SetLength(10);
return new ChainStream(
new Stream[] { firstStream, secondStream }
);
}
}
} // namespace Nuclex.Support.IO
#endif // UNITTEST

View file

@ -0,0 +1,526 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.IO;
using NUnit.Framework;
namespace Nuclex.Support.IO {
/// <summary>Unit Test for the partial stream</summary>
[TestFixture]
internal class PartialStreamTest {
#region class TestStream
/// <summary>Testing stream that allows specific features to be disabled</summary>
private class TestStream : Stream {
/// <summary>Initializes a new test stream</summary>
/// <param name="wrappedStream">Stream that will be wrapped</param>
/// <param name="allowRead">Whether to allow reading from the stream</param>
/// <param name="allowWrite">Whether to allow writing to the stream</param>
/// <param name="allowSeek">Whether to allow seeking within the stream</param>
public TestStream(
Stream wrappedStream, bool allowRead, bool allowWrite, bool allowSeek
) {
this.stream = wrappedStream;
this.readAllowed = allowRead;
this.writeAllowed = allowWrite;
this.seekAllowed = allowSeek;
}
/// <summary>Whether data can be read from the stream</summary>
public override bool CanRead {
get { return this.readAllowed; }
}
/// <summary>Whether the stream supports seeking</summary>
public override bool CanSeek {
get { return this.seekAllowed; }
}
/// <summary>Whether data can be written into the stream</summary>
public override bool CanWrite {
get { return this.writeAllowed; }
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written
/// to the underlying device.
/// </summary>
public override void Flush() {
++this.flushCallCount;
this.stream.Flush();
}
/// <summary>Length of the stream in bytes</summary>
public override long Length {
get {
enforceSeekAllowed();
return this.stream.Length;
}
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Position {
get {
enforceSeekAllowed();
return this.stream.Position;
}
set {
enforceSeekAllowed();
this.stream.Position = value;
}
}
/// <summary>
/// Reads a sequence of bytes from the stream and advances the position of
/// the file pointer by the number of bytes read.
/// </summary>
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="offset">
/// Offset in the buffer at which the stream will place the data read
/// </param>
/// <param name="count">Maximum number of bytes that will be read</param>
/// <returns>
/// The number of bytes that were actually read from the stream and written into
/// the provided buffer
/// </returns>
public override int Read(byte[] buffer, int offset, int count) {
enforceReadAllowed();
return this.stream.Read(buffer, offset, count);
}
/// <summary>Changes the position of the file pointer</summary>
/// <param name="offset">
/// Offset to move the file pointer by, relative to the position indicated by
/// the <paramref name="origin" /> parameter.
/// </param>
/// <param name="origin">
/// Reference point relative to which the file pointer is placed
/// </param>
/// <returns>The new absolute position within the stream</returns>
public override long Seek(long offset, SeekOrigin origin) {
enforceSeekAllowed();
return this.stream.Seek(offset, origin);
}
/// <summary>Changes the length of the stream</summary>
/// <param name="value">New length the stream shall have</param>
public override void SetLength(long value) {
enforceSeekAllowed();
this.stream.SetLength(value);
}
/// <summary>
/// Writes a sequence of bytes to the stream and advances the position of
/// the file pointer by the number of bytes written.
/// </summary>
/// <param name="buffer">
/// Buffer containing the data that will be written to the stream
/// </param>
/// <param name="offset">
/// Offset in the buffer at which the data to be written starts
/// </param>
/// <param name="count">Number of bytes that will be written into the stream</param>
public override void Write(byte[] buffer, int offset, int count) {
enforceWriteAllowed();
this.stream.Write(buffer, offset, count);
}
/// <summary>Number of times the Flush() method has been called</summary>
public int FlushCallCount {
get { return this.flushCallCount; }
}
/// <summary>Throws an exception if reading is not allowed</summary>
private void enforceReadAllowed() {
if(!this.readAllowed) {
throw new NotSupportedException("Reading has been disabled");
}
}
/// <summary>Throws an exception if writing is not allowed</summary>
private void enforceWriteAllowed() {
if(!this.writeAllowed) {
throw new NotSupportedException("Writing has been disabled");
}
}
/// <summary>Throws an exception if seeking is not allowed</summary>
private void enforceSeekAllowed() {
if(!this.seekAllowed) {
throw new NotSupportedException("Seeking has been disabled");
}
}
/// <summary>Stream being wrapped for testing</summary>
private Stream stream;
/// <summary>whether to allow reading from the wrapped stream</summary>
private bool readAllowed;
/// <summary>Whether to allow writing to the wrapped stream</summary>
private bool writeAllowed;
/// <summary>Whether to allow seeking within the wrapped stream</summary>
private bool seekAllowed;
/// <summary>Number of times the Flush() method has been called</summary>
private int flushCallCount;
}
#endregion // class TestStream
/// <summary>Tests whether the partial stream constructor is working</summary>
[Test]
public void TestConstructor() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
Assert.AreEqual(100, partialStream.Length);
}
}
/// <summary>
/// Verifies that the partial stream constructor throws an exception if
/// it's invoked with an invalid start offset
/// </summary>
[Test]
public void TestThrowOnInvalidStartInConstructor() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
Assert.Throws<ArgumentException>(
delegate() { Console.WriteLine(new PartialStream(memoryStream, -1, 10)); }
);
}
}
/// <summary>
/// Verifies that the partial stream constructor throws an exception if
/// it's invoked with an invalid start offset
/// </summary>
[Test]
public void TestThrowOnInvalidLengthInConstructor() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
Assert.Throws<ArgumentException>(
delegate() { Console.WriteLine(new PartialStream(memoryStream, 100, 24)); }
);
}
}
/// <summary>
/// Verifies that the partial stream constructor throws an exception if
/// it's invoked with a start offset on an unseekable stream
/// </summary>
[Test]
public void TestThrowOnUnseekableStreamWithOffsetInConstructor() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
TestStream testStream = new TestStream(memoryStream, true, true, false);
Assert.Throws<ArgumentException>(
delegate() { Console.WriteLine(new PartialStream(testStream, 23, 100)); }
);
}
}
/// <summary>
/// Tests whether the CanRead property reports its status correctly
/// </summary>
[Test]
public void TestCanReadProperty() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream yesStream = new TestStream(memoryStream, true, true, true);
TestStream noStream = new TestStream(memoryStream, false, true, true);
Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanRead);
Assert.IsFalse(new PartialStream(noStream, 0, 0).CanRead);
}
}
/// <summary>
/// Tests whether the CanWrite property reports its status correctly
/// </summary>
[Test]
public void TestCanWriteProperty() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream yesStream = new TestStream(memoryStream, true, true, true);
TestStream noStream = new TestStream(memoryStream, true, false, true);
Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanWrite);
Assert.IsFalse(new PartialStream(noStream, 0, 0).CanWrite);
}
}
/// <summary>
/// Tests whether the CanSeek property reports its status correctly
/// </summary>
[Test]
public void TestCanSeekProperty() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream yesStream = new TestStream(memoryStream, true, true, true);
TestStream noStream = new TestStream(memoryStream, true, true, false);
Assert.IsTrue(new PartialStream(yesStream, 0, 0).CanSeek);
Assert.IsFalse(new PartialStream(noStream, 0, 0).CanSeek);
}
}
/// <summary>
/// Tests whether the CompleteStream property returns the original stream
/// </summary>
[Test]
public void TestCompleteStreamProperty() {
using(MemoryStream memoryStream = new MemoryStream()) {
PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
Assert.AreSame(memoryStream, partialStream.CompleteStream);
}
}
/// <summary>Tests whether the Flush() method can be called</summary>
[Test]
public void TestFlush() {
using(MemoryStream memoryStream = new MemoryStream()) {
PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
partialStream.Flush();
}
}
/// <summary>
/// Tests whether the Position property correctly reports the file pointer position
/// </summary>
[Test]
public void TestGetPosition() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
Assert.AreEqual(0, partialStream.Position);
byte[] test = new byte[10];
int bytesRead = partialStream.Read(test, 0, 10);
Assert.AreEqual(10, bytesRead);
Assert.AreEqual(10, partialStream.Position);
bytesRead = partialStream.Read(test, 0, 10);
Assert.AreEqual(10, bytesRead);
Assert.AreEqual(20, partialStream.Position);
}
}
/// <summary>
/// Tests whether the Position property is correctly updated
/// </summary>
[Test]
public void TestSetPosition() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
PartialStream partialStream = new PartialStream(memoryStream, 23, 100);
Assert.AreEqual(0, partialStream.Position);
partialStream.Position = 7;
Assert.AreEqual(partialStream.Position, 7);
partialStream.Position = 14;
Assert.AreEqual(partialStream.Position, 14);
}
}
/// <summary>
/// Tests whether the Position property throws an exception if the stream does
/// not support seeking.
/// </summary>
[Test]
public void TestThrowOnGetPositionOnUnseekableStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream testStream = new TestStream(memoryStream, true, true, false);
PartialStream partialStream = new PartialStream(testStream, 0, 0);
Assert.Throws<NotSupportedException>(
delegate() { Console.WriteLine(partialStream.Position); }
);
}
}
/// <summary>
/// Tests whether the Position property throws an exception if the stream does
/// not support seeking.
/// </summary>
[Test]
public void TestThrowOnSetPositionOnUnseekableStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream testStream = new TestStream(memoryStream, true, true, false);
PartialStream partialStream = new PartialStream(testStream, 0, 0);
Assert.Throws<NotSupportedException>(
delegate() { partialStream.Position = 0; }
);
}
}
/// <summary>
/// Tests whether the Read() method throws an exception if the stream does
/// not support reading
/// </summary>
[Test]
public void TestThrowOnReadFromUnreadableStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
TestStream testStream = new TestStream(memoryStream, false, true, true);
PartialStream partialStream = new PartialStream(testStream, 0, 0);
byte[] test = new byte[10];
Assert.Throws<NotSupportedException>(
delegate() { Console.WriteLine(partialStream.Read(test, 0, 10)); }
);
}
}
/// <summary>
/// Tests whether the Seek() method of the partial stream is working
/// </summary>
[Test]
public void TestSeeking() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(20);
PartialStream partialStream = new PartialStream(memoryStream, 0, 20);
Assert.AreEqual(7, partialStream.Seek(-13, SeekOrigin.End));
Assert.AreEqual(14, partialStream.Seek(7, SeekOrigin.Current));
Assert.AreEqual(11, partialStream.Seek(11, SeekOrigin.Begin));
}
}
/// <summary>
/// Tests whether the Seek() method throws an exception if an invalid
/// reference point is provided
/// </summary>
[Test]
public void TestThrowOnInvalidSeekReferencePoint() {
using(MemoryStream memoryStream = new MemoryStream()) {
PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
Assert.Throws<ArgumentException>(
delegate() { partialStream.Seek(1, (SeekOrigin)12345); }
);
}
}
/// <summary>
/// Verifies that the partial stream throws an exception if the attempt is
/// made to change the length of the stream
/// </summary>
[Test]
public void TestThrowOnLengthChange() {
using(MemoryStream memoryStream = new MemoryStream()) {
PartialStream partialStream = new PartialStream(memoryStream, 0, 0);
Assert.Throws<NotSupportedException>(
delegate() { partialStream.SetLength(123); }
);
}
}
/// <summary>
/// Tests whether the Read() method returns 0 bytes if the attempt is made
/// to read data from an invalid position
/// </summary>
[Test]
public void TestReadFromInvalidPosition() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
memoryStream.Position = 1123;
byte[] test = new byte[10];
Assert.AreEqual(0, memoryStream.Read(test, 0, 10));
}
}
/// <summary>Verifies that the Read() method is working</summary>
[Test]
public void TestReadFromPartialStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
memoryStream.Position = 100;
memoryStream.Write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10);
PartialStream partialStream = new PartialStream(memoryStream, 95, 10);
byte[] buffer = new byte[15];
int bytesRead = partialStream.Read(buffer, 0, 15);
Assert.AreEqual(10, bytesRead);
Assert.AreEqual(
new byte[] { 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0 }, buffer
);
}
}
/// <summary>Verifies that the Write() method is working</summary>
[Test]
public void TestWriteToPartialStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(123);
memoryStream.Position = 60;
memoryStream.Write(new byte[] { 11, 12, 13, 14, 15 }, 0, 5);
PartialStream partialStream = new PartialStream(memoryStream, 50, 15);
partialStream.Position = 3;
partialStream.Write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10);
byte[] buffer = new byte[17];
memoryStream.Position = 49;
int bytesRead = memoryStream.Read(buffer, 0, 17);
Assert.AreEqual(17, bytesRead);
Assert.AreEqual(
new byte[] { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 15, 0 },
buffer
);
}
}
/// <summary>
/// Verifies that an exception is thrown if the Write() method of the partial stream
/// is attempted to be used to extend the partial stream's length
/// </summary>
[Test]
public void TestThrowOnExtendPartialStream() {
using(MemoryStream memoryStream = new MemoryStream()) {
memoryStream.SetLength(25);
PartialStream partialStream = new PartialStream(memoryStream, 10, 10);
partialStream.Position = 5;
Assert.Throws<NotSupportedException>(
delegate() { partialStream.Write(new byte[] { 1, 2, 3, 4, 5, 6 }, 0, 6); }
);
}
}
}
} // namespace Nuclex.Support.IO
#endif // UNITTEST

View file

@ -0,0 +1,329 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.IO {
/// <summary>Unit Test for the ring buffer class</summary>
[TestFixture]
internal class RingMemoryStreamTest {
/// <summary>Prepares some test data for the units test methods</summary>
[TestFixtureSetUp]
public void Setup() {
this.testBytes = new byte[20];
for(int i = 0; i < 20; ++i)
this.testBytes[i] = (byte)i;
}
/// <summary>
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity
/// </summary>
[Test]
public void TestWriteTooLargeChunk() {
Assert.Throws<OverflowException>(
delegate() { new RingMemoryStream(10).Write(this.testBytes, 0, 11); }
);
}
/// <summary>
/// Ensures that the ring buffer still accepts write attempts that would fill the
/// entire buffer in one go.
/// </summary>
[Test]
public void TestWriteBarelyFittingChunk() {
new RingMemoryStream(10).Write(this.testBytes, 0, 10);
}
/// <summary>
/// Ensures that the ring buffer correctly manages write attempts that have to
/// be split at the end of the ring buffer
/// </summary>
[Test]
public void TestWriteSplitBlock() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 8);
testRing.Read(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 7);
byte[] actual = new byte[10];
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 5, 6 }, actual);
}
/// <summary>
/// Ensures that the ring buffer correctly manages write attempts that write into
/// the gap after the ring buffer's data has become split
/// </summary>
[Test]
public void TestWriteSplitAndLinearBlock() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 8);
testRing.Read(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 2);
byte[] actual = new byte[10];
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 0, 1 }, actual);
}
/// <summary>
/// Ensures that the ring buffer still detects write that would exceed its capacity
/// if they write into the gap after the ring buffer's data has become split
/// </summary>
[Test]
public void TestWriteSplitAndLinearTooLargeBlock() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 8);
testRing.Read(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
Assert.Throws<OverflowException>(
delegate() { testRing.Write(this.testBytes, 0, 3); }
);
}
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
[Test]
public void TestSplitBlockWrappedRead() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 10);
testRing.Read(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
byte[] actual = new byte[10];
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual);
}
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
[Test]
public void TestSplitBlockLinearRead() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 10);
testRing.Read(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
byte[] actual = new byte[5];
testRing.Read(actual, 0, 5);
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9 }, actual);
}
/// <summary>
/// Tests whether the ring buffer correctly returns partial data if more
/// data is requested than is contained in it.
/// </summary>
[Test]
public void TestEndOfStream() {
byte[] tempBytes = new byte[10];
RingMemoryStream testRing = new RingMemoryStream(10);
Assert.AreEqual(0, testRing.Read(tempBytes, 0, 5));
testRing.Write(this.testBytes, 0, 5);
Assert.AreEqual(5, testRing.Read(tempBytes, 0, 10));
testRing.Write(this.testBytes, 0, 6);
testRing.Read(tempBytes, 0, 5);
testRing.Write(this.testBytes, 0, 9);
Assert.AreEqual(10, testRing.Read(tempBytes, 0, 20));
}
/// <summary>
/// Validates that the ring buffer can extend its capacity without loosing data
/// </summary>
[Test]
public void TestCapacityIncrease() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 10);
testRing.Capacity = 20;
byte[] actual = new byte[10];
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
}
/// <summary>
/// Validates that the ring buffer can reduce its capacity without loosing data
/// </summary>
[Test]
public void TestCapacityDecrease() {
RingMemoryStream testRing = new RingMemoryStream(20);
testRing.Write(this.testBytes, 0, 10);
testRing.Capacity = 10;
byte[] actual = new byte[10];
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
}
/// <summary>
/// Checks that an exception is thrown when the ring buffer's capacity is
/// reduced so much it would have to give up some of its contained data
/// </summary>
[Test]
public void TestCapacityDecreaseException() {
RingMemoryStream testRing = new RingMemoryStream(20);
testRing.Write(this.testBytes, 0, 20);
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { testRing.Capacity = 10; }
);
}
/// <summary>Tests whether the Capacity property returns the current capacity</summary>
[Test]
public void TestCapacity() {
RingMemoryStream testRing = new RingMemoryStream(123);
Assert.AreEqual(123, testRing.Capacity);
}
/// <summary>Ensures that the CanRead property returns true</summary>
[Test]
public void TestCanRead() {
Assert.IsTrue(new RingMemoryStream(10).CanRead);
}
/// <summary>Ensures that the CanSeek property returns false</summary>
[Test]
public void TestCanSeek() {
Assert.IsFalse(new RingMemoryStream(10).CanSeek);
}
/// <summary>Ensures that the CanWrite property returns true</summary>
[Test]
public void TestCanWrite() {
Assert.IsTrue(new RingMemoryStream(10).CanWrite);
}
/// <summary>
/// Tests whether the auto reset feature works (resets the buffer pointer to the
/// left end of the buffer when it gets empty; mainly a performance feature).
/// </summary>
[Test]
public void TestAutoReset() {
byte[] tempBytes = new byte[10];
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 8);
testRing.Read(tempBytes, 0, 2);
testRing.Read(tempBytes, 0, 2);
testRing.Read(tempBytes, 0, 1);
testRing.Read(tempBytes, 0, 1);
Assert.AreEqual(2, testRing.Length);
}
/// <summary>
/// Verifies that an exception is thrown when the Position property of the ring
/// memory stream is used to retrieve the current file pointer position
/// </summary>
[Test]
public void TestThrowOnRetrievePosition() {
Assert.Throws<NotSupportedException>(
delegate() { Console.WriteLine(new RingMemoryStream(10).Position); }
);
}
/// <summary>
/// Verifies that an exception is thrown when the Position property of the ring
/// memory stream is used to modify the current file pointer position
/// </summary>
[Test]
public void TestThrowOnAssignPosition() {
Assert.Throws<NotSupportedException>(
delegate() { new RingMemoryStream(10).Position = 0; }
);
}
/// <summary>
/// Verifies that an exception is thrown when the Seek() method of the ring memory
/// stream is attempted to be used
/// </summary>
[Test]
public void TestThrowOnSeek() {
Assert.Throws<NotSupportedException>(
delegate() { new RingMemoryStream(10).Seek(0, SeekOrigin.Begin); }
);
}
/// <summary>
/// Verifies that an exception is thrown when the SetLength() method of the ring
/// memory stream is attempted to be used
/// </summary>
[Test]
public void TestThrowOnSetLength() {
Assert.Throws<NotSupportedException>(
delegate() { new RingMemoryStream(10).SetLength(10); }
);
}
/// <summary>
/// Tests the Flush() method of the ring memory stream, which is either a dummy
/// implementation or has no side effects
/// </summary>
[Test]
public void TestFlush() {
new RingMemoryStream(10).Flush();
}
/// <summary>
/// Tests whether the length property is updated in accordance to the data written
/// into the ring memory stream
/// </summary>
[Test]
public void TestLengthOnLinearBlock() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(new byte[10], 0, 10);
Assert.AreEqual(10, testRing.Length);
}
/// <summary>
/// Tests whether the length property is updated in accordance to the data written
/// into the ring memory stream when the data is split within the stream
/// </summary>
[Test]
public void TestLengthOnSplitBlock() {
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(new byte[10], 0, 10);
testRing.Read(new byte[5], 0, 5);
testRing.Write(new byte[5], 0, 5);
Assert.AreEqual(10, testRing.Length);
}
/// <summary>Test data for the ring buffer unit tests</summary>
private byte[] testBytes;
}
} // namespace Nuclex.Support.IO
#endif // UNITTEST

129
Tests/IntegerHelperTest.cs Normal file
View file

@ -0,0 +1,129 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Contains unit tests for the integer helper class</summary>
[TestFixture]
internal class IntegerHelperTest {
/// <summary>
/// Verifies that the next power of 2 calculation works for long integers
/// </summary>
[Test]
public void TestNextPowerOf2ULong() {
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(0UL));
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(1UL));
Assert.AreEqual(2UL, IntegerHelper.NextPowerOf2(2UL));
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(3UL));
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(4UL));
Assert.AreEqual(
9223372036854775808UL, IntegerHelper.NextPowerOf2(4611686018427387905UL)
);
Assert.AreEqual(
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775807UL)
);
Assert.AreEqual(
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775808UL)
);
}
/// <summary>
/// Verifies that the next power of 2 calculation works for long integers
/// </summary>
[Test]
public void TestNextPowerOf2Long() {
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(0L));
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(1L));
Assert.AreEqual(2L, IntegerHelper.NextPowerOf2(2L));
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(3L));
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(4L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(2305843009213693953L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387903L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387904L));
}
/// <summary>
/// Verifies that the next power of 2 calculation works for integers
/// </summary>
[Test]
public void TestNextPowerOf2UInt() {
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(0U));
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(1U));
Assert.AreEqual(2U, IntegerHelper.NextPowerOf2(2U));
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(3U));
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(4U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(1073741825U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483647U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483648U));
}
/// <summary>
/// Verifies that the next power of 2 calculation works for integers
/// </summary>
[Test]
public void TestNextPowerOf2Int() {
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(0));
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(1));
Assert.AreEqual(2, IntegerHelper.NextPowerOf2(2));
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(3));
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(4));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(536870913));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741823));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741824));
}
/// <summary>Verifies that the bit counting method for integers works</summary>
[Test]
public void TestCountBitsInInteger() {
Assert.AreEqual(0, IntegerHelper.CountBits(0));
Assert.AreEqual(32, IntegerHelper.CountBits(-1));
Assert.AreEqual(16, IntegerHelper.CountBits(0x55555555));
Assert.AreEqual(16, IntegerHelper.CountBits(0xAAAAAAAA));
for (int bitIndex = 0; bitIndex < 32; ++bitIndex) {
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
}
}
/// <summary>Verifies that the bit counting method for long integers works</summary>
[Test]
public void TestCountBitsInLongInteger() {
Assert.AreEqual(0, IntegerHelper.CountBits(0L));
Assert.AreEqual(64, IntegerHelper.CountBits(-1L));
Assert.AreEqual(32, IntegerHelper.CountBits(0x5555555555555555));
Assert.AreEqual(32, IntegerHelper.CountBits(0xAAAAAAAAAAAAAAAA));
for (int bitIndex = 0; bitIndex < 64; ++bitIndex) {
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
}
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,141 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Licensing {
/// <summary>Unit test for the license key class</summary>
[TestFixture]
internal class LicenseKeyTest {
/// <summary>Tests the default constructor of the license key class</summary>
[Test]
public void DefaultConstructorCanBeUsed() {
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
}
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
[Test]
public void LicenseKeysCanBeConvertedToGuidsAndBack() {
for(int i = 0; i < 128; ++i) {
// Create a new BitArray with the n.th bit set
BitArray guidBits = new BitArray(128);
guidBits[i] = true;
// Create a GUID from this Bitarray
byte[] guidBytes = new byte[16];
guidBits.CopyTo(guidBytes, 0);
Guid originalGuid = new Guid(guidBytes);
// Convert the GUID into a license key and back to a GUID
string licenseKey = new LicenseKey(originalGuid).ToString();
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
// Verify that the original GUID matches the fore-and-back converted one
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
}
}
/// <summary>Tests whether license keys can be modified without destroying them</summary>
[Test]
public void LicenseKeysCanBeModified() {
for(int i = 0; i < 4; ++i) {
for(int j = 0; j < 8; ++j) {
LicenseKey testKey = new LicenseKey(
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
);
string originalString = testKey.ToString();
testKey[i] &= ~(1 << j);
string modifiedString = testKey.ToString();
Assert.IsTrue(
originalString != modifiedString, "Modified string differs from original"
);
testKey[i] |= (1 << j);
string revertedString = testKey.ToString();
Assert.AreEqual(
originalString, revertedString, "Original state restorable"
);
} // for j
} // for i
}
/// <summary>Tests whether license keys can be modified without destroying them</summary>
[Test]
public void ParsingInvalidLicenseKeyThrowsArgumentException() {
Assert.Throws<ArgumentException>(
delegate() { LicenseKey.Parse("hello world"); }
);
}
/// <summary>
/// Tests whether an exception is thrown if the indexer of a license key is used
/// with an invalid index to retrieve a component of the key
/// </summary>
[Test]
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
LicenseKey key = new LicenseKey();
Assert.Throws<IndexOutOfRangeException>(
delegate() { Console.WriteLine(key[-1]); }
);
}
/// <summary>
/// Tests whether an exception is thrown if the indexer of a license key is used
/// with an invalid index to set a component of the key
/// </summary>
[Test]
public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
LicenseKey key = new LicenseKey();
Assert.Throws<IndexOutOfRangeException>(
delegate() { key[-1] = 0; }
);
}
/// <summary>
/// Verifies that a license key can be converted into a byte array
/// </summary>
[Test]
public void LicenseKeyCanBeConvertedToByteArray() {
Guid someGuid = Guid.NewGuid();
LicenseKey someKey = new LicenseKey(someGuid);
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
}
}
} // namespace Nuclex.Support.Licensing
#endif // UNITTEST

View file

@ -0,0 +1,90 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit tests for the observable helper</summary>
[TestFixture]
internal class ObservableHelperTest {
#region class TestReferenceType
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestReferenceType {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestReferenceType
#region struct TestValueType
/// <summary>Example class on which unit test generates change notifications</summary>
public struct TestValueType {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // struct TestValueType
/// <summary>
/// Verifies that the name of a property accessed in a lambda expression
/// can be obtained.
/// </summary>
[Test]
public void CanObtainPropertyNameFromLambdaExpression() {
string propertyName = ObservableHelper.GetPropertyName(
() => SomeReferenceType.SomeProperty
);
Assert.AreEqual("SomeProperty", propertyName);
}
/// <summary>
/// Verifies that the name of a property assigned in a lambda expression
/// can be obtained.
/// </summary>
[Test]
public void CanObtainPropertyNameFromBoxedLambdaExpression() {
string propertyName = ObservableHelper.GetPropertyName(
() => (object)(SomeValueType.SomeProperty)
);
Assert.AreEqual("SomeProperty", propertyName);
}
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestReferenceType SomeReferenceType { get; set; }
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestValueType SomeValueType { get; set; }
}
}
#endif // UNITTEST

171
Tests/ObservableTest.cs Normal file
View file

@ -0,0 +1,171 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.ComponentModel;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support {
/// <summary>Unit tests for observable class</summary>
[TestFixture]
internal class ObservableTest {
#region class TestObservable
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestObservable : Observable {
/// <summary>Triggers the property changed event for the specified property</summary>
/// <param name="propertyName">
/// Name of the property that will be reported as changed
/// </param>
public void FirePropertyChanged(string propertyName) {
OnPropertyChanged(propertyName);
}
/// <summary>Fires the property changed event for the 'SomePropety' property</summary>
public void FireSomePropertyChanged() {
#pragma warning disable 0618
OnPropertyChanged(() => SomeProperty);
#pragma warning restore 0618
}
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestObservable
#region class MockedSubscriber
/// <summary>Mocked change notification subscriber</summary>
public class MockedSubscriber {
/// <summary>Called when the value of a property has changed</summary>
/// <param name="sender">Object of which a property has changed</param>
/// <param name="arguments">Contains the name of the changed property</param>
public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) {
this.wasNotified = true;
this.changedPropertyName = arguments.PropertyName;
}
/// <summary>Whether the subscriber was notified of a property change</summary>
public bool WasNotified {
get { return this.wasNotified; }
}
/// <summary>
/// Checks whether a change notification for the specified property was received
/// </summary>
/// <param name="propertyName">Name of the property that will be checked for</param>
/// <returns>
/// True if a change notification for the specified property was received
/// </returns>
public bool WasNotifiedOfChangeTo(string propertyName) {
if(!this.wasNotified) {
return false;
}
if(string.IsNullOrEmpty(propertyName)) {
return string.IsNullOrEmpty(this.changedPropertyName);
}
return (propertyName == this.changedPropertyName);
}
/// <summary>Whether a change notification was received</summary>
private bool wasNotified;
/// <summary>Name of the property for which a change notification was received</summary>
private string changedPropertyName;
}
#endregion // class MockedSubscriber
/// <summary>Called before each unit test is run</summary>
[SetUp]
public void Setup() {
this.testObservable = new TestObservable();
this.subscriber = new MockedSubscriber();
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged;
}
/// <summary>
/// Verifies that the name of the changed property can be specified manually
/// when triggering the PropertyChanged event
/// </summary>
[Test]
public void PropertyNameCanBeSpecifiedManually() {
this.testObservable.FirePropertyChanged("SomeProperty");
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
}
#if DEBUG // The check is conditionally performed only in debug mode
/// <summary>
/// Verifies that specifying the name of a property that doesn't exist
/// causes an ArgumentException to be thrown
/// </summary>
[Test]
public void SpecifyingInvalidPropertyNameThrowsArgumentException() {
Assert.Throws<ArgumentException>(
delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); }
);
}
#endif
/// <summary>
/// Verifies that the observable is capable of deducing the name of the property
/// from a lambda expression
/// </summary>
[Test]
public void PropertyNameCanBeDeducedFromLambdaExpression() {
this.testObservable.FireSomePropertyChanged();
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
}
/// <summary>
/// Verifies that change notifications for all properties of a type can
/// be generated
/// </summary>
[Test]
public void WildcardChangeNotificationsCanBeSent() {
this.testObservable.FirePropertyChanged(string.Empty);
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
this.testObservable.FirePropertyChanged(null);
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty));
}
/// <summary>Observable object being tested</summary>
private TestObservable testObservable;
/// <summary>Subscriber to the observable object being tested</summary>
private MockedSubscriber subscriber;
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,657 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Parsing {
/// <summary>Ensures that the command line parser is working properly</summary>
[TestFixture]
internal class CommandLineTest {
#region class ArgumentTest
/// <summary>Unit test for the command line option class</summary>
[TestFixture]
public class ArgumentTest {
/// <summary>
/// Verifies that the name of a command line argument without a value can
/// be extracted
/// </summary>
[Test]
public void TestNameExtraction() {
CommandLine.Argument argument = CommandLine.Argument.OptionOnly(
new StringSegment("--test"), 2, 4
);
Assert.AreEqual("--test", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.IsNull(argument.Associator);
Assert.IsNull(argument.Value);
}
/// <summary>
/// Verifies that the name of a command line argument without a value can be
/// extracted when the argument is contained in a substring of a larger string
/// </summary>
[Test]
public void TestNameExtractionFromSubstring() {
CommandLine.Argument argument = CommandLine.Argument.OptionOnly(
new StringSegment("||--test||", 2, 6), 4, 4
);
Assert.AreEqual("--test", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.IsNull(argument.Associator);
Assert.IsNull(argument.Value);
}
/// <summary>
/// Varifies that the name and value of a command line argument can be extracted
/// </summary>
[Test]
public void TestValueExtraction() {
CommandLine.Argument argument = new CommandLine.Argument(
new StringSegment("--test=123"), 2, 4, 7, 3
);
Assert.AreEqual("--test=123", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.AreEqual("=", argument.Associator);
Assert.AreEqual("123", argument.Value);
}
/// <summary>
/// Varifies that the name and value of a command line argument can be extracted
/// when the argument is contained in a substring of a larger string
/// </summary>
[Test]
public void TestValueExtractionFromSubstring() {
CommandLine.Argument argument = new CommandLine.Argument(
new StringSegment("||--test=123||", 2, 10), 4, 4, 9, 3
);
Assert.AreEqual("--test=123", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.AreEqual("=", argument.Associator);
Assert.AreEqual("123", argument.Value);
}
/// <summary>
/// Varifies that the name and value of a command line argument can be extracted
/// when the option is assigned a quoted value
/// </summary>
[Test]
public void TestQuotedValueExtraction() {
CommandLine.Argument argument = new CommandLine.Argument(
new StringSegment("--test=\"123\"", 0, 12), 2, 4, 8, 3
);
Assert.AreEqual("--test=\"123\"", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.AreEqual("=", argument.Associator);
Assert.AreEqual("123", argument.Value);
}
/// <summary>
/// Varifies that the associator of a command line argument with an open ended
/// value assignment can be retrieved
/// </summary>
[Test]
public void TestValuelessAssociatorRetrieval() {
CommandLine.Argument argument = CommandLine.Argument.OptionOnly(
new StringSegment("--test="), 2, 4
);
Assert.AreEqual("--test=", argument.Raw);
Assert.AreEqual("--", argument.Initiator);
Assert.AreEqual("test", argument.Name);
Assert.AreEqual("=", argument.Associator);
Assert.IsNull(argument.Value);
}
/// <summary>
/// Varifies that the associator of a command line option with an open ended value
/// assignment can be retrieved when the option is contained in a substring of
/// a larger string
/// </summary>
[Test]
public void TestValuelessAssociatorRetrievalFromSubstring() {
CommandLine.Argument option = CommandLine.Argument.OptionOnly(
new StringSegment("||--test=||", 2, 7), 4, 4
);
Assert.AreEqual("--test=", option.Raw);
Assert.AreEqual("--", option.Initiator);
Assert.AreEqual("test", option.Name);
Assert.AreEqual("=", option.Associator);
Assert.IsNull(option.Value);
}
/// <summary>
/// Varifies that a command line argument without an option name can be retrieved
/// </summary>
[Test]
public void TestNamelessValueRetrieval() {
CommandLine.Argument argument = CommandLine.Argument.ValueOnly(
new StringSegment("\"hello world\""), 1, 11
);
Assert.AreEqual("\"hello world\"", argument.Raw);
Assert.IsNull(argument.Initiator);
Assert.IsNull(argument.Name);
Assert.IsNull(argument.Associator);
Assert.AreEqual("hello world", argument.Value);
}
/// <summary>
/// Varifies that a command line argument without an option name can be retrieved
/// that is contained in a substring of larger string
/// </summary>
[Test]
public void TestNamelessValueRetrievalFromSubstring() {
CommandLine.Argument argument = CommandLine.Argument.ValueOnly(
new StringSegment("||\"hello world\"||", 2, 13), 3, 11
);
Assert.AreEqual("\"hello world\"", argument.Raw);
Assert.IsNull(argument.Initiator);
Assert.IsNull(argument.Name);
Assert.IsNull(argument.Associator);
Assert.AreEqual("hello world", argument.Value);
}
}
#endregion // class ArgumentTest
/// <summary>Verifies that the default constructor is working</summary>
[Test]
public void TestDefaultConstructor() {
CommandLine commandLine = new CommandLine();
Assert.AreEqual(0, commandLine.Arguments.Count);
}
/// <summary>
/// Validates that the parser can handle an argument initiator with an
/// assignment that is missing a name
/// </summary>
[Test]
public void TestParseAmbiguousNameResolution() {
CommandLine commandLine = CommandLine.Parse("--:test");
// Without a name, this is not a valid command line option, so it will
// be parsed as a loose value instead.
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("--:test", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("--:test", commandLine.Arguments[0].Value);
}
/// <summary>
/// Verifies that a lone short argument initiator without anything behind
/// can be parsed
/// </summary>
[Test]
public void TestParseShortArgumentInitiatorOnly() {
CommandLine commandLine = CommandLine.Parse("-");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("-", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("-", commandLine.Arguments[0].Value);
}
/// <summary>
/// Verifies that a lone long argument initiator without anything behind
/// can be parsed
/// </summary>
[Test]
public void TestParseLongArgumentInitiatorOnly() {
CommandLine commandLine = CommandLine.Parse("--");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("--", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("--", commandLine.Arguments[0].Value);
}
/// <summary>
/// Validates that the parser can handle multiple lone argument initators without
/// a following argument
/// </summary>
[Test]
public void TestParseArgumentInitiatorAtEnd() {
CommandLine commandLine = CommandLine.Parse("-hello:-world -");
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("-hello:-world", commandLine.Arguments[0].Raw);
Assert.AreEqual("-", commandLine.Arguments[0].Initiator);
Assert.AreEqual("hello", commandLine.Arguments[0].Name);
Assert.AreEqual(":", commandLine.Arguments[0].Associator);
Assert.AreEqual("-world", commandLine.Arguments[0].Value);
Assert.AreEqual("-", commandLine.Arguments[1].Raw);
Assert.IsNull(commandLine.Arguments[1].Initiator);
Assert.IsNull(commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.AreEqual("-", commandLine.Arguments[1].Value);
}
/// <summary>Validates that quoted arguments can be parsed</summary>
[Test]
public void TestParseQuotedValue() {
CommandLine commandLine = CommandLine.Parse("hello -world --this -is=\"a test\"");
Assert.AreEqual(4, commandLine.Arguments.Count);
Assert.AreEqual("hello", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("hello", commandLine.Arguments[0].Value);
Assert.AreEqual("-world", commandLine.Arguments[1].Raw);
Assert.AreEqual("-", commandLine.Arguments[1].Initiator);
Assert.AreEqual("world", commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.IsNull(commandLine.Arguments[1].Value);
Assert.AreEqual("--this", commandLine.Arguments[2].Raw);
Assert.AreEqual("--", commandLine.Arguments[2].Initiator);
Assert.AreEqual("this", commandLine.Arguments[2].Name);
Assert.IsNull(commandLine.Arguments[2].Associator);
Assert.IsNull(commandLine.Arguments[2].Value);
Assert.AreEqual("-is=\"a test\"", commandLine.Arguments[3].Raw);
Assert.AreEqual("-", commandLine.Arguments[3].Initiator);
Assert.AreEqual("is", commandLine.Arguments[3].Name);
Assert.AreEqual("=", commandLine.Arguments[3].Associator);
Assert.AreEqual("a test", commandLine.Arguments[3].Value);
}
/// <summary>Validates that null can be parsed</summary>
[Test]
public void TestParseNull() {
CommandLine commandLine = CommandLine.Parse((string)null);
Assert.AreEqual(0, commandLine.Arguments.Count);
}
/// <summary>Validates that a single argument without quotes can be parsed</summary>
[Test]
public void TestParseSingleNakedValue() {
CommandLine commandLine = CommandLine.Parse("hello");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("hello", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("hello", commandLine.Arguments[0].Value);
}
/// <summary>
/// Validates that the parser can handle a quoted argument that's missing
/// the closing quote
/// </summary>
[Test]
public void TestParseQuotedArgumentWithoutClosingQuote() {
CommandLine commandLine = CommandLine.Parse("\"Quoted argument");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("\"Quoted argument", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("Quoted argument", commandLine.Arguments[0].Value);
}
/// <summary>
/// Validates that the parser correctly handles a quoted value assignment that's
/// missing the closing quote
/// </summary>
[Test]
public void TestParseQuotedValueWithoutClosingQuote() {
CommandLine commandLine = CommandLine.Parse("--test=\"Quoted argument");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("--test=\"Quoted argument", commandLine.Arguments[0].Raw);
Assert.AreEqual("--", commandLine.Arguments[0].Initiator);
Assert.AreEqual("test", commandLine.Arguments[0].Name);
Assert.AreEqual("=", commandLine.Arguments[0].Associator);
Assert.AreEqual("Quoted argument", commandLine.Arguments[0].Value);
}
/// <summary>
/// Validates that the parser can handle an command line consisting of only spaces
/// </summary>
[Test]
public void TestParseSpacesOnly() {
CommandLine commandLine = CommandLine.Parse(" \t ");
Assert.AreEqual(0, commandLine.Arguments.Count);
}
/// <summary>
/// Validates that the parser can handle a quoted option
/// </summary>
[Test]
public void TestParseQuotedOption() {
CommandLine commandLine = CommandLine.Parse("--\"hello\"");
// Quoted options are not supported, so this becomes a loose value
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("--\"hello\"", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("--\"hello\"", commandLine.Arguments[0].Value);
}
/// <summary>
/// Validates that the parser can handle multiple lone argument initators without
/// a following argument
/// </summary>
[Test]
public void TestParseMultipleLoneArgumentInitiators() {
CommandLine commandLine = CommandLine.Parse("--- --");
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("---", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("---", commandLine.Arguments[0].Value);
Assert.AreEqual("--", commandLine.Arguments[1].Raw);
Assert.IsNull(commandLine.Arguments[1].Initiator);
Assert.IsNull(commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.AreEqual("--", commandLine.Arguments[1].Value);
}
/// <summary>
/// Verifies that the parser correctly handles options with embedded option initiators
/// </summary>
[Test]
public void TestParseOptionWithEmbeddedInitiator() {
CommandLine commandLine = CommandLine.Parse("-hello/world=123 -test-case");
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("-hello/world=123", commandLine.Arguments[0].Raw);
Assert.AreEqual("-", commandLine.Arguments[0].Initiator);
Assert.AreEqual("hello/world", commandLine.Arguments[0].Name);
Assert.AreEqual("=", commandLine.Arguments[0].Associator);
Assert.AreEqual("123", commandLine.Arguments[0].Value);
Assert.AreEqual("-test-case", commandLine.Arguments[1].Raw);
Assert.AreEqual("-", commandLine.Arguments[1].Initiator);
Assert.AreEqual("test-case", commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.IsNull(commandLine.Arguments[1].Value);
}
/// <summary>
/// Validates that arguments and values without spaces inbetween can be parsed
/// </summary>
[Test]
public void TestParseOptionAndValueWithoutSpaces() {
CommandLine commandLine = CommandLine.Parse("\"value\"-option\"value\"");
Assert.AreEqual(3, commandLine.Arguments.Count);
Assert.AreEqual("\"value\"", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("value", commandLine.Arguments[0].Value);
Assert.AreEqual("-option", commandLine.Arguments[1].Raw);
Assert.AreEqual("-", commandLine.Arguments[1].Initiator);
Assert.AreEqual("option", commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.IsNull(commandLine.Arguments[1].Value);
Assert.AreEqual("\"value\"", commandLine.Arguments[2].Raw);
Assert.IsNull(commandLine.Arguments[2].Initiator);
Assert.IsNull(commandLine.Arguments[2].Name);
Assert.IsNull(commandLine.Arguments[2].Associator);
Assert.AreEqual("value", commandLine.Arguments[2].Value);
}
/// <summary>
/// Validates that options with modifiers at the end of the command line
/// are parsed successfully
/// </summary>
[Test]
public void TestParseOptionWithModifierAtEnd() {
CommandLine commandLine = CommandLine.Parse("--test-value- -test+");
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("--test-value-", commandLine.Arguments[0].Raw);
Assert.AreEqual("--", commandLine.Arguments[0].Initiator);
Assert.AreEqual("test-value", commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("-", commandLine.Arguments[0].Value);
Assert.AreEqual("-test+", commandLine.Arguments[1].Raw);
Assert.AreEqual("-", commandLine.Arguments[1].Initiator);
Assert.AreEqual("test", commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.AreEqual("+", commandLine.Arguments[1].Value);
}
/// <summary>
/// Validates that options with values assigned to them are parsed successfully
/// </summary>
[Test]
public void TestParseOptionWithAssignment() {
CommandLine commandLine = CommandLine.Parse("-hello: -world=321");
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("-hello:", commandLine.Arguments[0].Raw);
Assert.AreEqual("-", commandLine.Arguments[0].Initiator);
Assert.AreEqual("hello", commandLine.Arguments[0].Name);
Assert.AreEqual(":", commandLine.Arguments[0].Associator);
Assert.IsNull(commandLine.Arguments[0].Value);
Assert.AreEqual("-world=321", commandLine.Arguments[1].Raw);
Assert.AreEqual("-", commandLine.Arguments[1].Initiator);
Assert.AreEqual("world", commandLine.Arguments[1].Name);
Assert.AreEqual("=", commandLine.Arguments[1].Associator);
Assert.AreEqual("321", commandLine.Arguments[1].Value);
}
/// <summary>
/// Validates that options with an empty value at the end of the command line
/// string are parsed successfully
/// </summary>
[Test]
public void TestParseOptionAtEndOfString() {
CommandLine commandLine = CommandLine.Parse("--test:");
Assert.AreEqual(1, commandLine.Arguments.Count);
Assert.AreEqual("--test:", commandLine.Arguments[0].Raw);
Assert.AreEqual("--", commandLine.Arguments[0].Initiator);
Assert.AreEqual("test", commandLine.Arguments[0].Name);
Assert.AreEqual(":", commandLine.Arguments[0].Associator);
Assert.IsNull(commandLine.Arguments[0].Value);
}
/// <summary>
/// Verifies that the parser can recognize windows command line options if
/// configured to windows mode
/// </summary>
[Test]
public void TestWindowsOptionInitiator() {
CommandLine commandLine = CommandLine.Parse("/hello //world", true);
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("/hello", commandLine.Arguments[0].Raw);
Assert.AreEqual("/", commandLine.Arguments[0].Initiator);
Assert.AreEqual("hello", commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.IsNull(commandLine.Arguments[0].Value);
Assert.AreEqual("//world", commandLine.Arguments[1].Raw);
Assert.IsNull(commandLine.Arguments[1].Initiator);
Assert.IsNull(commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.AreEqual("//world", commandLine.Arguments[1].Value);
}
/// <summary>
/// Verifies that the parser ignores windows command line options if
/// configured to non-windows mode
/// </summary>
[Test]
public void TestNonWindowsOptionValues() {
CommandLine commandLine = CommandLine.Parse("/hello //world", false);
Assert.AreEqual(2, commandLine.Arguments.Count);
Assert.AreEqual("/hello", commandLine.Arguments[0].Raw);
Assert.IsNull(commandLine.Arguments[0].Initiator);
Assert.IsNull(commandLine.Arguments[0].Name);
Assert.IsNull(commandLine.Arguments[0].Associator);
Assert.AreEqual("/hello", commandLine.Arguments[0].Value);
Assert.AreEqual("//world", commandLine.Arguments[1].Raw);
Assert.IsNull(commandLine.Arguments[1].Initiator);
Assert.IsNull(commandLine.Arguments[1].Name);
Assert.IsNull(commandLine.Arguments[1].Associator);
Assert.AreEqual("//world", commandLine.Arguments[1].Value);
}
/// <summary>
/// Tests whether the existence of named arguments can be checked
/// </summary>
[Test]
public void HasArgumentWorksForWindowsStyleArguments() {
CommandLine test = CommandLine.Parse("/first:x /second:y /second:z third", true);
Assert.IsTrue(test.HasArgument("first"));
Assert.IsTrue(test.HasArgument("second"));
Assert.IsFalse(test.HasArgument("third"));
Assert.IsFalse(test.HasArgument("fourth"));
}
/// <summary>
/// Tests whether the existence of named arguments can be checked
/// </summary>
[Test]
public void HasArgumentWorksForUnixStyleArguments() {
CommandLine test = CommandLine.Parse("--first=x --second=y --second=z third", false);
Assert.IsTrue(test.HasArgument("first"));
Assert.IsTrue(test.HasArgument("second"));
Assert.IsFalse(test.HasArgument("third"));
Assert.IsFalse(test.HasArgument("fourth"));
}
/// <summary>
/// Tests whether a command line can be built with the command line class
/// </summary>
[Test]
public void TestCommandLineFormatting() {
CommandLine commandLine = new CommandLine(true);
commandLine.AddValue("single");
commandLine.AddValue("with space");
commandLine.AddOption("option");
commandLine.AddOption("@@", "extravagant-option");
commandLine.AddAssignment("name", "value");
commandLine.AddAssignment("name", "value with spaces");
commandLine.AddAssignment("@@", "name", "value");
commandLine.AddAssignment("@@", "name", "value with spaces");
Assert.AreEqual(8, commandLine.Arguments.Count);
Assert.AreEqual("single", commandLine.Arguments[0].Value);
Assert.AreEqual("with space", commandLine.Arguments[1].Value);
Assert.AreEqual("option", commandLine.Arguments[2].Name);
Assert.AreEqual("@@", commandLine.Arguments[3].Initiator);
Assert.AreEqual("extravagant-option", commandLine.Arguments[3].Name);
Assert.AreEqual("name", commandLine.Arguments[4].Name);
Assert.AreEqual("value", commandLine.Arguments[4].Value);
Assert.AreEqual("name", commandLine.Arguments[5].Name);
Assert.AreEqual("value with spaces", commandLine.Arguments[5].Value);
Assert.AreEqual("@@", commandLine.Arguments[6].Initiator);
Assert.AreEqual("name", commandLine.Arguments[6].Name);
Assert.AreEqual("value", commandLine.Arguments[6].Value);
Assert.AreEqual("name", commandLine.Arguments[7].Name);
Assert.AreEqual("@@", commandLine.Arguments[7].Initiator);
Assert.AreEqual("value with spaces", commandLine.Arguments[7].Value);
string commandLineString = commandLine.ToString();
Assert.AreEqual(
"single \"with space\" " +
"-option @@extravagant-option " +
"-name=value -name=\"value with spaces\" " +
"@@name=value @@name=\"value with spaces\"",
commandLineString
);
}
/// <summary>
/// Tests whether a command line can be built that contains empty arguments
/// </summary>
[Test]
public void TestNullArgumentFormatting() {
CommandLine commandLine = new CommandLine(false);
commandLine.AddValue(string.Empty);
commandLine.AddValue("hello");
commandLine.AddValue(null);
commandLine.AddValue("-test");
Assert.AreEqual(4, commandLine.Arguments.Count);
string commandLineString = commandLine.ToString();
Assert.AreEqual("\"\" hello \"\" \"-test\"", commandLineString);
}
}
} // namespace Nuclex.Support.Parsing
#endif // UNITTEST

View file

@ -0,0 +1,232 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace Nuclex.Support.Parsing {
/// <summary>Verifies that the parser helper methods are correct</summary>
[TestFixture]
internal class ParserHelperTest {
/// <summary>Ensures that the SkipSpaces() method can handle null strings</summary>
[Test]
public void CanSkipSpacesInNullString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipSpaces((string)null, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipSpaces() method can handle empty strings</summary>
[Test]
public void CanSkipSpacesInEmptyString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipSpaces(string.Empty, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipSpaces() method can skip spaces</summary>
[Test]
public void SpacesCanBeSkipped() {
int index = 7;
ParserHelper.SkipSpaces(" Test Test ", ref index);
Assert.AreEqual(10, index);
}
/// <summary>Ensures that the SkipNonSpaces() method can handle null strings</summary>
[Test]
public void CanSkipNonSpacesInNullString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipNonSpaces((string)null, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNonSpaces() method can handle empty strings</summary>
[Test]
public void CanSkipNonSpacesInEmptyString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipNonSpaces(string.Empty, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNonSpaces() method can skip non-space characters</summary>
[Test]
public void NonSpacesCanBeSkipped() {
int index = 7;
ParserHelper.SkipNonSpaces("Test Test Test", ref index);
Assert.AreEqual(11, index);
}
/// <summary>Ensures that the SkipNumbers() method can handle null strings</summary>
[Test]
public void CanSkipNumbersInNullString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipNumericals((string)null, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNumbers() method can handle empty strings</summary>
[Test]
public void CanSkipNumbersInEmptyString() {
int index = 0;
Assert.DoesNotThrow(
delegate() { ParserHelper.SkipNumericals(string.Empty, ref index); }
);
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNumbers() method can skip numbers</summary>
[Test]
public void NumbersCanBeSkipped() {
int index = 6;
ParserHelper.SkipNumericals("123abc456def789", ref index);
Assert.AreEqual(9, index);
}
/// <summary>Ensures that the SkipIntegers() method can handle null strings</summary>
[Test]
public void CanSkipIntegersInNullString() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipInteger((string)null, ref index));
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNumbers() method can handle empty strings</summary>
[Test]
public void CanSkipIntegersInEmptyString() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipInteger(string.Empty, ref index));
Assert.AreEqual(0, index);
}
/// <summary>Verifies that a prefix alone can not be skipped as an integer</summary>
[Test]
public void PrefixAloneIsNotAnInteger() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipInteger("+Test", ref index));
Assert.AreEqual(0, index);
Assert.IsFalse(ParserHelper.SkipInteger("-", ref index));
Assert.AreEqual(0, index);
}
/// <summary>Verifies that a prefixed integer can be skipped</summary>
[Test]
public void PrefixedIntegersCanBeSkipped() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipInteger("+123", ref index));
Assert.AreEqual(4, index);
}
/// <summary>Verifies that an integer without a prefix can be skipped</summary>
[Test]
public void PlainIntegersCanBeSkipped() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipInteger("12345", ref index));
Assert.AreEqual(5, index);
}
/// <summary>
/// Verifies that trying to skip text as if it was an integer skips nothing
/// </summary>
[Test]
public void SkippingTextAsIntegerReturnsFalse() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipInteger("hello", ref index));
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipIntegers() method can handle null strings</summary>
[Test]
public void CanSkipStringInNullString() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipString((string)null, ref index));
Assert.AreEqual(0, index);
}
/// <summary>Ensures that the SkipNumbers() method can handle empty strings</summary>
[Test]
public void CanSkipStringInEmptyString() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipString(string.Empty, ref index));
Assert.AreEqual(0, index);
}
/// <summary>Verifies that a string consisting of a single word can be skipped</summary>
[Test]
public void SingleWordStringsCanBeSkipped() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipString("hello", ref index));
Assert.AreEqual(5, index);
}
/// <summary>
/// Verifies that a space character is not skipped over when skipping a string
/// </summary>
[Test]
public void SpaceTerminatesUnquotedStrings() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipString("hello world", ref index));
Assert.AreEqual(5, index);
}
/// <summary>Verifies that a string in quotes continues until the closing quote</summary>
[Test]
public void QuotedStringsCanBeSkipped() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipString("\"This is a test\"", ref index));
Assert.AreEqual(16, index);
}
/// <summary>Verifies that a string in quotes continues until the closing quote</summary>
[Test]
public void QuotedStringsStopAtClosingQuote() {
int index = 0;
Assert.IsTrue(ParserHelper.SkipString("\"This is a test\" but this not.", ref index));
Assert.AreEqual(16, index);
}
/// <summary>Verifies that a string in quotes continues until the closing quote</summary>
[Test]
public void QuotedStringRequiresClosingQuote() {
int index = 0;
Assert.IsFalse(ParserHelper.SkipString("\"This is missing the closing quote", ref index));
Assert.AreEqual(0, index);
}
}
} // namespace Nuclex.Support.Parsing
#endif // UNITTEST

246
Tests/PathHelperTest.cs Normal file
View file

@ -0,0 +1,246 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the path helper class</summary>
[TestFixture]
internal class PathHelperTest {
/// <summary>
/// Tests whether the relative path creator keeps the absolute path if
/// the location being passed is not relative to the base path.
/// </summary>
[Test]
public void TestRelativeWindowsPathOfNonRelativePath() {
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2"),
platformify("D:/Folder1/Folder2")
),
Is.EqualTo(platformify("D:/Folder1/Folder2"))
);
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2/"),
platformify("D:/Folder1/Folder2/")
),
Is.EqualTo(platformify("D:/Folder1/Folder2/"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to the parent folder of the base path for windows paths.
/// </summary>
[Test]
public void TestRelativeWindowsPathToParentFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2"),
platformify("C:/Folder1")
),
Is.EqualTo(platformify(".."))
);
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2/"),
platformify("C:/Folder1/")
),
Is.EqualTo(platformify("../"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative path to
/// the parent folder of the base path for windows paths with more than one level.
/// </summary>
[Test]
public void TestRelativeWindowsPathToParentFolderTwoLevels() {
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2/Folder3"),
platformify("C:/Folder1")
),
Is.EqualTo(platformify("../.."))
);
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2/Folder3/"),
platformify("C:/Folder1/")
),
Is.EqualTo(platformify("../../"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to the parent folder of the base path for unix paths.
/// </summary>
[Test]
public void TestRelativeUnixPathToParentFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2"),
platformify("/Folder1")
),
Is.EqualTo(platformify(".."))
);
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2/"),
platformify("/Folder1/")
),
Is.EqualTo(platformify("../"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative path to
/// the parent folder of the base path for unix paths with more than one level.
/// </summary>
[Test]
public void TestRelativeUnixPathToParentFolderTwoLevels() {
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2/Folder3"),
platformify("/Folder1")
),
Is.EqualTo(platformify("../.."))
);
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2/Folder3/"),
platformify("/Folder1/")
),
Is.EqualTo(platformify("../../"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to a nested folder in the base path for windows paths.
/// </summary>
[Test]
public void TestRelativeWindowsPathToNestedFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1"),
platformify("C:/Folder1/Folder2")
),
Is.EqualTo(platformify("Folder2"))
);
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/"),
platformify("C:/Folder1/Folder2/")
),
Is.EqualTo(platformify("Folder2/"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to a nested folder in the base path for unix paths.
/// </summary>
[Test]
public void TestRelativeUnixPathToNestedFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1"),
platformify("/Folder1/Folder2")
),
Is.EqualTo(platformify("Folder2"))
);
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/"),
platformify("/Folder1/Folder2/")
),
Is.EqualTo(platformify("Folder2/"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to another folder on the same level as base path for windows paths.
/// </summary>
[Test]
public void TestRelativeWindowsPathToSiblingFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2/"),
platformify("C:/Folder1/Folder2345")
),
Is.EqualTo(platformify("../Folder2345"))
);
Assert.That(
PathHelper.MakeRelative(
platformify("C:/Folder1/Folder2345/"),
platformify("C:/Folder1/Folder2")
),
Is.EqualTo(platformify("../Folder2"))
);
}
/// <summary>
/// Tests whether the relative path creator correctly builds the relative
/// path to another folder on the same level as base path for unix paths.
/// </summary>
[Test]
public void TestRelativeUnixPathToSiblingFolder() {
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2/"),
platformify("/Folder1/Folder2345")
),
Is.EqualTo(platformify("../Folder2345"))
);
Assert.That(
PathHelper.MakeRelative(
platformify("/Folder1/Folder2345/"),
platformify("/Folder1/Folder2")
),
Is.EqualTo(platformify("../Folder2"))
);
}
/// <summary>
/// Converts unix-style directory separators into the format used by the current platform
/// </summary>
/// <param name="path">Path to converts into the platform-dependent format</param>
/// <returns>Platform-specific version of the provided unix-style path</returns>
private string platformify(string path) {
return path.Replace('/', Path.DirectorySeparatorChar);
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,124 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.ComponentModel;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit tests for the property change event argument helper</summary>
[TestFixture]
internal class PropertyChangedEventArgsHelperTest {
#region class TestViewModel
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestViewModel {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestViewModel
/// <summary>
/// Verifies that a property change notification matching the property
/// passed to the AreAffecting() method is recognized
/// </summary>
[Test]
public void MatchingPropertyChangeNotificationIsRecognized() {
var arguments = new PropertyChangedEventArgs("SomeProperty");
#pragma warning disable 0618
Assert.IsTrue(arguments.AreAffecting(() => ViewModel.SomeProperty));
#pragma warning restore 0618
Assert.IsTrue(arguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Ensures that a mismatching property change notification will
/// not report the property as being affected.
/// </summary>
[Test]
public void MismatchingPropertyIsReportedAsUnaffected() {
var arguments = new PropertyChangedEventArgs("AnotherProperty");
#pragma warning disable 0618
Assert.IsFalse(arguments.AreAffecting(() => ViewModel.SomeProperty));
#pragma warning restore 0618
Assert.IsFalse(arguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Verifies that any specific property is reported as being affected
/// when the property change notification is a null wildcard
/// </summary>
[Test]
public void SpecificPropertyIsAffectedByNullWildcard() {
var nullArguments = new PropertyChangedEventArgs(null);
#pragma warning disable 0618
Assert.IsTrue(nullArguments.AreAffecting(() => ViewModel.SomeProperty));
#pragma warning restore 0618
Assert.IsTrue(nullArguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Verifies that any specific property is reported as being affected
/// when the property change notification is an empty wildcard
/// </summary>
[Test]
public void SpecificPropertyIsAffectedByEmptyWildcard() {
var emptyArguments = new PropertyChangedEventArgs(string.Empty);
#pragma warning disable 0618
Assert.IsTrue(emptyArguments.AreAffecting(() => ViewModel.SomeProperty));
#pragma warning disable 0618
Assert.IsTrue(emptyArguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Tests whether the helper can recognize a wildcard property change
/// notification using null as the wildcard.
/// </summary>
[Test]
public void NullWildcardIsRecognized() {
var nullArguments = new PropertyChangedEventArgs(null);
Assert.IsTrue(nullArguments.AffectAllProperties());
}
/// <summary>
/// Tests whether the helper can recognize a wildcard property change
/// notification using an empty string as the wildcard.
/// </summary>
[Test]
public void EmptyWildcardIsRecognized() {
var emptyArguments = new PropertyChangedEventArgs(string.Empty);
Assert.IsTrue(emptyArguments.AffectAllProperties());
}
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestViewModel ViewModel { get; set; }
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,466 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using NUnit.Framework;
namespace Nuclex.Support.Settings {
/// <summary>Unit tests for the configuration file store</summary>
[TestFixture]
internal class ConfigurationFileStoreTest {
/// <summary>
/// Verifies that loading an empty file doesn't lead to an exception
/// </summary>
[Test]
public void CanParseEmptyFile() {
Assert.That(() => load(string.Empty), Throws.Nothing);
}
/// <summary>
/// Verifies that categories can be parsed from a configuration file
/// </summary>
[Test]
public void CanParseCategories() {
string[] categoryNames = new string[] { "Category1", "Category 2" };
string fileContents =
"[" + categoryNames[0] + "]\r\n" +
" [ " + categoryNames[1] + " ] \r\n";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.EquivalentTo(categoryNames));
}
/// <summary>
/// Verifies that malformed categories can be handled by the parser
/// </summary>
[Test]
public void MalformedCategoriesAreIgnored() {
string fileContents =
"[ Not a category\r\n" +
" [";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that empty lines in the configuration file have no meaning
/// </summary>
[Test]
public void EmptyLinesAreSkipped() {
string fileContents =
"\r\n" +
" ";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that category definitions after a comment sign are ignored
/// </summary>
[Test]
public void CommentedOutCategoriesAreIgnored() {
string fileContents =
"#[NotACategory]\r\n" +
"; [ Also Not A Category ]\r\n";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that assignments without an option name are ignored by the parser
/// </summary>
[Test]
public void NamelessAssignmentsAreIgnored() {
string fileContents =
"=\r\n" +
" = \r\n" +
" = hello";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
Assert.That(configurationFile.EnumerateOptions(), Is.Empty);
}
/// <summary>
/// Verifies that assignments without an option name are ignored by the parser
/// </summary>
[Test]
public void OptionsCanHaveEmptyValues() {
string fileContents =
"a =\r\n" +
"b = \r\n" +
"c = ; hello";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
var options = new List<OptionInfo>(configurationFile.EnumerateOptions());
Assert.That(options.Count, Is.EqualTo(3));
for(int index = 0; index < options.Count; ++index) {
Assert.That(
configurationFile.Get<string>(null, options[index].Name), Is.Null.Or.Empty
);
}
}
/// <summary>
/// Verifies that values assigned to options can contain space charcters
/// </summary>
[Test]
public void OptionValuesCanContainSpaces() {
string fileContents =
"test = hello world";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.Get<string>(null, "test"), Is.EqualTo("hello world"));
}
/// <summary>
/// Verifies that values enclosed in quotes can embed comment characters
/// </summary>
[Test]
public void OptionValuesWithQuotesCanEmbedComments() {
string fileContents =
"test = \"This ; is # not a comment\" # but this is";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(
configurationFile.Get<string>(null, "test"),
Is.EqualTo("\"This ; is # not a comment\"")
);
}
/// <summary>
/// Verifies that values can end on a quote without causing trouble
/// </summary>
[Test]
public void CommentsCanEndWithAQuote() {
string fileContents =
"test = \"This value ends with a quote\"";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(
configurationFile.Get<string>(null, "test"),
Is.EqualTo("\"This value ends with a quote\"")
);
}
/// <summary>
/// Verifies that values can forget the closing quote without causing trouble
/// </summary>
[Test]
public void ClosingQuoteCanBeOmmitted() {
string fileContents =
"test = \"No closing quote";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(
configurationFile.Get<string>(null, "test"),
Is.EqualTo("\"No closing quote")
);
}
/// <summary>
/// Verifies that text placed after the closing quote will also be part of
/// an option's value
/// </summary>
[Test]
public void TextAfterClosingQuoteBecomesPartOfValue() {
string fileContents =
"test = \"Begins here\" end ends here";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(
configurationFile.Get<string>(null, "test"),
Is.EqualTo("\"Begins here\" end ends here")
);
}
/// <summary>
/// Verifies that text placed after the closing quote will also be part of
/// an option's value
/// </summary>
[Test]
public void OptionValuesCanBeChanged() {
string fileContents = "test = 123 ; comment";
ConfigurationFileStore configurationFile = load(fileContents);
configurationFile.Set(null, "test", "hello world");
Assert.That(
save(configurationFile),
Contains.Substring("hello world").And.ContainsSubstring("comment")
);
}
/// <summary>
/// Verifies that options can be added to the configuration file
/// </summary>
[Test]
public void OptionsCanBeAdded() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>(null, "test", "123");
Assert.That(configurationFile.Get<string>(null, "test"), Is.EqualTo("123"));
}
/// <summary>
/// Verifies that options can be added to the configuration file
/// </summary>
[Test]
public void CategoriesCanBeAdded() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>("general", "sol", "42");
Assert.That(
configurationFile.EnumerateCategories(), Is.EquivalentTo(new string[] { "general" })
);
Assert.That(save(configurationFile), Contains.Substring("[general]"));
}
/// <summary>
/// Verifies that accessing an option that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingOptionThrowsException() {
var configurationFile = new ConfigurationFileStore();
Assert.That(
() => configurationFile.Get<string>(null, "doesn't exist"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
/// <summary>
/// Verifies that accessing a category that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingCategoryThrowsException() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>(null, "test", "123");
Assert.That(
() => configurationFile.Get<string>("doesn't exist", "test"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
/// <summary>
/// Verifies that it's possible to enumerate a category that doesn't exist
/// </summary>
[Test]
public void NonExistingCategoryCanBeEnumerated() {
var configurationFile = new ConfigurationFileStore();
Assert.That(configurationFile.EnumerateOptions("doesn't exist"), Is.Empty);
}
/// <summary>
/// Verifies that it's possible to create an option without a value
/// </summary>
[Test]
public void ValuelessOptionsCanBeCreated() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>(null, "test", null);
Assert.That(configurationFile.Get<string>(null, "test"), Is.Null.Or.Empty);
}
/// <summary>
/// Verifies that it's possible to assign an empty value to an option
/// </summary>
[Test]
public void OptionValueCanBeCleared() {
string fileContents = "test = 123 ; comment";
ConfigurationFileStore configurationFile = load(fileContents);
configurationFile.Set<string>(null, "test", null);
Assert.That(configurationFile.Get<string>(null, "test"), Is.Null.Or.Empty);
}
/// <summary>
/// Verifies that it's possible to remove options from the configuration file
/// </summary>
[Test]
public void OptionsCanBeRemoved() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>(null, "test", null);
Assert.That(configurationFile.Remove(null, "test"), Is.True);
string value;
Assert.That(configurationFile.TryGet<string>(null, "test", out value), Is.False);
}
/// <summary>
/// Verifies that options are removed from the configuration file correctly
/// </summary>
[Test]
public void RemovingOptionShiftsFollowingOptionsUp() {
string fileContents =
"first = 1\r\n" +
"second = 2";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.Remove(null, "first"), Is.True);
configurationFile.Set<string>(null, "second", "yay! first!");
Assert.That(save(configurationFile), Has.No.ContainsSubstring("1"));
Assert.That(save(configurationFile), Contains.Substring("second"));
Assert.That(save(configurationFile), Contains.Substring("yay! first!"));
}
/// <summary>
/// Verifies that it's not an error to remove an option from a non-existing category
/// </summary>
[Test]
public void CanRemoveOptionFromNonExistingCategory() {
var configurationFile = new ConfigurationFileStore();
Assert.That(configurationFile.Remove("nothing", "first"), Is.False);
}
/// <summary>
/// Verifies that it's not an error to remove a non-existing option
/// </summary>
[Test]
public void CanRemoveNonExistingOption() {
var configurationFile = new ConfigurationFileStore();
Assert.That(configurationFile.Remove(null, "first"), Is.False);
}
/// <summary>
/// Verifies that the configuration file store can identify various types of values
/// </summary>
[
Test,
TestCase("nothing=", typeof(string)),
TestCase("text = world", typeof(string)),
TestCase("short=9", typeof(int)),
TestCase("integer = 123", typeof(int)),
TestCase("integer = 123 ", typeof(int)),
TestCase("string=x", typeof(string)),
TestCase("string = 123s", typeof(string)),
TestCase("float = 123.45", typeof(float)),
TestCase("float = 123.45 ", typeof(float)),
TestCase("boolean = true", typeof(bool)),
TestCase("boolean = false", typeof(bool)),
TestCase("boolean = yes", typeof(bool)),
TestCase("boolean = no", typeof(bool))
]
public void OptionTypeCanBeIdentified(string assignment, Type expectedType) {
ConfigurationFileStore configurationFile = load(assignment);
OptionInfo info;
using(
IEnumerator<OptionInfo> enumerator = configurationFile.EnumerateOptions().GetEnumerator()
) {
Assert.That(enumerator.MoveNext(), Is.True);
info = enumerator.Current;
Assert.That(enumerator.MoveNext(), Is.False);
}
Assert.That(info.OptionType, Is.EqualTo(expectedType));
}
/// <summary>
/// Verifies that configuration files containing duplicate option names can not
/// be used with the configuration file store
/// </summary>
[Test]
public void FilesWithDuplicateOptionNamesCannotBeProcessed() {
string fileContents =
"duplicate name = 1\r\n" +
"duplicate name = 2";
Assert.That(() => load(fileContents), Throws.Exception);
}
/// <summary>
/// Verifies that attempting to cast a value to an incompatible data type causes
/// a FormatException to be thrown
/// </summary>
[Test]
public void ImpossibleCastCausesFormatException() {
string fileContents = "fail = yesnomaybe";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(
() => configurationFile.Get<bool>(null, "fail"),
Throws.Exception.AssignableTo<FormatException>()
);
}
/// <summary>
/// Verifies that configuration files containing duplicate option names can not
/// be used with the configuration file store
/// </summary>
[
Test,
TestCase("value = yes", true),
TestCase("value = true", true),
TestCase("value = no", false),
TestCase("value = false", false)
]
public void BooleanLiteralsAreUnderstood(string fileContents, bool expectedValue) {
ConfigurationFileStore configurationFile = load(fileContents);
if(expectedValue) {
Assert.That(configurationFile.Get<bool>(null, "value"), Is.True);
} else {
Assert.That(configurationFile.Get<bool>(null, "value"), Is.False);
}
}
/// <summary>Loads a configuration file from a string</summary>
/// <param name="fileContents">Contents of the configuration file</param>
/// <returns>The configuration file loaded from the string</returns>
private static ConfigurationFileStore load(string fileContents) {
using(var reader = new StringReader(fileContents)) {
return ConfigurationFileStore.Parse(reader);
}
}
/// <summary>Saves a configuration file into a string</summary>
/// <param name="configurationFile">Configuration file that will be saved</param>
/// <returns>Contents of the configuration file</returns>
private static string save(ConfigurationFileStore configurationFile) {
var builder = new StringBuilder();
using(var writer = new StringWriter(builder)) {
configurationFile.Save(writer);
writer.Flush();
}
return builder.ToString();
}
}
} // namespace Nuclex.Support.Settings
#endif // UNITTEST

View file

@ -0,0 +1,164 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace Nuclex.Support.Settings {
/// <summary>Unit tests for the memory settings store</summary>
[TestFixture]
internal class MemoryStoreTest {
/// <summary>Verifies that constructed a memory store throws an exception</summary>
[Test]
public void CanBeCreated() {
Assert.That(() => new MemoryStore(), Throws.Nothing);
}
/// <summary>
/// Verifies that it's possible to enumerate the options in a non-existing category
/// </summary>
[Test]
public void NonExistingCategoriesCanBeEnumerated() {
var memoryStore = new MemoryStore();
Assert.That(memoryStore.EnumerateOptions("doesn't exist"), Is.Empty);
}
/// <summary>
/// Verifies that accessing an option that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingOptionThrowsException() {
var memoryStore = new MemoryStore();
Assert.That(
() => memoryStore.Get<string>(null, "doesn't exist"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
/// <summary>
/// Verifies that accessing a category that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingCategoryThrowsException() {
var memoryStore = new MemoryStore();
memoryStore.Set<string>(null, "test", "123");
Assert.That(
() => memoryStore.Get<string>("doesn't exist", "test"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
/// <summary>
/// Verifies that settings can be stored in the memory store
/// </summary>
[Test]
public void SettingsCanBeAssignedAndRetrieved() {
var memoryStore = new MemoryStore();
memoryStore.Set<string>("general", "sol", "42");
Assert.That(memoryStore.Get<string>("general", "sol"), Is.EqualTo("42"));
}
/// <summary>
/// Verifies that it's possible to remove options from the memory store
/// </summary>
[Test]
public void OptionsCanBeRemoved() {
var memoryStore = new MemoryStore();
memoryStore.Set<string>(null, "test", null);
Assert.That(memoryStore.Remove(null, "test"), Is.True);
string value;
Assert.That(memoryStore.TryGet<string>(null, "test", out value), Is.False);
}
/// <summary>
/// Verifies that it's not an error to remove an option from a non-existing category
/// </summary>
[Test]
public void CanRemoveOptionFromNonExistingCategory() {
var memoryStore = new MemoryStore();
Assert.That(memoryStore.Remove("nothing", "first"), Is.False);
}
/// <summary>
/// Verifies that it's not an error to remove a non-existing option
/// </summary>
[Test]
public void CanRemoveNonExistingOption() {
var memoryStore = new MemoryStore();
Assert.That(memoryStore.Remove(null, "first"), Is.False);
}
/// <summary>
/// Verifies that the root category is not part of the enumerated categories
/// </summary>
[Test]
public void RootCategoryIsNotEnumerated() {
var memoryStore = new MemoryStore();
Assert.That(memoryStore.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that the root category is not part of the enumerated categories
/// </summary>
[Test]
public void OptionsInRootCategoryCanBeEnumerated() {
var memoryStore = new MemoryStore();
string[] optionNames = new string[] { "first", "second" };
memoryStore.Set<int>(null, optionNames[0], 1);
memoryStore.Set<int>(null, optionNames[1], 2);
var optionInfos = new List<OptionInfo>(memoryStore.EnumerateOptions());
Assert.That(optionInfos.Count, Is.EqualTo(2));
var enumeratedOptionNames = new List<string>() {
optionInfos[0].Name, optionInfos[1].Name
};
Assert.That(enumeratedOptionNames, Is.EquivalentTo(optionNames));
}
/// <summary>
/// Verifies that the root category is not part of the enumerated categories
/// </summary>
[Test]
public void CategoriesCanBeCreated() {
var memoryStore = new MemoryStore();
memoryStore.Set<string>(null, "not", "used");
memoryStore.Set<string>("test", "message", "hello world");
Assert.That(memoryStore.EnumerateCategories(), Is.EquivalentTo(new string[] { "test" }));
}
}
} // namespace Nuclex.Support.Settings
#endif // UNITTEST

View file

@ -0,0 +1,365 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST && WINDOWS
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Win32;
using NUnit.Framework;
namespace Nuclex.Support.Settings {
/// <summary>Unit tests for the windows registry settings store</summary>
[TestFixture]
internal class WindowsRegistryStoreTest {
#region class TestContext
/// <summary>Sets up a temporary registry key for the unit test</summary>
private class TestContext : IDisposable {
/// <summary>Initializes a new test context</summary>
public TestContext() {
this.keyName = Guid.NewGuid().ToString();
this.registryKey = Registry.CurrentUser.CreateSubKey(this.keyName);
this.store = new WindowsRegistryStore(this.registryKey, writable: true);
}
/// <summary>Immediately frees all resources owned by the test context</summary>
public void Dispose() {
if(this.store != null) {
this.store.Dispose();
this.store = null;
this.registryKey = null;
} else if(this.registryKey != null) {
this.registryKey.Dispose();
this.registryKey = null;
}
if(this.keyName != null) {
Registry.CurrentUser.DeleteSubKeyTree(this.keyName);
this.keyName = null;
}
}
/// <summary>Store created on a temporary registry key</summary>
public WindowsRegistryStore Store {
get { return this.store; }
}
/// <summary>Name of the temporary registry key</summary>
private string keyName;
/// <summary>Registry key (ownership transfered to the store)</summary>
private RegistryKey registryKey;
/// <summary>Store that is accessing the registry key</summary>
private WindowsRegistryStore store;
}
#endregion // class TestContext
/// <summary>Verifies that new instances of the registry store can be created</summary>
[Test]
public void CanBeCreated() {
Assert.That(
() => { using(var context = new TestContext()) { } }, Throws.Nothing
);
}
/// <summary>Verifies that new instances of the registry store can be created</summary>
[Test]
public void RegistryHivesCanBeOpened() {
Assert.That(
() => {
using(
var store = new WindowsRegistryStore(
RegistryHive.CurrentUser, "", writable: false
)
) { }
},
Throws.Nothing
);
}
/// <summary>Verifies that booleans can be stored in the registry</summary>
[Test]
public void BooleansCanBeStored() {
using(var context = new TestContext()) {
context.Store.Set(null, "test", true);
Assert.That(context.Store.Get<bool>(null, "test"), Is.True);
context.Store.Set(null, "test", false);
Assert.That(context.Store.Get<bool>(null, "test"), Is.False);
}
}
/// <summary>Verifies that integers can be stored in the registry</summary>
[Test]
public void IntegersCanBeStored() {
using(var context = new TestContext()) {
context.Store.Set(null, "test", 123);
Assert.That(context.Store.Get<int>(null, "test"), Is.EqualTo(123));
context.Store.Set(null, "test", 456);
Assert.That(context.Store.Get<int>(null, "test"), Is.EqualTo(456));
}
}
/// <summary>Verifies that floats can be stored in the registry</summary>
[Test]
public void FloatsCanBeStored() {
float testValue = float.Parse("123.456", CultureInfo.InvariantCulture);
using(var context = new TestContext()) {
context.Store.Set(null, "test", testValue);
Assert.That(context.Store.Get<float>(null, "test"), Is.EqualTo(testValue));
testValue = float.Parse("654.321", CultureInfo.InvariantCulture);
context.Store.Set(null, "test", testValue);
Assert.That(context.Store.Get<float>(null, "test"), Is.EqualTo(testValue));
}
}
/// <summary>Verifies that strings can be stored in the registry</summary>
[Test]
public void StringsCanBeStored() {
using(var context = new TestContext()) {
context.Store.Set(null, "test", "hello world");
Assert.That(context.Store.Get<string>(null, "test"), Is.EqualTo("hello world"));
context.Store.Set(null, "test", "world hello");
Assert.That(context.Store.Get<string>(null, "test"), Is.EqualTo("world hello"));
}
}
/// <summary>Verifies that long integers can be stored in the registry</summary>
[Test]
public void LongIntegersCanBeStored() {
using(var context = new TestContext()) {
context.Store.Set(null, "test", long.MaxValue);
Assert.That(context.Store.Get<long>(null, "test"), Is.EqualTo(long.MaxValue));
context.Store.Set(null, "test", long.MinValue);
Assert.That(context.Store.Get<long>(null, "test"), Is.EqualTo(long.MinValue));
}
}
/// <summary>Verifies that string arrays can be stored in the registry</summary>
[Test]
public void StringArraysCanBeStored() {
string[] english = new string[] { "one", "two", "three" };
string[] german = new string[] { "eins", "zwei", "drei" };
using(var context = new TestContext()) {
context.Store.Set(null, "test", english);
Assert.That(context.Store.Get<string[]>(null, "test"), Is.EquivalentTo(english));
context.Store.Set(null, "test", german);
Assert.That(context.Store.Get<string[]>(null, "test"), Is.EquivalentTo(german));
}
}
/// <summary>
/// Verifies that it's possible to enumerate a category that doesn't exist
/// </summary>
[Test]
public void NonExistingCategoryCanBeEnumerated() {
using(var context = new TestContext()) {
Assert.That(context.Store.EnumerateOptions("doesn't exist"), Is.Empty);
}
}
/// <summary>Verifies that byte arrays can be stored in the registry</summary>
[Test]
public void ByteArraysCanBeStored() {
byte[] ascending = new byte[] { 1, 2, 3 };
byte[] descending = new byte[] { 9, 8, 7 };
using(var context = new TestContext()) {
context.Store.Set(null, "test", ascending);
Assert.That(context.Store.Get<byte[]>(null, "test"), Is.EquivalentTo(ascending));
context.Store.Set(null, "test", descending);
Assert.That(context.Store.Get<byte[]>(null, "test"), Is.EquivalentTo(descending));
}
}
/// <summary>Verifies that strings can be stored in the registry</summary>
[Test]
public void ValuesCanBeStoredInCategories() {
using(var context = new TestContext()) {
context.Store.Set("main", "test", "hello world");
string value;
Assert.That(context.Store.TryGet<string>(null, "test", out value), Is.False);
Assert.That(context.Store.Get<string>("main", "test"), Is.EqualTo("hello world"));
}
}
/// <summary>Verifies that the subkeys of a registry key can be enumerated</summary>
[Test]
public void CategoriesCanBeEnumerated() {
string[] names = new string[] { "one", "two", "three" };
using(var context = new TestContext()) {
context.Store.Set(names[0], "sol", 21);
context.Store.Set(names[1], "sol", 42);
context.Store.Set(names[2], "sol", 84);
Assert.That(context.Store.EnumerateCategories(), Is.EquivalentTo(names));
}
}
/// <summary>Verifies that the values under a registry subkey can be enumerated</summary>
[Test]
public void OptionsInCategoryCanBeEnumerated() {
string[] names = new string[] { "one", "two", "three" };
using(var context = new TestContext()) {
context.Store.Set("test", names[0], 1);
context.Store.Set("test", names[1], 2);
context.Store.Set("test", names[2], 3);
var optionInfos = new List<OptionInfo>(context.Store.EnumerateOptions("test"));
Assert.That(optionInfos.Count, Is.EqualTo(3));
}
}
/// <summary>Verifies that the values under a registry key can be enumerated</summary>
[Test]
public void RootOptionsCanBeEnumerated() {
string[] names = new string[] { "one", "two", "three" };
using(var context = new TestContext()) {
context.Store.Set(null, names[0], 1);
context.Store.Set(null, names[1], 2);
context.Store.Set(null, names[2], 3);
var optionInfos = new List<OptionInfo>(context.Store.EnumerateOptions(null));
Assert.That(optionInfos.Count, Is.EqualTo(3));
string[] actualNames = new string[] {
optionInfos[0].Name, optionInfos[1].Name, optionInfos[2].Name
};
Assert.That(actualNames, Is.EquivalentTo(names));
}
}
/// <summary>
/// Verifies that accessing an option that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingOptionThrowsException() {
using(var context = new TestContext()) {
Assert.That(
() => context.Store.Get<string>(null, "doesn't exist"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
}
/// <summary>
/// Verifies that accessing a category that doesn't exist throws an exception
/// </summary>
[Test]
public void AccessingNonExistingCategoryThrowsException() {
using(var context = new TestContext()) {
Assert.That(
() => context.Store.Get<string>("doesn't exist", "test"),
Throws.Exception.AssignableTo<KeyNotFoundException>()
);
}
}
/// <summary>
/// Verifies that values can be removed from a registry key
/// </summary>
[Test]
public void ValuesCanBeRemovedFromRoot() {
using(var context = new TestContext()) {
context.Store.Set(null, "nothing", "short-lived");
Assert.That(context.Store.Remove(null, "nothing"), Is.True);
Assert.That(context.Store.Remove(null, "nothing"), Is.False);
Assert.That(context.Store.EnumerateOptions(), Is.Empty);
}
}
/// <summary>
/// Verifies that values can be removed from the subkey of a registry key
/// </summary>
[Test]
public void ValuesCanBeRemovedFromCategory() {
using(var context = new TestContext()) {
context.Store.Set("limbo", "nothing", "short-lived");
Assert.That(context.Store.Remove("limbo", "nothing"), Is.True);
Assert.That(context.Store.Remove("limbo", "nothing"), Is.False);
Assert.That(context.Store.EnumerateOptions("limbo"), Is.Empty);
}
}
/// <summary>
/// Verifies that values can be removed from a non-existing subkey without
/// causing an error
/// </summary>
[Test]
public void RemovingValueFromNonExistingCategoryCanBeHandled() {
using(var context = new TestContext()) {
Assert.That(context.Store.Remove("empty", "nothing"), Is.False);
}
}
/// <summary>
/// Verifies that the store identifies the types of values stored in
/// a registry when they are enumerated
/// </summary>
[Test]
public void ValueTypesAreIdentifiedWhenEnumerating() {
Type[] types = new Type[] {
typeof(int),
typeof(long),
typeof(byte[]),
typeof(string),
typeof(string[])
};
using(var context = new TestContext()) {
context.Store.Set<int>(null, "0", 123);
context.Store.Set<long>(null, "1", 456L);
context.Store.Set<byte[]>(null, "2", new byte[] { 7, 8, 9 });
context.Store.Set<string>(null, "3", "text");
context.Store.Set<string[]>(null, "4", new string[] { "many", "words" });
var optionInfos = new List<OptionInfo>(context.Store.EnumerateOptions());
for(int index = 0; index < optionInfos.Count; ++index) {
int typeIndex = int.Parse(optionInfos[index].Name);
Assert.That(optionInfos[index].OptionType, Is.EqualTo(types[typeIndex]));
}
}
}
}
} // namespace Nuclex.Support.Settings
#endif // UNITTEST

68
Tests/SharedTest.cs Normal file
View file

@ -0,0 +1,68 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the shared instance provider class</summary>
[TestFixture]
internal class SharedTest {
#region class Dummy
/// <summary>Dummy class for testing the shared instance provider</summary>
private class Dummy {
/// <summary>Initializes a new dummy</summary>
public Dummy() {}
}
#endregion // class Dummy
/// <summary>
/// Verifies that the shared instance provider returns the same instance of a class
/// when asked for the same class twice.
/// </summary>
[Test]
public void TestSameInstance() {
#pragma warning disable 0618
Dummy dummyInstance = Shared<Dummy>.Instance;
Dummy otherDummyInstance = Shared<Dummy>.Instance;
#pragma warning restore 0618
// Make sure they're the same instance. We could have put an instance counter in
// the dummy class, but this might or might not work well across multiple tests
// because the order in which tests are executed is undefined and Shared<> changes
// its global state when the first test is run by remembering the instance.
//
// Maybe this really is a defect in Shared<> and the class should be equipped with
// a method such as Discard() or Dispose() to get rid of the instance?
Assert.AreSame(dummyInstance, otherDummyInstance);
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,374 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>
/// Unit test for the helper class to .NET's string builder
/// </summary>
[TestFixture]
internal class StringBuilderHelperTest {
/// <summary>
/// Verifies that bytes are correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendByte() {
StringBuilder builder = new StringBuilder();
builder.Append((byte)255, GarbagePolicy.Avoid);
Assert.AreEqual(((byte)255).ToString(), builder.ToString());
builder.Clear();
builder.Append((byte)255, GarbagePolicy.Accept);
Assert.AreEqual(((byte)255).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a byte with value 0 is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNullByte() {
StringBuilder builder = new StringBuilder();
builder.Append((byte)0, GarbagePolicy.Avoid);
Assert.AreEqual(((byte)0).ToString(), builder.ToString());
builder.Clear();
builder.Append((byte)0, GarbagePolicy.Accept);
Assert.AreEqual(((byte)0).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a positive integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendPositiveInteger() {
StringBuilder builder = new StringBuilder();
builder.Append(12345, GarbagePolicy.Avoid);
Assert.AreEqual((12345).ToString(), builder.ToString());
builder.Clear();
builder.Append(12345, GarbagePolicy.Accept);
Assert.AreEqual((12345).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that an integer with value 0 is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNullInteger() {
StringBuilder builder = new StringBuilder();
builder.Append(0, GarbagePolicy.Avoid);
Assert.AreEqual((0).ToString(), builder.ToString());
builder.Clear();
builder.Append(0, GarbagePolicy.Accept);
Assert.AreEqual((0).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a negative integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNegativeInteger() {
StringBuilder builder = new StringBuilder();
builder.Append(-12345, GarbagePolicy.Avoid);
Assert.AreEqual((-12345).ToString(), builder.ToString());
builder.Clear();
builder.Append(-12345, GarbagePolicy.Accept);
Assert.AreEqual((-12345).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a positive long integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendPositiveLong() {
StringBuilder builder = new StringBuilder();
builder.Append(12345L, GarbagePolicy.Avoid);
Assert.AreEqual((12345L).ToString(), builder.ToString());
builder.Clear();
builder.Append(12345L, GarbagePolicy.Accept);
Assert.AreEqual((12345L).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a long integer with value 0 is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNullLong() {
StringBuilder builder = new StringBuilder();
builder.Append(0L, GarbagePolicy.Avoid);
Assert.AreEqual((0L).ToString(), builder.ToString());
builder.Clear();
builder.Append(0L, GarbagePolicy.Accept);
Assert.AreEqual((0L).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a negative long integer is correctly appended to a string builder
/// </summary>
[Test]
public void TestAppendNegativeLong() {
StringBuilder builder = new StringBuilder();
builder.Append(-12345L, GarbagePolicy.Avoid);
Assert.AreEqual((-12345L).ToString(), builder.ToString());
builder.Clear();
builder.Append(-12345L, GarbagePolicy.Accept);
Assert.AreEqual((-12345L).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that negative floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendNegativeFloat() {
StringBuilder builder = new StringBuilder();
builder.Append(-0.125f, GarbagePolicy.Avoid);
Assert.AreEqual((-0.125f).ToString(), builder.ToString());
builder.Clear();
builder.Append(-0.125f, GarbagePolicy.Accept);
Assert.AreEqual((-0.125f).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that positive floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendPositiveFloat() {
StringBuilder builder = new StringBuilder();
builder.Append(10.0625f, GarbagePolicy.Avoid);
Assert.AreEqual((10.0625f).ToString(), builder.ToString());
builder.Clear();
builder.Append(10.0625f, GarbagePolicy.Accept);
Assert.AreEqual((10.0625f).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that very small floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendSmallFloat() {
StringBuilder builder = new StringBuilder();
builder.Append(0.00390625f, GarbagePolicy.Avoid);
Assert.AreEqual((0.00390625f).ToString(), builder.ToString());
builder.Clear();
builder.Append(0.00390625f, GarbagePolicy.Accept);
Assert.AreEqual((0.00390625f).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that very large floating point values are correctly converted
/// </summary>
[Test]
public void TestAppendHugeFloat() {
StringBuilder builder = new StringBuilder();
builder.Append(1000000000.0f, GarbagePolicy.Avoid);
Assert.AreEqual((1000000000.0f).ToString("F1"), builder.ToString());
builder.Clear();
builder.Append(1000000000.0f, GarbagePolicy.Accept);
Assert.AreEqual((1000000000.0f).ToString(), builder.ToString());
}
/// <summary>Tests whether the number of decimal places can be restricted</summary>
[Test]
public void TestAppendFloatLimitDecimalPlaces() {
StringBuilder builder = new StringBuilder();
builder.Append(0.00390625f, 3);
Assert.AreEqual((0.003f).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that a float with no decimal places is correctly appended
/// </summary>
[Test]
public void TestAppendFloatWithoutDecimalPlaces() {
StringBuilder builder = new StringBuilder();
builder.Append(0.00390625f, 0);
Assert.AreEqual((0.0f).ToString(), builder.ToString()); // Note: no rounding!
}
/// <summary>
/// Verifies the behavior of the helper with unsupported floating point values
/// </summary>
[Test]
public void TestAppendOutOfRangeFloat() {
StringBuilder builder = new StringBuilder();
Assert.IsFalse(builder.Append(float.PositiveInfinity, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(float.NegativeInfinity, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(float.NaN, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(0.000000059604644775390625f, GarbagePolicy.Avoid));
}
/// <summary>
/// Verifies that negative double precision floating point values are
/// correctly converted
/// </summary>
[Test]
public void TestAppendNegativeDouble() {
StringBuilder builder = new StringBuilder();
builder.Append(-32.015625, GarbagePolicy.Avoid);
Assert.AreEqual((-32.015625).ToString(), builder.ToString());
builder.Clear();
builder.Append(-32.015625, GarbagePolicy.Accept);
Assert.AreEqual((-32.015625).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that positive double precision floating point values are
/// correctly converted
/// </summary>
[Test]
public void TestAppendPositiveDouble() {
StringBuilder builder = new StringBuilder();
builder.Append(10.0625, GarbagePolicy.Avoid);
Assert.AreEqual((10.0625).ToString(), builder.ToString());
builder.Clear();
builder.Append(10.0625, GarbagePolicy.Accept);
Assert.AreEqual((10.0625).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that very small double precision floating point values are
/// correctly converted
/// </summary>
[Test]
public void TestAppendSmallDouble() {
StringBuilder builder = new StringBuilder();
builder.Append(0.00390625, GarbagePolicy.Avoid);
Assert.AreEqual((0.00390625).ToString(), builder.ToString());
builder.Clear();
builder.Append(0.00390625, GarbagePolicy.Accept);
Assert.AreEqual((0.00390625).ToString(), builder.ToString());
}
/// <summary>
/// Verifies that very large double precision floating point values are
/// correctly converted
/// </summary>
[Test]
public void TestAppendHugeDouble() {
StringBuilder builder = new StringBuilder();
builder.Append(1000000000000000000.0, GarbagePolicy.Avoid);
Assert.AreEqual((1000000000000000000.0).ToString("F1"), builder.ToString());
builder.Clear();
builder.Append(1000000000000000000.0, GarbagePolicy.Accept);
Assert.AreEqual((1000000000000000000.0).ToString(), builder.ToString());
}
/// <summary>Tests whether the number of decimal places can be restricted</summary>
[Test]
public void TestAppendDoubleLimitDecimalPlaces() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 0.00390625, 3);
Assert.AreEqual((0.003).ToString(), builder.ToString()); // Note: no rounding!
}
/// <summary>
/// Verifies that a double with no decimal places is correctly appended
/// </summary>
[Test]
public void TestAppendDoubleWithoutDecimalPlaces() {
StringBuilder builder = new StringBuilder();
StringBuilderHelper.Append(builder, 0.00390625, 0);
Assert.AreEqual((0.0).ToString(), builder.ToString());
}
/// <summary>
/// Verifies the behavior of the helper with unsupported double precision
/// floating point values
/// </summary>
[Test]
public void TestAppendOutOfRangeDouble() {
StringBuilder builder = new StringBuilder();
Assert.IsFalse(builder.Append(double.PositiveInfinity, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(double.NegativeInfinity, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(double.NaN, GarbagePolicy.Avoid));
Assert.IsFalse(builder.Append(1.1102230246251565404236316680908e-16, GarbagePolicy.Avoid));
}
/// <summary>
/// Verifies that the contents of a string builder can be cleared
/// </summary>
[Test]
public void TestClear() {
StringBuilder builder = new StringBuilder("Hello World");
StringBuilderHelper.Clear(builder);
Assert.AreEqual(string.Empty, builder.ToString());
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

216
Tests/StringHelperTest.cs Normal file
View file

@ -0,0 +1,216 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the string helper class</summary>
[TestFixture]
internal class StringHelperTest {
/// <summary>
/// Verifies that the IndexNotOfAny() method works identical to the framework's
/// implementation of the IndexOfAny() method, only inverted.
/// </summary>
[Test]
public void TestIndexNotOfAny() {
string positive = "xxxxxOOOOO";
string negative = "OOOOOxxxxx";
Assert.AreEqual(
positive.IndexOfAny(new char[] { 'O' }),
StringHelper.IndexNotOfAny(negative, new char[] { 'O' })
);
}
/// <summary>
/// Verifies that the IndexNotOfAny() method works identical to the framework's
/// implementation of the IndexOfAny() method, only inverted, using a start index.
/// </summary>
[Test]
public void TestIndexNotOfAnyWithStartIndex() {
string positive = "OOOOOxxxxxOOOOO";
string negative = "xxxxxOOOOOxxxxx";
Assert.AreEqual(
positive.IndexOfAny(new char[] { 'O' }, 5),
StringHelper.IndexNotOfAny(negative, new char[] { 'O' }, 5)
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method works identical to the framework's
/// implementation of the LastIndexOfAny() method, only inverted.
/// </summary>
[Test]
public void TestLastIndexNotOfAny() {
string positive = "xxxxxOOOOO";
string negative = "OOOOOxxxxx";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'x' }),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'x' })
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method works identical to the framework's
/// implementation of the LastIndexOfAny() method, only inverted, using a start index.
/// </summary>
[Test]
public void TestLastIndexNotOfAnyWithStartIndex() {
string positive = "OOOOOxxxxxOOOOO";
string negative = "xxxxxOOOOOxxxxx";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'x' }, 5),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'x' }, 5)
);
}
/// <summary>
/// Verifies that the IndexNotOfAny() method works with multiple characters
/// </summary>
[Test]
public void TestMultipleCharIndexNotOfAny() {
string positive = "abcde12345";
string negative = "12345abcde";
Assert.AreEqual(
positive.IndexOfAny(new char[] { '1', '2', '3', '4', '5' }),
StringHelper.IndexNotOfAny(negative, new char[] { '1', '2', '3', '4', '5' })
);
}
/// <summary>
/// Verifies that the IndexNotOfAny() method works with multiple characters,
/// using a start index
/// </summary>
[Test]
public void TestMultipleCharIndexNotOfAnyWithStartIndex() {
string positive = "12345abcde12345";
string negative = "abcde12345abcde";
Assert.AreEqual(
positive.IndexOfAny(new char[] { '1', '2', '3', '4', '5' }, 5),
StringHelper.IndexNotOfAny(negative, new char[] { '1', '2', '3', '4', '5' }, 5)
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method works with multiple characters
/// </summary>
[Test]
public void TestMultipleCharLastIndexNotOfAny() {
string positive = "abcde12345";
string negative = "12345abcde";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method works with multiple characters,
/// using a start index
/// </summary>
[Test]
public void TestMultipleCharLastIndexNotOfAnyWithStartIndex() {
string positive = "12345abcde12345";
string negative = "abcde12345abcde";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
);
}
/// <summary>
/// Verifies that the IndexNotOfAny() method fails when only matches are found
/// </summary>
[Test]
public void TestIndexNotOfAnyMatchesOnly() {
string positive = "1234512345";
string negative = "abcdeabcde";
Assert.AreEqual(
positive.IndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
StringHelper.IndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
);
}
/// <summary>
/// Verifies that the IndexNotOfAny() method fails when only matches are found,
/// using a start index
/// </summary>
[Test]
public void TestIndexNotOfAnyMatchesOnlyWithStartIndex() {
string positive = "abcde1234512345";
string negative = "12345abcdeabcde";
Assert.AreEqual(
positive.IndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
StringHelper.IndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method fails when only matches are found
/// </summary>
[Test]
public void TestLastIndexNotOfAnyMatchesOnly() {
string positive = "1234512345";
string negative = "abcdeabcde";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
);
}
/// <summary>
/// Verifies that the LastIndexNotOfAny() method fails when only matches are found,
/// using a start index
/// </summary>
[Test]
public void TestLastIndexNotOfAnyMatchesOnlyWithStartIndex() {
string positive = "abcde1234512345";
string negative = "12345abcdeabcde";
Assert.AreEqual(
positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
);
}
// TODO: Also need unit tests for the 'length' argument
// to guarantee the methods stop searching at the exact character
}
} // namespace Nuclex.Support
#endif // UNITTEST

211
Tests/StringSegmentTest.cs Normal file
View file

@ -0,0 +1,211 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the string segment class</summary>
[TestFixture]
internal class StringSegmentTest {
/// <summary>
/// Tests whether the default constructor of the StringSegment class throws the
/// right exception when being passed 'null' instead of a string
/// </summary>
[Test]
public void SimpleConstructorThrowsWhenStringIsNull() {
Assert.Throws<ArgumentNullException>(
delegate() { new StringSegment(null); }
);
}
/// <summary>
/// Tests whether the simple constructor of the StringSegment class accepts
/// an empty string
/// </summary>
[Test]
public void SimpleConstructorAcceptsEmptyString() {
new StringSegment(string.Empty);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class throws the
/// right exception when being passed 'null' instead of a string
/// </summary>
[Test]
public void ConstructorThrowsWhenStringIsNull() {
Assert.Throws<ArgumentNullException>(
delegate() { new StringSegment(null, 0, 0); }
);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class accepts
/// an empty string
/// </summary>
[Test]
public void ConstructorAcceptsEmptyString() {
new StringSegment(string.Empty, 0, 0);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class throws the
/// right exception when being passed an invalid start offset
/// </summary>
[Test]
public void ConstructorThrowsOnInvalidOffset() {
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { new StringSegment(string.Empty, -1, 0); }
);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class throws the
/// right exception when being passed an invalid string length
/// </summary>
[Test]
public void ConstructorThrowsOnInvalidLength() {
Assert.Throws<ArgumentOutOfRangeException>(
delegate() { new StringSegment(string.Empty, 0, -1); }
);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class throws the
/// right exception when being passed a string length that's too large
/// </summary>
[Test]
public void ConstructorThrowsOnLengthOverrun() {
Assert.Throws<ArgumentException>(
delegate() { new StringSegment("hello", 3, 3); }
);
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
[Test]
public void TextPropertyStoresOriginalString() {
StringSegment testSegment = new StringSegment("hello", 1, 3);
Assert.AreEqual("hello", testSegment.Text);
}
/// <summary>Tests whether the 'Offset' property works as expected</summary>
[Test]
public void OffsetPropertyIsStored() {
StringSegment testSegment = new StringSegment("hello", 1, 3);
Assert.AreEqual(1, testSegment.Offset);
}
/// <summary>Tests whether the 'Count' property works as expected</summary>
[Test]
public void CountPropertyIsStored() {
StringSegment testSegment = new StringSegment("hello", 1, 3);
Assert.AreEqual(3, testSegment.Count);
}
/// <summary>
/// Tests whether two differing instances produce different hash codes
/// </summary>
[Test]
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
Assert.AreNotEqual(
helloWorldSegment.GetHashCode(), howAreYouSegment.GetHashCode()
);
}
/// <summary>
/// Tests whether two equivalent instances produce an identical hash code
/// </summary>
[Test]
public void EquivalentInstancesHaveSameHashcode() {
StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
Assert.AreEqual(
helloWorld1Segment.GetHashCode(), helloWorld2Segment.GetHashCode()
);
}
/// <summary>Tests the equals method performing a comparison against null</summary>
[Test]
public void EqualsAgainstNullIsAlwaysFalse() {
StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
Assert.IsFalse(
helloWorldSegment.Equals(null)
);
}
/// <summary>Tests the equality operator with differing instances</summary>
[Test]
public void DifferingInstancesAreNotEqual() {
StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
Assert.IsFalse(helloWorldSegment == howAreYouSegment);
}
/// <summary>Tests the equality operator with equivalent instances</summary>
[Test]
public void EquivalentInstancesAreEqual() {
StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
Assert.IsTrue(helloWorld1Segment == helloWorld2Segment);
}
/// <summary>Tests the inequality operator with differing instances</summary>
[Test]
public void DifferingInstancesAreUnequal() {
StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
Assert.IsTrue(helloWorldSegment != howAreYouSegment);
}
/// <summary>Tests the inequality operator with equivalent instances</summary>
[Test]
public void EquivalentInstancesAreNotUnequal() {
StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
Assert.IsFalse(helloWorld1Segment != helloWorld2Segment);
}
/// <summary>Tests the ToString() method of the string segment</summary>
[Test]
public void TestToString() {
StringSegment helloWorldSegment = new StringSegment("hello world", 4, 3);
Assert.AreEqual("o w", helloWorldSegment.ToString());
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View file

@ -0,0 +1,355 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Collections.Generic;
using System.Threading;
using NUnit.Framework;
namespace Nuclex.Support.Threading {
/// <summary>Unit Test for the CPU core-affine thread pool</summary>
[TestFixture]
internal class AffineThreadPoolTest {
#region class TestTask
/// <summary>ThreadPool task that can be used for testing</summary>
private class TestTask : IDisposable {
/// <summary>Initializes a new test task</summary>
public TestTask() {
this.callbackEvent = new ManualResetEvent(false);
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose() {
if(this.callbackEvent != null) {
this.callbackEvent.Close();
this.callbackEvent = null;
}
}
/// <summary>Callback that can be added to the thread pool as a task</summary>
/// <param name="state">User defined state</param>
public void Callback(object state) {
this.LastCallbackState = state;
this.callbackEvent.Set();
}
/// <summary>Event that will be set when the callback is executed</summary>
public ManualResetEvent CallbackEvent {
get { return this.callbackEvent; }
}
/// <summary>
/// State parameter that was provide when the callback was called
/// </summary>
public volatile object LastCallbackState;
/// <summary>Event that will be set when the callback is invoked</summary>
private ManualResetEvent callbackEvent;
}
#endregion // class TestTask
#region class WaitTask
/// <summary>ThreadPool task that can be used for testing</summary>
private class WaitTask : IDisposable {
/// <summary>Initializes a new test task</summary>
public WaitTask() {
this.startEvent = new ManualResetEvent(false);
this.finishEvent = new ManualResetEvent(false);
this.waitEvent = new ManualResetEvent(false);
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose() {
if(this.waitEvent != null) {
this.waitEvent.Close();
this.waitEvent = null;
}
if(this.finishEvent != null) {
this.finishEvent.Close();
this.finishEvent = null;
}
if(this.startEvent != null) {
this.startEvent.Close();
this.startEvent = null;
}
}
/// <summary>Callback that can be added to the thread pool as a task</summary>
/// <param name="state">User defined state</param>
public void Callback(object state) {
this.LastCallbackState = state;
this.startEvent.Set();
this.waitEvent.WaitOne();
this.finishEvent.Set();
}
/// <summary>Event that will be set when the callback has started</summary>
public ManualResetEvent StartEvent {
get { return this.startEvent; }
}
/// <summary>Event that will be set when the callback has finished</summary>
public ManualResetEvent FinishEvent {
get { return this.finishEvent; }
}
/// <summary>Event that blocks the callback</summary>
public ManualResetEvent WaitEvent {
get { return this.waitEvent; }
}
/// <summary>
/// State parameter that was provide when the callback was called
/// </summary>
public volatile object LastCallbackState;
/// <summary>Event that will be set when the callback has started</summary>
private ManualResetEvent startEvent;
/// <summary>Event that will be set when the callback has finished</summary>
private ManualResetEvent finishEvent;
/// <summary>Event used to block the callback</summary>
private ManualResetEvent waitEvent;
}
#endregion // class WaitTask
#if false
#region class ThrowingDisposable
/// <summary>Throws an exception when it is disposed</summary>
private class ThrowingDisposable : IDisposable {
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose() {
throw new ArithmeticException("Simulated exception for unit testing");
}
}
#endregion // class ThrowingDisposable
/// <summary>
/// Verifies that the Thread Pool's default assertion handler is working
/// </summary>
[Test]
public void TestDefaultAssertionHandler() {
// We can't test a failing assertion because our tests need to run
// unattended on a build server without blocking for user input.
AffineThreadPool.DefaultAssertionHandler(
true, "Unit test", "This should not fail"
);
}
#endif
/// <summary>Tests whether the QueueUserWorkItem() method is working</summary>
[Test]
public void TestQueueUserWorkItem() {
using(TestTask task = new TestTask()) {
AffineThreadPool.QueueUserWorkItem(task.Callback);
Assert.IsTrue(task.CallbackEvent.WaitOne(1000));
}
}
/// <summary>
/// Verifies that the QueueUserWorkItem() method is passing the state parameter
/// on to the callback
/// </summary>
[Test]
public void TestQueueUserWorkItemWithState() {
using(TestTask task = new TestTask()) {
object state = new object();
AffineThreadPool.QueueUserWorkItem(task.Callback, state);
Assert.IsTrue(task.CallbackEvent.WaitOne(1000));
Assert.AreSame(state, task.LastCallbackState);
}
}
/// <summary>
/// Tests whether the thread pool can handle an exception from a user work item
/// </summary>
[Test]
public void TestExceptionFromUserWorkItem() {
using(ManualResetEvent exceptionEvent = new ManualResetEvent(false)) {
AffineThreadPool.ExceptionDelegate oldExceptionHandler =
AffineThreadPool.ExceptionHandler;
AffineThreadPool.ExceptionHandler = delegate(Exception exception) {
exceptionEvent.Set();
};
try {
AffineThreadPool.QueueUserWorkItem(
delegate(object state) { throw new KeyNotFoundException(); }
);
Assert.IsTrue(exceptionEvent.WaitOne(1000));
}
finally {
AffineThreadPool.ExceptionHandler = oldExceptionHandler;
}
}
}
/// <summary>
/// Verifies that the affine thread pool's maximum thread count equals
/// the number of logical processors in the system
/// </summary>
[Test]
public void TestMaxThreadsProperty() {
Assert.AreEqual(Environment.ProcessorCount, AffineThreadPool.MaxThreads);
}
#if WINDOWS
/// <summary>
/// Verifies that the ProcessThread instance for a system thread id can
/// be determined using the GetProcessThread() method
/// </summary>
[Test]
public void CanGetProcessThreadForManagedThread() {
if(Environment.OSVersion.Platform == PlatformID.Win32NT) {
Thread.BeginThreadAffinity();
try {
int threadId = AffineThreadPool.GetCurrentThreadId();
Assert.IsNotNull(AffineThreadPool.GetProcessThread(threadId));
Assert.IsNull(AffineThreadPool.GetProcessThread(0));
}
finally {
Thread.EndThreadAffinity();
}
}
}
#endif // WINDOWS
/// <summary>
/// Tests whether the afine thread pool's default exception handler works
/// as expected
/// </summary>
[Test]
public void TestDefaultExceptionHandler() {
Assert.Throws<ArrayTypeMismatchException>(
delegate() {
AffineThreadPool.ExceptionHandler(new ArrayTypeMismatchException("Test"));
}
);
}
/// <summary>
/// Verifies that the waiting work items count and active thread count are
/// updated by the thread pool.
/// </summary>
[Test]
public void TestWaitingWorkItemsProperty() {
int eventCount = AffineThreadPool.Processors;
WaitTask[] tasks = new WaitTask[eventCount];
int createdTasks = 0;
try {
// CHECK: Is there danger that the thread pool still has not finished
// queued items for other unit tests, thereby failing to meet
// our expected task counts?
// Create the tasks, counting up the created task counter. If an exception
// occurs, we will roll back from there.
for(createdTasks = 0; createdTasks < eventCount; ++createdTasks) {
tasks[createdTasks] = new WaitTask();
}
// Schedule the blocking tasks in the thread pool so it will not be able
// to process the next task we add to the queue
for(int index = 0; index < eventCount; ++index) {
AffineThreadPool.QueueUserWorkItem(tasks[index].Callback);
}
// Wait for the tasks to start so they aren't preempted by the tasks we're
// going to add (which would finish immediately). The affine thread pool
// works on a first come first serve basis, but we don't want to rely on this
// implementation detail in the unit test.
for(int index = 0; index < eventCount; ++index) {
Assert.IsTrue(
tasks[index].StartEvent.WaitOne(10000),
"Task " + index.ToString() + " was started"
);
}
// All Thread should now be active and no work items should be waiting
Assert.AreEqual(
createdTasks, AffineThreadPool.ActiveThreads,
"ActiveThreads property equals number of tasks"
);
Assert.AreEqual(
0, AffineThreadPool.WaitingWorkItems,
"No waiting work items are in the queue"
);
// Add a task to the queue and make sure the waiting work item count goes up
AffineThreadPool.QueueUserWorkItem(delegate(object state) { });
Assert.AreEqual(
1, AffineThreadPool.WaitingWorkItems,
"Added work item is waiting in the queue"
);
// The same again. Now we should have 2 work items sitting in the queue
AffineThreadPool.QueueUserWorkItem(delegate(object state) { });
Assert.AreEqual(
2, AffineThreadPool.WaitingWorkItems,
"Both added work items are waiting in the queue"
);
// Let the WaitTasks finish so we're not blocking the thread pool any longer
for(int index = 0; index < eventCount; ++index) {
tasks[index].WaitEvent.Set();
}
// Wait for the tasks to end before we get rid of them
for(int index = 0; index < eventCount; ++index) {
Assert.IsTrue(
tasks[index].FinishEvent.WaitOne(1000),
"Task " + index.ToString() + " has finished"
);
}
}
finally {
for(--createdTasks; createdTasks >= 0; --createdTasks) {
tasks[createdTasks].Dispose();
}
}
}
}
} // namespace Nuclex.Support.Threading
#endif // UNITTEST

View file

@ -0,0 +1,257 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_CONCURRENT_COLLECTIONS
using System;
using System.Threading;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Threading {
/// <summary>Unit Test for the parallel background worker class</summary>
[TestFixture]
internal class ParallelBackgroundWorkerTest {
#region class TestWorker
/// <summary>Implementation of a background worker used for unit testing</summary>
#pragma warning disable 0618
private class TestWorker : ParallelBackgroundWorker<object> {
#pragma warning restore 0618
/// <summary>Initializes a new parallel background worker with unlimited threads</summary>
public TestWorker() : base() { }
/// <summary>
/// Initializes a new parallel background worker running the specified number
/// of tasks in parallel
/// </summary>
/// <param name="threadCount">
/// Number of tasks to run in parallel (if positive) or number of CPU cores to leave
/// unused (if negative).
/// </param>
/// <remarks>
/// If a negative number of threads is used, at least one thread will be always
/// be created, so specifying -2 on a single-core system will still occupy
/// the only core.
/// </remarks>
public TestWorker(int threadCount) : base(threadCount) { }
/// <summary>
/// Initializes a new parallel background worker that uses the specified name for
/// its worker threads.
/// </summary>
/// <param name="name">Name that will be assigned to the worker threads</param>
public TestWorker(string name) : base(name) { }
/// <summary>
/// Initializes a new parallel background worker that uses the specified name for
/// its worker threads and running the specified number of tasks in parallel.
/// </summary>
/// <param name="name">Name that will be assigned to the worker threads</param>
/// <param name="threadCount">
/// Number of tasks to run in parallel (if positive) or number of CPU cores to leave
/// unused (if negative).
/// </param>
/// <remarks>
/// If a negative number of threads is used, at least one thread will be always
/// be created, so specifying -2 on a single-core system will still occupy
/// the only core.
/// </remarks>
public TestWorker(string name, int threadCount) : base(name, threadCount) { }
/// <summary>Called in a thread to execute a single task</summary>
/// <param name="task">Task that should be executed</param>
/// <param name="cancellationToken">
/// Cancellation token through which the method can be signalled to cancel
/// </param>
protected override void Run(object task, CancellationToken cancellationToken) {
if(this.ThrowException) {
throw new Exception("Something went wrong");
}
if(this.WaitEvent != null) {
this.WaitEvent.WaitOne();
}
this.WasCancelled = cancellationToken.IsCancellationRequested;
if(this.Tasks != null) {
lock(this.Tasks) {
this.Tasks.Add(task);
}
}
}
/// <summary>Whether the work tasks should throw exceptions</summary>
public bool ThrowException;
/// <summary>Event that can be used to stop work tasks from completing</summary>
public ManualResetEvent WaitEvent;
/// <summary>Set by work tasks if they have been cancelled</summary>
public bool WasCancelled;
/// <summary>Work tasks that have reached execution</summary>
public ICollection<object> Tasks;
}
#endregion // class TestWorker
/// <summary>Verifies that the background worker has a default constructor</summary>
[Test]
public void CanBeDefaultConstructed() {
using(new TestWorker()) { }
}
/// <summary>
/// Verifies that a background worker can be constructed that uses a fixed number
/// of threads
/// </summary>
[Test]
public void CanUseFixedNumberOfThreads() {
using(new TestWorker(4)) { }
}
/// <summary>
/// Verifies that a background worker can be constructed that leaves free a fixed
/// number of CPU cores
/// </summary>
[Test]
public void CanPreserveFixedNumberOfCores() {
using(new TestWorker(-2)) { }
}
/// <summary>
/// Verifies that a background worker can be constructed using a specific name
/// for its worker threads
/// </summary>
[Test]
public void CanUseNamedThreads() {
using(new TestWorker("Test Task Thread")) { }
}
/// <summary>
/// Verifies that a background worker can be constructed that uses a fixed number
/// of threads using a specific name
/// </summary>
[Test]
public void CanUseFixedNumberOfNamedThreads() {
using(new TestWorker("Test Task Thread", 4)) { }
}
/// <summary>
/// Verifies that a background worker can be constructed that leaves free a fixed
/// number of CPU cores and uses a specific name for its worker threads.
/// </summary>
[Test]
public void CanPreserveFixedNumberOfCoresAndUseNamedThreads() {
using(new TestWorker("Test Task Thread", -2)) { }
}
/// <summary>
/// Verifies that exceptions happening inside the tasks are collected and re-thrown
/// in the Join() method.
/// </summary>
[Test]
public void ExceptionsAreReThrownInJoin() {
using(var testWorker = new TestWorker()) {
testWorker.ThrowException = true;
testWorker.AddTask(new object());
testWorker.AddTask(new object());
Assert.Throws<AggregateException>(
() => {
testWorker.Join();
}
);
try {
testWorker.Join();
Assert.Fail(
"Calling ParallelBackgroundWorker.Join() multiple times should re-throw " +
"exceptions multiple times"
);
}
catch(AggregateException aggregateException) {
Assert.AreEqual(2, aggregateException.InnerExceptions.Count);
}
}
}
/// <summary>
/// Verifies that tasks can be cancelled while they are running
/// </summary>
[Test]
public void TasksCanBeCancelled() {
using(var waitEvent = new ManualResetEvent(false)) {
using(var testWorker = new TestWorker()) {
testWorker.WaitEvent = waitEvent;
testWorker.AddTask(new object());
testWorker.CancelRunningTasks();
waitEvent.Set();
Assert.IsTrue(testWorker.Wait(1000));
Assert.IsTrue(testWorker.WasCancelled);
}
} // disposes waitEvent
}
/// <summary>Verifies that calling Join() waits for all queued tasks</summary>
[Test]
public void JoinWaitsForQueuedTasks() {
var tasks = new List<object>(100);
for(int index = 0; index < 100; ++index) {
tasks.Add(new object());
}
using(var waitEvent = new ManualResetEvent(false)) {
using(var testWorker = new TestWorker(2)) {
testWorker.WaitEvent = waitEvent;
testWorker.Tasks = new List<object>();
for(int index = 0; index < 100; ++index) {
testWorker.AddTask(tasks[index]);
}
CollectionAssert.IsEmpty(testWorker.Tasks);
waitEvent.Set();
testWorker.Join();
lock(testWorker.Tasks) {
CollectionAssert.AreEquivalent(tasks, testWorker.Tasks);
}
}
} // disposes waitEvent
}
}
} // namespace Nuclex.Support.Threading
#endif // UNITTEST
#endif // !NO_CONCURRENT_COLLECTIONS

View file

@ -0,0 +1,459 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if !NO_CONCURRENT_COLLECTIONS
using System;
using System.Threading;
using System.Collections.Generic;
#if UNITTEST
using NUnit.Framework;
namespace Nuclex.Support.Threading {
/// <summary>Unit Test for the thread runner class</summary>
[TestFixture]
internal class ThreadRunnerTest {
#region class DefaultDisposeRunner
/// <summary>Implementation of a thread runner to check default dispose behavior</summary>
private class DefaultDisposeRunner : ThreadRunner {
/// <summary>Reports an error</summary>
/// <param name="exception">Error that will be reported</param>
protected override void ReportError(Exception exception) { }
/// <summary>Called when the status of the busy flag changes</summary>
protected override void BusyChanged() { }
}
#endregion // class DefaultDisposeRunner
#region class DummyRunner
/// <summary>Implementation of a thread runner used for unit testing</summary>
private class DummyRunner : ThreadRunner {
/// <summary>Initializes a new dummy thread runner</summary>
public DummyRunner() : base() {
this.completionGate = new ManualResetEvent(initialState: false);
}
/// <summary>Immediately frees all resources used by the instance</summary>
public new void Dispose() {
base.Dispose(100);
if(this.completionGate != null) {
this.completionGate.Dispose();
this.completionGate = null;
}
}
/// <summary>Waits for the task for complete (all of 100 milliseconds)</summary>
/// <returns>True if the task completed, false if it continues running</returns>
public bool WaitForCompletion() {
return this.completionGate.WaitOne(100);
}
/// <summary>How often the status of the busy flag has changed</summary>
public int BusyChangeCount {
get { return this.busyChangeCount; }
}
/// <summary>Error that has been reported the last time a task was run</summary>
public Exception ReportedError {
get { return this.reportedError; }
}
/// <summary>Reports an error</summary>
/// <param name="exception">Error that will be reported</param>
protected override void ReportError(Exception exception) {
this.reportedError = exception;
}
/// <summary>Called when the status of the busy flag changes</summary>
protected override void BusyChanged() {
++busyChangeCount;
if((busyChangeCount >= 2) && (base.IsBusy == false)) {
this.completionGate.Set();
}
}
/// <summary>Last error that was reported in the thread</summary>
private Exception reportedError;
/// <summary>Number of times the busy state of the runner has changed</summary>
private int busyChangeCount;
/// <summary>Triggered when the busy event has performed a double flank</summary>
private ManualResetEvent completionGate;
}
#endregion // class DummyRunner
#region class DummyTask
/// <summary>Dummy task that can be executed by a thread runner</summary>
private class DummyTask : IDisposable {
/// <summary>Initializes a new dummy task</summary>
/// <param name="delayMilliseconds">How long the task shoudl take to execute</param>
public DummyTask(int delayMilliseconds) {
this.startGate = new ManualResetEvent(initialState: false);
this.delayMilliseconds = delayMilliseconds;
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose() {
if(this.startGate != null) {
this.startGate.Dispose();
this.startGate = null;
}
}
/// <summary>Waits for the task to start (all of 100 milliseconds)</summary>
/// <returns>True if the start started, false if it didn't</returns>
public bool WaitForStart() {
return this.startGate.WaitOne(100);
}
/// <summary>Sets the task up to fail with the specified error</summary>
/// <param name="error">Error the task will fail with</param>
public void FailWith(Exception error) {
this.error = error;
}
/// <summary>Runs the task with no arguments</summary>
public void Run() {
this.startGate.Set();
++this.executionCount;
Thread.Sleep(this.delayMilliseconds);
if(this.error != null) {
throw this.error;
}
}
/// <summary>Runs the task with one argument</summary>
/// <param name="firstArgument">First argument passed from the runner</param>
public void Run(float firstArgument) {
this.startGate.Set();
++this.executionCount;
this.firstArgument = firstArgument;
Thread.Sleep(this.delayMilliseconds);
if(this.error != null) {
throw this.error;
}
}
/// <summary>Runs the task with two argument</summary>
/// <param name="firstArgument">First argument passed from the runner</param>
/// <param name="secondArgument">Second argument passed from the runner</param>
public void Run(float firstArgument, string secondArgument) {
this.startGate.Set();
++this.executionCount;
this.firstArgument = firstArgument;
this.secondArgument = secondArgument;
Thread.Sleep(this.delayMilliseconds);
if(this.error != null) {
throw this.error;
}
}
/// <summary>Runs the task with no arguments</summary>
/// <param name="cancellationToken">Token by which cancellation can be signalled</param>
public void RunCancellable(CancellationToken cancellationToken) {
this.startGate.Set();
++this.executionCount;
if(delayMilliseconds == 0) {
Thread.Sleep(0);
} else {
if(cancellationToken.WaitHandle.WaitOne(delayMilliseconds)) {
this.wasCancelled = cancellationToken.IsCancellationRequested;
cancellationToken.ThrowIfCancellationRequested();
}
}
if(this.error != null) {
throw this.error;
}
}
/// <summary>Runs the task with one argument</summary>
/// <param name="firstArgument">First argument passed from the runner</param>
/// <param name="cancellationToken">Token by which cancellation can be signalled</param>
public void RunCancellable(float firstArgument, CancellationToken cancellationToken) {
this.startGate.Set();
++this.executionCount;
this.firstArgument = firstArgument;
if(delayMilliseconds == 0) {
Thread.Sleep(0);
} else {
if(cancellationToken.WaitHandle.WaitOne(delayMilliseconds)) {
this.wasCancelled = cancellationToken.IsCancellationRequested;
cancellationToken.ThrowIfCancellationRequested();
}
}
if(this.error != null) {
throw this.error;
}
}
/// <summary>Runs the task with two argument</summary>
/// <param name="firstArgument">First argument passed from the runner</param>
/// <param name="secondArgument">Second argument passed from the runner</param>
/// <param name="cancellationToken">Token by which cancellation can be signalled</param>
public void RunCancellable(
float firstArgument, string secondArgument, CancellationToken cancellationToken
) {
this.startGate.Set();
++this.executionCount;
this.firstArgument = firstArgument;
this.secondArgument = secondArgument;
if(delayMilliseconds == 0) {
Thread.Sleep(0);
} else {
if(cancellationToken.WaitHandle.WaitOne(delayMilliseconds)) {
this.wasCancelled = cancellationToken.IsCancellationRequested;
cancellationToken.ThrowIfCancellationRequested();
}
}
if(this.error != null) {
throw this.error;
}
}
/// <summary>How many times the task was run</summary>
public int ExecutionCount {
get { return this.executionCount; }
}
/// <summary>Whether the task was cancelled by the runner itself</summary>
public bool WasCancelled {
get { return this.wasCancelled; }
}
/// <summary>What the first argument was during the last call</summary>
public float FirstArgument {
get { return this.firstArgument; }
}
/// <summary>What the second argument was during the last call</summary>
public string SecondArgument {
get { return this.secondArgument; }
}
/// <summary>Last error that was reported in the thread</summary>
private Exception error;
/// <summary>Triggered when the task has started</summary>
private ManualResetEvent startGate;
/// <summary>How long the task should take to execute in milliseconds</summary>
private int delayMilliseconds;
/// <summary>How many times the task has been executed</summary>
private volatile int executionCount;
/// <summary>Whether the task has been cancelled</summary>
private volatile bool wasCancelled;
/// <summary>First argument that was passed to the task</summary>
private volatile float firstArgument;
/// <summary>Second argument that was passed to the task</summary>
private volatile string secondArgument;
}
#endregion // class DummyRunner
/// <summary>Verifies that the thread runner has a default constructor</summary>
[Test]
public void CanBeDefaultConstructed() {
using(new DummyRunner()) { }
}
/// <summary>Checks that the runner sets and unsets its busy flag</summary>
[Test]
public void BusyFlagIsToggled() {
using(var runner = new DummyRunner()) {
int busyFlagChangeCount = runner.BusyChangeCount;
Assert.IsFalse(runner.IsBusy);
runner.RunInBackground((Action)delegate() { });
Assert.IsTrue(runner.WaitForCompletion());
Assert.GreaterOrEqual(busyFlagChangeCount + 2, runner.BusyChangeCount);
Assert.IsFalse(runner.IsBusy);
}
}
/// <summary>Lets the thread runner run a simple task in the background</summary>
[Test]
public void CanRunSimpleTaskInBackground() {
using(var task = new DummyTask(0)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(new Action(task.Run));
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.IsFalse(task.WasCancelled);
}
}
/// <summary>
/// Checks that the thread runner is able to pass a single argument to a task
/// </summary>
[Test]
public void CanPassSingleArgumentToSimpleTask() {
using(var task = new DummyTask(0)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(new Action<float>(task.Run), 12.43f);
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.AreEqual(12.43f, task.FirstArgument);
Assert.IsFalse(task.WasCancelled);
}
}
/// <summary>
/// Checks that the thread runner is able to pass two arguments to a task
/// </summary>
[Test]
public void CanPassTwoArgumentsToSimpleTask() {
using(var task = new DummyTask(0)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(new Action<float, string>(task.Run), 98.67f, "Hello");
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.AreEqual(98.67f, task.FirstArgument);
Assert.AreEqual("Hello", task.SecondArgument);
Assert.IsFalse(task.WasCancelled);
}
}
/// <summary>
/// Verifies that an error happening in a simple task is reported correctly
/// </summary>
[Test]
public void SimpleTaskErrorIsReported() {
using(var task = new DummyTask(0)) {
var error = new InvalidOperationException("Mooh!");
task.FailWith(error);
using(var runner = new DummyRunner()) {
runner.RunInBackground(new Action(task.Run));
Assert.IsTrue(runner.WaitForCompletion());
Assert.AreSame(error, runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.IsFalse(task.WasCancelled);
}
}
/// <summary>Lets the thread runner run a cancellable task in the background</summary>
[Test]
public void CanRunCancellableTaskInBackground() {
using(var task = new DummyTask(100)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(new CancellableAction(task.RunCancellable));
Assert.IsTrue(task.WaitForStart());
runner.CancelAllBackgroundOperations();
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.IsTrue(task.WasCancelled);
}
}
/// <summary>
/// Checks that the thread runner is able to pass a single argument to a task
/// that can be cancelled
/// </summary>
[Test]
public void CanPassSingleArgumentToCancellableTask() {
using(var task = new DummyTask(100)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(new CancellableAction<float>(task.RunCancellable), 12.43f);
Assert.IsTrue(task.WaitForStart());
runner.CancelAllBackgroundOperations();
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.AreEqual(12.43f, task.FirstArgument);
Assert.IsTrue(task.WasCancelled);
}
}
/// <summary>
/// Checks that the thread runner is able to pass two arguments to a task
/// that can be cancelled
/// </summary>
[Test]
public void CanPassTwoArgumentsToCancellableTask() {
using(var task = new DummyTask(100)) {
using(var runner = new DummyRunner()) {
runner.RunInBackground(
new CancellableAction<float, string>(task.RunCancellable), 98.67f, "Hello"
);
Assert.IsTrue(task.WaitForStart());
runner.CancelAllBackgroundOperations();
Assert.IsTrue(runner.WaitForCompletion());
Assert.IsNull(runner.ReportedError);
}
Assert.AreEqual(1, task.ExecutionCount);
Assert.AreEqual(98.67f, task.FirstArgument);
Assert.AreEqual("Hello", task.SecondArgument);
Assert.IsTrue(task.WasCancelled);
}
}
}
} // namespace Nuclex.Support.Threading
#endif // UNITTEST
#endif // !NO_CONCURRENT_COLLECTIONS

141
Tests/TypeHelperTest.cs Normal file
View file

@ -0,0 +1,141 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.Reflection;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the strign segment class</summary>
[TestFixture]
internal class TypeHelperTest {
#region class NoDefaultConstructor
/// <summary>Test class that doesn't have a default constructor</summary>
private class NoDefaultConstructor {
/// <summary>Initializes a new instance of the test class</summary>
/// <param name="dummy">Dummy argument so this is no default constructor</param>
public NoDefaultConstructor(int dummy) { }
}
#endregion // class NoDefaultConstructor
#region class NonPublicDefaultConstructor
/// <summary>Test class that has a non-public default constructor</summary>
private class NonPublicDefaultConstructor {
/// <summary>Initializes a new instance of the test class</summary>
protected NonPublicDefaultConstructor() { }
}
#endregion // class NonPublicDefaultConstructor
#region class PublicDefaultConstructor
/// <summary>Test class that has a public default constructor</summary>
private class PublicDefaultConstructor {
/// <summary>Initializes a new instance of the test class</summary>
public PublicDefaultConstructor() { }
}
#endregion // class PublicDefaultConstructor
#region class Base
/// <summary>Base class used to test the helper methods</summary>
private class Base {
/// <summary>A simple public field</summary>
public int PublicBaseField;
/// <summary>An automatic property with a hidden backing field</summary>
public int PublicBaseProperty { get; set; }
}
#endregion // class Base
#region class Derived
/// <summary>Derived class used to test the helper methods</summary>
private class Derived : Base {
/// <summary>A simple public field</summary>
public int PublicDerivedField;
/// <summary>An automatic property with a hidden backing field</summary>
public int PublicDerivedProperty { get; set; }
}
#endregion // class Derived
#region class HasIgnoreAttribute
/// <summary>Class that carries an IgnoreAttribute</summary>
[Ignore]
private class HasIgnoreAttribute { }
#endregion // class HasIgnoreAttribute
/// <summary>
/// Verifies that the type helper can determine whether a class is carrying an attribute
/// </summary>
[Test]
public void CanDetermineIfTypeHasAttribute() {
Assert.IsTrue(typeof(HasIgnoreAttribute).HasAttribute<IgnoreAttribute>());
Assert.IsFalse(typeof(HasIgnoreAttribute).HasAttribute<TestAttribute>());
}
/// <summary>
/// Verifies that the GetFieldInfosIncludingBaseClasses() will include the backing
/// fields of automatically implemented properties in base classes
/// </summary>
[Test]
public void CanGetBackingFieldsForPropertiesInBaseClasses() {
FieldInfo[] fieldInfos = typeof(Derived).GetFieldInfosIncludingBaseClasses(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
);
Assert.AreEqual(4, fieldInfos.Length);
}
/// <summary>
/// Useless test that avoids a compile warning about unused fields
/// </summary>
[Test]
public void AvoidCompilerWarnings() {
var derived = new Derived() {
PublicBaseField = 123,
PublicBaseProperty = 321,
PublicDerivedField = 456
};
}
/// <summary>Tests whether the default constructor detection works as expected</summary>
[Test]
public void TestDefaultConstructorDetection() {
Assert.IsFalse(typeof(NoDefaultConstructor).HasDefaultConstructor());
Assert.IsFalse(typeof(NonPublicDefaultConstructor).HasDefaultConstructor());
Assert.IsTrue(typeof(PublicDefaultConstructor).HasDefaultConstructor());
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

113
Tests/WeakReferenceTest.cs Normal file
View file

@ -0,0 +1,113 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the strongly typed weak reference class</summary>
[TestFixture]
internal class WeakReferenceTest {
#region class Dummy
/// <summary>Dummy class for testing the shared instance provider</summary>
[Serializable]
private class Dummy {
/// <summary>Initializes a new dummy</summary>
public Dummy() { }
}
#endregion // class Dummy
/// <summary>Tests whether the simple constructor works</summary>
[Test]
public void TestSimpleConstructor() {
new WeakReference<Dummy>(new Dummy());
}
/// <summary>Test whether the full constructor works</summary>
[Test]
public void TestFullConstructor() {
new WeakReference<Dummy>(new Dummy(), false);
}
/// <summary>
/// Test whether the target object can be retrieved from the weak reference
/// </summary>
[Test]
public void TestTargetRetrieval() {
Dummy strongReference = new Dummy();
WeakReference<Dummy> weakReference = new WeakReference<Dummy>(strongReference);
// We can not just call GC.Collect() and base our test on the assumption that
// the garbage collector will actually collect the Dummy instance. This is up
// to the garbage collector to decide. But we can keep a strong reference in
// parallel and safely assume that the WeakReference will not be invalidated!
Assert.AreSame(strongReference, weakReference.Target);
}
/// <summary>
/// Test whether the target object can be reassigned in the weak reference
/// </summary>
[Test]
public void TestTargetReassignment() {
Dummy strongReference1 = new Dummy();
Dummy strongReference2 = new Dummy();
WeakReference<Dummy> weakReference = new WeakReference<Dummy>(strongReference1);
Assert.AreSame(strongReference1, weakReference.Target);
weakReference.Target = strongReference2;
Assert.AreSame(strongReference2, weakReference.Target);
}
/// <summary>
/// Test whether the target object can be reassigned in the weak reference
/// </summary>
[Test]
public void TestSerialization() {
BinaryFormatter formatter = new BinaryFormatter();
using(MemoryStream memory = new MemoryStream()) {
WeakReference<Dummy> weakReference1 = new WeakReference<Dummy>(new Dummy());
formatter.Serialize(memory, weakReference1);
memory.Position = 0;
object weakReference2 = formatter.Deserialize(memory);
// We cannot make any more predictions but for the type of the weak reference.
// The pointee might have been garbage collected just now or the serializer
// might have decided not to serialize the pointee at all (which is a valid
// decision if the serializer found no strong reference to the pointee) in
// another of the object graph.
Assert.IsNotNull(weakReference2 as WeakReference<Dummy>);
}
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

313
Tests/XmlHelperTest.cs Normal file
View file

@ -0,0 +1,313 @@
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if UNITTEST
using System;
using System.IO;
using System.Text;
using System.Xml;
#if !USE_XMLDOCUMENT
using System.Xml.Linq;
#endif
using System.Xml.Schema;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit Test for the XML helper class</summary>
[TestFixture]
internal class XmlHelperTest {
/// <summary>A broken XML schema</summary>
private const string brokenSchemaXml =
"This is not a valid schema";
/// <summary>An XML schema with a syntax error</summary>
private const string syntaxErrorSchemaXml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<xs:schema" +
" elementFormDefault=\"qualified\"" +
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">" +
" <xs:attribute minOccurs=\"0\" maxOccurs=\"2\" name=\"x\" type=\"xs:double\" />" +
" <xs:attribute minOccurs=\"0\" maxOccurs=\"2\" name=\"y\" type=\"xs:double\" />" +
"</xs:schema>";
/// <summary>A valid XML schema for a list of 2D points</summary>
private const string pointsSchemaXml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<xs:schema" +
" elementFormDefault=\"qualified\"" +
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">" +
" <xs:complexType name=\"vectorType\">" +
" <xs:attribute name=\"x\" type=\"xs:double\" />" +
" <xs:attribute name=\"y\" type=\"xs:double\" />" +
" </xs:complexType>" +
" <xs:complexType name=\"pointsType\">" +
" <xs:sequence minOccurs=\"0\" maxOccurs=\"unbounded\">" +
" <xs:element name=\"point\" type=\"vectorType\" />" +
" </xs:sequence>" +
" </xs:complexType>" +
" <xs:element name=\"points\" type=\"pointsType\" />" +
"</xs:schema>";
/// <summary>A broken XML document</summary>
private const string brokenXml =
"This is not a valid XML file";
/// <summary>
/// Well-formed XML document that is not conformant to the schema above
/// </summary>
private const string unconformantXml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<points" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsi:noNamespaceSchemaLocation=\"skin.xsd\">" +
" <point x=\"10\" y=\"20\" z=\"30\" />" +
" <point x=\"1\" y=\"2\" />" +
"</points>";
/// <summary>Well-formed XML document that is conformant to the schema</summary>
private const string conformantXml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<points" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsi:noNamespaceSchemaLocation=\"skin.xsd\">" +
" <point x=\"1\" y=\"2\" />" +
" <point x=\"10\" y=\"20\" />" +
"</points>";
#region TempFileKeeper
/// <summary>
/// Creates a temporary file and automatically deletes it on dispose
/// </summary>
private class TempFileKeeper : IDisposable {
/// <summary>
/// Creates a temporary file with the specified contents using the UTF8 encoding
/// </summary>
/// <param name="fileContents">
/// Contents that will be written into the temporary file
/// </param>
public TempFileKeeper(string fileContents) : this(fileContents, Encoding.UTF8) { }
/// <summary>Creates a temporary file with the specified contents</summary>
/// <param name="fileContents">
/// Contents that will be written into the temporary file
/// </param>
/// <param name="encoding">
/// Encoding to use for writing the contents into the file
/// </param>
public TempFileKeeper(string fileContents, Encoding encoding) {
string tempFile = Path.GetTempFileName();
try {
using(
FileStream tempFileStream = new FileStream(
tempFile, FileMode.Truncate, FileAccess.Write, FileShare.None
)
) {
StreamWriter writer = new StreamWriter(tempFileStream, encoding);
writer.Write(fileContents);
writer.Flush();
}
}
catch(Exception) {
File.Delete(tempFile);
throw;
}
this.tempFilePath = tempFile;
}
/// <summary>Called when the instance is collected by the GC</summary>
~TempFileKeeper() {
Dispose();
}
/// <summary>Immediately releases all resources used by the instance</summary>
public void Dispose() {
if(this.tempFilePath != null) {
File.Delete(this.tempFilePath);
this.tempFilePath = null;
GC.SuppressFinalize(this);
}
}
/// <summary>Implicitely converts a TempFileKeeper into a file path</summary>
/// <param name="tempFileKeeper">TempFileKeeper that will be converted</param>
/// <returns>The path to the temporary file managed by the TempFileKeeper</returns>
public static implicit operator string(TempFileKeeper tempFileKeeper) {
return tempFileKeeper.tempFilePath;
}
/// <summary>Path to the temporary file the TempFileKeeper is managing</summary>
private string tempFilePath;
}
#endregion // class TempFileKeeper
/// <summary>
/// Verifies that an exception is thrown when a schema fails to load
/// </summary>
[Test]
public void LoadSchemaThrowsOnInvalidSchema() {
using(
TempFileKeeper tempFile = new TempFileKeeper(brokenSchemaXml)
) {
Assert.Throws<XmlException>(delegate() { XmlHelper.LoadSchema(tempFile); });
}
}
/// <summary>
/// Verifies that an exception is thrown when a schema contains a syntax error
/// </summary>
[Test]
public void LoadSchemaThrowsOnSyntaxErrors() {
using(
TempFileKeeper tempFile = new TempFileKeeper(syntaxErrorSchemaXml)
) {
Assert.Throws<XmlSchemaException>(delegate() { XmlHelper.LoadSchema(tempFile); });
}
}
/// <summary>
/// Verfifies that TryLoadSchema() can fail without throwing an exception
/// when the schema is not a valid XML document
/// </summary>
[Test]
public void TryLoadSchemaHandlesMissingFiles() {
XmlSchema schema;
Assert.IsFalse(XmlHelper.TryLoadSchema("-- hello world --", out schema));
Assert.IsNull(schema);
}
/// <summary>
/// Verfifies that TryLoadSchema() can fail without throwing an exception
/// when the schema is not a valid XML document
/// </summary>
[Test]
public void TryLoadSchemaHandlesBrokenSchemas() {
using(
TempFileKeeper tempFile = new TempFileKeeper(brokenSchemaXml)
) {
XmlSchema schema;
Assert.IsFalse(XmlHelper.TryLoadSchema(tempFile, out schema));
Assert.IsNull(schema);
}
}
/// <summary>
/// Verfifies that TryLoadSchema() can fail without throwing an exception
/// when the schema contains a syntax error
/// </summary>
[Test]
public void TryLoadSchemaHandlesSyntaxErrors() {
using(
TempFileKeeper tempFile = new TempFileKeeper(syntaxErrorSchemaXml)
) {
XmlSchema schema;
Assert.IsFalse(XmlHelper.TryLoadSchema(tempFile, out schema));
Assert.IsNull(schema);
}
}
/// <summary>Tests whether a normal, valid schema can be loaded successfully</summary>
[Test]
public void SchemasCanBeLoadedFromFiles() {
using(
TempFileKeeper tempFile = new TempFileKeeper(pointsSchemaXml)
) {
XmlHelper.LoadSchema(tempFile);
}
}
/// <summary>Tests whether a normal, valid schema can be loaded successfully</summary>
[Test]
public void TryLoadSchemaCanLoadSchemas() {
using(
TempFileKeeper tempFile = new TempFileKeeper(pointsSchemaXml)
) {
XmlSchema schema;
Assert.IsTrue(XmlHelper.TryLoadSchema(tempFile, out schema));
Assert.NotNull(schema);
}
}
/// <summary>
/// Verifies that an exception is thrown when an invalid XML document is loaded
/// </summary>
[Test]
public void LoadingInvalidDocumentThrows() {
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
using(
TempFileKeeper tempFile = new TempFileKeeper(brokenXml)
) {
Assert.Throws<XmlException>(
delegate() { XmlHelper.LoadDocument(schema, tempFile); }
);
}
}
}
/// <summary>
/// Verifies that an exception is thrown when a nonconformant XML document is loaded
/// </summary>
[Test]
public void SchemaValidationFailureCausesException() {
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
using(
TempFileKeeper tempFile = new TempFileKeeper(unconformantXml)
) {
Assert.Throws<XmlSchemaValidationException>(
delegate() { XmlHelper.LoadDocument(schema, tempFile); }
);
}
}
}
/// <summary>
/// Tests whether a normal, conformant XML document can be loaded successfully
/// </summary>
[Test]
public void LoadedDocumentIsValidatedAgainstSchema() {
using(TextReader schemaReader = new StringReader(pointsSchemaXml)) {
XmlSchema schema = XmlHelper.LoadSchema(schemaReader);
using(
TempFileKeeper tempFile = new TempFileKeeper(conformantXml)
) {
#if USE_XMLDOCUMENT
XmlDocument document = XmlHelper.LoadDocument(schema, tempFile);
#else
XDocument document = XmlHelper.LoadDocument(schema, tempFile);
#endif
}
}
}
}
} // namespace Nuclex.Support
#endif // UNITTEST