// **************************************************************** // Copyright 2007, Charlie Poole // This is free software licensed under the NUnit license. You may // obtain a copy of the license at http://nunit.org/?p=license&r=2.4 // **************************************************************** using System; using System.Collections; namespace NUnit.Framework.Constraints { #region CollectionConstraint /// /// CollectionConstraint is the abstract base class for /// constraints that operate on collections. /// public abstract class CollectionConstraint : Constraint { protected static bool IsEmpty( IEnumerable enumerable ) { ICollection collection = enumerable as ICollection; if ( collection != null ) return collection.Count == 0; else return !enumerable.GetEnumerator().MoveNext(); } /// /// CollectionTally counts (tallies) the number of /// occurences of each object in one or more enuerations. /// protected internal class CollectionTally { // Internal hash used to count occurences private Hashtable hash = new Hashtable(); // We use this for any null entries found, since // the key to a hash may not be null. static object NULL = new object(); private int getTally(object obj) { if ( obj == null ) obj = NULL; object val = hash[obj]; return val == null ? 0 : (int)val; } private void setTally(object obj, int tally) { if ( obj == null ) obj = NULL; hash[obj] = tally; } /// /// Construct a CollectionTally object from a collection /// /// public CollectionTally( IEnumerable c ) { foreach( object obj in c ) setTally( obj, getTally( obj ) + 1 ); } /// /// Remove the counts for a collection from the tally, /// so long as their are sufficient items to remove. /// The tallies are not permitted to become negative. /// /// The collection to remove /// True if there were enough items to remove, otherwise false public bool CanRemove( IEnumerable c ) { foreach( object obj in c ) { int tally = getTally(obj); if( tally > 0 ) setTally(obj, tally - 1 ); else return false; } return true; } /// /// Test whether all the counts are equal to a given value /// /// The value to be looked for /// True if all counts are equal to the value, otherwise false public bool AllCountsEqualTo( int count ) { foreach( DictionaryEntry entry in hash ) if ( (int)entry.Value != count ) return false; return true; } /// /// Get the count of the number of times an object is present in the tally /// public int this[object obj] { get { return getTally(obj); } } } /// /// Test whether the constraint is satisfied by a given value /// /// The value to be tested /// True for success, false for failure public override bool Matches(object actual) { this.actual = actual; IEnumerable enumerable = actual as IEnumerable; if ( enumerable == null ) throw new ArgumentException( "The actual value must be an IEnumerable", "actual" ); return doMatch( enumerable ); } /// /// Protected method to be implemented by derived classes /// /// /// protected abstract bool doMatch(IEnumerable collection); } #endregion #region EmptyCollectionConstraint /// /// EmptyCollectionConstraint tests whether a colletion is empty. /// public class EmptyCollectionConstraint : CollectionConstraint { /// /// Check that the collection is empty /// /// /// protected override bool doMatch(IEnumerable collection) { return IsEmpty( collection ); } /// /// Write the constraint description to a MessageWriter /// /// public override void WriteDescriptionTo(MessageWriter writer) { writer.Write( "" ); } } #endregion #region UniqueItemsConstraint /// /// UniqueItemsConstraint tests whether all the items in a /// collection are unique. /// public class UniqueItemsConstraint : CollectionConstraint { /// /// Check that all items are unique. /// /// /// protected override bool doMatch(IEnumerable actual) { return new CollectionTally( actual ).AllCountsEqualTo( 1 ); } /// /// Write a description of this constraint to a MessageWriter /// /// public override void WriteDescriptionTo(MessageWriter writer) { writer.Write("all items unique"); } } #endregion #region CollectionContainsConstraint /// /// CollectionContainsConstraint is used to test whether a collection /// contains an expected object as a member. /// public class CollectionContainsConstraint : CollectionConstraint { private object expected; /// /// Construct a CollectionContainsConstraint /// /// public CollectionContainsConstraint(object expected) { this.expected = expected; } /// /// Test whether the expected item is contained in the collection /// /// /// protected override bool doMatch(IEnumerable actual) { foreach (object obj in actual) if ( Object.Equals( obj, expected ) ) return true; return false; } /// /// Write a descripton of the constraint to a MessageWriter /// /// public override void WriteDescriptionTo(MessageWriter writer) { writer.WritePredicate( "collection containing" ); writer.WriteExpectedValue(expected); } } #endregion #region CollectionEquivalentConstraint /// /// CollectionEquivalentCOnstraint is used to determine whether two /// collections are equivalent. /// public class CollectionEquivalentConstraint : CollectionConstraint { private IEnumerable expected; /// /// Construct a CollectionEquivalentConstraint /// /// public CollectionEquivalentConstraint(IEnumerable expected) { this.expected = expected; } /// /// Test whether two collections are equivalent /// /// /// protected override bool doMatch(IEnumerable actual) { // This is just an optimization if( expected is ICollection && actual is ICollection ) if( ((ICollection)actual).Count != ((ICollection)expected).Count ) return false; CollectionTally tally = new CollectionTally( expected ); return tally.CanRemove( actual ) && tally.AllCountsEqualTo( 0 ); } /// /// Write a description of this constraint to a MessageWriter /// /// public override void WriteDescriptionTo(MessageWriter writer) { writer.WritePredicate("equivalent to"); writer.WriteExpectedValue(expected); } } #endregion #region CollectionSubsetConstraint /// /// CollectionSubsetConstraint is used to determine whether /// one collection is a subset of another /// public class CollectionSubsetConstraint : CollectionConstraint { private IEnumerable expected; /// /// Construct a CollectionSubsetConstraint /// /// The collection that the actual value is expected to be a subset of public CollectionSubsetConstraint(IEnumerable expected) { this.expected = expected; } /// /// Test whether the actual collection is a subset of /// the expected collection provided. /// /// /// protected override bool doMatch(IEnumerable actual) { return new CollectionTally( expected ).CanRemove( actual ); } /// /// Write a description of this constraint to a MessageWriter /// /// public override void WriteDescriptionTo(MessageWriter writer) { writer.WritePredicate( "subset of" ); writer.WriteExpectedValue(expected); } } #endregion }