PetaTest.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Reflection;
  6. using System.Linq.Expressions;
  7. using System.IO;
  8. using System.Diagnostics;
  9. using System.Text.RegularExpressions;
  10. namespace PetaTest
  11. {
  12. // Use to mark a class as a testfixture
  13. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
  14. public class TestFixtureAttribute : TestBaseAttribute
  15. {
  16. public TestFixtureAttribute(params object[] args) : base(args) { }
  17. }
  18. // Use to mark a method as a test
  19. [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
  20. public class TestAttribute : TestBaseAttribute
  21. {
  22. public TestAttribute(params object[] args) : base(args) { }
  23. }
  24. // Base class for Test and TestFixture attributes
  25. public abstract class TestBaseAttribute : Attribute
  26. {
  27. public TestBaseAttribute(params object[] Arguments)
  28. {
  29. this.Arguments = Arguments;
  30. }
  31. public object[] Arguments { get; private set; }
  32. public string Source { get; set; }
  33. public bool Active { get; set; }
  34. public virtual IEnumerable<object[]> GetArguments(Type owningType, object testFixtureInstance)
  35. {
  36. if (Source != null)
  37. {
  38. if (testFixtureInstance != null)
  39. {
  40. var iter_method = owningType.GetMethod(Source, BindingFlags.Instance | BindingFlags.Public);
  41. return (IEnumerable<object[]>)iter_method.Invoke(testFixtureInstance, null);
  42. }
  43. else
  44. {
  45. var iter_method = owningType.GetMethod(Source, BindingFlags.Static | BindingFlags.Public);
  46. return (IEnumerable<object[]>)iter_method.Invoke(null, null);
  47. }
  48. }
  49. else
  50. {
  51. return new object[][] { Arguments };
  52. }
  53. }
  54. }
  55. [AttributeUsage(AttributeTargets.Method)]
  56. public class SetUpAttribute : SetUpTearDownAttributeBase
  57. {
  58. public SetUpAttribute() : base(true, false) { }
  59. }
  60. [AttributeUsage(AttributeTargets.Method)]
  61. public class TearDownAttribute : SetUpTearDownAttributeBase
  62. {
  63. public TearDownAttribute() : base(false, false) { }
  64. }
  65. [AttributeUsage(AttributeTargets.Method)]
  66. public class TestFixtureSetUpAttribute : SetUpTearDownAttributeBase
  67. {
  68. public TestFixtureSetUpAttribute() : base(true, true) { }
  69. }
  70. [AttributeUsage(AttributeTargets.Method)]
  71. public class TestFixtureTearDownAttribute : SetUpTearDownAttributeBase
  72. {
  73. public TestFixtureTearDownAttribute() : base(false, true) { }
  74. }
  75. // Introducing "The Asserts" - all pretty self explanatory
  76. [SkipInStackTrace]
  77. public static partial class Assert
  78. {
  79. public static void Throw(bool Condition, Func<string> message)
  80. {
  81. if (!Condition)
  82. throw new AssertionException(message());
  83. }
  84. public static void IsTrue(bool test)
  85. {
  86. Throw(test, () => "Expression is not true");
  87. }
  88. public static void IsFalse(bool test)
  89. {
  90. Throw(!test, () => "Expression is not false");
  91. }
  92. public static void AreSame(object a, object b)
  93. {
  94. Throw(object.ReferenceEquals(a, b), () => "Object references are not the same");
  95. }
  96. public static void AreNotSame(object a, object b)
  97. {
  98. Throw(!object.ReferenceEquals(a, b), () => "Object references are the same");
  99. }
  100. private static bool TestEqual(object a, object b)
  101. {
  102. if (a == null && b == null)
  103. return true;
  104. if (a == null || b == null)
  105. return false;
  106. return Object.Equals(a, b);
  107. }
  108. private static void AreEqual(object a, object b, Func<bool> Compare)
  109. {
  110. Throw(Compare(), () => string.Format("Objects are not equal\n lhs: {0}\n rhs: {1}", Utils.FormatValue(a), Utils.FormatValue(b)));
  111. }
  112. private static void AreNotEqual(object a, object b, Func<bool> Compare)
  113. {
  114. Throw(!Compare(), () => string.Format("Objects are not equal\n lhs: {0}\n rhs: {1}", Utils.FormatValue(a), Utils.FormatValue(b)));
  115. }
  116. public static void AreEqual(object a, object b)
  117. {
  118. AreEqual(a, b, () => TestEqual(a, b));
  119. }
  120. public static void AreNotEqual(object a, object b)
  121. {
  122. AreNotEqual(a, b, () => TestEqual(a, b));
  123. }
  124. public static void AreEqual(double a, double b, double within)
  125. {
  126. AreEqual(a, b, () => Math.Abs(a - b) < within);
  127. }
  128. public static void AreNotEqual(double a, double b, double within)
  129. {
  130. AreNotEqual(a, b, () => Math.Abs(a - b) < within);
  131. }
  132. public static void AreEqual<T>(T a, T b)
  133. {
  134. AreEqual(a, b, () => Object.Equals(a, b));
  135. }
  136. public static void AreNotEqual<T>(T a, T b)
  137. {
  138. AreNotEqual(a, b, () => Object.Equals(a, b));
  139. }
  140. public static void AreEqual(string a, string b, bool ignoreCase = false)
  141. {
  142. Throw(string.Compare(a, b, ignoreCase) == 0, () =>
  143. {
  144. var offset = Utils.CountCommonPrefix(a, b, ignoreCase);
  145. var xa = Utils.FormatValue(Utils.GetStringExtract(a, offset));
  146. var xb = Utils.FormatValue(Utils.GetStringExtract(b, offset));
  147. return string.Format("Strings are not equal at offset {0}\n lhs: {1}\n rhs: {2}\n{3}^", offset, xa, xb, new string(' ', Utils.CountCommonPrefix(xa, xb, ignoreCase) + 7));
  148. });
  149. }
  150. public static void AreEqual(string a, string b)
  151. {
  152. AreEqual(a, b, false);
  153. }
  154. public static void AreNotEqual(string a, string b, bool ignoreCase = false)
  155. {
  156. Throw(string.Compare(a, b, ignoreCase) != 0, () => string.Format("Strings are not equal\n lhs: {0}\n rhs: {1}", Utils.FormatValue(a), Utils.FormatValue(b)));
  157. }
  158. public static void IsEmpty(string val)
  159. {
  160. Throw(val != null && val.Length == 0, () => string.Format("String is not empty: {0}", Utils.FormatValue(val)));
  161. }
  162. public static void IsNotEmpty(string val)
  163. {
  164. Throw(val != null && val.Length != 0, () => "String is empty");
  165. }
  166. public static void IsNullOrEmpty(string val)
  167. {
  168. Throw(string.IsNullOrEmpty(val), () => string.Format("String is not empty: {0}", Utils.FormatValue(val)));
  169. }
  170. public static void IsNotNullOrEmpty(string val)
  171. {
  172. Throw(!string.IsNullOrEmpty(val), () => string.Format("String is not empty: {0}", Utils.FormatValue(val)));
  173. }
  174. public static void IsEmpty(System.Collections.IEnumerable collection)
  175. {
  176. Throw(collection != null && collection.Cast<object>().Count() == 0, () => string.Format("Collection is not empty\n Items: {0}", Utils.FormatValue(collection)));
  177. }
  178. public static void IsNotEmpty(System.Collections.IEnumerable collection)
  179. {
  180. Throw(collection != null && collection.Cast<object>().Count() != 0, () => "Collection is empty");
  181. }
  182. public static void Contains(System.Collections.IEnumerable collection, object item)
  183. {
  184. Throw(collection.Cast<object>().Contains(item), () => string.Format("Collection doesn't contain {0}\n Items: {1}", Utils.FormatValue(item), Utils.FormatValue(collection)));
  185. }
  186. public static void DoesNotContain(System.Collections.IEnumerable collection, object item)
  187. {
  188. Throw(!collection.Cast<object>().Contains(item), () => string.Format("Collection does contain {0}", Utils.FormatValue(item)));
  189. }
  190. public static void Contains(string str, string contains, bool ignoreCase)
  191. {
  192. Throw(str.IndexOf(contains, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) >= 0,
  193. () => string.Format("String doesn't contain substring\n expected: {0}\n found: {1}", Utils.FormatValue(contains), Utils.FormatValue(str)));
  194. }
  195. public static void Contains(string str, string contains)
  196. {
  197. Contains(str, contains, false);
  198. }
  199. public static void DoesNotContain(string str, string contains, bool ignoreCase = false)
  200. {
  201. Throw(str.IndexOf(contains, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) < 0,
  202. () => string.Format("String does contain substring\n didn't expect: {0}\n found: {1}", Utils.FormatValue(contains), Utils.FormatValue(str)));
  203. }
  204. public static void Matches(string str, string regex, RegexOptions options = RegexOptions.None)
  205. {
  206. Throw(new Regex(regex, options).IsMatch(str), () => string.Format("String doesn't match expression\n regex: \"{0}\"\n found: {1}", regex, Utils.FormatValue(str)));
  207. }
  208. public static void DoesNotMatch(string str, string regex, RegexOptions options = RegexOptions.None)
  209. {
  210. Throw(!(new Regex(regex, options).IsMatch(str)), () => string.Format("String matches expression\n regex: \"{0}\"\n found: {1}", regex, Utils.FormatValue(str)));
  211. }
  212. public static void IsNull(object val)
  213. {
  214. Throw(val == null, () => string.Format("Object reference is not null - {0}", Utils.FormatValue(val)));
  215. }
  216. public static void IsNotNull(object val)
  217. {
  218. Throw(val != null, () => "Object reference is null");
  219. }
  220. public static void Compare(object a, object b, Func<int, bool> Check, string comparison)
  221. {
  222. Throw(Check((a as IComparable).CompareTo(b)), () => string.Format("Comparison failed: {0} {1} {2}", Utils.FormatValue(a), comparison, Utils.FormatValue(b)));
  223. }
  224. public static void Greater<T>(T a, T b)
  225. {
  226. Compare(a, b, r => r > 0, ">");
  227. }
  228. public static void GreaterOrEqual<T>(T a, T b)
  229. {
  230. Compare(a, b, r => r >= 0, ">");
  231. }
  232. public static void Less<T>(T a, T b)
  233. {
  234. Compare(a, b, r => r < 0, ">");
  235. }
  236. public static void LessOrEqual<T>(T a, T b)
  237. {
  238. Compare(a, b, r => r <= 0, ">");
  239. }
  240. public static void IsInstanceOf(Type t, object o)
  241. {
  242. IsNotNull(o); Throw(o.GetType() == t, () => string.Format("Object type mismatch, expected {0} found {1}", t.FullName, o.GetType().FullName));
  243. }
  244. public static void IsNotInstanceOf(Type t, object o)
  245. {
  246. IsNotNull(o); Throw(o.GetType() != t, () => string.Format("Object type mismatch, should not be {0}", t.FullName));
  247. }
  248. public static void IsInstanceOf<T>(object o)
  249. {
  250. IsInstanceOf(typeof(T), o);
  251. }
  252. public static void IsNotInstanceOf<T>(object o)
  253. {
  254. IsNotInstanceOf(typeof(T), o);
  255. }
  256. public static void IsAssignableFrom(Type t, object o)
  257. {
  258. IsNotNull(o); Throw(o.GetType().IsAssignableFrom(t), () => string.Format("Object type mismatch, expected a type assignable from {0} found {1}", t.FullName, o.GetType().FullName));
  259. }
  260. public static void IsNotAssignableFrom(Type t, object o)
  261. {
  262. IsNotNull(o); Throw(!o.GetType().IsAssignableFrom(t), () => string.Format("Object type mismatch, didn't expect a type assignable from {0} found {1}", t.FullName, o.GetType().FullName));
  263. }
  264. public static void IsAssignableFrom<T>(object o)
  265. {
  266. IsAssignableFrom(typeof(T), o);
  267. }
  268. public static void IsNotAssignableFrom<T>(object o)
  269. {
  270. IsNotAssignableFrom(typeof(T), o);
  271. }
  272. public static void IsAssignableTo(Type t, object o)
  273. {
  274. IsNotNull(o); Throw(t.IsAssignableFrom(o.GetType()), () => string.Format("Object type mismatch, expected a type assignable to {0} found {1}", t.FullName, o.GetType().FullName));
  275. }
  276. public static void IsNotAssignableTo(Type t, object o)
  277. {
  278. IsNotNull(o); Throw(!t.IsAssignableFrom(o.GetType()), () => string.Format("Object type mismatch, didn't expect a type assignable to {0} found {1}", t.FullName, o.GetType().FullName));
  279. }
  280. public static void IsAssignableTo<T>(object o)
  281. {
  282. IsAssignableTo(typeof(T), o);
  283. }
  284. public static void IsNotAssignableTo<T>(object o)
  285. {
  286. IsNotAssignableTo(typeof(T), o);
  287. }
  288. public static Exception Throws(Type t, Action code)
  289. {
  290. try
  291. {
  292. code();
  293. }
  294. catch (Exception x)
  295. {
  296. Throw(t.IsAssignableFrom(x.GetType()), () => string.Format("Wrong exception type caught, expected {0} received {1}", t.FullName, Utils.FormatValue(x)));
  297. return x;
  298. }
  299. throw new AssertionException(string.Format("Failed to throw exception of type {0}", t.FullName));
  300. }
  301. public static TX Throws<TX>(Action code) where TX : Exception
  302. {
  303. return (TX)Throws(typeof(TX), code);
  304. }
  305. public static void DoesNotThrow(Action code)
  306. {
  307. try
  308. {
  309. code();
  310. }
  311. catch (Exception x)
  312. {
  313. Throw(false, () => string.Format("Unexpected exception {0}", Utils.FormatValue(x)));
  314. }
  315. }
  316. public static void ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> code)
  317. {
  318. int index = 0;
  319. foreach (var i in source)
  320. {
  321. try
  322. {
  323. code(i);
  324. }
  325. catch (Exception x)
  326. {
  327. throw new AssertionException(string.Format("Collection assertion failed at item {0}\n Collection: {1}\n Inner Exception: {2}", index, Utils.FormatValue(source), x.Message));
  328. }
  329. index++;
  330. }
  331. }
  332. public static void AllItemsAreNotNull<T>(IEnumerable<T> coll)
  333. {
  334. int index = 0;
  335. foreach (var i in coll)
  336. {
  337. if (i == null)
  338. {
  339. throw new AssertionException(string.Format("Collection has a null item at index {0}", index));
  340. }
  341. index++;
  342. }
  343. }
  344. public static void AllItemsAreUnique<T>(IEnumerable<T> coll)
  345. {
  346. var list = coll.ToList();
  347. for (int i = 0; i < list.Count; i++)
  348. {
  349. for (int j = i + 1; j < list.Count; j++)
  350. {
  351. if (object.Equals(list[i], list[j]))
  352. throw new AssertionException(string.Format("Collection items are not unique\n [{0}] = {1}\n [{2}] = {3}\n", i, list[i], j, list[j]));
  353. }
  354. }
  355. }
  356. public static void AllItemsAreEqual<Ta, Tb>(IEnumerable<Ta> a, IEnumerable<Tb> b, Func<Ta, Tb, bool> CompareEqual)
  357. {
  358. var e1 = a.GetEnumerator();
  359. var e2 = b.GetEnumerator();
  360. int index = 0;
  361. while (true)
  362. {
  363. bool have1 = e1.MoveNext();
  364. bool have2 = e2.MoveNext();
  365. if (!have1 && !have2)
  366. return;
  367. if (!have1 || !have2 || !CompareEqual(e1.Current, e2.Current))
  368. throw new AssertionException(string.Format("Collection are not equal at index {0}\n a[{0}] = {1}\n b[{0}] = {2}\n", index, e1.Current, e2.Current));
  369. index++;
  370. }
  371. }
  372. public static void AllItemsAreEqual<T>(IEnumerable<T> a, IEnumerable<T> b)
  373. {
  374. AllItemsAreEqual<T, T>(a, b, (x, y) => object.Equals(x, y));
  375. }
  376. public static void AllItemsAreInstancesOf(Type t, System.Collections.IEnumerable coll)
  377. {
  378. int index = 0;
  379. foreach (object o in coll)
  380. {
  381. if (o == null || o.GetType() != t)
  382. {
  383. throw new AssertionException(string.Format("Collection item at index {0} is of the wrong type, expected {1} but found {2}", index, t.FullName, o == null ? "null" : o.GetType().FullName));
  384. }
  385. index++;
  386. }
  387. }
  388. public static void AllItemsAreInstancesOf<T>(System.Collections.IEnumerable coll)
  389. {
  390. AllItemsAreInstancesOf(typeof(T), coll);
  391. }
  392. private static int IndexOf<Ta, Tb>(Ta Item, List<Tb> list, Func<Ta, Tb, bool> CompareEqual)
  393. {
  394. for (int j = 0; j < list.Count; j++)
  395. {
  396. if (CompareEqual(Item, list[j]))
  397. return j;
  398. }
  399. return -1;
  400. }
  401. public static void IsSubsetOf<Ta, Tb>(IEnumerable<Ta> subset, IEnumerable<Tb> superset, Func<Ta, Tb, bool> CompareEqual)
  402. {
  403. var list = superset.ToList();
  404. int index = 0;
  405. foreach (var i in subset)
  406. {
  407. int pos = IndexOf<Ta, Tb>(i, list, CompareEqual);
  408. if (pos < 0)
  409. throw new AssertionException(string.Format("Collection is not a subset (check subset index {0}\n subset = {1}\n superset = {2}", index, Utils.FormatValue(subset), Utils.FormatValue(superset)));
  410. list.RemoveAt(pos);
  411. index++;
  412. }
  413. }
  414. public static void IsSubsetOf<T>(IEnumerable<T> subset, IEnumerable<T> superset)
  415. {
  416. IsSubsetOf<T, T>(subset, superset, (x, y) => Object.Equals(x, y));
  417. }
  418. public static void IsNotSubsetOf<Ta, Tb>(IEnumerable<Ta> subset, IEnumerable<Tb> superset, Func<Ta, Tb, bool> CompareEqual)
  419. {
  420. var list = superset.ToList();
  421. foreach (var i in subset)
  422. {
  423. int pos = IndexOf<Ta, Tb>(i, list, CompareEqual);
  424. if (pos < 0)
  425. return;
  426. list.RemoveAt(pos);
  427. }
  428. throw new AssertionException(string.Format("Collection is a subset\n subset = {0}\n superset = {1}", Utils.FormatValue(subset), Utils.FormatValue(superset)));
  429. }
  430. public static void IsNotSubsetOf<T>(IEnumerable<T> subset, IEnumerable<T> superset)
  431. {
  432. IsNotSubsetOf<T, T>(subset, superset, (x, y) => Object.Equals(x, y));
  433. }
  434. static bool TestEquivalent<Ta, Tb>(IEnumerable<Ta> a, IEnumerable<Tb> b, Func<Ta, Tb, bool> CompareEqual)
  435. {
  436. var list = b.ToList();
  437. foreach (var i in a)
  438. {
  439. int pos = IndexOf(i, list, CompareEqual);
  440. if (pos < 0)
  441. return false;
  442. list.RemoveAt(pos);
  443. }
  444. return list.Count == 0;
  445. }
  446. public static void AreEquivalent<Ta, Tb>(IEnumerable<Ta> a, IEnumerable<Tb> b, Func<Ta, Tb, bool> CompareEqual)
  447. {
  448. Throw(TestEquivalent(a, b, CompareEqual), () => string.Format("Collections are not equivalent\n lhs: {0}\n rhs: {1}", Utils.FormatValue(a), Utils.FormatValue(b)));
  449. }
  450. public static void AreNotEquivalent<Ta, Tb>(IEnumerable<Ta> a, IEnumerable<Tb> b, Func<Ta, Tb, bool> CompareEqual)
  451. {
  452. Throw(!TestEquivalent(a, b, CompareEqual), () => string.Format("Collections are not equivalent\n lhs: {0}\n rhs: {1}", Utils.FormatValue(a), Utils.FormatValue(b)));
  453. }
  454. public static void AreEquivalent<T>(IEnumerable<T> a, IEnumerable<T> b)
  455. {
  456. AreEquivalent<T, T>(a, b, (x, y) => Object.Equals(x, y));
  457. }
  458. public static void AreNotEquivalent<T>(IEnumerable<T> a, IEnumerable<T> b)
  459. {
  460. AreNotEquivalent<T, T>(a, b, (x, y) => Object.Equals(x, y));
  461. }
  462. }
  463. // Runner - runs a set of tests
  464. public class Runner
  465. {
  466. public Runner()
  467. {
  468. }
  469. public ResultsWriter Output
  470. {
  471. get;
  472. set;
  473. }
  474. public static bool? BoolFromString(string str)
  475. {
  476. if (string.IsNullOrWhiteSpace(str)) return null;
  477. str = str.ToLowerInvariant();
  478. return (str == "yes" || str == "y" || str == "true" || str == "t" || str == "1");
  479. }
  480. public static int RunMain(string[] args)
  481. {
  482. return new Runner().Run(args);
  483. }
  484. // Run all test fixtures in the calling assembly - unless one or more
  485. // marked as active in which case only those will be run
  486. public int Run(string[] args)
  487. {
  488. // Parse command line args
  489. bool? showreport = null;
  490. bool? htmlreport = null;
  491. bool? dirtyexit = null;
  492. bool? runall = null;
  493. bool? verbose = null;
  494. string output_file = null;
  495. foreach (var a in args)
  496. {
  497. if (a.StartsWith("-") || a.StartsWith("/"))
  498. {
  499. int colon_pos = a.IndexOf(":");
  500. string sw = a.Substring(1, colon_pos < 0 ? a.Length - 1 : colon_pos - 1);
  501. string val = colon_pos < 0 ? null : a.Substring(colon_pos + 1);
  502. switch (sw)
  503. {
  504. case "showreport": showreport = BoolFromString(val) ?? true; break;
  505. case "htmlreport": htmlreport = BoolFromString(val) ?? true; break;
  506. case "dirtyexit": dirtyexit = BoolFromString(val) ?? true; break;
  507. case "runall": runall = BoolFromString(val) ?? true; break;
  508. case "verbose": verbose = BoolFromString(val) ?? false; break;
  509. case "out": output_file = val; break;
  510. default:
  511. Console.WriteLine("Warning: Unknown switch '{0}' ignored", a);
  512. break;
  513. }
  514. }
  515. }
  516. if (htmlreport ?? true)
  517. Output = new HtmlResultsWriter(showreport, output_file);
  518. else
  519. Output = new PlainTextResultsWriter(output_file == null ? null : new StreamWriter(output_file));
  520. Output.Verbose = verbose ?? false;
  521. var totalTime = Stopwatch.StartNew();
  522. _statsStack.Clear();
  523. _statsStack.Push(new Stats());
  524. var old = Console.Out;
  525. Console.SetOut(Output);
  526. RunInternal(Assembly.GetCallingAssembly(), null, null, runall ?? false);
  527. Console.SetOut(old);
  528. totalTime.Stop();
  529. Output.Complete(_statsStack.Pop(), _otherTimes.ElapsedMilliseconds, totalTime.ElapsedMilliseconds);
  530. if (dirtyexit ?? true)
  531. System.Diagnostics.Process.GetCurrentProcess().Kill();
  532. return 0;
  533. }
  534. // Helper to create instances of test fixtures
  535. private object CreateInstance(Type t, object[] args)
  536. {
  537. try
  538. {
  539. _otherTimes.Start();
  540. return Activator.CreateInstance(t, args);
  541. }
  542. catch (Exception x)
  543. {
  544. Output.WriteException(x);
  545. Stats.Errors++;
  546. return null;
  547. }
  548. finally
  549. {
  550. _otherTimes.Stop();
  551. }
  552. }
  553. // Internally called to recursively run tests in an assembly, testfixture, test method etc...
  554. private void RunInternal(object scope, object instance, object[] arguments, bool RunAll)
  555. {
  556. // Assembly?
  557. var a = scope as Assembly;
  558. if (a != null)
  559. {
  560. StartTest(a, null);
  561. RunAll = RunAll || !a.HasActive();
  562. foreach (var type in a.GetTypes().Where(i => i.IsTestFixture() && (RunAll || i.HasActive())))
  563. RunInternal(type, null, null, RunAll);
  564. EndTest();
  565. }
  566. // Test Fixture class
  567. var t = scope as Type;
  568. if (t != null)
  569. {
  570. if (arguments == null)
  571. {
  572. bool runAllTestFixturesInstances = RunAll || !t.IsActive();
  573. bool runAllTestMethods = RunAll || !t.HasActiveMethods();
  574. foreach (TestFixtureAttribute tfa in t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Where(x => runAllTestFixturesInstances || ((TestFixtureAttribute)x).Active))
  575. foreach (var args in tfa.GetArguments(t, null))
  576. RunInternal(t, null, args, runAllTestMethods);
  577. }
  578. else
  579. {
  580. StartTest(t, arguments);
  581. var inst = CreateInstance(t, arguments);
  582. if (inst != null)
  583. RunInternal(null, inst, null, RunAll);
  584. EndTest();
  585. }
  586. }
  587. // Test Fixture instance
  588. if (instance != null && instance.GetType().IsTestFixture())
  589. {
  590. var tf = instance;
  591. if (scope == null)
  592. {
  593. if (!RunSetupTeardown(instance, true, true))
  594. return;
  595. foreach (var m in tf.GetType().GetMethods().Where(x => RunAll || x.IsActive()))
  596. RunInternal(m, instance, null, RunAll);
  597. RunSetupTeardown(instance, false, true);
  598. }
  599. var method = scope as MethodInfo;
  600. if (method != null)
  601. {
  602. if (arguments == null)
  603. {
  604. foreach (TestAttribute i in method.GetCustomAttributes(typeof(TestAttribute), false).Where(x => RunAll || ((TestAttribute)x).Active))
  605. foreach (var args in i.GetArguments(method.DeclaringType, instance))
  606. {
  607. if (args.Length != method.GetParameters().Length)
  608. {
  609. Output.WriteWarning("{0} provided in an incorrect number of arguments (expected {1} but found {2}) - skipped", i.GetType().FullName, method.GetParameters().Length, args.Length);
  610. Stats.Warnings++;
  611. continue;
  612. }
  613. RunInternal(method, instance, args, RunAll);
  614. }
  615. }
  616. else
  617. {
  618. RunTest(method, tf, arguments);
  619. }
  620. }
  621. }
  622. }
  623. // Run a single test
  624. public void RunTest(MethodInfo Target, object instance, object[] Params)
  625. {
  626. StartTest(Target, Params);
  627. var sw = new Stopwatch();
  628. try
  629. {
  630. if (!RunSetupTeardown(instance, true, false))
  631. {
  632. EndTest();
  633. return;
  634. }
  635. sw.Start();
  636. Target.Invoke(instance, Params);
  637. Stats.Elapsed = sw.ElapsedMilliseconds;
  638. Stats.Passed++;
  639. }
  640. catch (Exception x)
  641. {
  642. Stats.Elapsed = sw.ElapsedMilliseconds;
  643. var invoc = x as TargetInvocationException;
  644. if (invoc != null)
  645. x = invoc.InnerException;
  646. Output.WriteException(x);
  647. Stats.Errors++;
  648. }
  649. RunSetupTeardown(instance, false, false);
  650. EndTest();
  651. }
  652. Stopwatch _otherTimes = new Stopwatch();
  653. [SkipInStackTrace]
  654. public bool RunSetupTeardown(object instance, bool setup, bool fixture)
  655. {
  656. try
  657. {
  658. foreach (var m in instance.GetType().GetMethods().Where(x => x.GetCustomAttributes(typeof(SetUpTearDownAttributeBase), false)
  659. .Cast<SetUpTearDownAttributeBase>().Any((SetUpTearDownAttributeBase y) => y.ForSetup == setup && y.ForFixture == fixture)))
  660. {
  661. _otherTimes.Start();
  662. try
  663. {
  664. m.Invoke(instance, null);
  665. }
  666. finally
  667. {
  668. _otherTimes.Stop();
  669. }
  670. }
  671. return true;
  672. }
  673. catch (Exception x)
  674. {
  675. var invoc = x as TargetInvocationException;
  676. if (invoc != null)
  677. x = invoc.InnerException;
  678. Output.WriteException(x);
  679. Stats.Errors++;
  680. return false;
  681. }
  682. }
  683. private void StartTest(object Target, object[] Params)
  684. {
  685. var stats = new Stats() { Target = Target };
  686. _statsStack.Push(stats);
  687. Output.StartTest(Target, Params);
  688. }
  689. private void EndTest()
  690. {
  691. var old = Stats;
  692. _statsStack.Pop();
  693. Stats.Add(old);
  694. Output.EndTest(old);
  695. }
  696. private Stack<Stats> _statsStack = new Stack<Stats>();
  697. public Stats Stats { get { return _statsStack.Peek(); } }
  698. }
  699. public class Stats
  700. {
  701. public object Target { get; set; }
  702. public int Errors { get; set; }
  703. public int Warnings { get; set; }
  704. public int Passed { get; set; }
  705. public long Elapsed { get; set; }
  706. public void Add(Stats other)
  707. {
  708. Errors += other.Errors;
  709. Warnings += other.Warnings;
  710. Passed += other.Passed;
  711. Elapsed += other.Elapsed;
  712. }
  713. }
  714. // The exception thrown when an assertion fails
  715. public class AssertionException : Exception
  716. {
  717. public AssertionException(string message) : base(message) { }
  718. }
  719. // Used to mark utility functions that throw assertion exceptions so the stack trace can be unwound to the actual place the assertion originates
  720. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
  721. public class SkipInStackTraceAttribute : Attribute
  722. {
  723. }
  724. // Base class for setup/teardown attributes
  725. public class SetUpTearDownAttributeBase : Attribute
  726. {
  727. public SetUpTearDownAttributeBase(bool forSetup, bool forFixture)
  728. {
  729. this.ForSetup = forSetup;
  730. this.ForFixture = forFixture;
  731. }
  732. public bool ForSetup { get; set; }
  733. public bool ForFixture { get; set; }
  734. }
  735. // A bunch of utility functions and extension methods
  736. public static class Utils
  737. {
  738. public static IEnumerable<StackFrame> SimplifyStackTrace(StackTrace st)
  739. {
  740. foreach (var f in st.GetFrames())
  741. {
  742. if (f.GetMethod().GetCustomAttributes(typeof(SkipInStackTraceAttribute), false).Length != 0 ||
  743. (f.GetMethod().DeclaringType != null && f.GetMethod().DeclaringType.GetCustomAttributes(typeof(SkipInStackTraceAttribute), false).Length != 0) ||
  744. f.GetFileName() == null)
  745. continue;
  746. if (f.GetMethod().IsSpecialName | f.GetMethod().Name.StartsWith("<"))
  747. break;
  748. yield return f;
  749. }
  750. }
  751. public static IEnumerable<Tuple<int, string>> ExtractLinesFromTextFile(string file, int line, int extra = 2)
  752. {
  753. try
  754. {
  755. if (line <= extra)
  756. line = extra + 1;
  757. return System.IO.File.ReadAllLines(file).Skip(line - extra - 1).Take(extra * 2 + 1).Select((l, i) => new Tuple<int, string>(i + line - extra, l));
  758. }
  759. catch (Exception)
  760. {
  761. return new Tuple<int, string>[] { };
  762. }
  763. }
  764. // Format any value for diagnostic display
  765. public static string FormatValue(object value)
  766. {
  767. if (value == null)
  768. return "null";
  769. var str = value as string;
  770. if (str != null)
  771. {
  772. str = str.Replace("\"", "\\\"").Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t").Replace("\0", "\\0");
  773. return string.Format("\"{0}\"", str);
  774. }
  775. if (value.GetType() == typeof(int) || value.GetType() == typeof(long) || value.GetType() == typeof(bool))
  776. return value.ToString();
  777. var d = value as System.Collections.IDictionary;
  778. if (d != null)
  779. return string.Format("{{{0}}}", string.Join(", ", d.AsDictionaryEntries().Select(de => string.Format("{{ {0}, {1} }}", FormatValue(de.Key), FormatValue(de.Value)))));
  780. var e = value as System.Collections.IEnumerable;
  781. if (e != null)
  782. return string.Format("[{0}]", string.Join(", ", e.Cast<object>().Select(v => FormatValue(v))));
  783. var x = value as Exception;
  784. if (x != null)
  785. return string.Format("[{0}] {1}", value.GetType().FullName, x.Message);
  786. return string.Format("[{0}] {1}", value.GetType().FullName, value.ToString());
  787. }
  788. public static IEnumerable<System.Collections.DictionaryEntry> AsDictionaryEntries(this System.Collections.IDictionary dictionary)
  789. {
  790. foreach (var de in dictionary)
  791. yield return (System.Collections.DictionaryEntry)de;
  792. }
  793. public static string FormatArguments(object[] args)
  794. {
  795. return string.Format("({0})", args == null ? "" : string.Join(", ", args.Select(v => FormatValue(v))));
  796. }
  797. // Format the name of a test target
  798. public static string FormatTarget(object o)
  799. {
  800. var mb = o as MethodBase;
  801. if (mb != null)
  802. return "test " + mb.Name;
  803. var t = o as Type;
  804. if (t != null && t.IsClass)
  805. return "testfixture " + t.Name;
  806. var a = o as Assembly;
  807. if (a != null)
  808. return "assembly " + a.FullName;
  809. return null;
  810. }
  811. public static int CountCommonPrefix(string a, string b, bool IgnoreCase)
  812. {
  813. int i = 0;
  814. while (i < Math.Min(a.Length, b.Length) && (IgnoreCase ? (char.ToUpperInvariant(a[i]) == char.ToUpperInvariant(b[i])) : (a[i] == b[i])))
  815. i++;
  816. return i;
  817. }
  818. public static string GetStringExtract(string str, int offset)
  819. {
  820. if (offset > 15)
  821. str = "..." + str.Substring(offset - 10);
  822. if (str.Length > 30)
  823. str = str.Substring(0, 20) + "...";
  824. return str;
  825. }
  826. public static bool IsTestFixture(this Type t)
  827. {
  828. return t.IsClass && !t.IsAbstract && t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Any();
  829. }
  830. public static bool IsTestMethod(this MethodInfo mi)
  831. {
  832. return mi.GetCustomAttributes(typeof(TestAttribute), false).Any();
  833. }
  834. public static bool IsActive(this ICustomAttributeProvider p)
  835. {
  836. return p.GetCustomAttributes(typeof(TestBaseAttribute), false).Any(a => ((TestBaseAttribute)a).Active);
  837. }
  838. public static bool HasActiveMethods(this Type t)
  839. {
  840. return t.GetMethods().Any(m => m.IsActive());
  841. }
  842. public static bool HasActive(this Type t)
  843. {
  844. return t.IsActive() || t.HasActiveMethods();
  845. }
  846. public static bool HasActive(this Assembly a)
  847. {
  848. return a.GetTypes().Any(t => t.HasActive());
  849. }
  850. }
  851. // Base class for result writers
  852. public abstract class ResultsWriter : TextWriter
  853. {
  854. public bool Verbose;
  855. public abstract void StartTest(object Target, object[] Arguments);
  856. public abstract void EndTest(Stats stats);
  857. public virtual void Complete(Stats stats, long OtherTimes, long ActualTime)
  858. {
  859. }
  860. public virtual void WriteWarning(string str, params object[] args)
  861. {
  862. WriteLine(str, args);
  863. }
  864. public virtual void WriteError(string str, params object[] args)
  865. {
  866. WriteLine(str, args);
  867. }
  868. public abstract void WriteException(Exception x);
  869. }
  870. // Plain text results writer (aka console output)
  871. public class PlainTextResultsWriter : ResultsWriter
  872. {
  873. TextWriter target;
  874. public PlainTextResultsWriter(TextWriter target = null)
  875. {
  876. this.target = target == null ? Console.Out : target;
  877. }
  878. public override void StartTest(object Target, object[] Arguments)
  879. {
  880. WriteIndented(string.Format("{0}{1}\n", Utils.FormatTarget(Target), Utils.FormatArguments(Arguments)));
  881. _indentDepth += (Target as MethodBase) != null ? 2 : 1;
  882. }
  883. public override void EndTest(Stats stats)
  884. {
  885. _indentDepth -= (stats.Target as MethodBase) != null ? 2 : 1;
  886. }
  887. public override void Complete(Stats stats, long OtherTimes, long ActualTime)
  888. {
  889. bool Success = stats.Errors == 0 && stats.Warnings == 0;
  890. var delim = new string(Success ? '-' : '*', 40);
  891. target.WriteLine("\nTest cases: {0,10:#,##0}ms\nSetup/teardown: {1,10:#,##0}ms\nTest framework: {2,10:#,##0}ms",
  892. stats.Elapsed, OtherTimes, ActualTime - (stats.Elapsed + OtherTimes));
  893. if (Success)
  894. target.WriteLine("\n{0}\nAll {1} tests passed\n{0}\n", delim, stats.Passed, stats.Elapsed);
  895. else
  896. target.WriteLine("\n{0}\n{1} Errors, {2} Warnings, {3} passed\n{0}\n", delim, stats.Errors, stats.Warnings, stats.Passed);
  897. target.Flush();
  898. }
  899. public override void Write(char value)
  900. {
  901. Write(value.ToString());
  902. }
  903. public override void Write(char[] buffer, int index, int count)
  904. {
  905. Write(new String(buffer, index, count));
  906. }
  907. public override void Write(string str)
  908. {
  909. if (Verbose)
  910. WriteIndented(str);
  911. }
  912. public void WriteIndented(string str)
  913. {
  914. string indent = new string(' ', _indentDepth * 2);
  915. if (_indentPending)
  916. target.Write(indent);
  917. _indentPending = str.EndsWith("\n");
  918. if (_indentPending)
  919. str = str.Substring(0, str.Length - 1);
  920. str = str.Replace("\n", "\n" + indent);
  921. target.Write(str);
  922. if (_indentPending)
  923. target.Write("\n");
  924. }
  925. public override Encoding Encoding
  926. {
  927. get { return Encoding.UTF8; }
  928. }
  929. public override void WriteException(Exception x)
  930. {
  931. var assert = x as AssertionException;
  932. if (assert != null)
  933. WriteIndented(string.Format("\nAssertion failed - {0}\n\n", assert.Message));
  934. else
  935. WriteIndented(string.Format("\nException {0}: {1}\n\n", x.GetType().FullName, x.Message));
  936. StackFrame first = null;
  937. foreach (var f in Utils.SimplifyStackTrace(new StackTrace(x, true)))
  938. {
  939. if (first == null) first = f;
  940. WriteIndented(string.Format(" {0} - {1}({2})\n", f.GetMethod().Name, f.GetFileName(), f.GetFileLineNumber()));
  941. }
  942. if (first != null)
  943. {
  944. WriteIndented("\n");
  945. foreach (var l in Utils.ExtractLinesFromTextFile(first.GetFileName(), first.GetFileLineNumber()))
  946. WriteIndented(string.Format(" {0:00000}:{1}{2}\n", l.Item1, l.Item1 == first.GetFileLineNumber() ? "->" : " ", l.Item2));
  947. }
  948. WriteIndented("\n");
  949. }
  950. bool _indentPending = true;
  951. int _indentDepth = 0;
  952. }
  953. // HTML Results Writer
  954. public class HtmlResultsWriter : PlainTextResultsWriter
  955. {
  956. public HtmlResultsWriter(bool? showreport, string output_file)
  957. {
  958. _showreport = showreport;
  959. _output_file = output_file == null ? "report.html" : output_file;
  960. _tagHtml.Append(new Tag("head").Append(StylesAndScript));
  961. var body = new Tag("body");
  962. _tagHtml.Append(body);
  963. _current = body;
  964. _dateOfTest = DateTime.Now;
  965. }
  966. bool? _showreport;
  967. string _output_file;
  968. Tag _tagHtml = new Tag("html");
  969. Tag _current;
  970. Tag _pre;
  971. DateTime _dateOfTest;
  972. StringWriter _bufferedOutput;
  973. public override void StartTest(object Target, object[] Arguments)
  974. {
  975. // Work out title
  976. string title = "", css_class = "";
  977. var a = Target as Assembly;
  978. var t = Target as Type;
  979. var m = Target as MethodInfo;
  980. if (a != null)
  981. {
  982. title = "Test Results for " + a.ManifestModule.Name;
  983. css_class = "assembly";
  984. }
  985. else if (t != null)
  986. {
  987. title = string.Format("Test Fixture {0}{1}", t.FullName, Utils.FormatArguments(Arguments));
  988. css_class = "testfixture";
  989. }
  990. else if (m != null)
  991. {
  992. title = string.Format("{0}{1}", m.Name, Utils.FormatArguments(Arguments));
  993. css_class = "test";
  994. }
  995. // Create the test div
  996. var divTest = new Tag("div");
  997. divTest.SetAttribute("class", css_class);
  998. divTest.Append(new Tag("div").SetAttribute("class", "title")
  999. .Append(new Tag("span").AddClass("indicator"))
  1000. .Append(new Tag("a").AddClass("toggle").SetAttribute("href", "#").EncodeAndAppend(title)));
  1001. // Make room for assembly summary which we'll populate later
  1002. if (a != null)
  1003. divTest.Append(new Tag("div").SetAttribute("class", "summary"));
  1004. // Make the collapsable content div
  1005. var divContent = new Tag("div").SetAttribute("class", "content");
  1006. divTest.Append(divContent);
  1007. // Prepare to continue writing in the collapsable content div
  1008. _current.Append(divTest);
  1009. _current = divContent;
  1010. _pre = null;
  1011. _bufferedOutput = Verbose ? null : new StringWriter();
  1012. base.StartTest(Target, Arguments);
  1013. }
  1014. public override void EndTest(Stats stats)
  1015. {
  1016. base.EndTest(stats);
  1017. // If this was a test, and it passed, write something to indicate that
  1018. if (stats.Target as MethodInfo != null && stats.Passed == 1)
  1019. {
  1020. if (_pre != null)
  1021. WriteToTestOutput("\n");
  1022. WriteToTestOutput("Test passed!\n");
  1023. _current.Parent.Find("div", "title", null).Append(string.Format(" <small>{0}ms</small>", stats.Elapsed));
  1024. }
  1025. // Pop the content div
  1026. _current = _current.Parent;
  1027. // Flag the test/testfixture/assembly as pass/fail and collapse passed tests
  1028. if (stats.Errors == 0)
  1029. _current.AddClass("pass" + ((stats.Target as MethodInfo != null) ? " collapsed" : ""));
  1030. else
  1031. _current.AddClass("fail");
  1032. // Write the summary
  1033. var t = _current.Find("div", "summary", null);
  1034. if (t != null)
  1035. {
  1036. t.Append(string.Format("Passed: {0} Failed: {1} Warnings: {2} Time in Test Cases: {3}ms", stats.Passed, stats.Errors, stats.Warnings, stats.Elapsed));
  1037. }
  1038. // Pop stack
  1039. _current = _current.Parent;
  1040. }
  1041. public override void Complete(Stats stats, long OtherTimes, long ActualTime)
  1042. {
  1043. base.Complete(stats, OtherTimes, ActualTime);
  1044. _current.Append(new Tag("div").AddClass("misc_info")
  1045. .Append(string.Format("Time in test cases {0:#,##0}ms, setup/teardown {1:#,##0}ms, test framework {2:#,##0}ms<br/>", stats.Elapsed, OtherTimes, ActualTime - (stats.Elapsed + OtherTimes)))
  1046. .Append(string.Format("Tests run at {0} by {1} on {2} under {3}<br/>", _dateOfTest, System.Environment.UserName, System.Environment.MachineName, System.Environment.OSVersion))
  1047. .Append(string.Format("Command line: {0}", string.Join(" ", System.Environment.GetCommandLineArgs())))
  1048. );
  1049. // Save report
  1050. using (var output = new StreamWriter(_output_file))
  1051. {
  1052. _tagHtml.Render(output);
  1053. }
  1054. if (!_showreport.HasValue)
  1055. {
  1056. System.GC.Collect();
  1057. try
  1058. {
  1059. base.WriteIndented("View Report?");
  1060. var key = Console.ReadKey();
  1061. base.WriteIndented("\n");
  1062. _showreport = key.KeyChar == 'Y' || key.KeyChar == 'y';
  1063. }
  1064. catch
  1065. {
  1066. _showreport = true;
  1067. }
  1068. }
  1069. if (_showreport.Value)
  1070. {
  1071. System.Diagnostics.Process.Start(_output_file);
  1072. }
  1073. }
  1074. public void WriteToTestOutput(string str)
  1075. {
  1076. if (_pre == null)
  1077. {
  1078. _pre = new Tag("pre");
  1079. _current.Append(_pre);
  1080. }
  1081. _pre.EncodeAndAppend(str);
  1082. }
  1083. public override void Write(string str)
  1084. {
  1085. base.Write(str);
  1086. WriteToTestOutput(str);
  1087. if (_bufferedOutput != null)
  1088. _bufferedOutput.Write(str);
  1089. }
  1090. public override void WriteException(Exception x)
  1091. {
  1092. if (_bufferedOutput != null)
  1093. {
  1094. WriteIndented(_bufferedOutput.ToString());
  1095. _bufferedOutput = new StringWriter();
  1096. }
  1097. base.WriteException(x);
  1098. // Split exception message into title and detail
  1099. string title, detail = null;
  1100. var assert = x as AssertionException;
  1101. if (assert != null)
  1102. {
  1103. int cr = assert.Message.IndexOf('\n');
  1104. if (cr > 0)
  1105. {
  1106. title = assert.Message.Substring(0, cr);
  1107. detail = assert.Message.Substring(cr + 1);
  1108. }
  1109. else
  1110. title = assert.Message;
  1111. title = string.Format("Assertion failed - {0}", title);
  1112. }
  1113. else
  1114. {
  1115. title = string.Format("Exception {0}: {1}", x.GetType().FullName, x.Message);
  1116. }
  1117. var div = new Tag("div").SetAttribute("class", "exception");
  1118. div.Append(new Tag("div").SetAttribute("class", "errormessage").EncodeAndAppend(title));
  1119. if (detail != null)
  1120. {
  1121. div.Append(new Tag("p").EncodeAndAppend("Detail"));
  1122. div.Append(new Tag("pre").EncodeAndAppend(detail));
  1123. }
  1124. div.Append(new Tag("p").EncodeAndAppend("Stack Trace"));
  1125. var st = new Tag("pre").SetAttribute("class", "stacktrace");
  1126. div.Append(st);
  1127. StackFrame first = null;
  1128. foreach (var f in Utils.SimplifyStackTrace(new StackTrace(x, true)))
  1129. {
  1130. if (first == null) first = f;
  1131. st.EncodeAndAppend(string.Format("{0} - {1}({2})\n", f.GetMethod().Name, f.GetFileName(), f.GetFileLineNumber()));
  1132. }
  1133. if (first != null)
  1134. {
  1135. div.Append(new Tag("p").EncodeAndAppend("Location"));
  1136. var code = new Tag("pre").SetAttribute("class", "code");
  1137. div.Append(code);
  1138. foreach (var l in Utils.ExtractLinesFromTextFile(first.GetFileName(), first.GetFileLineNumber()))
  1139. {
  1140. if (l.Item1 == first.GetFileLineNumber())
  1141. code.Append("<span class=\"highlighted\">");
  1142. code.EncodeAndAppend(string.Format(" {0:00000}:{1}{2}\n", l.Item1, l.Item1 == first.GetFileLineNumber() ? "->" : " ", l.Item2));
  1143. if (l.Item1 == first.GetFileLineNumber())
  1144. code.Append("</span>");
  1145. }
  1146. }
  1147. _pre = null;
  1148. _current.Append(div);
  1149. }
  1150. // Report works in Chrome, FF4 and Safari. Doesn't work in IE (oh well)
  1151. private const string StylesAndScript = @"<style>
  1152. body { font-family:Arial; margin:20px; font-size:10pt}
  1153. div.assembly>div.title { font-size:20pt; margin-bottom:20px; border-bottom:1px solid silver; padding-bottom:20px; }
  1154. div.testfixture>div.title { font-size:16pt; }
  1155. div.testfixture { margin-bottom:20px; }
  1156. div.test { margin-left:20px; }
  1157. div.test div.title { font-size:12pt; }
  1158. div.errormessage { color:Red; padding-top:10px; }
  1159. div.pass>div.title { color:#808080; }
  1160. span.highlighted { color:Red; }
  1161. pre.code { background-color:#f0f0f0; }
  1162. div.summary { border:1px solid silver; padding:10px; margin-bottom:20px; text-align:center; }
  1163. div.fail div.summary { background-color:Red; color:White; }
  1164. div.pass div.summary { background-color:Lime; }
  1165. div.collapsed div.content { display:none; }
  1166. div.test div.content { border-left:2px solid #d0d0d0; padding-left:10px; margin-left:10px; }
  1167. a { text-decoration:none; color:#606060; }
  1168. a:hover { color:orange; }
  1169. div>div.title>span.indicator { display:inline-block; width:10px; height:10px; background-color:lime; border-radius:7px}
  1170. div.fail>div.title>span.indicator { display:inline-block; width:10px; height:10px; background-color:red; }
  1171. div.assembly>div.title>span.indicator { display:none; }
  1172. div.misc_info { color:#808080; }
  1173. </style>
  1174. <script>
  1175. window.addEventListener(""load"", setup, false);
  1176. function setup() {
  1177. var divs = document.getElementsByClassName(""toggle"");
  1178. for (var i = 0; i < divs.length; i++) {
  1179. divs[i].onclick = function () {
  1180. var top = this.parentNode.parentNode;
  1181. if (top.className.indexOf("" collapsed"") < 0)
  1182. top.className += "" collapsed"";
  1183. else
  1184. top.className = top.className.replace("" collapsed"", """");
  1185. return false;
  1186. }
  1187. }
  1188. }
  1189. </script>";
  1190. }
  1191. public class Tag
  1192. {
  1193. public Tag(string tagName)
  1194. {
  1195. Name = tagName;
  1196. }
  1197. public string Name { get; set; }
  1198. public Tag Parent { get; set; }
  1199. public Dictionary<string, string> Attributes = new Dictionary<string, string>();
  1200. List<object> _Content;
  1201. public void Render(TextWriter w)
  1202. {
  1203. w.Write("<"); w.Write(Name);
  1204. foreach (var a in Attributes)
  1205. {
  1206. w.Write(" "); w.Write(a.Key);
  1207. if (a.Value != null)
  1208. {
  1209. w.Write("=\""); w.Write(Encode(a.Value)); w.Write("\"");
  1210. }
  1211. }
  1212. w.Write(">");
  1213. if (Name != "pre")
  1214. w.Write("\n");
  1215. if (_Content != null)
  1216. {
  1217. bool NeedNewLine = false;
  1218. foreach (var i in _Content)
  1219. {
  1220. var t = i as Tag;
  1221. if (t != null)
  1222. {
  1223. if (NeedNewLine)
  1224. w.Write("\n");
  1225. t.Render(w);
  1226. NeedNewLine = false;
  1227. }
  1228. var s = i as String;
  1229. if (s != null)
  1230. {
  1231. w.Write(s);
  1232. NeedNewLine = true;
  1233. }
  1234. }
  1235. }
  1236. if (Name != "pre")
  1237. w.Write("\n");
  1238. w.Write("</"); w.Write(Name); w.Write(">\n");
  1239. }
  1240. public Tag Append(Tag t)
  1241. {
  1242. if (_Content == null)
  1243. _Content = new List<object>();
  1244. t.Parent = this;
  1245. _Content.Add(t);
  1246. return this;
  1247. }
  1248. public Tag Append(string s)
  1249. {
  1250. if (_Content == null)
  1251. _Content = new List<object>();
  1252. _Content.Add(s);
  1253. return this;
  1254. }
  1255. public Tag EncodeAndAppend(string s)
  1256. {
  1257. Append(Encode(s));
  1258. return this;
  1259. }
  1260. public Tag SetAttribute(string name, string value)
  1261. {
  1262. Attributes[name] = value;
  1263. return this;
  1264. }
  1265. public Tag AddClass(string className)
  1266. {
  1267. if (Attributes.ContainsKey("class"))
  1268. Attributes["class"] = Attributes["class"] + " " + className;
  1269. else
  1270. Attributes["class"] = className;
  1271. return this;
  1272. }
  1273. public Tag Find(string tagName, string className, string id)
  1274. {
  1275. foreach (Tag t in _Content.Where(x => x as Tag != null))
  1276. {
  1277. if (tagName != null && t.Name != tagName)
  1278. continue;
  1279. if (className != null && t.Attributes["class"] != className)
  1280. continue;
  1281. if (id != null && t.Attributes["id"] != id)
  1282. continue;
  1283. return t;
  1284. }
  1285. return null;
  1286. }
  1287. public static string Encode(string str)
  1288. {
  1289. return str.Replace("&", "&amp;").Replace("<", "&lt;").Replace("\"", "&quot;").Replace("\'", "&#039;").Replace(">", "&gt;");
  1290. }
  1291. }
  1292. }