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