PageRenderTime 89ms CodeModel.GetById 15ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/PetaPoco.Tests/PetaTest.cs

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