You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
581 lines
19 KiB
581 lines
19 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.CodeDom.Compiler; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Reflection; |
|
using System.Security.Cryptography; |
|
using System.Text; |
|
using System.Xml; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
using Microsoft.CSharp; |
|
using NUnit.Framework; |
|
|
|
namespace Debugger.Tests |
|
{ |
|
[TestFixture] |
|
public partial class DebuggerTests: DebuggerTestsBase |
|
{ |
|
|
|
} |
|
|
|
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 Thread CurrentThread { get; private set; } |
|
public StackFrame CurrentStackFrame { get; private set; } |
|
public Thread EvalThread { get { return this.CurrentThread; } } |
|
|
|
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<string>(); |
|
ignoreProperties = new List<string>(); |
|
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(bool hasXml = true) |
|
{ |
|
if (!process.HasExited) { |
|
process.AsyncContinue(); |
|
process.WaitForExit(); |
|
} |
|
if (hasXml) |
|
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); |
|
// Normalize the line endings |
|
sourceCode = sourceCode.Replace("\r\n", "\n").Replace("\n", "\r\n"); |
|
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; |
|
|
|
debugger.Options = new Options(); |
|
debugger.Options.EnableJustMyCode = true; |
|
debugger.Options.StepOverNoSymbols = true; |
|
debugger.Options.StepOverDebuggerAttributes = true; |
|
debugger.Options.StepOverAllProperties = false; |
|
debugger.Options.StepOverFieldAccessProperties = true; |
|
debugger.Options.SymbolsSearchPaths = new string[0]; |
|
debugger.Options.PauseOnHandledExceptions = false; |
|
|
|
log = ""; |
|
lastLogMessage = null; |
|
process = debugger.Start(exeFilename, Path.GetDirectoryName(exeFilename), testName, false); |
|
process.LogMessage += delegate(object sender, MessageEventArgs e) { |
|
log += e.Message; |
|
lastLogMessage = e.Message; |
|
LogEvent("LogMessage", e.Message.Replace("\r",@"\r").Replace("\n",@"\n")); |
|
}; |
|
process.ModuleLoaded += delegate(object sender, ModuleEventArgs e) { |
|
LogEvent("ModuleLoaded", e.Module.Name + (e.Module.HasSymbols ? " (Has symbols)" : " (No symbols)")); |
|
}; |
|
process.Paused += delegate(object sender, DebuggerPausedEventArgs e) { |
|
this.CurrentThread = e.Thread; |
|
if (e.Thread != null && e.Thread.IsInValidState) { |
|
this.CurrentStackFrame = e.Thread.MostRecentStackFrame; |
|
} else { |
|
this.CurrentStackFrame = null; |
|
} |
|
foreach(Thread exceptionThread in e.ExceptionsThrown) { |
|
Value exception = exceptionThread.CurrentException; |
|
LogEvent("ExceptionThrown", exception.Type.FullName); |
|
if (!exceptionThread.InterceptException()) { |
|
LogEvent("CanNotInterceptException", exception.Type.FullName); |
|
} |
|
} |
|
LogEvent("Paused", CurrentStackFrame != null && CurrentStackFrame.NextStatement != null ? CurrentStackFrame.NextStatement.ToString() : string.Empty); |
|
}; |
|
process.Exited += delegate(object sender, DebuggerEventArgs e) { |
|
LogEvent("Exited", null); |
|
}; |
|
|
|
LogEvent("Started", 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<object>()); |
|
} |
|
|
|
public void ObjectDump(string name, object obj) |
|
{ |
|
XmlElement dumpNode = testDoc.CreateElement(XmlConvert.EncodeName(name.Replace(" ", "_"))); |
|
testNode.AppendChild(dumpNode); |
|
Serialize(dumpNode, obj, 16, new List<object>()); |
|
} |
|
|
|
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())); |
|
} |
|
} |
|
|
|
class LocalVariable |
|
{ |
|
public string Name { get; set; } |
|
public IType Type { get; set; } |
|
public Value Value { get; set; } |
|
} |
|
|
|
public void DumpLocalVariables() |
|
{ |
|
DumpLocalVariables("LocalVariables"); |
|
} |
|
|
|
public void DumpLocalVariables(string msg) |
|
{ |
|
ObjectDump( |
|
msg, |
|
this.CurrentStackFrame.GetLocalVariables(this.CurrentStackFrame.IP).Select(v => new LocalVariable() { Name = v.Name, Type = v.Type, Value = v.GetValue(this.CurrentStackFrame)}) |
|
); |
|
} |
|
|
|
List<string> expandProperties; |
|
List<string> ignoreProperties; |
|
|
|
protected void ExpandProperties(params string[] props) |
|
{ |
|
expandProperties = new List<string>(props); |
|
} |
|
|
|
bool ListContains(List<string> 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<object> 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<object>(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<MemberInfo> members = new List<MemberInfo>(); |
|
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<string> vals = new List<string>(); |
|
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++; |
|
} |
|
} |
|
} |
|
|
|
protected 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.ReferencedAssemblies.Add("System.Core.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]); |
|
} |
|
|
|
protected static bool IsDotnet45Installed() |
|
{ |
|
Version dotnet45Beta = new Version(4, 0, 30319, 17379); |
|
return Environment.Version >= dotnet45Beta; |
|
} |
|
} |
|
}
|
|
|