PageRenderTime 45ms CodeModel.GetById 30ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NUnit/framework/Constraints/PathConstraints.cs

#
C# | 321 lines | 153 code | 43 blank | 125 comment | 29 complexity | fc23a3557f0972dad0e2b810052b93b0 MD5 | raw file
  1// ****************************************************************
  2// Copyright 2008, 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
 11namespace NUnit.Framework.Constraints
 12{
 13    #region PathConstraint
 14    /// <summary>
 15	/// PathConstraint serves as the abstract base of constraints
 16	/// that operate on paths and provides several helper methods.
 17	/// </summary>
 18	public abstract class PathConstraint : Constraint
 19	{
 20        private static readonly char[] DirectorySeparatorChars = new char[] { '\\', '/' };
 21
 22		/// <summary>
 23		/// The expected path used in the constraint
 24		/// </summary>
 25		protected string expectedPath;
 26		
 27		/// <summary>
 28		/// The actual path being tested
 29		/// </summary>
 30		protected string actualPath;
 31
 32        /// <summary>
 33        /// Flag indicating whether a caseInsensitive comparison should be made
 34        /// </summary>
 35        protected bool caseInsensitive = Path.DirectorySeparatorChar == '\\';
 36
 37        /// <summary>
 38		/// Construct a PathConstraint for a give expected path
 39		/// </summary>
 40		/// <param name="expected">The expected path</param>
 41		protected PathConstraint( string expected ) : base(expected)
 42		{
 43			this.expectedPath = expected;
 44        }
 45
 46        /// <summary>
 47        /// Modifies the current instance to be case-insensitve
 48        /// and returns it.
 49        /// </summary>
 50        public PathConstraint IgnoreCase
 51        {
 52            get { caseInsensitive = true; return this; }
 53        }
 54
 55        /// <summary>
 56        /// Modifies the current instance to be case-sensitve
 57        /// and returns it.
 58        /// </summary>
 59        public PathConstraint RespectCase
 60        {
 61            get { caseInsensitive = false; return this; }
 62        }
 63
 64        /// <summary>
 65        /// Test whether the constraint is satisfied by a given value
 66        /// </summary>
 67        /// <param name="actual">The value to be tested</param>
 68        /// <returns>True for success, false for failure</returns>
 69		public override bool Matches (object actual)
 70		{
 71			this.actual = actual;
 72			this.actualPath = actual as string;
 73
 74			if (actualPath == null)
 75                return false;
 76			
 77			return IsMatch(expectedPath, actualPath);
 78		}
 79		
 80        /// <summary>
 81        /// Returns true if the expected path and actual path match
 82        /// </summary>
 83		protected abstract bool IsMatch(string expectedPath, string actualPath);
 84		
 85        /// <summary>
 86        /// Returns the string representation of this constraint
 87        /// </summary>
 88        protected override string GetStringRepresentation()
 89        {
 90            return string.Format( "<{0} \"{1}\" {2}>", DisplayName, expectedPath, caseInsensitive ? "ignorecase" : "respectcase" );
 91        }
 92
 93        #region Helper Methods
 94        /// <summary>
 95		/// Canonicalize the provided path
 96		/// </summary>
 97		/// <param name="path"></param>
 98		/// <returns>The path in standardized form</returns>
 99		protected static string Canonicalize( string path )
100		{
101			bool initialSeparator = false;
102			
103			if (path.Length > 0)
104				foreach(char c in DirectorySeparatorChars)
105					if (path[0] == c)
106						initialSeparator = true;
107			
108			ArrayList parts = new ArrayList( path.Split( DirectorySeparatorChars ) );
109
110			for( int index = 0; index < parts.Count; )
111			{
112				string part = (string)parts[index];
113		
114				switch( part )
115				{
116					case ".":
117						parts.RemoveAt( index );
118						break;
119				
120					case "..":
121						parts.RemoveAt( index );
122						if ( index > 0 )
123							parts.RemoveAt( --index );
124						break;
125					default:
126						index++;
127						break;
128				}
129			}
130
131            // Trailing separator removal
132            if ((string)parts[parts.Count - 1] == "")
133                parts.RemoveAt(parts.Count - 1);
134
135            string result = String.Join(Path.DirectorySeparatorChar.ToString(), 
136				(string[])parts.ToArray( typeof( string ) ) );
137			
138			if (initialSeparator)
139				result = Path.DirectorySeparatorChar.ToString() + result;
140			
141			return result;
142		}
143
144		/// <summary>
145		/// Test whether two paths are the same
146		/// </summary>
147		/// <param name="path1">The first path</param>
148		/// <param name="path2">The second path</param>
149        /// <param name="ignoreCase">Indicates whether case should be ignored</param>
150		/// <returns></returns>
151		protected static bool IsSamePath( string path1, string path2, bool ignoreCase )
152		{
153			return string.Compare( path1, path2, ignoreCase ) == 0;
154		}
155
156		/// <summary>
157		/// Test whether one path is under another path
158		/// </summary>
159		/// <param name="path1">The first path - supposed to be the parent path</param>
160		/// <param name="path2">The second path - supposed to be the child path</param>
161        /// <param name="ignoreCase">Indicates whether case should be ignored</param>
162		/// <returns></returns>
163		protected static bool IsSubPath( string path1, string path2, bool ignoreCase )
164		{
165			int length1 = path1.Length;
166			int length2 = path2.Length;
167
168			// if path1 is longer or equal, then path2 can't be under it
169			if ( length1 >= length2 )
170				return false;
171
172			// path 2 is longer than path 1: see if initial parts match
173			if ( string.Compare( path1, path2.Substring( 0, length1 ), ignoreCase ) != 0 )
174				return false;
175			
176			// must match through or up to a directory separator boundary
177			return	path2[length1-1] == Path.DirectorySeparatorChar ||
178				length2 > length1 && path2[length1] == Path.DirectorySeparatorChar;
179        }
180
181		/// <summary>
182		/// Test whether one path is the same as or under another path
183		/// </summary>
184		/// <param name="path1">The first path - supposed to be the parent path</param>
185		/// <param name="path2">The second path - supposed to be the child path</param>
186		/// <returns></returns>
187		protected bool IsSamePathOrUnder( string path1, string path2 )
188		{
189			int length1 = path1.Length;
190			int length2 = path2.Length;
191
192			// if path1 is longer, then path2 can't be under it
193			if ( length1 > length2 )
194				return false;
195
196			// if lengths are the same, check for equality
197			if ( length1 == length2 )
198				return string.Compare( path1, path2, caseInsensitive ) == 0;
199
200			// path 2 is longer than path 1: see if initial parts match
201			if ( string.Compare( path1, path2.Substring( 0, length1 ), caseInsensitive ) != 0 )
202				return false;
203			
204			// must match through or up to a directory separator boundary
205			return	path2[length1-1] == Path.DirectorySeparatorChar ||
206				path2[length1] == Path.DirectorySeparatorChar;
207        }
208#endregion
209    }
210    #endregion
211
212    #region SamePathConstraint
213    /// <summary>
214	/// Summary description for SamePathConstraint.
215	/// </summary>
216	public class SamePathConstraint : PathConstraint
217	{
218        /// <summary>
219        /// Initializes a new instance of the <see cref="T:SamePathConstraint"/> class.
220        /// </summary>
221        /// <param name="expected">The expected path</param>
222		public SamePathConstraint( string expected ) : base( expected ) { }
223
224        /// <summary>
225        /// Test whether the constraint is satisfied by a given value
226        /// </summary>
227        /// <param name="expectedPath">The expected path</param>
228        /// <param name="actualPath">The actual path</param>
229        /// <returns>True for success, false for failure</returns>
230		protected override bool IsMatch(string expectedPath, string actualPath)
231		{
232			return IsSamePath( Canonicalize(expectedPath), Canonicalize(actualPath), caseInsensitive );
233		}
234
235        /// <summary>
236        /// Write the constraint description to a MessageWriter
237        /// </summary>
238        /// <param name="writer">The writer on which the description is displayed</param>
239		public override void WriteDescriptionTo(MessageWriter writer)
240		{
241			writer.WritePredicate( "Path matching" );
242			writer.WriteExpectedValue( expectedPath );
243		}
244    }
245    #endregion
246	
247	#region SubPathConstraint
248    /// <summary>
249    /// SubPathConstraint tests that the actual path is under the expected path
250    /// </summary>
251	public class SubPathConstraint : PathConstraint
252	{
253        /// <summary>
254        /// Initializes a new instance of the <see cref="T:SubPathConstraint"/> class.
255        /// </summary>
256        /// <param name="expected">The expected path</param>
257		public SubPathConstraint( string expected ) : base( expected ) { }
258
259        /// <summary>
260        /// Test whether the constraint is satisfied by a given value
261        /// </summary>
262        /// <param name="expectedPath">The expected path</param>
263        /// <param name="actualPath">The actual path</param>
264        /// <returns>True for success, false for failure</returns>
265		protected override bool IsMatch(string expectedPath, string actualPath)
266		{
267			if (actualPath == null)
268                throw new ArgumentException("The actual value may not be null", "actual");
269
270			return IsSubPath( Canonicalize(expectedPath), Canonicalize(actualPath), caseInsensitive );
271		}
272
273        /// <summary>
274        /// Write the constraint description to a MessageWriter
275        /// </summary>
276        /// <param name="writer">The writer on which the description is displayed</param>
277		public override void WriteDescriptionTo(MessageWriter writer)
278		{
279			writer.WritePredicate( "Path under" );
280			writer.WriteExpectedValue( expectedPath );
281		}
282    }
283	#endregion
284
285    #region SamePathOrUnderConstraint
286    /// <summary>
287    /// SamePathOrUnderConstraint tests that one path is under another
288    /// </summary>
289	public class SamePathOrUnderConstraint : PathConstraint
290	{
291        /// <summary>
292        /// Initializes a new instance of the <see cref="T:SamePathOrUnderConstraint"/> class.
293        /// </summary>
294        /// <param name="expected">The expected path</param>
295		public SamePathOrUnderConstraint( string expected ) : base( expected ) { }
296
297        /// <summary>
298        /// Test whether the constraint is satisfied by a given value
299        /// </summary>
300        /// <param name="expectedPath">The expected path</param>
301        /// <param name="actualPath">The actual path</param>
302        /// <returns>True for success, false for failure</returns>
303		protected override bool IsMatch(string expectedPath, string actualPath)
304		{
305			string path1 = Canonicalize(expectedPath);
306			string path2 = Canonicalize(actualPath);
307			return IsSamePath(path1, path2, caseInsensitive) || IsSubPath(path1, path2, caseInsensitive);
308		}
309
310        /// <summary>
311        /// Write the constraint description to a MessageWriter
312        /// </summary>
313        /// <param name="writer">The writer on which the description is displayed</param>
314		public override void WriteDescriptionTo(MessageWriter writer)
315		{
316			writer.WritePredicate( "Path under or matching" );
317			writer.WriteExpectedValue( expectedPath );
318		}
319    }
320    #endregion
321}