Nailed the unit test coverage for all classes in the root namespace at 100%; fixed ugly german comment in ReadOnlyDictionary; moved AbstractFactory interface for FactoryEmployer in Nuclex.Support.Plugins into its own file; wrote unit tests for the PluginHelper, Shared, StringSegment and WeakReference classes

git-svn-id: file:///srv/devel/repo-conversion/nusu@93 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2008-11-26 19:15:36 +00:00
parent ddff1d8353
commit cb0355193d
16 changed files with 738 additions and 46 deletions

View File

@ -95,11 +95,15 @@
<Compile Include="Source\PathHelper.Test.cs"> <Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon> <DependentUpon>PathHelper.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Plugins\Attributes.cs" /> <Compile Include="Source\Plugins\AbstractFactory.cs" />
<Compile Include="Source\Plugins\NoPluginAttribute.cs" />
<Compile Include="Source\Plugins\Employer.cs" /> <Compile Include="Source\Plugins\Employer.cs" />
<Compile Include="Source\Plugins\FactoryEmployer.cs" /> <Compile Include="Source\Plugins\FactoryEmployer.cs" />
<Compile Include="Source\Plugins\InstanceEmployer.cs" /> <Compile Include="Source\Plugins\InstanceEmployer.cs" />
<Compile Include="Source\Plugins\PluginHelper.cs" /> <Compile Include="Source\Plugins\PluginHelper.cs" />
<Compile Include="Source\Plugins\PluginHelper.Test.cs">
<DependentUpon>PluginHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\PluginHost.cs" /> <Compile Include="Source\Plugins\PluginHost.cs" />
<Compile Include="Source\Plugins\PluginRepository.cs" /> <Compile Include="Source\Plugins\PluginRepository.cs" />
<Compile Include="Source\Scheduling\AbortedException.cs" /> <Compile Include="Source\Scheduling\AbortedException.cs" />
@ -112,11 +116,17 @@
<Compile Include="Source\Scheduling\ThreadCallbackOperation.cs" /> <Compile Include="Source\Scheduling\ThreadCallbackOperation.cs" />
<Compile Include="Source\Scheduling\ThreadOperation.cs" /> <Compile Include="Source\Scheduling\ThreadOperation.cs" />
<Compile Include="Source\Shared.cs" /> <Compile Include="Source\Shared.cs" />
<Compile Include="Source\Shared.Test.cs">
<DependentUpon>Shared.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringHelper.cs" /> <Compile Include="Source\StringHelper.cs" />
<Compile Include="Source\StringHelper.Test.cs"> <Compile Include="Source\StringHelper.Test.cs">
<DependentUpon>StringHelper.cs</DependentUpon> <DependentUpon>StringHelper.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\StringSegment.cs" /> <Compile Include="Source\StringSegment.cs" />
<Compile Include="Source\StringSegment.Test.cs">
<DependentUpon>StringSegment.cs</DependentUpon>
</Compile>
<Compile Include="Source\Tracking\IdleStateEventArgs.cs" /> <Compile Include="Source\Tracking\IdleStateEventArgs.cs" />
<Compile Include="Source\Tracking\Internal\ObservedWeightedWaitable.cs" /> <Compile Include="Source\Tracking\Internal\ObservedWeightedWaitable.cs" />
<Compile Include="Source\Tracking\Internal\WeightedWaitableWrapperCollection.cs" /> <Compile Include="Source\Tracking\Internal\WeightedWaitableWrapperCollection.cs" />
@ -136,6 +146,9 @@
<Compile Include="Source\Tracking\Waitable.cs" /> <Compile Include="Source\Tracking\Waitable.cs" />
<Compile Include="Source\Tracking\WeightedWaitable.cs" /> <Compile Include="Source\Tracking\WeightedWaitable.cs" />
<Compile Include="Source\WeakReference.cs" /> <Compile Include="Source\WeakReference.cs" />
<Compile Include="Source\WeakReference.Test.cs">
<DependentUpon>WeakReference.cs</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Documents\Nuclex.Support.txt" /> <Content Include="Documents\Nuclex.Support.txt" />

View File

