PageRenderTime 183ms CodeModel.GetById 26ms RepoModel.GetById 7ms app.codeStats 0ms

/UKVSTS/WebTestPlugins/UKVSTS.WebTestPlugins/ScriptedParamDetails.cs

#
C# | 323 lines | 185 code | 53 blank | 85 comment | 17 complexity | 28ba74ae936d93aab8202ca734a14293 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Reflection;
  5. using Microsoft.VisualStudio.TestTools.WebTesting;
  6. using System.CodeDom;
  7. using Microsoft.CSharp;
  8. using System.IO;
  9. using System.CodeDom.Compiler;
  10. namespace UKVSTS.WebTestPlugins
  11. {
  12. class ScriptedParamDetails
  13. {
  14. /// <summary>
  15. /// The body of the script
  16. /// </summary>
  17. private String m_Code;
  18. /// <summary>
  19. /// The name of the parameter that is using this script
  20. /// </summary>
  21. private String m_ParamName;
  22. /// <summary>
  23. /// The compiled up script
  24. /// </summary>
  25. private MethodInfo m_Method;
  26. /// <summary>
  27. /// Use a delegate to store the address of the compiled up script.
  28. /// This will be quicker than calling it through reflection each time.
  29. /// </summary>
  30. private delegate object ScriptsGetValueDelegate (WebTestRequest request, WebTest test , WebTestContext context);
  31. /// <summary>
  32. /// The address of our compiled script.
  33. /// </summary>
  34. private ScriptsGetValueDelegate m_GetValueFunc;
  35. /// <summary>
  36. /// The namespace we will put our compiled script into.
  37. /// </summary>
  38. private const String GEN_NAMESPACE = "UKVSTS.WebTestPlugIns.Temp";
  39. /// <summary>
  40. /// The name for the method containing our script.
  41. /// </summary>
  42. private const String GEN_METHODNAME = "GetValue";
  43. /// <summary>
  44. /// Do we create a debug version of the code - this is to support dev of this plugin
  45. /// rather than support for the end user.
  46. /// </summary>
  47. private bool debug = true;
  48. /// <summary>
  49. /// List of namespaces that we will give import for the script block.
  50. /// </summary>
  51. private static String[] GEN_NAMESPACE_IMPORTS = new String [] {
  52. "System",
  53. "Microsoft.VisualStudio.TestTools.WebTesting"
  54. };
  55. /// <summary>
  56. /// Createas and compiles a script block
  57. /// </summary>
  58. /// <param name="paramName">The name of the param that is using this script</param>
  59. /// <param name="code">The body of this script</param>
  60. public ScriptedParamDetails(String paramName, String code)
  61. {
  62. debug = System.Diagnostics.Debugger.IsAttached;
  63. m_Code = code;
  64. m_ParamName = paramName;
  65. try
  66. {
  67. m_Method = CreateCodeAndReturnMethod(code, debug);
  68. // Create a delegate for this newly created method - this will be faster to
  69. // call the function than MethodInfo.Invoke
  70. // ..
  71. m_GetValueFunc = (ScriptsGetValueDelegate)Delegate.CreateDelegate(typeof(ScriptsGetValueDelegate), m_Method);
  72. }
  73. catch (Exception ex)
  74. {
  75. ScriptedParamException spx = ex as ScriptedParamException;
  76. if (spx == null)
  77. {
  78. spx = new ScriptedParamException(ex);
  79. }
  80. spx.ParamName = ParamName;
  81. throw;
  82. }
  83. }
  84. /// <summary>
  85. /// Uses the codedom surroung the script with a metod, class and namespace.
  86. /// </summary>
  87. /// <param name="code">The body of the script</param>
  88. /// <param name="debug">Flag to indicate if a debug version should be created</param>
  89. /// <returns>The MethodInfo object that reflects the compiled function</returns>
  90. static MethodInfo CreateCodeAndReturnMethod(String code, bool debug)
  91. {
  92. // Create the container where our code will be written.
  93. //
  94. CodeCompileUnit compileUnit = new CodeCompileUnit();
  95. // Create a namespace for our class and include and reference namespaces
  96. CodeNamespace ns = CreateNamespace(compileUnit);
  97. // Invent a unique name for this type.
  98. //
  99. String typeName = "ScriptClass" + Guid.NewGuid().ToString().Replace('-', '_');
  100. // Create the class definition
  101. //
  102. CodeTypeDeclaration td = CreateTypeDeclaration(ns, typeName);
  103. // Create the metod that contains our script.
  104. //
  105. td.Members.Add(CreateMethod(code));
  106. // Build it
  107. //
  108. Assembly assm = CompileCodeToAssembly(compileUnit, debug);
  109. // Find our method
  110. //
  111. Type t = assm.GetType(GEN_NAMESPACE + "." + typeName);
  112. return t.GetMethod(GEN_METHODNAME, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
  113. }
  114. static CodeMemberMethod CreateMethod (String body)
  115. {
  116. // Defines a method that returns a Object.
  117. //
  118. CodeMemberMethod method = new CodeMemberMethod();
  119. method.Name = GEN_METHODNAME;
  120. method.Attributes = MemberAttributes.Static | MemberAttributes.Public;
  121. method.ReturnType = new CodeTypeReference("System.Object");
  122. // Pass in a couple of useful args.
  123. //
  124. method.Parameters.Add(new CodeParameterDeclarationExpression("WebTestRequest", "Request"));
  125. method.Parameters.Add(new CodeParameterDeclarationExpression("WebTest", "WebTest"));
  126. method.Parameters.Add(new CodeParameterDeclarationExpression("WebTestContext", "Context"));
  127. // If the script is the form
  128. // <% = .....
  129. // then we want to subsitute a return statement for the =
  130. // If there is no equals, then we don't want to inser a return.
  131. bool needsReturn = false;
  132. int i = 0;
  133. int charsToTrim = 0;
  134. do
  135. {
  136. if (body[i] == '=')
  137. {
  138. needsReturn = true;
  139. charsToTrim = i + 1;
  140. break;
  141. }
  142. } while (Char.IsWhiteSpace(body[i++]));
  143. // Do we need to trim any chars of the string (i.e. white space and a =).
  144. //
  145. if (charsToTrim > 0)
  146. {
  147. body = body.Substring(charsToTrim);
  148. }
  149. if (needsReturn == true)
  150. {
  151. method.Statements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(body)));
  152. }
  153. else
  154. {
  155. method.Statements.Add(new CodeSnippetExpression(body));
  156. }
  157. return method;
  158. }
  159. static CodeTypeDeclaration CreateTypeDeclaration(CodeNamespace ns, String typeName)
  160. {
  161. // Creates a new type declaration.
  162. CodeTypeDeclaration newType = new CodeTypeDeclaration(typeName);
  163. // Sets the member attributes for the type.
  164. newType.Attributes = MemberAttributes.Public | MemberAttributes.Static;
  165. ns.Types.Add(newType);
  166. return newType;
  167. }
  168. private static CodeNamespace CreateNamespace(CodeCompileUnit compileUnit)
  169. {
  170. // Declare a new namespace.
  171. //
  172. CodeNamespace ns = new CodeNamespace(GEN_NAMESPACE);
  173. // Add the new namespace to the compile unit.
  174. compileUnit.Namespaces.Add(ns);
  175. // Add the new namespace import for the System namespace.
  176. foreach (String import in GEN_NAMESPACE_IMPORTS)
  177. {
  178. ns.Imports.Add(new CodeNamespaceImport(import));
  179. }
  180. return ns;
  181. }
  182. public static Assembly CompileCodeToAssembly(CodeCompileUnit compileUnit, bool debug)
  183. {
  184. // Generate the code with the C# code provider.
  185. CSharpCodeProvider provider = new CSharpCodeProvider();
  186. TextWriter tw = null;
  187. try
  188. {
  189. if (debug)
  190. {
  191. StreamWriter sw = new StreamWriter(GEN_NAMESPACE + "." + compileUnit.Namespaces[0].Types[0].Name + ".cs");
  192. tw = new IndentedTextWriter(sw, " ");
  193. }
  194. else
  195. {
  196. // Use an in memory buffer to generate the code to.
  197. //
  198. tw = new StringWriter(new StringBuilder(1024));
  199. }
  200. // Generate source code using the code provider.
  201. //
  202. provider.GenerateCodeFromCompileUnit(compileUnit, tw, new CodeGeneratorOptions());
  203. }
  204. finally
  205. {
  206. if (tw != null)
  207. {
  208. tw.Close();
  209. }
  210. }
  211. CompilerParameters compilerParams = new CompilerParameters();
  212. // Need to ensure we have a reference to Microsoft.VisualStudio.QualityTools.WebTestFramework.dll.
  213. // However this isn't in the GAC - it is in memory, so find the location from the loaded assembly
  214. //
  215. compilerParams.ReferencedAssemblies.Add(typeof(WebTest).Assembly.Location);
  216. compilerParams.GenerateInMemory = !debug;
  217. compilerParams.IncludeDebugInformation = debug;
  218. CompilerResults results = provider.CompileAssemblyFromDom(compilerParams, compileUnit);
  219. String errString = String.Empty;
  220. foreach (CompilerError err in results.Errors)
  221. {
  222. if (debug)
  223. {
  224. System.Diagnostics.Debug.WriteLine(String.Format("{0}({1},{2}): {3} {4}: {5}",
  225. err.FileName,
  226. err.Line,
  227. err.Column,
  228. err.IsWarning ? "Warning" : "Error",
  229. err.ErrorNumber,
  230. err.ErrorText));
  231. }
  232. if (err.IsWarning == false)
  233. {
  234. errString += err.ErrorText + "\n";
  235. }
  236. }
  237. if (errString != String.Empty)
  238. {
  239. throw new ScriptedParamException("An error has occured when compiling a script: " + errString
  240. + ". Please ensure that the script is of the format <%= value %> or <% return value; %>");
  241. }
  242. return results.CompiledAssembly;
  243. }
  244. public String Code
  245. {
  246. get { return m_Code; }
  247. }
  248. public String ParamName
  249. {
  250. get { return m_ParamName; }
  251. }
  252. /// <summary>
  253. /// Executes the script and returns whatever the script evaluated.
  254. /// </summary>
  255. /// <param name="e"></param>
  256. /// <returns></returns>
  257. public String InvokeScript(PreRequestEventArgs e)
  258. {
  259. try
  260. {
  261. Object retval = m_GetValueFunc(e.Request, e.WebTest, e.WebTest.Context);
  262. return retval == null ? "(null)" : retval.ToString();
  263. }
  264. catch (Exception ex)
  265. {
  266. throw new ScriptedParamException("Script execution error \"" + ex.Message + "\"", ParamName, ex);
  267. }
  268. }
  269. }
  270. }