PageRenderTime 125ms CodeModel.GetById 112ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NUnit/framework/Constraints/NUnitEqualityComparer.cs

#
C# | 330 lines | 208 code | 61 blank | 61 comment | 75 complexity | 3dbfaaa6303b0fa899eede36de1dcade MD5 | raw file
  1// ****************************************************************
  2// Copyright 2009, Charlie Poole
  3// This is free software licensed under the NUnit license. You may
  4// obtain a copy of the license at http://nunit.org
  5// ****************************************************************
  6
  7using System;
  8using System.IO;
  9using System.Collections;
 10#if NET_2_0
 11using System.Collections.Generic;
 12#endif
 13
 14namespace NUnit.Framework.Constraints
 15{
 16    /// <summary>
 17    /// NUnitEqualityComparer encapsulates NUnit's handling of
 18    /// equality tests between objects.
 19    /// </summary>
 20    public class NUnitEqualityComparer
 21    {
 22        #region Static and Instance Fields
 23        /// <summary>
 24        /// If true, all string comparisons will ignore case
 25        /// </summary>
 26        private bool caseInsensitive;
 27
 28        /// <summary>
 29        /// If true, arrays will be treated as collections, allowing
 30        /// those of different dimensions to be compared
 31        /// </summary>
 32        private bool compareAsCollection;
 33
 34        /// <summary>
 35        /// If non-zero, equality comparisons within the specified 
 36        /// tolerance will succeed.
 37        /// </summary>
 38        private Tolerance tolerance = Tolerance.Empty;
 39
 40        /// <summary>
 41        /// Comparison object used in comparisons for some constraints.
 42        /// </summary>
 43        private EqualityAdapter externalComparer;
 44
 45        private ArrayList failurePoints;
 46
 47        private static readonly int BUFFER_SIZE = 4096;
 48        #endregion
 49
 50        #region Properties
 51
 52        /// <summary>
 53        /// Returns the default NUnitEqualityComparer
 54        /// </summary>
 55        public static NUnitEqualityComparer Default
 56        {
 57            get { return new NUnitEqualityComparer(); }
 58        }
 59        /// <summary>
 60        /// Gets and sets a flag indicating whether case should
 61        /// be ignored in determining equality.
 62        /// </summary>
 63        public bool IgnoreCase
 64        {
 65            get { return caseInsensitive; }
 66            set { caseInsensitive = value; }
 67        }
 68
 69        /// <summary>
 70        /// Gets and sets a flag indicating that arrays should be
 71        /// compared as collections, without regard to their shape.
 72        /// </summary>
 73        public bool CompareAsCollection
 74        {
 75            get { return compareAsCollection; }
 76            set { compareAsCollection = value; }
 77        }
 78
 79        /// <summary>
 80        /// Gets and sets an external comparer to be used to
 81        /// test for equality. It is applied to members of
 82        /// collections, in place of NUnit's own logic.
 83        /// </summary>
 84        public EqualityAdapter ExternalComparer
 85        {
 86            get { return externalComparer; }
 87            set { externalComparer = value; }
 88        }
 89
 90        /// <summary>
 91        /// Gets and sets a tolerance used to compare objects of 
 92        /// certin types.
 93        /// </summary>
 94        public Tolerance Tolerance
 95        {
 96            get { return tolerance; }
 97            set { tolerance = value; }
 98        }
 99
100        /// <summary>
101        /// Gets the list of failure points for the last Match performed.
102        /// </summary>
103        public IList FailurePoints
104        {
105            get { return failurePoints; }
106        }
107        #endregion
108
109        #region Public Methods
110        /// <summary>
111        /// Compares two objects for equality.
112        /// </summary>
113        public bool ObjectsEqual(object x, object y)
114        {
115            this.failurePoints = new ArrayList();
116
117            if (x == null && y == null)
118                return true;
119
120            if (x == null || y == null)
121                return false;
122			
123			if (object.ReferenceEquals(x, y))
124				return true;
125
126            Type xType = x.GetType();
127            Type yType = y.GetType();
128
129            if (xType.IsArray && yType.IsArray && !compareAsCollection)
130                return ArraysEqual((Array)x, (Array)y);
131
132            if (x is IDictionary && y is IDictionary)
133                return DictionariesEqual((IDictionary)x, (IDictionary)y);
134
135            if (x is ICollection && y is ICollection)
136                return CollectionsEqual((ICollection)x, (ICollection)y);
137
138            if (x is IEnumerable && y is IEnumerable && !(x is string && y is string))
139                return EnumerablesEqual((IEnumerable)x, (IEnumerable)y);
140
141            if (externalComparer != null)
142                return externalComparer.ObjectsEqual(x, y);
143
144            if (x is string && y is string)
145                return StringsEqual((string)x, (string)y);
146
147            if (x is Stream && y is Stream)
148                return StreamsEqual((Stream)x, (Stream)y);
149
150            if (x is DirectoryInfo && y is DirectoryInfo)
151                return DirectoriesEqual((DirectoryInfo)x, (DirectoryInfo)y);
152
153            if (Numerics.IsNumericType(x) && Numerics.IsNumericType(y))
154                return Numerics.AreEqual(x, y, ref tolerance);
155
156            if (tolerance != null && tolerance.Value is TimeSpan)
157            {
158                TimeSpan amount = (TimeSpan)tolerance.Value;
159
160                if (x is DateTime && y is DateTime)
161                    return ((DateTime)x - (DateTime)y).Duration() <= amount;
162
163                if (x is TimeSpan && y is TimeSpan)
164                    return ((TimeSpan)x - (TimeSpan)y).Duration() <= amount;
165            }
166
167            return x.Equals(y);
168        }
169        #endregion
170
171        #region Helper Methods
172        /// <summary>
173        /// Helper method to compare two arrays
174        /// </summary>
175        private bool ArraysEqual(Array x, Array y)
176        {
177            int rank = x.Rank;
178
179            if (rank != y.Rank)
180                return false;
181
182            for (int r = 1; r < rank; r++)
183                if (x.GetLength(r) != y.GetLength(r))
184                    return false;
185
186            return CollectionsEqual((ICollection)x, (ICollection)y);
187        }
188
189        private bool DictionariesEqual(IDictionary x, IDictionary y)
190        {
191            if (x.Count != y.Count)
192                return false;
193
194            CollectionTally tally = new CollectionTally(this, x.Keys);
195            if (!tally.TryRemove(y.Keys) || tally.Count > 0)
196                return false;
197
198            foreach (object key in x.Keys)
199                if (!ObjectsEqual(x[key], y[key]))
200                    return false;
201
202            return true;
203        }
204
205        private bool CollectionsEqual(ICollection x, ICollection y)
206        {
207            IEnumerator expectedEnum = x.GetEnumerator();
208            IEnumerator actualEnum = y.GetEnumerator();
209
210            int count;
211            for (count = 0; expectedEnum.MoveNext() && actualEnum.MoveNext(); count++)
212            {
213                if (!ObjectsEqual(expectedEnum.Current, actualEnum.Current))
214                    break;
215            }
216
217            if (count == x.Count && count == y.Count)
218                return true;
219
220            failurePoints.Insert(0, count);
221            return false;
222        }
223
224        private bool StringsEqual(string x, string y)
225        {
226            string s1 = caseInsensitive ? x.ToLower() : x;
227            string s2 = caseInsensitive ? y.ToLower() : y;
228
229            return s1.Equals(s2);
230        }
231
232        private bool EnumerablesEqual(IEnumerable x, IEnumerable y)
233        {
234            IEnumerator expectedEnum = x.GetEnumerator();
235            IEnumerator actualEnum = y.GetEnumerator();
236
237            int count = 0;
238            for (; ; )
239            {
240                bool expectedHasData = expectedEnum.MoveNext();
241                bool actualHasData = actualEnum.MoveNext();
242
243                if (!expectedHasData && !actualHasData)
244                    return true;
245
246                if (expectedHasData != actualHasData ||
247                    !ObjectsEqual(expectedEnum.Current, actualEnum.Current))
248                {
249                    failurePoints.Insert(0, count);
250                    return false;
251                }
252            }
253        }
254
255        /// <summary>
256        /// Method to compare two DirectoryInfo objects
257        /// </summary>
258        /// <param name="x">first directory to compare</param>
259        /// <param name="y">second directory to compare</param>
260        /// <returns>true if equivalent, false if not</returns>
261        private bool DirectoriesEqual(DirectoryInfo x, DirectoryInfo y)
262        {
263            // Do quick compares first
264            if (x.Attributes != y.Attributes ||
265                x.CreationTime != y.CreationTime ||
266                x.LastAccessTime != y.LastAccessTime)
267            {
268                return false;
269            }
270
271            // TODO: Find a cleaner way to do this
272            return new SamePathConstraint(x.FullName).Matches(y.FullName);
273        }
274
275        private bool StreamsEqual(Stream x, Stream y)
276        {
277            if (x == y) return true;
278
279            if (!x.CanRead)
280                throw new ArgumentException("Stream is not readable", "expected");
281            if (!y.CanRead)
282                throw new ArgumentException("Stream is not readable", "actual");
283            if (!x.CanSeek)
284                throw new ArgumentException("Stream is not seekable", "expected");
285            if (!y.CanSeek)
286                throw new ArgumentException("Stream is not seekable", "actual");
287
288            if (x.Length != y.Length) return false;
289
290            byte[] bufferExpected = new byte[BUFFER_SIZE];
291            byte[] bufferActual = new byte[BUFFER_SIZE];
292
293            BinaryReader binaryReaderExpected = new BinaryReader(x);
294            BinaryReader binaryReaderActual = new BinaryReader(y);
295
296            long expectedPosition = x.Position;
297            long actualPosition = y.Position;
298
299            try
300            {
301                binaryReaderExpected.BaseStream.Seek(0, SeekOrigin.Begin);
302                binaryReaderActual.BaseStream.Seek(0, SeekOrigin.Begin);
303
304                for (long readByte = 0; readByte < x.Length; readByte += BUFFER_SIZE)
305                {
306                    binaryReaderExpected.Read(bufferExpected, 0, BUFFER_SIZE);
307                    binaryReaderActual.Read(bufferActual, 0, BUFFER_SIZE);
308
309                    for (int count = 0; count < BUFFER_SIZE; ++count)
310                    {
311                        if (bufferExpected[count] != bufferActual[count])
312                        {
313                            failurePoints.Insert(0, readByte + count);
314                            //FailureMessage.WriteLine("\tIndex : {0}", readByte + count);
315                            return false;
316                        }
317                    }
318                }
319            }
320            finally
321            {
322                x.Position = expectedPosition;
323                y.Position = actualPosition;
324            }
325
326            return true;
327        }
328        #endregion
329    }
330}