// // // // // $Revision$ // using System; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Xml; using Microsoft.CSharp; using NUnit.Framework; namespace Debugger.Tests { public class DebuggerTestsBase { string expetedOutputEnvVar = "SD_TESTS_DEBUGGER_XML_OUT"; protected NDebugger debugger; protected Process process; protected string log; protected string lastLogMessage; protected string testName; protected XmlDocument testDoc; protected XmlElement testNode; protected XmlElement snapshotNode; protected int shapshotID; public StackFrame SelectedStackFrame { get { return process.SelectedStackFrame; } } public void Continue() { process.Continue(); } [TestFixtureSetUp] public virtual void TestFixtureSetUp() { debugger = new NDebugger(); debugger.MTA2STA.CallMethod = CallMethod.Manual; } [TestFixtureTearDown] public virtual void TestFixtureTearDown() { } [SetUp] public virtual void SetUp() { System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; testName = null; expandProperties = new List(); ignoreProperties = new List(); ignoreProperties.AddRange(new string [] { "*.GetType", "*.GetHashCode", "*.GetEnumerator", "*.AppDomain", "*.Process", "MemberInfo.DebugModule", "MemberInfo.IsStatic", "MemberInfo.IsPublic", "MemberInfo.IsAssembly", "MemberInfo.IsFamily", "MemberInfo.IsPrivate", "MemberInfo.MetadataToken", "MemberInfo.MemberType", "Type.DeclaringType", "Type.IsAbstract", "Type.IsAnsiClass", "Type.IsAutoLayout", "Type.IsLayoutSequential", "Type.IsNestedPublic", "Type.IsSealed", "Type.IsSerializable", "Type.IsVisible", "Type.IsNotPublic", "Type.Name", "Type.Namespace", "Type.UnderlyingSystemType", "MethodBase.CallingConvention", "MethodBase.GetMethodBody", "MethodBase.GetMethodImplementationFlags", "MethodBase.GetParameters", "MethodBase.IsFinal", "MethodBase.IsHideBySig", "MethodBase.IsVirtual", "MethodBase.IsSpecialName", "MethodBase.ReturnParameter", "MethodBase.ParameterCount", "PropertyInfo.CanRead", "PropertyInfo.CanWrite", "PropertyInfo.GetGetMethod", "PropertyInfo.GetSetMethod", }); testDoc = new XmlDocument(); testDoc.AppendChild(testDoc.CreateXmlDeclaration("1.0","utf-8",null)); testDoc.AppendChild(testDoc.CreateElement("DebuggerTests")); testNode = testDoc.CreateElement("Test"); testDoc.DocumentElement.AppendChild(testNode); } [TearDown] public virtual void TearDown() { } protected void EndTest() { if (!process.HasExited) { process.AsyncContinue(); process.WaitForExit(); } CheckXmlOutput(); } protected void CheckXmlOutput() { string startMark = "#if EXPECTED_OUTPUT\r\n"; string endMark = "#endif // EXPECTED_OUTPUT"; MemoryStream newXmlStream = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); settings.Encoding = Encoding.UTF8; settings.Indent = true; settings.NewLineOnAttributes = true; XmlWriter writer = XmlTextWriter.Create(newXmlStream, settings); testDoc.Save(writer); newXmlStream.Seek(0, SeekOrigin.Begin); string actualXml = new StreamReader(newXmlStream).ReadToEnd() + "\r\n"; string sourceCode = GetResource(testName); int startIndex = sourceCode.IndexOf(startMark); int endIndex = sourceCode.IndexOf(endMark); if (startIndex == -1 || endIndex == -1) { Assert.Fail("Test " + testName + " failed. Expected XML output not found."); } string expectedXml = sourceCode.Substring(startIndex + startMark.Length, endIndex - (startIndex + startMark.Length)); if (actualXml != expectedXml) { // Update the source code file with the new output string path = Environment.GetEnvironmentVariable(expetedOutputEnvVar); if (path != null) { string filename = Path.Combine(path, testName); string newSourceCode = File.ReadAllText(filename, Encoding.UTF8); startIndex = newSourceCode.IndexOf(startMark); endIndex = newSourceCode.IndexOf(endMark); newSourceCode = newSourceCode.Substring(0, startIndex + startMark.Length) + actualXml + newSourceCode.Substring(endIndex); File.WriteAllText(filename, newSourceCode, Encoding.UTF8); } //Assert.Fail("Test " + testName + " failed. XML output differs from expected."); Assert.AreEqual(expectedXml, actualXml, "Test " + testName + " failed. XML output differs from expected."); } } protected void StartTest() { string testName = Path.GetFileName(new System.Diagnostics.StackTrace(true).GetFrame(1).GetFileName()); StartTest(testName, true); } protected void StartTestNoWait() { string testName = Path.GetFileName(new System.Diagnostics.StackTrace(true).GetFrame(1).GetFileName()); StartTest(testName, false); } void StartTest(string testName, bool wait) { this.testName = testName; string exeFilename = CompileTest(testName); testNode.SetAttribute("name", testName); shapshotID = 0; log = ""; lastLogMessage = null; process = debugger.Start(exeFilename, Path.GetDirectoryName(exeFilename), testName); process.LogMessage += delegate(object sender, MessageEventArgs e) { log += e.Message; lastLogMessage = e.Message; LogEvent("LogMessage", e.Message.Replace("\r",@"\r").Replace("\n",@"\n")); }; process.Modules.Added += delegate(object sender, CollectionItemEventArgs e) { LogEvent("ModuleLoaded", e.Item.Filename + (e.Item.HasSymbols ? " (Has symbols)" : " (No symbols)")); }; process.Paused += delegate(object sender, ProcessEventArgs e) { LogEvent("DebuggingPaused", e.Process.PauseSession.PausedReason.ToString() + " " + e.Process.SelectedStackFrame.NextStatement.ToString()); }; // process.DebuggingResumed += delegate(object sender, ProcessEventArgs e) { // LogEvent("DebuggingResumed", e.Process.PausedReason.ToString()); // }; process.ExceptionThrown += delegate(object sender, ExceptionEventArgs e) { StringBuilder msg = new StringBuilder(); if (process.SelectedThread.InterceptCurrentException()) { msg.Append(e.Exception.ToString()); } else { // For example, happens on stack overflow msg.Append("Could not intercept: "); msg.Append(e.Exception.ToString()); } LogEvent("ExceptionThrown", msg.ToString()); }; process.Exited += delegate(object sender, EventArgs e) { LogEvent("ProcessExited", null); }; LogEvent("ProcessStarted", null); if (wait) { process.WaitForPause(); } } protected XmlElement LogEvent(string name, string content) { XmlElement eventNode = testDoc.CreateElement(name); if (content != null) { eventNode.AppendChild(testDoc.CreateTextNode(content)); } testNode.AppendChild(eventNode); return eventNode; } public void ObjectDump(object obj) { Serialize(testNode, obj, 16, new List()); } public void ObjectDump(string name, object obj) { XmlElement dumpNode = testDoc.CreateElement(XmlConvert.EncodeName(name.Replace(" ", "_"))); testNode.AppendChild(dumpNode); Serialize(dumpNode, obj, 16, new List()); } public void ObjectDumpToString(string name, object obj) { XmlElement dumpNode = testDoc.CreateElement(XmlConvert.EncodeName(name.Replace(" ", "_"))); testNode.AppendChild(dumpNode); if (obj == null) { dumpNode.AppendChild(dumpNode.OwnerDocument.CreateTextNode("null")); } else { dumpNode.AppendChild(dumpNode.OwnerDocument.CreateTextNode(obj.ToString())); } } List expandProperties; List ignoreProperties; protected void ExpandProperties(params string[] props) { expandProperties = new List(props); } bool ListContains(List list, MemberInfo memberInfo) { Type declaringType = memberInfo.DeclaringType; while(declaringType != null) { if (list.Contains(declaringType.Name + "." + memberInfo.Name) || list.Contains(declaringType.Name + ".*") || list.Contains("*." + memberInfo.Name) || list.Contains("*.*") || list.Contains("*")) return true; declaringType = declaringType.BaseType; } return false; } bool ExpandProperty(MemberInfo memberInfo) { return (memberInfo.IsDefined(typeof(Debugger.Tests.ExpandAttribute), true)) || ListContains(expandProperties, memberInfo); } bool IgnoreProperty(MemberInfo memberInfo) { return (memberInfo.IsDefined(typeof(Debugger.Tests.IgnoreAttribute), true)) || ListContains(ignoreProperties, memberInfo); } public void Serialize(XmlElement container, object obj, int maxDepth, List parents) { XmlDocument doc = container.OwnerDocument; if (obj == null) { container.AppendChild(doc.CreateTextNode("null")); return; } if (maxDepth == -1) { container.AppendChild(doc.CreateTextNode("{Max depth reached}")); return; } if (parents.Contains(obj)) { container.AppendChild(doc.CreateTextNode("{Recusion detected}")); return; } if (obj.GetType().Namespace == "System") { container.AppendChild(doc.CreateTextNode(obj.ToString())); return; } parents = new List(parents); // Clone parents.Add(obj); Type type = obj.GetType(); if (!(obj is IEnumerable)) { string name = XmlConvert.EncodeName(type.Name); if (name.Contains("__AnonymousType")) name = "AnonymousType"; XmlElement newContainer = doc.CreateElement(name); container.AppendChild(newContainer); container = newContainer; } List members = new List(); members.AddRange(type.GetMembers()); members.Sort(delegate(MemberInfo a, MemberInfo b) { return a.Name.CompareTo(b.Name);}); foreach(MemberInfo member in members) { if (type.BaseType == typeof(Array)) continue; MethodInfo method; object val; PropertyInfo propertyInfo = member as PropertyInfo; MethodInfo methodInfo = member as MethodInfo; if (propertyInfo != null) { if (propertyInfo.GetGetMethod() == null) continue; method = propertyInfo.GetGetMethod(); } else if (methodInfo != null) { if (!methodInfo.Name.StartsWith("Get")) continue; method = methodInfo; } else { continue; } if (method.GetParameters().Length > 0) continue; if (IgnoreProperty(member)) continue; try { val = method.Invoke(obj, new object[] {}); } catch (System.Exception e) { while(e.InnerException != null) e = e.InnerException; if (e is NotImplementedException || e is NotSupportedException) continue; if (type.IsDefined(typeof(Debugger.Tests.IgnoreOnExceptionAttribute), true) || member.IsDefined(typeof(Debugger.Tests.IgnoreOnExceptionAttribute), true)) continue; val = "{Exception: " + e.Message + "}"; } if (val is IEnumerable && !(val is string)) { List vals = new List(); foreach(object o in (IEnumerable)val) { vals.Add(o.ToString()); } if (vals.Count != 0) { container.SetAttribute(member.Name, "{" + string.Join(", ", vals.ToArray()) + "}"); } } else { bool isDefault = false; if (method.ReturnType == typeof(bool)) { isDefault = false.Equals(val); } else if (method.ReturnType == typeof(string)) { isDefault = val == null; } else if (method.ReturnType == typeof(int)) { isDefault = 0.Equals(val); } else if (method.ReturnType == typeof(uint)) { isDefault = ((uint)0).Equals(val); } else { isDefault = val == null; } if (val == null) val = "null"; if (!isDefault) { container.SetAttribute(member.Name, val.ToString()); } } if (ExpandProperty(member)) { XmlElement propertyNode = doc.CreateElement(member.Name); container.AppendChild(propertyNode); Serialize(propertyNode, val, maxDepth - 1, parents); } } // Save all objects of an enumerable object if (obj is IEnumerable) { int id = 1; foreach(object enumObject in (IEnumerable)obj) { XmlElement enumRoot = doc.CreateElement("Item"); container.AppendChild(enumRoot); Serialize(enumRoot, enumObject, maxDepth - 1, parents); id++; } } } string GetResource(string filename) { string resourcePrefix = "Debugger.Tests.Tests."; Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePrefix + filename); if (stream == null) throw new System.Exception("Resource " + filename + " not found"); return new StreamReader(stream).ReadToEnd(); } string CompileTest(string testName) { string code = GetResource(testName); string md5 = ToHexadecimal(new MD5CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(code))); string path = Path.GetTempPath(); path = Path.Combine(path, "SharpDevelop4.0"); path = Path.Combine(path, "DebuggerTestsX86"); path = Path.Combine(path, testName + "." + md5); Directory.CreateDirectory(path); string codeFilename = Path.Combine(path, testName); string exeFilename = Path.Combine(path, testName.Replace(".cs", ".exe")); if (File.Exists(exeFilename)) { return exeFilename; } StreamWriter file = new StreamWriter(codeFilename); file.Write(code); file.Close(); CompilerParameters compParams = new CompilerParameters(); compParams.GenerateExecutable = true; compParams.GenerateInMemory = false; compParams.TreatWarningsAsErrors = false; compParams.IncludeDebugInformation = true; compParams.ReferencedAssemblies.Add("System.dll"); compParams.OutputAssembly = exeFilename; compParams.CompilerOptions = "/unsafe /platform:x86 /target:winexe"; compParams.ReferencedAssemblies.Add(typeof(TestFixtureAttribute).Assembly.Location); CSharpCodeProvider compiler = new CSharpCodeProvider(); CompilerResults result = compiler.CompileAssemblyFromFile(compParams, codeFilename); if (result.Errors.Count > 0) { throw new System.Exception("There was an error(s) during compilation of test program:\n" + result.Errors[0].ToString()); } return exeFilename; } string CopyThisAssembly() { Assembly assembly = Assembly.GetExecutingAssembly(); string md5 = ToHexadecimal(new MD5CryptoServiceProvider().ComputeHash(File.ReadAllBytes(assembly.Location))); string exeName = Path.GetFileName(assembly.Location); string pdbName = Path.GetFileNameWithoutExtension(assembly.Location) + ".pdb"; string oldDir = Path.GetDirectoryName(assembly.Location); string newDir = Path.GetTempPath(); newDir = Path.Combine(newDir, "SharpDevelop3.0"); newDir = Path.Combine(newDir, "DebuggerTests"); newDir = Path.Combine(newDir, md5); Directory.CreateDirectory(newDir); if (!File.Exists(Path.Combine(newDir, exeName))) { File.Copy(Path.Combine(oldDir, exeName), Path.Combine(newDir, exeName)); } if (!File.Exists(Path.Combine(newDir, pdbName))) { File.Copy(Path.Combine(oldDir, pdbName), Path.Combine(newDir, pdbName)); } return Path.Combine(newDir, exeName); } void CopyPdb() { Assembly assembly = Assembly.GetExecutingAssembly(); string dir = Path.GetDirectoryName(assembly.Location); string iniFilePath = Path.Combine(dir, "__AssemblyInfo__.ini"); string iniFileContent = File.ReadAllText(iniFilePath, Encoding.Unicode); string originalExePath = iniFileContent.Remove(0, iniFileContent.IndexOf("file:///") + "file:///".Length).TrimEnd(' ', '\0'); string originalDir = Path.GetDirectoryName(originalExePath); string originalPdbPath = Path.Combine(originalDir, Path.GetFileNameWithoutExtension(originalExePath) + ".pdb"); string pdbPath = Path.Combine(dir, Path.GetFileNameWithoutExtension(originalExePath) + ".pdb"); if (!File.Exists(pdbPath)) { File.Copy(originalPdbPath, pdbPath); } } static string ToHexadecimal(byte[] bytes) { char[] chars = new char[] {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',}; string hex = ""; foreach(byte b in bytes) { hex += chars[b >> 4]; hex += chars[b & 0x0F]; } return hex; } public static void Main(string[] args) { if (args.Length != 1) throw new System.Exception("Needs test name as argument"); string testName = args[0]; Type type = Type.GetType(testName); type.GetMethod("Main").Invoke(null, new object[0]); } } }