1 //
2 //
3 // Authors:
4 //   Marek Habersack (mhabersack@novell.com)
5 //
6 // (C) 2010 Novell, Inc http://novell.com/
7 //
8 
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29 using System;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Reflection;
33 using System.Text;
34 using System.Web;
35 using System.Web.Hosting;
36 
37 using Mono.Options;
38 
39 using StandAloneRunnerSupport;
40 
41 namespace StandAloneRunner
42 {
43 	sealed class StandAloneRunner
44 	{
45 		static string testsAssembly;
46 
Usage(int exitCode, OptionSet options)47 		static void Usage (int exitCode, OptionSet options)
48 		{
49 			Console.WriteLine (@"Usage: standalone-runner [OPTIONS] <TESTS_ASSEMBLY>");
50 			if (options != null) {
51 				Console.WriteLine ();
52 				Console.WriteLine ("Available options:");
53 				options.WriteOptionDescriptions (Console.Out);
54 			}
55 			Console.WriteLine ();
56 
57 			Environment.Exit (exitCode);
58 		}
59 
Die(string format, params object[] args)60 		public static void Die (string format, params object[] args)
61 		{
62 			Console.WriteLine ("Standalone test failure in assembly '{0}'.", testsAssembly);
63 			Console.WriteLine ();
64 
65 			if (args == null || args.Length == 0)
66 				Console.WriteLine ("Error: " + format);
67 			else
68 				Console.WriteLine ("Error: " + format, args);
69 
70 			Environment.Exit (1);
71 		}
72 
Main(string[] args)73 		static void Main (string[] args)
74 		{
75 			try {
76 				var success = Run (args);
77 
78 				if (!success)
79 					Environment.Exit (1);
80 			} catch (Exception ex) {
81 				Die ("Exception caught:{0}{1}", Environment.NewLine, ex.ToString ());
82 			}
83 		}
84 
Run(string[] args)85 		static bool Run (string[] args)
86 		{
87 			bool showHelp = false;
88 			string testName = null;
89 			string outputName = null;
90 			bool verbose = false;
91 
92 			var options = new OptionSet () {
93 				{"?|h|help", "Show short usage screen.", v => showHelp = true},
94 				{"t=|test=", "Run this test only (full type name)", (string s) => testName = s},
95 				{"o=|output=", "Output test results to the console and to the indicated file", (string s) => outputName = s},
96 				{"v|verbose", "Print the running test name", v => verbose = true}
97 			};
98 
99 			List <string> extra = options.Parse (args);
100 			int extraCount = extra.Count;
101 
102 			if (showHelp || extraCount < 1)
103 				Usage (showHelp ? 0 : 1, options);
104 
105 			testsAssembly = extra [0];
106 
107 			if (!File.Exists (testsAssembly))
108 				Die ("Tests assembly '{0}' does not exist.", testsAssembly);
109 
110 			Assembly asm = Assembly.LoadFrom (testsAssembly);
111 			var tests = new List <StandaloneTest> ();
112 
113 			LoadTestsFromAssembly (asm, tests);
114 			if (tests.Count == 0)
115 				Die ("No tests present in the '{0}' assembly. Tests must be public types decorated with the TestCase attribute and implementing the ITestCase interface.", testsAssembly);
116 
117 			ApplicationManager appMan = ApplicationManager.GetApplicationManager ();
118 			int runCounter = 0;
119 			int failedCounter = 0;
120 			var reports = new List <string> ();
121 
122 			Console.WriteLine ("Running tests:");
123 			DateTime start = DateTime.Now;
124 
125 			foreach (StandaloneTest test in tests) {
126 				if (test.Info.Disabled)
127 					continue;
128 
129 				if (!String.IsNullOrEmpty (testName)) {
130 					if (String.Compare (test.TestType.FullName, testName) != 0)
131 						continue;
132 				}
133 
134 				test.Run (appMan, verbose);
135 				runCounter++;
136 				if (!test.Success) {
137 					failedCounter++;
138 					reports.Add (FormatReport (test));
139 				}
140 			}
141 
142 			DateTime end = DateTime.Now;
143 			Console.WriteLine ();
144 			StreamWriter writer;
145 
146 			if (!String.IsNullOrEmpty (outputName))
147 				writer = new StreamWriter (outputName);
148 			else
149 				writer = null;
150 
151 			try {
152 				string report;
153 				if (reports.Count > 0) {
154 					int repCounter = 0;
155 					int numWidth = reports.Count.ToString ().Length;
156 					string indent = String.Empty.PadLeft (numWidth + 2);
157 					string numFormat = "{0," + numWidth + "}) ";
158 
159 					foreach (string r in reports) {
160 						repCounter++;
161 						Console.Write (numFormat, repCounter);
162 						report = FormatLines (indent, r, Environment.NewLine, true);
163 						Console.WriteLine (report);
164 						if (writer != null) {
165 							writer.Write (numFormat, repCounter);
166 							writer.WriteLine (report);
167 						}
168 					}
169 				} else
170 					Console.WriteLine ();
171 
172 				report = String.Format ("Tests run: {0}; Total tests: {1}; Failed: {2}; Not run: {3}; Time taken: {4}",
173 							runCounter, tests.Count, failedCounter, tests.Count - runCounter, end - start);
174 				Console.WriteLine (report);
175 				if (writer != null)
176 					writer.WriteLine (report);
177 			} finally {
178 				if (writer != null) {
179 					writer.Close ();
180 					writer.Dispose ();
181 				}
182 			}
183 
184 			return failedCounter == 0;
185 		}
186 
FormatReport(StandaloneTest test)187 		static string FormatReport (StandaloneTest test)
188 		{
189 			var sb = new StringBuilder ();
190 			string newline = Environment.NewLine;
191 
192 			sb.AppendFormat ("{0}{1}", test.Info.Name, newline);
193 			sb.AppendFormat ("{0,16}: {1}{2}", "Test", test.TestType, newline);
194 
195 			if (!String.IsNullOrEmpty (test.Info.Description))
196 				sb.AppendFormat ("{0,16}: {1}{2}", "Description", test.Info.Description, newline);
197 
198 			if (!String.IsNullOrEmpty (test.FailedUrl))
199 				sb.AppendFormat ("{0,16}: {1}{2}", "Failed URL", test.FailedUrl, newline);
200 
201 			if (!String.IsNullOrEmpty (test.FailedUrlCallbackName))
202 				sb.AppendFormat ("{0,16}: {1}{2}", "Callback method", test.FailedUrlCallbackName, newline);
203 
204 			if (!String.IsNullOrEmpty (test.FailedUrlDescription))
205 				sb.AppendFormat ("{0,16}: {1}{2}", "URL description", test.FailedUrlDescription, newline);
206 
207 			if (!String.IsNullOrEmpty (test.FailureDetails))
208 				sb.AppendFormat ("{0,16}:{2}{1}{2}", "Failure details", FormatLines ("   ", test.FailureDetails, newline, false), newline);
209 
210 			if (test.Exception != null)
211 				sb.AppendFormat ("{0,16}:{2}{1}{2}", "Exception", FormatLines ("   ", test.Exception.ToString (), newline, false), newline);
212 
213 			return sb.ToString ();
214 		}
215 
FormatLines(string indent, string text, string newline, bool skipFirst)216 		static string FormatLines (string indent, string text, string newline, bool skipFirst)
217 		{
218 			var sb = new StringBuilder ();
219 			bool first = true;
220 
221 			foreach (string s in text.Split (new string[] { newline }, StringSplitOptions.None)) {
222 				if (skipFirst && first)
223 					first = false;
224 				else
225 					sb.Append (indent);
226 				sb.Append (s);
227 				sb.Append (newline);
228 			}
229 
230 			sb.Length -= newline.Length;
231 			return sb.ToString ();
232 		}
233 
LoadTestsFromAssembly(Assembly asm, List <StandaloneTest> tests)234 		static void LoadTestsFromAssembly (Assembly asm, List <StandaloneTest> tests)
235 		{
236 			Type[] types = asm.GetExportedTypes ();
237 			if (types.Length == 0)
238 				return;
239 
240 			object[] attributes;
241 			foreach (var t in types) {
242 				if (!t.IsClass || t.IsAbstract || t.IsGenericTypeDefinition)
243 					continue;
244 
245 				attributes = t.GetCustomAttributes (typeof (TestCaseAttribute), false);
246 				if (attributes.Length == 0)
247 					continue;
248 
249 				if (!typeof (ITestCase).IsAssignableFrom (t))
250 					continue;
251 
252 				tests.Add (new StandaloneTest (t, attributes [0] as TestCaseAttribute));
253 			}
254 		}
255 	}
256 }
257