From cb0355193d7351a92a8585bccb0b2300d4b29519 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Wed, 26 Nov 2008 19:15:36 +0000 Subject: [PATCH] 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 --- Nuclex.Support.csproj | 15 +- Source/Collections/ReadOnlyDictionary.cs | 4 +- Source/PathHelper.Test.cs | 45 ++++ Source/Plugins/AbstractFactory.cs | 39 ++++ Source/Plugins/Employer.cs | 2 +- Source/Plugins/FactoryEmployer.cs | 39 ++-- .../{Attributes.cs => NoPluginAttribute.cs} | 0 Source/Plugins/PluginHelper.Test.cs | 84 ++++++++ Source/Plugins/PluginHelper.cs | 9 +- Source/Shared.Test.cs | 70 +++++++ Source/Shared.cs | 3 +- Source/StringHelper.Test.cs | 139 ++++++++++++- Source/StringSegment.Test.cs | 195 ++++++++++++++++++ Source/StringSegment.cs | 24 ++- Source/WeakReference.Test.cs | 115 +++++++++++ Source/WeakReference.cs | 1 + 16 files changed, 738 insertions(+), 46 deletions(-) create mode 100644 Source/Plugins/AbstractFactory.cs rename Source/Plugins/{Attributes.cs => NoPluginAttribute.cs} (100%) create mode 100644 Source/Plugins/PluginHelper.Test.cs create mode 100644 Source/Shared.Test.cs create mode 100644 Source/StringSegment.Test.cs create mode 100644 Source/WeakReference.Test.cs diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index c97e4d2..dbaaf46 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -95,11 +95,15 @@ PathHelper.cs - + + + + PluginHelper.cs + @@ -112,11 +116,17 @@ + + Shared.cs + StringHelper.cs + + StringSegment.cs + @@ -136,6 +146,9 @@ + + WeakReference.cs + diff --git a/Source/Collections/ReadOnlyDictionary.cs b/Source/Collections/ReadOnlyDictionary.cs index c4bf9d6..fbbfc76 100644 --- a/Source/Collections/ReadOnlyDictionary.cs +++ b/Source/Collections/ReadOnlyDictionary.cs @@ -98,8 +98,8 @@ namespace Nuclex.Support.Collections { } } - /// Collection aller Werte im Dictionary - public ICollection Values { // TODO: RO-Wrappen! + /// Collection of all values contained in the Dictionary + public ICollection Values { get { if(this.readonlyValueCollection == null) { this.readonlyValueCollection = new ReadOnlyCollection( diff --git a/Source/PathHelper.Test.cs b/Source/PathHelper.Test.cs index 4d4e335..9aa193c 100644 --- a/Source/PathHelper.Test.cs +++ b/Source/PathHelper.Test.cs @@ -77,6 +77,29 @@ namespace Nuclex.Support { ); } + /// + /// 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. + /// + [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("../../")) + ); + } + + /// /// Tests whether the relative path creator correctly builds the relative /// path to the parent folder of the base path for unix paths. @@ -99,6 +122,28 @@ namespace Nuclex.Support { ); } + /// + /// 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. + /// + [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("../../")) + ); + } + /// /// Tests whether the relative path creator correctly builds the relative /// path to a nested folder in the base path for windows paths. diff --git a/Source/Plugins/AbstractFactory.cs b/Source/Plugins/AbstractFactory.cs new file mode 100644 index 0000000..b8aa6e7 --- /dev/null +++ b/Source/Plugins/AbstractFactory.cs @@ -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 { + + /// Abstract factory for a concrete type + /// Interface or base class of the product of the factory + public interface IAbstractFactory { + + /// The concrete type as implemented by the factory instance + Type ConcreteType { get; } + + /// Creates a new instance of the type to which the factory is specialized + /// The newly created instance + ProductType CreateInstance(); + + } + +} // namespace Nuclex.Support.Plugins diff --git a/Source/Plugins/Employer.cs b/Source/Plugins/Employer.cs index 43988b6..34b0d84 100644 --- a/Source/Plugins/Employer.cs +++ b/Source/Plugins/Employer.cs @@ -34,7 +34,7 @@ namespace Nuclex.Support.Plugins { /// Determines whether the type suites the employer's requirements /// Type which will be assessed - /// True if the type can be employed + /// True if the type can be employed, otherwise false public virtual bool CanEmploy(Type type) { return PluginHelper.HasDefaultConstructor(type); } diff --git a/Source/Plugins/FactoryEmployer.cs b/Source/Plugins/FactoryEmployer.cs index d4425de..3707b93 100644 --- a/Source/Plugins/FactoryEmployer.cs +++ b/Source/Plugins/FactoryEmployer.cs @@ -23,21 +23,10 @@ using System.Collections.Generic; namespace Nuclex.Support.Plugins { - /// Abstract factory - /// Interface or base class of the product of the factory - public interface IFactory { - - /// The concrete type as implemented by the factory instance - Type ConcreteType { get; } - - /// Creates a new instance of the type to which the factory is specialized - /// The newly created instance - T CreateInstance(); - - } - /// Employer to create factories of suiting types found in plugins - /// Interface or base class that the types need to implement + /// + /// Interface or base class that the types need to implement + /// /// /// /// 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. /// /// - public class FactoryEmployer : Employer { + public class FactoryEmployer : Employer { - #region class Factory + #region class ConcreteFactory /// Concrete factory for the types in a plugin assembly - private class Factory : IFactory { + private class ConcreteFactory : IAbstractFactory { /// Initializes a factory and configures it for the specified product /// Type of which the factory creates instances - public Factory(Type type) { + public ConcreteFactory(Type type) { this.concreteType = type; } @@ -72,8 +61,8 @@ namespace Nuclex.Support.Plugins { /// Create a new instance of the type that the factory is configured to /// The newly created instance - public T CreateInstance() { - return (T)Activator.CreateInstance(this.concreteType); + public ProductType CreateInstance() { + return (ProductType)Activator.CreateInstance(this.concreteType); } /// Concrete product which the factory instance creates @@ -85,11 +74,11 @@ namespace Nuclex.Support.Plugins { /// Initializes a new FactoryEmployer public FactoryEmployer() { - this.employedFactories = new List>(); + this.employedFactories = new List>(); } /// List of all factories that the instance employer has created - public List> Factories { + public List> Factories { get { return this.employedFactories; } } @@ -99,17 +88,17 @@ namespace Nuclex.Support.Plugins { public override bool CanEmploy(Type type) { return PluginHelper.HasDefaultConstructor(type) && - typeof(T).IsAssignableFrom(type); + typeof(ProductType).IsAssignableFrom(type); } /// Employs the specified plugin type /// Type to be employed public override void Employ(Type type) { - this.employedFactories.Add(new Factory(type)); + this.employedFactories.Add(new ConcreteFactory(type)); } /// All factories that the instance employer has created - private List> employedFactories; + private List> employedFactories; } diff --git a/Source/Plugins/Attributes.cs b/Source/Plugins/NoPluginAttribute.cs similarity index 100% rename from Source/Plugins/Attributes.cs rename to Source/Plugins/NoPluginAttribute.cs diff --git a/Source/Plugins/PluginHelper.Test.cs b/Source/Plugins/PluginHelper.Test.cs new file mode 100644 index 0000000..45c6a74 --- /dev/null +++ b/Source/Plugins/PluginHelper.Test.cs @@ -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 { + + /// Unit Test for the plugin helper class + [TestFixture] + public class PluginHelperTest { + + #region class NoDefaultConstructor + + /// Test class that doesn't have a default constructor + private class NoDefaultConstructor { + /// Initializes a new instance of the test class + /// Dummy argument so this is no default constructor + public NoDefaultConstructor(int dummy) { } + } + + #endregion // class NoDefaultConstructor + + #region class NonPublicDefaultConstructor + + /// Test class that has a non-public default constructor + private class NonPublicDefaultConstructor { + /// Initializes a new instance of the test class + protected NonPublicDefaultConstructor() { } + } + + #endregion // class NonPublicDefaultConstructor + + #region class PublicDefaultConstructor + + /// Test class that has a public default constructor + private class PublicDefaultConstructor { + /// Initializes a new instance of the test class + public PublicDefaultConstructor() { } + } + + #endregion // class PublicDefaultConstructor + + /// Tests whether the default constructor detection works as expected + [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 diff --git a/Source/Plugins/PluginHelper.cs b/Source/Plugins/PluginHelper.cs index d13f538..0a799cf 100644 --- a/Source/Plugins/PluginHelper.cs +++ b/Source/Plugins/PluginHelper.cs @@ -19,20 +19,21 @@ License along with this library #endregion using System; +using System.Reflection; namespace Nuclex.Support.Plugins { /// Supporting functions for the plugin classes - internal static class PluginHelper { + public static class PluginHelper { /// Determines whether the given type has a default constructor /// Type which is to be checked /// True if the type has a default constructor public static bool HasDefaultConstructor(Type type) { - System.Reflection.ConstructorInfo[] constructors = type.GetConstructors(); + ConstructorInfo[] constructors = type.GetConstructors(); - foreach(System.Reflection.ConstructorInfo constructor in constructors) - if(constructor.IsPublic && (constructor.GetParameters().Length != 0)) + foreach(ConstructorInfo constructor in constructors) + if(constructor.IsPublic && (constructor.GetParameters().Length == 0)) return true; return false; diff --git a/Source/Shared.Test.cs b/Source/Shared.Test.cs new file mode 100644 index 0000000..e220d24 --- /dev/null +++ b/Source/Shared.Test.cs @@ -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 { + + /// Unit Test for the shared instance provider class + [TestFixture] + public class SharedTest { + + #region class Dummy + + /// Dummy class for testing the shared instance provider + private class Dummy { + /// Initializes a new dummy + public Dummy() {} + } + + #endregion // class Dummy + + /// + /// Verifies that the shared instance provider returns the same instance of a class + /// when asked for the same class twice. + /// + [Test] + public void TestSameInstance() { + Dummy dummyInstance = Shared.Instance; + Dummy otherDummyInstance = Shared.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 diff --git a/Source/Shared.cs b/Source/Shared.cs index ae94b18..9fab20e 100644 --- a/Source/Shared.cs +++ b/Source/Shared.cs @@ -19,6 +19,7 @@ License along with this library #endregion using System; +using System.Diagnostics; namespace Nuclex.Support { @@ -30,7 +31,7 @@ namespace Nuclex.Support { /// Returns the global instance of the class public static SharedType Instance { - [System.Diagnostics.DebuggerStepThrough] + [DebuggerStepThrough] get { return instance; } diff --git a/Source/StringHelper.Test.cs b/Source/StringHelper.Test.cs index ce9a8c4..2d81f65 100644 --- a/Source/StringHelper.Test.cs +++ b/Source/StringHelper.Test.cs @@ -47,6 +47,21 @@ namespace Nuclex.Support { ); } + /// + /// Verifies that the IndexNotOfAny() method works identical to the framework's + /// implementation of the IndexOfAny() method, only inverted, using a start index. + /// + [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) + ); + } + /// /// Verifies that the LastIndexNotOfAny() method works identical to the framework's /// implementation of the LastIndexOfAny() method, only inverted. @@ -63,14 +78,17 @@ namespace Nuclex.Support { } /// - /// 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. /// [Test] - public void TestMultipleCharIndexNotOfAny() { - string haystack = "0123456789"; + public void TestLastIndexNotOfAnyWithStartIndex() { + string positive = "OOOOOxxxxxOOOOO"; + string negative = "xxxxxOOOOOxxxxx"; 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 /// [Test] - public void TestMultipleCharLastIndexNotOfAny() { - string haystack = "0123456789"; + public void TestMultipleCharIndexNotOfAny() { + string positive = "abcde12345"; + string negative = "12345abcde"; 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' }) ); } + /// + /// Verifies that the IndexNotOfAny() method works with multiple characters, + /// using a start index + /// + [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) + ); + } + + /// + /// Verifies that the LastIndexNotOfAny() method works with multiple characters + /// + [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' }) + ); + } + + /// + /// Verifies that the LastIndexNotOfAny() method works with multiple characters, + /// using a start index + /// + [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) + ); + } + + /// + /// Verifies that the IndexNotOfAny() method fails when only matches are found + /// + [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' }) + ); + } + + /// + /// Verifies that the IndexNotOfAny() method fails when only matches are found, + /// using a start index + /// + [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) + ); + } + + /// + /// Verifies that the LastIndexNotOfAny() method fails when only matches are found + /// + [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' }) + ); + } + + /// + /// Verifies that the LastIndexNotOfAny() method fails when only matches are found, + /// using a start index + /// + [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 diff --git a/Source/StringSegment.Test.cs b/Source/StringSegment.Test.cs new file mode 100644 index 0000000..57964d2 --- /dev/null +++ b/Source/StringSegment.Test.cs @@ -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 { + + /// Unit Test for the strign segment class + [TestFixture] + public class StringSegmentTest { + + /// + /// Tests whether the default constructor of the StringSegment class throws the + /// right exception when being passed 'null' instead of a string + /// + [Test, ExpectedException(typeof(ArgumentNullException))] + public void TestNullStringInSimpleConstructor() { + new StringSegment(null); + } + + /// + /// Tests whether the simple constructor of the StringSegment class accepts + /// an empty string + /// + [Test] + public void TestEmptyStringInSimpleConstructor() { + new StringSegment(string.Empty); + } + + /// + /// Tests whether the full constructor of the StringSegment class throws the + /// right exception when being passed 'null' instead of a string + /// + [Test, ExpectedException(typeof(ArgumentNullException))] + public void TestNullStringInFullConstructor() { + new StringSegment(null, 0, 0); + } + + /// + /// Tests whether the full constructor of the StringSegment class accepts + /// an empty string + /// + [Test] + public void TestEmptyStringInFullConstructor() { + new StringSegment(string.Empty, 0, 0); + } + + /// + /// Tests whether the full constructor of the StringSegment class throws the + /// right exception when being passed an invalid start offset + /// + [Test, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestInvalidOffsetInConstructor() { + new StringSegment(string.Empty, -1, 0); + } + + /// + /// Tests whether the full constructor of the StringSegment class throws the + /// right exception when being passed an invalid string length + /// + [Test, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestInvalidLengthInConstructor() { + new StringSegment(string.Empty, 0, -1); + } + + /// + /// Tests whether the full constructor of the StringSegment class throws the + /// right exception when being passed a string length that's too large + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestExcessiveLengthInConstructor() { + new StringSegment("hello", 3, 3); + } + + /// Tests whether the 'Text' property works as expected + [Test] + public void TestTextProperty() { + StringSegment testSegment = new StringSegment("hello", 1, 3); + Assert.AreEqual("hello", testSegment.Text); + } + + /// Tests whether the 'Offset' property works as expected + [Test] + public void TestOffsetProperty() { + StringSegment testSegment = new StringSegment("hello", 1, 3); + Assert.AreEqual(1, testSegment.Offset); + } + + /// Tests whether the 'Count' property works as expected + [Test] + public void TestCountProperty() { + StringSegment testSegment = new StringSegment("hello", 1, 3); + Assert.AreEqual(3, testSegment.Count); + } + + /// + /// Tests whether two differing instances produce different hash codes + /// + [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() + ); + } + + /// + /// Tests whether two equivalent instances produce an identical hash code + /// + [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() + ); + } + + /// Tests the equals method performing a comparison against null + [Test] + public void TestEqualsOnNull() { + StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7); + + Assert.IsFalse( + helloWorldSegment.Equals(null) + ); + } + + /// Tests the equality operator with differing instances + [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); + } + + /// Tests the equality operator with equivalent instances + [Test] + public void TestEqualityOnEquivalentInstances() { + StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7); + StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7); + + Assert.IsTrue(helloWorld1Segment == helloWorld2Segment); + } + + /// Tests the inequality operator with differing instances + [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); + } + + /// Tests the inequality operator with equivalent instances + [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 diff --git a/Source/StringSegment.cs b/Source/StringSegment.cs index a401a61..4d4fc54 100644 --- a/Source/StringSegment.cs +++ b/Source/StringSegment.cs @@ -25,8 +25,22 @@ using System.Runtime.InteropServices; namespace Nuclex.Support { /// Delimits a section of a string + /// + /// + /// The design of this class pretty much mirrors that of the + /// 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. + /// + /// + /// 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. + /// + /// [Serializable, StructLayout(LayoutKind.Sequential)] - internal struct StringSegment { + public struct StringSegment { /// /// Initializes a new instance of the class that delimits @@ -35,7 +49,7 @@ namespace Nuclex.Support { /// String that will be wrapped /// String is null public StringSegment(string text) { - if(text == null) { + if(text == null) { // questionable, but matches behavior of ArraySegment class throw new ArgumentNullException("text"); } @@ -59,8 +73,8 @@ namespace Nuclex.Support { /// /// String is null public StringSegment(string text, int offset, int count) { - if(text == null) { - throw new ArgumentNullException("array"); + if(text == null) { // questionable, but matches behavior of ArraySegment class + throw new ArgumentNullException("text"); } if(offset < 0) { throw new ArgumentOutOfRangeException( @@ -72,7 +86,7 @@ namespace Nuclex.Support { "count", "Argument out of range, non-negative number required" ); } - if((text.Length - offset) < count) { + if(count > (text.Length - offset)) { throw new ArgumentException( "Invalid argument, specified offset and count exceed string length" ); diff --git a/Source/WeakReference.Test.cs b/Source/WeakReference.Test.cs new file mode 100644 index 0000000..c2bc336 --- /dev/null +++ b/Source/WeakReference.Test.cs @@ -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 { + + /// Unit Test for the strongly typed weak reference class + [TestFixture] + public class WeakReferenceTest { + + #region class Dummy + + /// Dummy class for testing the shared instance provider + [Serializable] + private class Dummy { + /// Initializes a new dummy + public Dummy() { } + } + + #endregion // class Dummy + + /// Tests whether the simple constructor works + [Test] + public void TestSimpleConstructor() { + new WeakReference(new Dummy()); + } + + /// Test whether the full constructor works + [Test] + public void TestFullConstructor() { + new WeakReference(new Dummy(), false); + } + + /// + /// Test whether the target object can be retrieved from the weak reference + /// + [Test] + public void TestTargetRetrieval() { + Dummy strongReference = new Dummy(); + WeakReference weakReference = new WeakReference(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); + } + + /// + /// Test whether the target object can be reassigned in the weak reference + /// + [Test] + public void TestTargetReassignment() { + Dummy strongReference1 = new Dummy(); + Dummy strongReference2 = new Dummy(); + WeakReference weakReference = new WeakReference(strongReference1); + + Assert.AreSame(strongReference1, weakReference.Target); + weakReference.Target = strongReference2; + Assert.AreSame(strongReference2, weakReference.Target); + } + + /// + /// Test whether the target object can be reassigned in the weak reference + /// + [Test] + public void TestSerialization() { + BinaryFormatter formatter = new BinaryFormatter(); + + using(MemoryStream memory = new MemoryStream()) { + WeakReference weakReference1 = new WeakReference(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); + } + } + + } + +} // namespace Nuclex.Support + +#endif // UNITTEST diff --git a/Source/WeakReference.cs b/Source/WeakReference.cs index e98a181..2ce26b7 100644 --- a/Source/WeakReference.cs +++ b/Source/WeakReference.cs @@ -28,6 +28,7 @@ namespace Nuclex.Support { /// Type-safe weak reference, referencing an object while still allowing /// that object to be garbage collected. /// + [Serializable] public class WeakReference : WeakReference where ReferencedType : class {