@ -98,8 +98,8 @@ namespace Nuclex.Support.Collections {
} }
} }
/// <summary>Collection aller Werte im Dictionary</summary> /// <summary>Collection of all values contained in the Dictionary</summary>
public ICollection<ValueType> Values { // TODO: RO-Wrappen! public ICollection<ValueType> Values {
get { get {
if(this.readonlyValueCollection == null) { if(this.readonlyValueCollection == null) {
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>( this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(

View File

@ -77,6 +77,29 @@ namespace Nuclex.Support {
); );
} }
/// <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> /// <summary>
/// Tests whether the relative path creator correctly builds the relative /// Tests whether the relative path creator correctly builds the relative
/// path to the parent folder of the base path for unix paths. /// path to the parent folder of the base path for unix paths.
@ -99,6 +122,28 @@ namespace Nuclex.Support {
); );
} }
/// <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> /// <summary>
/// Tests whether the relative path creator correctly builds the relative /// Tests whether the relative path creator correctly builds the relative
/// path to a nested folder in the base path for windows paths. /// path to a nested folder in the base path for windows paths.

View File

@ -0,0 +1,39 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2008 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
namespace Nuclex.Support.Plugins {
/// <summary>Abstract factory for a concrete type</summary>
/// <typeparam name="ProductType">Interface or base class of the product of the factory</typeparam>
public interface IAbstractFactory<ProductType> {
/// <summary>The concrete type as implemented by the factory instance</summary>
Type ConcreteType { get; }
/// <summary>Creates a new instance of the type to which the factory is specialized</summary>
/// <returns>The newly created instance</returns>
ProductType CreateInstance();
}
} // namespace Nuclex.Support.Plugins

View File

@ -34,7 +34,7 @@ namespace Nuclex.Support.Plugins {
/// <summary>Determines whether the type suites the employer's requirements</summary> /// <summary>Determines whether the type suites the employer's requirements</summary>
/// <param name="type">Type which will be assessed</param> /// <param name="type">Type which will be assessed</param>
/// <returns>True if the type can be employed</returns> /// <returns>True if the type can be employed, otherwise false</returns>
public virtual bool CanEmploy(Type type) { public virtual bool CanEmploy(Type type) {
return PluginHelper.HasDefaultConstructor(type); return PluginHelper.HasDefaultConstructor(type);
} }

View File

@ -23,21 +23,10 @@ using System.Collections.Generic;
namespace Nuclex.Support.Plugins { namespace Nuclex.Support.Plugins {
/// <summary>Abstract factory</summary>
/// <typeparam name="T">Interface or base class of the product of the factory</typeparam>
public interface IFactory<T> {
/// <summary>The concrete type as implemented by the factory instance</summary>
Type ConcreteType { get; }
/// <summary>Creates a new instance of the type to which the factory is specialized</summary>
/// <returns>The newly created instance</returns>
T CreateInstance();
}
/// <summary>Employer to create factories of suiting types found in plugins</summary> /// <summary>Employer to create factories of suiting types found in plugins</summary>
/// <typeparam name="T">Interface or base class that the types need to implement</typeparam> /// <typeparam name="ProductType">
/// Interface or base class that the types need to implement
/// </typeparam>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This employer will not directly instanciate any compatible types found in /// This employer will not directly instanciate any compatible types found in
@ -52,16 +41,16 @@ namespace Nuclex.Support.Plugins {
/// a human-readable name, capabilities or an icon. /// a human-readable name, capabilities or an icon.
/// </para> /// </para>
/// </remarks> /// </remarks>
public class FactoryEmployer<T> : Employer { public class FactoryEmployer<ProductType> : Employer {
#region class Factory #region class ConcreteFactory
/// <summary>Concrete factory for the types in a plugin assembly</summary> /// <summary>Concrete factory for the types in a plugin assembly</summary>
private class Factory : IFactory<T> { private class ConcreteFactory : IAbstractFactory<ProductType> {
/// <summary>Initializes a factory and configures it for the specified product</summary> /// <summary>Initializes a factory and configures it for the specified product</summary>
/// <param name="type">Type of which the factory creates instances</param> /// <param name="type">Type of which the factory creates instances</param>
public Factory(Type type) { public ConcreteFactory(Type type) {
this.concreteType = type; this.concreteType = type;
} }
@ -72,8 +61,8 @@ namespace Nuclex.Support.Plugins {
/// <summary>Create a new instance of the type that the factory is configured to</summary> /// <summary>Create a new instance of the type that the factory is configured to</summary>
/// <returns>The newly created instance</returns> /// <returns>The newly created instance</returns>
public T CreateInstance() { public ProductType CreateInstance() {
return (T)Activator.CreateInstance(this.concreteType); return (ProductType)Activator.CreateInstance(this.concreteType);
} }
/// <summary>Concrete product which the factory instance creates</summary> /// <summary>Concrete product which the factory instance creates</summary>
@ -85,11 +74,11 @@ namespace Nuclex.Support.Plugins {
/// <summary>Initializes a new FactoryEmployer</summary> /// <summary>Initializes a new FactoryEmployer</summary>
public FactoryEmployer() { public FactoryEmployer() {
this.employedFactories = new List<IFactory<T>>(); this.employedFactories = new List<IAbstractFactory<ProductType>>();
} }
/// <summary>List of all factories that the instance employer has created</summary> /// <summary>List of all factories that the instance employer has created</summary>
public List<IFactory<T>> Factories { public List<IAbstractFactory<ProductType>> Factories {
get { return this.employedFactories; } get { return this.employedFactories; }
} }
@ -99,17 +88,17 @@ namespace Nuclex.Support.Plugins {
public override bool CanEmploy(Type type) { public override bool CanEmploy(Type type) {
return return
PluginHelper.HasDefaultConstructor(type) && PluginHelper.HasDefaultConstructor(type) &&
typeof(T).IsAssignableFrom(type); typeof(ProductType).IsAssignableFrom(type);
} }
/// <summary>Employs the specified plugin type</summary> /// <summary>Employs the specified plugin type</summary>
/// <param name="type">Type to be employed</param> /// <param name="type">Type to be employed</param>
public override void Employ(Type type) { public override void Employ(Type type) {
this.employedFactories.Add(new Factory(type)); this.employedFactories.Add(new ConcreteFactory(type));
} }
/// <summary>All factories that the instance employer has created</summary> /// <summary>All factories that the instance employer has created</summary>
private List<IFactory<T>> employedFactories; private List<IAbstractFactory<ProductType>> employedFactories;
} }

View File

@ -0,0 +1,84 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2008 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
namespace Nuclex.Support.Plugins {
/// <summary>Unit Test for the plugin helper class</summary>
[TestFixture]
public class PluginHelperTest {
#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
/// <summary>Tests whether the default constructor detection works as expected</summary>
[Test]
public void TestDefaultConstructorDetection() {
Assert.IsFalse(
PluginHelper.HasDefaultConstructor(typeof(NoDefaultConstructor))
);
Assert.IsFalse(
PluginHelper.HasDefaultConstructor(typeof(NonPublicDefaultConstructor))
);
Assert.IsTrue(
PluginHelper.HasDefaultConstructor(typeof(PublicDefaultConstructor))
);
}
}
} // namespace Nuclex.Support.Plugins
#endif // UNITTEST

View File

@ -19,20 +19,21 @@ License along with this library
#endregion #endregion
using System; using System;
using System.Reflection;
namespace Nuclex.Support.Plugins { namespace Nuclex.Support.Plugins {
/// <summary>Supporting functions for the plugin classes</summary> /// <summary>Supporting functions for the plugin classes</summary>
internal static class PluginHelper { public static class PluginHelper {
/// <summary>Determines whether the given type has a default constructor</summary> /// <summary>Determines whether the given type has a default constructor</summary>
/// <param name="type">Type which is to be checked</param> /// <param name="type">Type which is to be checked</param>
/// <returns>True if the type has a default constructor</returns> /// <returns>True if the type has a default constructor</returns>
public static bool HasDefaultConstructor(Type type) { public static bool HasDefaultConstructor(Type type) {
System.Reflection.ConstructorInfo[] constructors = type.GetConstructors(); ConstructorInfo[] constructors = type.GetConstructors();
foreach(System.Reflection.ConstructorInfo constructor in constructors) foreach(ConstructorInfo constructor in constructors)
if(constructor.IsPublic && (constructor.GetParameters().Length != 0)) if(constructor.IsPublic && (constructor.GetParameters().Length == 0))
return true; return true;
return false; return false;

70
Source/Shared.Test.cs Normal file
View File

@ -0,0 +1,70 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2008 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
namespace Nuclex.Support {
/// <summary>Unit Test for the shared instance provider class</summary>
[TestFixture]
public 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() {
Dummy dummyInstance = Shared<Dummy>.Instance;
Dummy otherDummyInstance = Shared<Dummy>.Instance;
// 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.IsTrue(
ReferenceEquals(dummyInstance, otherDummyInstance)
);
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View File

@ -19,6 +19,7 @@ License along with this library
#endregion #endregion
using System; using System;
using System.Diagnostics;
namespace Nuclex.Support { namespace Nuclex.Support {
@ -30,7 +31,7 @@ namespace Nuclex.Support {
/// <summary>Returns the global instance of the class</summary> /// <summary>Returns the global instance of the class</summary>
public static SharedType Instance { public static SharedType Instance {
[System.Diagnostics.DebuggerStepThrough] [DebuggerStepThrough]
get { get {
return instance; return instance;
} }

View File

@ -47,6 +47,21 @@ namespace Nuclex.Support {
); );
} }
/// <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> /// <summary>
/// Verifies that the LastIndexNotOfAny() method works identical to the framework's /// Verifies that the LastIndexNotOfAny() method works identical to the framework's
/// implementation of the LastIndexOfAny() method, only inverted. /// implementation of the LastIndexOfAny() method, only inverted.
@ -63,14 +78,17 @@ namespace Nuclex.Support {
} }
/// <summary> /// <summary>
/// Verifies that the IndexNotOfAny() method works with multiple characters /// Verifies that the LastIndexNotOfAny() method works identical to the framework's
/// implementation of the LastIndexOfAny() method, only inverted, using a start index.
/// </summary> /// </summary>
[Test] [Test]
public void TestMultipleCharIndexNotOfAny() { public void TestLastIndexNotOfAnyWithStartIndex() {
string haystack = "0123456789"; string positive = "OOOOOxxxxxOOOOO";
string negative = "xxxxxOOOOOxxxxx";
Assert.AreEqual( Assert.AreEqual(
5, StringHelper.IndexNotOfAny(haystack, new char[] { '4', '3', '2', '1', '0' }) positive.LastIndexOfAny(new char[] { 'x' }, 5),
StringHelper.LastIndexNotOfAny(negative, new char[] { 'x' }, 5)
); );
} }
@ -78,14 +96,121 @@ namespace Nuclex.Support {
/// Verifies that the IndexNotOfAny() method works with multiple characters /// Verifies that the IndexNotOfAny() method works with multiple characters
/// </summary> /// </summary>
[Test] [Test]
public void TestMultipleCharLastIndexNotOfAny() { public void TestMultipleCharIndexNotOfAny() {
string haystack = "0123456789"; string positive = "abcde12345";
string negative = "12345abcde";
Assert.AreEqual( Assert.AreEqual(
4, StringHelper.LastIndexNotOfAny(haystack, new char[] { '9', '8', '7', '6', '5' }) 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 } // namespace Nuclex.Support

View File

@ -0,0 +1,195 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2008 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.IO;
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
namespace Nuclex.Support {
/// <summary>Unit Test for the strign segment class</summary>
[TestFixture]
public 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, ExpectedException(typeof(ArgumentNullException))]
public void TestNullStringInSimpleConstructor() {
new StringSegment(null);
}
/// <summary>
/// Tests whether the simple constructor of the StringSegment class accepts
/// an empty string
/// </summary>
[Test]
public void TestEmptyStringInSimpleConstructor() {
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, ExpectedException(typeof(ArgumentNullException))]
public void TestNullStringInFullConstructor() {
new StringSegment(null, 0, 0);
}
/// <summary>
/// Tests whether the full constructor of the StringSegment class accepts
/// an empty string
/// </summary>
[Test]
public void TestEmptyStringInFullConstructor() {
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, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestInvalidOffsetInConstructor() {
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, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestInvalidLengthInConstructor() {
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, ExpectedException(typeof(ArgumentException))]
public void TestExcessiveLengthInConstructor() {
new StringSegment("hello", 3, 3);
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
[Test]
public void TestTextProperty() {
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 TestOffsetProperty() {
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 TestCountProperty() {
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 TestHashCodeOnDifferingInstances() {
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 TestHashCodeOnEquivalentInstances() {
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 TestEqualsOnNull() {
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 TestEqualityOnDifferingInstances() {
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 TestEqualityOnEquivalentInstances() {
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 TestInequalityOnDifferingInstances() {
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 TestInequalityOnEquivalentInstances() {
StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
Assert.IsFalse(helloWorld1Segment != helloWorld2Segment);
}
}
} // namespace Nuclex.Support
#endif // UNITTEST

View File

@ -25,8 +25,22 @@ using System.Runtime.InteropServices;
namespace Nuclex.Support { namespace Nuclex.Support {
/// <summary>Delimits a section of a string</summary> /// <summary>Delimits a section of a string</summary>
/// <remarks>
/// <para>
/// The design of this class pretty much mirrors that of the
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
/// specialized to be used for strings, which can not be expressed as arrays but
/// share a lot of the characteristics of an array.
/// </para>
/// <para>
/// In certain situations, passing a StringSegment instead of the the actual
/// section from a string is useful. For example, the caller might want to know
/// from which index of the original string the substring was taken. Used internally
/// in parsers, it can also prevent needless string copying and garbage generation.
/// </para>
/// </remarks>
[Serializable, StructLayout(LayoutKind.Sequential)] [Serializable, StructLayout(LayoutKind.Sequential)]
internal struct StringSegment { public struct StringSegment {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StringSegment" /> class that delimits /// Initializes a new instance of the <see cref="StringSegment" /> class that delimits
@ -35,7 +49,7 @@ namespace Nuclex.Support {
/// <param name="text">String that will be wrapped</param> /// <param name="text">String that will be wrapped</param>
/// <exception cref="System.ArgumentNullException">String is null</exception> /// <exception cref="System.ArgumentNullException">String is null</exception>
public StringSegment(string text) { public StringSegment(string text) {
if(text == null) { if(text == null) { // questionable, but matches behavior of ArraySegment class
throw new ArgumentNullException("text"); throw new ArgumentNullException("text");
} }
@ -59,8 +73,8 @@ namespace Nuclex.Support {
/// </exception> /// </exception>
/// <exception cref="System.ArgumentNullException">String is null</exception> /// <exception cref="System.ArgumentNullException">String is null</exception>
public StringSegment(string text, int offset, int count) { public StringSegment(string text, int offset, int count) {
if(text == null) { if(text == null) { // questionable, but matches behavior of ArraySegment class
throw new ArgumentNullException("array"); throw new ArgumentNullException("text");
} }
if(offset < 0) { if(offset < 0) {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
@ -72,7 +86,7 @@ namespace Nuclex.Support {
"count", "Argument out of range, non-negative number required" "count", "Argument out of range, non-negative number required"
); );
} }
if((text.Length - offset) < count) { if(count > (text.Length - offset)) {
throw new ArgumentException( throw new ArgumentException(
"Invalid argument, specified offset and count exceed string length" "Invalid argument, specified offset and count exceed string length"
); );

View File

@ -0,0 +1,115 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2008 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
namespace Nuclex.Support {
/// <summary>Unit Test for the strongly typed weak reference class</summary>
[TestFixture]
public 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

View File

@ -28,6 +28,7 @@ namespace Nuclex.Support {
/// Type-safe weak reference, referencing an object while still allowing /// Type-safe weak reference, referencing an object while still allowing
/// that object to be garbage collected. /// that object to be garbage collected.
/// </summary> /// </summary>
[Serializable]
public class WeakReference<ReferencedType> : WeakReference public class WeakReference<ReferencedType> : WeakReference
where ReferencedType : class { where ReferencedType : class {