Functional testing framework, initial commit.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 10 Apr 2019 14:56:01 +0000 (17:56 +0300)
committerMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 8 May 2019 10:29:51 +0000 (13:29 +0300)
40 files changed:
test-suite/LocalDebugger/LocalDebugger.csproj [new file with mode: 0644]
test-suite/LocalDebugger/LocalDebuggerProcess.cs [new file with mode: 0644]
test-suite/MIExampleTest/MIExampleTest.csproj [new file with mode: 0644]
test-suite/MIExampleTest/Program.cs [new file with mode: 0644]
test-suite/MIExampleTest/Program_second_source.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Breakpoint.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Checkpoint.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Debuggee.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/DebuggeeInfo.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/DebuggeeScript.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/DebuggerClient.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Exception.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Label.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/Logger.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MIBreakpoint.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MIDebugger.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MILocalDebuggerClient.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MIObject.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MIParser.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/MI/MITcpDebuggerClient.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/NetcoreDbgTest.csproj [new file with mode: 0644]
test-suite/NetcoreDbgTest/Script.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeBreakpoint.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeDebugger.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeLocaleDebuggerClient.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeProtocol.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolEvent.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolRequest.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolResponse.cs [new file with mode: 0644]
test-suite/NetcoreDbgTest/VSCode/VSCodeTcpDebuggerClient.cs [new file with mode: 0644]
test-suite/README.md [new file with mode: 0644]
test-suite/TestRunner/TestRunner.cs [new file with mode: 0644]
test-suite/TestRunner/TestRunner.csproj [new file with mode: 0644]
test-suite/VSCodeExampleTest/Program.cs [new file with mode: 0644]
test-suite/VSCodeExampleTest/VSCodeExampleTest.csproj [new file with mode: 0644]
test-suite/run_tests.ps1 [new file with mode: 0644]
test-suite/run_tests.sh [new file with mode: 0755]
test-suite/sdb_run_tests.ps1 [new file with mode: 0644]
test-suite/sdb_run_tests.sh [new file with mode: 0755]
test-suite/test-suite.sln [new file with mode: 0644]

diff --git a/test-suite/LocalDebugger/LocalDebugger.csproj b/test-suite/LocalDebugger/LocalDebugger.csproj
new file mode 100644 (file)
index 0000000..6203fd7
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+  <PropertyGroup>\r
+    <OutputType>Library</OutputType>\r
+    <TargetFramework>netcoreapp2.1</TargetFramework>\r
+  </PropertyGroup>\r
+\r
+</Project>\r
diff --git a/test-suite/LocalDebugger/LocalDebuggerProcess.cs b/test-suite/LocalDebugger/LocalDebuggerProcess.cs
new file mode 100644 (file)
index 0000000..db0b516
--- /dev/null
@@ -0,0 +1,38 @@
+using System.IO;
+using System.Diagnostics;
+
+namespace LocalDebugger
+{
+    public class LocalDebuggerProcess
+    {
+        public LocalDebuggerProcess(string debuggerPath, string debuggerArg)
+        {
+            DebuggerProcess = new Process();
+            DebuggerProcess.StartInfo.FileName = debuggerPath;
+            DebuggerProcess.StartInfo.Arguments = debuggerArg;
+            DebuggerProcess.StartInfo.UseShellExecute = false;
+            DebuggerProcess.StartInfo.RedirectStandardInput = true;
+            DebuggerProcess.StartInfo.RedirectStandardOutput = true;
+            Input = null;
+            Output = null;
+        }
+
+        public void Start()
+        {
+            DebuggerProcess.Start();
+            Input = DebuggerProcess.StandardInput;
+            Output = DebuggerProcess.StandardOutput;
+        }
+
+        public void Close()
+        {
+            DebuggerProcess.Kill();
+            DebuggerProcess.WaitForExit();
+            DebuggerProcess.Dispose();
+        }
+
+        public StreamWriter Input;
+        public StreamReader Output;
+        public Process DebuggerProcess;
+    }
+}
diff --git a/test-suite/MIExampleTest/MIExampleTest.csproj b/test-suite/MIExampleTest/MIExampleTest.csproj
new file mode 100644 (file)
index 0000000..ae80ae2
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+  </ItemGroup>
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+  </PropertyGroup>
+
+</Project>
diff --git a/test-suite/MIExampleTest/Program.cs b/test-suite/MIExampleTest/Program.cs
new file mode 100644 (file)
index 0000000..ce0704c
--- /dev/null
@@ -0,0 +1,186 @@
+using System;
+using System.IO;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.MI;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+
+namespace NetcoreDbgTest.Script
+{
+    // Context includes methods and constants which
+    // will be move to debugger API
+    class Context
+    {
+        public static void Prepare()
+        {
+            Assert.Equal(MIResultClass.Done,
+                         MIDebugger.Request("-file-exec-and-symbols "
+                                            + DebuggeeInfo.CorerunPath).Class);
+
+            Assert.Equal(MIResultClass.Done,
+                         MIDebugger.Request("-exec-arguments "
+                                            + DebuggeeInfo.TargetAssemblyPath).Class);
+
+            Assert.Equal(MIResultClass.Running, MIDebugger.Request("-exec-run").Class);
+        }
+
+        public static bool IsStoppedEvent(MIOutOfBandRecord record)
+        {
+            if (record.Type != MIOutOfBandRecordType.Async) {
+                return false;
+            }
+
+            var asyncRecord = (MIAsyncRecord)record;
+
+            if (asyncRecord.Class != MIAsyncRecordClass.Exec ||
+                asyncRecord.Output.Class != MIAsyncOutputClass.Stopped) {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static void WasEntryPointHit()
+        {
+            // TODO: Implement API for comfortable searching
+            // of out-of-band records
+            var records = MIDebugger.Receive();
+
+            foreach (MIOutOfBandRecord record in records) {
+                if (!IsStoppedEvent(record)) {
+                    continue;
+                }
+
+                var output = ((MIAsyncRecord)record).Output;
+
+                // Currently let's believe that all *stopped events have
+                // a reason of stopping
+                var reason = (MIConst)output["reason"];
+
+                if (reason.CString != "entry-point-hit") {
+                    continue;
+                }
+
+                var frame = (MITuple)(output["frame"]);
+                var func = (MIConst)(frame["func"]);
+                if (func.CString == DebuggeeInfo.TestName + ".Program.Main()") {
+                    return;
+                }
+            }
+
+            throw new NetcoreDbgTestCore.ResultNotSuccessException();
+        }
+
+        public static void WasBreakpointHit(Breakpoint breakpoint)
+        {
+            var records = MIDebugger.Receive();
+            var bp = (LineBreakpoint)breakpoint;
+
+            foreach (MIOutOfBandRecord record in records) {
+                if (!IsStoppedEvent(record)) {
+                    continue;
+                }
+
+                var output = ((MIAsyncRecord)record).Output;
+                var reason = (MIConst)output["reason"];
+
+                if (reason.CString != "breakpoint-hit") {
+                    continue;
+                }
+
+                var frame = (MITuple)(output["frame"]);
+                var fileName = (MIConst)(frame["file"]);
+                var numLine = (MIConst)(frame["line"]);
+
+                if (fileName.CString == bp.FileName &&
+                    numLine.CString == bp.NumLine.ToString()) {
+                    return;
+                }
+            }
+
+            throw new NetcoreDbgTestCore.ResultNotSuccessException();
+        }
+
+        public static void WasExit()
+        {
+            var records = MIDebugger.Receive();
+
+            foreach (MIOutOfBandRecord record in records) {
+                if (!IsStoppedEvent(record)) {
+                    continue;
+                }
+
+                var output = ((MIAsyncRecord)record).Output;
+                var reason = (MIConst)output["reason"];
+
+                if (reason.CString != "exited") {
+                    continue;
+                }
+
+                var exitCode = (MIConst)output["exit-code"];
+
+                if (exitCode.CString == "0") {
+                    return;
+                } else {
+                    throw new NetcoreDbgTestCore.ResultNotSuccessException();
+                }
+            }
+
+            throw new NetcoreDbgTestCore.ResultNotSuccessException();
+        }
+
+        public static void EnableBreakpoint(string bpName)
+        {
+            Breakpoint bp = DebuggeeInfo.Breakpoints[bpName];
+
+            Assert.Equal(BreakpointType.Line, bp.Type);
+
+            var lbp = (LineBreakpoint)bp;
+
+            Assert.Equal(MIResultClass.Done,
+                         MIDebugger.Request("-break-insert -f "
+                                            + lbp.FileName + ":" + lbp.NumLine).Class);
+        }
+
+        public static void Continue()
+        {
+            Assert.Equal(MIResultClass.Running, MIDebugger.Request("-exec-continue").Class);
+        }
+
+        static MIDebugger MIDebugger = new MIDebugger();
+    }
+}
+
+namespace MIExampleTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // first checkpoint (initialization) must provide "init" as id
+            Label.Checkpoint("init", "bp_test", () => {
+                Context.Prepare();
+                Context.WasEntryPointHit();
+                Context.EnableBreakpoint("bp");
+                Context.EnableBreakpoint("bp2");
+                Context.Continue();
+            });
+
+            Console.WriteLine("A breakpoint \"bp\" is set on this line"); Label.Breakpoint("bp");
+
+            Label.Checkpoint("bp_test", "bp2_test", () => {
+                Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp"]);
+                Context.Continue();
+            });
+
+            MIExampleTest2.Program.testfunc();
+
+            // last checkpoint must provide "finish" as id or empty string ("") as next checkpoint id
+            Label.Checkpoint("finish", "", () => {
+                Context.WasExit();
+            });
+        }
+    }
+}
diff --git a/test-suite/MIExampleTest/Program_second_source.cs b/test-suite/MIExampleTest/Program_second_source.cs
new file mode 100644 (file)
index 0000000..b417e45
--- /dev/null
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.MI;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+
+namespace MIExampleTest2
+{
+    class Program
+    {
+        public static void testfunc()
+        {
+            Console.WriteLine("A breakpoint \"bp2\" is set on this line"); Label.Breakpoint("bp2");
+
+            Label.Checkpoint("bp2_test", "finish", () => {
+                Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp2"]);
+                Context.Continue();
+            });
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/Breakpoint.cs b/test-suite/NetcoreDbgTest/Breakpoint.cs
new file mode 100644 (file)
index 0000000..f32be68
--- /dev/null
@@ -0,0 +1,39 @@
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+    public enum BreakpointType {
+        None,
+        Line
+    }
+
+    public class Breakpoint
+    {
+        public Breakpoint(string name, BreakpointType type = BreakpointType.None)
+        {
+            Name = name;
+            Type = type;
+        }
+
+        public string Name;
+        public BreakpointType Type;
+    }
+
+    public class LineBreakpoint : Breakpoint
+    {
+        public LineBreakpoint(string name,
+                              string fileName,
+                              int numLine,
+                              ProtocolType protocol = ProtocolType.None)
+            :base(name, BreakpointType.Line)
+        {
+            FileName = fileName;
+            NumLine = numLine;
+            Protocol = protocol;
+        }
+
+        public string FileName;
+        public int NumLine;
+        public ProtocolType Protocol;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/Checkpoint.cs b/test-suite/NetcoreDbgTest/Checkpoint.cs
new file mode 100644 (file)
index 0000000..e8ef481
--- /dev/null
@@ -0,0 +1,4 @@
+namespace NetcoreDbgTestCore
+{
+    public delegate void Checkpoint();
+}
diff --git a/test-suite/NetcoreDbgTest/Debuggee.cs b/test-suite/NetcoreDbgTest/Debuggee.cs
new file mode 100644 (file)
index 0000000..f65cb47
--- /dev/null
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+using NetcoreDbgTest;
+
+namespace NetcoreDbgTestCore
+{
+    public static class Debuggee
+    {
+        public static DebuggerClient DebuggerClient = null;
+        public static Dictionary<string, Breakpoint> Breakpoints = null;
+        public static string TestName = null;
+        public static string SourceFilesPath = null;
+        public static string TargetAssemblyPath = null;
+        public static string CorerunPath = null;
+
+        public static void Invoke(string id, string next_id, Checkpoint checkpoint)
+        {
+            checkpoint();
+        }
+
+        public static void Run(DebuggeeScript script,
+                               DebuggerClient debugger,
+                               NetcoreDbgTestCore.Environment env)
+        {
+            DebuggerClient = debugger;
+            Breakpoints = script.Breakpoints;
+            TestName = env.TestName;
+            SourceFilesPath = env.SourceFilesPath;
+            TargetAssemblyPath = env.TargetAssemblyPath;
+            CorerunPath = env.CorerunPath;
+
+            script.ExecuteCheckPoints();
+
+            DebuggerClient = null;
+            Breakpoints = null;
+            TestName = null;
+            SourceFilesPath = null;
+            TargetAssemblyPath = null;
+            CorerunPath = null;
+        }
+    }
+
+    public class Environment
+    {
+        public string TestName = null;
+        public string SourceFilesPath = null;
+        public string TargetAssemblyPath = null;
+        public string CorerunPath = "dotnet";
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/DebuggeeInfo.cs b/test-suite/NetcoreDbgTest/DebuggeeInfo.cs
new file mode 100644 (file)
index 0000000..622786b
--- /dev/null
@@ -0,0 +1,32 @@
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+    public static class DebuggeeInfo
+    {
+        public static System.Collections.Generic.Dictionary<string, Breakpoint> Breakpoints
+        {
+            get { return Debuggee.Breakpoints; }
+        }
+
+        public static string TestName
+        {
+            get { return Debuggee.TestName; }
+        }
+
+        public static string SourceFilesPath
+        {
+            get { return Debuggee.SourceFilesPath; }
+        }
+
+        public static string TargetAssemblyPath
+        {
+            get { return Debuggee.TargetAssemblyPath; }
+        }
+
+        public static string CorerunPath
+        {
+            get { return Debuggee.CorerunPath; }
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/DebuggeeScript.cs b/test-suite/NetcoreDbgTest/DebuggeeScript.cs
new file mode 100644 (file)
index 0000000..a9fc0f0
--- /dev/null
@@ -0,0 +1,361 @@
+using System;
+using System.Runtime;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Collections.Generic;
+using System.Text;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+using NetcoreDbgTest;
+
+namespace NetcoreDbgTestCore
+{
+    public class ScriptNotBuiltException : Exception
+    {
+        public ScriptNotBuiltException(EmitResult result)
+        {
+            Result = result;
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+
+            IEnumerable<Diagnostic> failures = Result.Diagnostics.Where(diagnostic =>
+                diagnostic.IsWarningAsError ||
+                diagnostic.Severity == DiagnosticSeverity.Error);
+
+            foreach (Diagnostic diagnostic in failures) {
+                sb.Append(String.Format("{0}: {1}\n", diagnostic.Id, diagnostic.GetMessage()));
+            }
+
+            return sb.ToString();
+        }
+
+        EmitResult Result;
+    }
+
+    public class DebuggeeScript
+    {
+        public DebuggeeScript(string pathToTestFiles, ProtocolType protocolType)
+        {
+            DebuggeeScriptDummyText =
+@"using System;
+using System.IO;
+using System.Diagnostics;
+using System.Collections.Generic;
+using NetcoreDbgTest;
+using NetcoreDbgTest.Script;
+using Xunit;
+using Newtonsoft.Json;
+
+using NetcoreDbgTest." + protocolType.ToString() + @";
+
+namespace NetcoreDbgTest.Script
+{
+}
+
+namespace NetcoreDbgTestCore
+{
+    public class GeneratedScript {
+        public static void ExecuteCheckPoints() {}
+    }
+}";
+
+            // we may have list of files separated by ';' symbol
+            string[] pathToFiles = pathToTestFiles.Split(';');
+            List<SyntaxTree> trees = new List<SyntaxTree>();
+
+            foreach (string pathToFile in pathToFiles) {
+                if (String.IsNullOrEmpty(pathToFile)) {
+                    continue;
+                }
+                string testSource = File.ReadAllText(pathToFile);
+                SyntaxTree testTree = CSharpSyntaxTree.ParseText(testSource)
+                              .WithFilePath(pathToFile);
+                trees.Add(testTree);
+            }
+
+            TestLabelsInfo = CollectTestLabelsInfo(trees, protocolType);
+            ScriptDeclarations = CollectScriptDeclarations(trees);
+            SyntaxTree = BuildTree();
+            Compilation compilation = CompileTree(SyntaxTree);
+            Assembly scriptAssembly = MakeAssembly(compilation);
+            generatedScriptClass = scriptAssembly.GetType("NetcoreDbgTestCore.GeneratedScript");
+            Breakpoints = TestLabelsInfo.Breakpoints;
+        }
+
+        public Dictionary<string, Breakpoint> Breakpoints;
+
+        public SyntaxTree SyntaxTree;
+
+        public void ExecuteCheckPoints()
+        {
+            MethodInfo minfo = generatedScriptClass.GetMethod("ExecuteCheckPoints");
+            try {
+                minfo.Invoke(null, null);
+            } catch (TargetInvocationException e) {
+                throw e.InnerException;
+            }
+        }
+
+        private TestLabelsInfo CollectTestLabelsInfo(List<SyntaxTree> trees, ProtocolType protocolType)
+        {
+            var visitor = new TestLabelsInfoCollector(protocolType);
+
+            foreach (SyntaxTree tree in trees) {
+                visitor.Visit(tree.GetRoot());
+            }
+
+            return visitor.TestLabelsInfo;
+        }
+
+        private SyntaxList<MemberDeclarationSyntax> CollectScriptDeclarations(List<SyntaxTree> trees)
+        {
+            var visitor = new ScriptDeclarationsCollector();
+
+            foreach (SyntaxTree tree in trees) {
+                visitor.Visit(tree.GetRoot());
+            }
+
+            return visitor.ScriptDeclarations;
+        }
+
+        private SyntaxTree BuildTree()
+        {
+            var tree = CSharpSyntaxTree.ParseText(DebuggeeScriptDummyText);
+
+            CSharpSyntaxRewriter visitor;
+
+            visitor = new GeneratedScriptInvokesBuilder(TestLabelsInfo);
+            tree = ((GeneratedScriptInvokesBuilder) (visitor)).Visit(tree.GetRoot()).SyntaxTree;
+            visitor = new GeneratedScriptDeclarationsBuilder(ScriptDeclarations);
+            tree = ((GeneratedScriptDeclarationsBuilder) (visitor)).Visit(tree.GetRoot()).SyntaxTree;
+
+            return tree;
+        }
+
+        private MetadataReference[] GetMetadataReferences()
+        {
+            var systemPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
+            var libList = new List<MetadataReference>();
+
+            string[] libNames = {
+                "System.Private.CoreLib.dll",
+                "System.Runtime.dll",
+                "System.Collections.dll",
+                "System.IO.FileSystem.dll",
+                "System.Diagnostics.Process.dll",
+                "System.ComponentModel.Primitives.dll",
+                "netstandard.dll",
+            };
+
+            foreach (var name in libNames) {
+                libList.Add(MetadataReference.CreateFromFile(Path.Combine(systemPath, name)));
+            }
+
+            libList.Add(MetadataReference.CreateFromFile(typeof(Label).Assembly.Location));
+            libList.Add(MetadataReference.CreateFromFile(typeof(Xunit.Assert).Assembly.Location));
+            libList.Add(MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonConvert).Assembly.Location));
+
+            return libList.ToArray();
+        }
+
+        private Compilation CompileTree(SyntaxTree tree)
+        {
+            var compilation = CSharpCompilation.Create(
+                "DebuggeeScript",
+                new SyntaxTree[] {tree},
+                GetMetadataReferences(),
+                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+            );
+
+            return compilation;
+        }
+
+        private Assembly MakeAssembly(Compilation compilation)
+        {
+            using (var ms = new MemoryStream())
+            {
+                EmitResult result = compilation.Emit(ms);
+
+                if (!result.Success) {
+                    Console.WriteLine("*** Build Error log start ***");
+                    foreach (var line in result.Diagnostics) {
+                        Console.WriteLine(line);
+                    }
+                    Console.WriteLine("*** Build Error log end ***");
+                    throw new ScriptNotBuiltException(result);
+                }
+
+                ms.Seek(0, SeekOrigin.Begin);
+                return Assembly.Load(ms.ToArray());
+            }
+        }
+
+        private Type generatedScriptClass = null;
+        private TestLabelsInfo TestLabelsInfo = null;
+        private SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+        private static string DebuggeeScriptDummyText;
+    }
+
+    public class TestLabelsInfo
+    {
+        public TestLabelsInfo()
+        {
+            CheckPointInvokes = new Dictionary<string, Tuple<string, InvocationExpressionSyntax>>();
+            Breakpoints = new Dictionary<string, Breakpoint>();
+        }
+
+        public Dictionary<string, Tuple<string, InvocationExpressionSyntax>> CheckPointInvokes;
+        public Dictionary<string, Breakpoint> Breakpoints;
+    }
+
+    public class TestLabelsInfoCollector : CSharpSyntaxWalker
+    {
+        public TestLabelsInfoCollector(ProtocolType protocolType)
+        {
+            TestLabelsInfo = new TestLabelsInfo();
+            TypeClassBP = Type.GetType("NetcoreDbgTest." + protocolType.ToString() + "."
+                                       + protocolType.ToString() +"LineBreakpoint");
+        }
+
+        public override void VisitInvocationExpression(InvocationExpressionSyntax node)
+        {
+            string fileName = Path.GetFileName(node.SyntaxTree.FilePath);
+            FileLinePositionSpan lineSpan = node.SyntaxTree.GetLineSpan(node.Span);
+            int numLine = lineSpan.StartLinePosition.Line + 1;
+
+            switch (node.Expression.ToString()) {
+                case "Label.Breakpoint":
+                    string name = node.ArgumentList.Arguments[0].Expression.ToString();
+                    // "name" -> name
+                    name = name.Trim(new char[]{ '\"' });
+
+                    LineBreakpoint lbp = (LineBreakpoint)Activator.CreateInstance(TypeClassBP,
+                                                                                  name, fileName, numLine);
+
+                    TestLabelsInfo.Breakpoints.Add(name, lbp);
+                    break;
+                case "Label.Checkpoint":
+                    string id = node.ArgumentList.Arguments[0].Expression.ToString();
+                    string next_id = node.ArgumentList.Arguments[1].Expression.ToString();
+                    // "id" -> id
+                    id = id.Trim(new char[]{ '\"' });
+                    next_id = next_id.Trim(new char[]{ '\"' });
+
+                    TestLabelsInfo.CheckPointInvokes.Add(
+                        id, new Tuple<string, InvocationExpressionSyntax>(next_id, node));
+                    break;
+            }
+        }
+
+        public TestLabelsInfo TestLabelsInfo;
+        private Type TypeClassBP;
+    }
+
+    public class GeneratedScriptInvokesBuilder : CSharpSyntaxRewriter
+    {
+        public GeneratedScriptInvokesBuilder(TestLabelsInfo testLabels)
+        {
+            TestLabelsInfo = testLabels;
+        }
+
+        public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
+        {
+            var debuggeeEnterMember =
+                SyntaxFactory.MemberAccessExpression(
+                    SyntaxKind.SimpleMemberAccessExpression,
+                    SyntaxFactory.IdentifierName("Debuggee"),
+                    SyntaxFactory.IdentifierName("Invoke")
+                );
+
+            List<InvocationExpressionSyntax> invokes = new List<InvocationExpressionSyntax>();
+
+            switch (node.Identifier.ToString()) {
+                case "ExecuteCheckPoints":
+                    string key = "init";
+                    Tuple<string, InvocationExpressionSyntax> Value;
+                    do {
+                        if (TestLabelsInfo.CheckPointInvokes.TryGetValue(key, out Value)) {
+                            invokes.Add(Value.Item2);
+                            if (key == "finish") {
+                                key = null;
+                            } else {
+                                key = Value.Item1;
+                            }
+                        } else {
+                            Console.Error.WriteLine("Error! Can't find \"" + key + "\" checkpoint");
+                            throw new ResultNotSuccessException();
+                        }
+                    } while (!String.IsNullOrEmpty(key));
+                    break;
+                default:
+                    return node;
+            }
+
+            List<StatementSyntax> statements = new List<StatementSyntax>();
+
+            foreach (var invoke in invokes) {
+                var invokeEnter =
+                    SyntaxFactory.InvocationExpression(debuggeeEnterMember)
+                    .WithArgumentList(invoke.ArgumentList);
+
+                statements.Add(SyntaxFactory.ExpressionStatement(invokeEnter));
+            }
+
+            return node.WithBody(SyntaxFactory.Block(statements.ToArray()));
+        }
+
+        private TestLabelsInfo TestLabelsInfo;
+    }
+
+    public class ScriptDeclarationsCollector : CSharpSyntaxWalker
+    {
+        public ScriptDeclarationsCollector()
+        {
+            ScriptDeclarations = new SyntaxList<MemberDeclarationSyntax>(
+                new List<MemberDeclarationSyntax>()
+            );
+        }
+
+        public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+        {
+            if (node.Name.ToString() == "NetcoreDbgTest.Script") {
+                ScriptDeclarations = ScriptDeclarations.AddRange(node.Members);
+                return;
+            }
+
+            foreach (SyntaxNode subNode in node.Members) {
+                this.Visit(subNode);
+            }
+        }
+
+        public SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+    }
+
+    public class GeneratedScriptDeclarationsBuilder : CSharpSyntaxRewriter
+    {
+        public GeneratedScriptDeclarationsBuilder(SyntaxList<MemberDeclarationSyntax> declarations)
+        {
+            ScriptDeclarations = declarations;
+        }
+
+        public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+        {
+            if (node.Name.ToString() == "NetcoreDbgTest.Script") {
+                return node.AddMembers(ScriptDeclarations.ToArray());
+            }
+
+            return node;
+        }
+
+        private SyntaxList<MemberDeclarationSyntax> ScriptDeclarations;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/DebuggerClient.cs b/test-suite/NetcoreDbgTest/DebuggerClient.cs
new file mode 100644 (file)
index 0000000..3e83e75
--- /dev/null
@@ -0,0 +1,43 @@
+namespace NetcoreDbgTestCore
+{
+    public enum ProtocolType {
+        None,
+        MI,
+        VSCode,
+    }
+
+    public class DebuggerClient
+    {
+        public DebuggerClient(ProtocolType protocol)
+        {
+            Protocol = protocol;
+        }
+
+        // Protocol specific handshake gives a guarantee
+        // of a debugger ability to receive commands and
+        // response messages
+        public virtual bool DoHandshake(int timeout)
+        {
+            return false;
+        }
+
+        // Send command to debugger
+        public virtual bool Send(string cmd)
+        {
+            return false;
+        }
+
+        // Receive protocol specific response
+        public virtual string[] Receive(int timeout)
+        {
+            return null;
+        }
+
+        // Close session with debugger
+        public virtual void Close()
+        {
+        }
+
+        public ProtocolType Protocol;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/Exception.cs b/test-suite/NetcoreDbgTest/Exception.cs
new file mode 100644 (file)
index 0000000..e4a8d58
--- /dev/null
@@ -0,0 +1,14 @@
+namespace NetcoreDbgTestCore
+{
+    public class Exception : System.Exception
+    {
+    }
+
+    public class ResultNotSuccessException : Exception
+    {
+    }
+
+    public class DebuggerNotResponsesException : Exception
+    {
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/Label.cs b/test-suite/NetcoreDbgTest/Label.cs
new file mode 100644 (file)
index 0000000..06e5c92
--- /dev/null
@@ -0,0 +1,19 @@
+using System.Diagnostics;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+    static public class Label
+    {
+        [Conditional("TEST_LABEL")]
+        public static void Breakpoint(string name)
+        {
+        }
+
+        [Conditional("TEST_LABEL")]
+        public static void Checkpoint(string id, string next_id, Checkpoint checkpoint)
+        {
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/Logger.cs b/test-suite/NetcoreDbgTest/Logger.cs
new file mode 100644 (file)
index 0000000..55ed599
--- /dev/null
@@ -0,0 +1,17 @@
+using System;
+
+namespace NetcoreDbgTest
+{
+    public static class Logger
+    {
+        public static void LogLine(string line)
+        {
+            Console.WriteLine(line);
+        }
+
+        public static void LogError(string error)
+        {
+            Console.Error.WriteLine(error);
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MIBreakpoint.cs b/test-suite/NetcoreDbgTest/MI/MIBreakpoint.cs
new file mode 100644 (file)
index 0000000..cdafbaa
--- /dev/null
@@ -0,0 +1,20 @@
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+    namespace MI
+    {
+        class MILineBreakpoint : LineBreakpoint
+        {
+            public MILineBreakpoint(string name, string srcName, int lineNum)
+                : base(name, srcName, lineNum, ProtocolType.MI)
+            {
+            }
+
+            public override string ToString()
+            {
+                return System.String.Format("{0}:{1}", FileName, NumLine);
+            }
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MIDebugger.cs b/test-suite/NetcoreDbgTest/MI/MIDebugger.cs
new file mode 100644 (file)
index 0000000..2a82232
--- /dev/null
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.MI;
+
+namespace NetcoreDbgTest.MI
+{
+    public class MIDebugger
+    {
+        public MIResultRecord Request(string command, int timeout = -1)
+        {
+            MIResultRecord resultRecord = null;
+
+            Logger.LogLine("> " + command);
+
+            if (!Debuggee.DebuggerClient.Send(command)) {
+                throw new DebuggerNotResponsesException();
+            }
+
+            while (true) {
+                string[] response = Debuggee.DebuggerClient.Receive(timeout);
+
+                if (response == null) {
+                    throw new DebuggerNotResponsesException();
+                }
+
+                foreach (string line in response) {
+                    Logger.LogLine("< " + line);
+                }
+
+                MIOutput output = MIParser.ParseOutput(response);
+
+                if (output.ResultRecord != null) {
+                    resultRecord = output.ResultRecord;
+                    break;
+                }
+            }
+
+            return resultRecord;
+        }
+
+        public MIOutOfBandRecord[] Receive(int timeout = -1)
+        {
+            string[] response = Debuggee.DebuggerClient.Receive(timeout);
+
+            if (response == null) {
+                throw new DebuggerNotResponsesException();
+            }
+
+            foreach (string line in response) {
+                Logger.LogLine("< " + line);
+            }
+
+            MIOutput output = MIParser.ParseOutput(response);
+
+            if (output.ResultRecord != null) {
+                // this output must hasn't result record
+                throw new MIParserException();
+            }
+
+            OutOfBandRecords.AddRange(output.OutOfBandRecords);
+
+            return OutOfBandRecords.ToArray();
+        }
+
+        List<MIOutOfBandRecord> OutOfBandRecords = new List<MIOutOfBandRecord>();
+        MIParser MIParser = new MIParser();
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MILocalDebuggerClient.cs b/test-suite/NetcoreDbgTest/MI/MILocalDebuggerClient.cs
new file mode 100644 (file)
index 0000000..49ae7f7
--- /dev/null
@@ -0,0 +1,86 @@
+using System.IO;
+using System.Collections.Generic;
+using System.Threading;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTestCore.MI
+{
+    public class MILocalDebuggerClient : DebuggerClient
+    {
+        public MILocalDebuggerClient(StreamWriter input, StreamReader output)
+            : base(ProtocolType.MI)
+        {
+            DebuggerInput = input;
+            DebuggerOutput = output;
+            GetInput = new AutoResetEvent(false);
+            GotInput = new AutoResetEvent(false);
+            InputThread = new Thread(ReaderThread);
+            InputThread.IsBackground = true;
+            InputThread.Start();
+        }
+
+        public override bool DoHandshake(int timeout)
+        {
+            string[] output = ReceiveOutputLines(timeout);
+
+            return output != null && output.Length == 1;
+        }
+
+        public override bool Send(string command)
+        {
+            DebuggerInput.WriteLine(command);
+
+            return true;
+        }
+
+        public override string[] Receive(int timeout)
+        {
+            return ReceiveOutputLines(timeout);
+        }
+
+        public override void Close()
+        {
+            DebuggerInput.Close();
+            DebuggerOutput.Close();
+        }
+
+        private void ReaderThread() {
+            while (true) {
+                GetInput.WaitOne();
+                InputString = DebuggerOutput.ReadLine();
+                GotInput.Set();
+            }
+        }
+
+        string[] ReceiveOutputLines(int timeout)
+        {
+            var output = new List<string>();
+
+            while (true) {
+                GetInput.Set();
+                bool success = GotInput.WaitOne(timeout);
+                if (!success)
+                    throw new DebuggerNotResponsesException();
+
+                if (InputString == null) {
+                    return null;
+                }
+
+                output.Add(InputString);
+
+                if (InputString == "(gdb)") {
+                    break;
+                }
+            }
+
+            return output.ToArray();
+        }
+
+        StreamWriter DebuggerInput;
+        StreamReader DebuggerOutput;
+        private Thread InputThread;
+        private AutoResetEvent GetInput, GotInput;
+        private string InputString;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MIObject.cs b/test-suite/NetcoreDbgTest/MI/MIObject.cs
new file mode 100644 (file)
index 0000000..bedaca3
--- /dev/null
@@ -0,0 +1,602 @@
+using System;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.MI
+{
+    public enum MIValueType
+    {
+        Const,
+        Tuple,
+        List,
+    }
+
+    public enum MIListElementType
+    {
+        Value,
+        Result,
+    }
+
+    public enum MIOutOfBandRecordType
+    {
+        Async,
+        Stream,
+    }
+
+    public class MIOutput
+    {
+        public MIOutput(MIOutOfBandRecord[] outOfBandRecords,
+                        MIResultRecord resultRecord)
+        {
+            OutOfBandRecords = outOfBandRecords;
+            ResultRecord = resultRecord;
+        }
+
+        public MIOutOfBandRecord[] OutOfBandRecords;
+        public MIResultRecord ResultRecord;
+    }
+
+    public class MIResultRecord
+    {
+        public MIResultRecord(MIToken token,
+                              MIResultClass resultClass,
+                              MIResult[] results)
+        {
+            Token = token;
+            Class = resultClass;
+            Results = new Dictionary<string, MIValue>();
+
+            if (results == null) {
+                return;
+            }
+
+            foreach (MIResult result in results) {
+                Results.Add(result.Variable, result.Value);
+            }
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder ();
+
+            if (Token != null) {
+                sb.Append(Token.ToString());
+            }
+
+            sb.Append("^");
+
+            sb.Append(Class.ToString());
+
+            foreach (KeyValuePair<string, MIValue>pair in Results) {
+                sb.Append("," + pair.Value.ToString());
+            }
+
+            return sb.ToString();
+        }
+
+        public MIValue this[string variable]
+        {
+            get { return Results[variable]; }
+        }
+
+        public MIToken Token = null;
+        public MIResultClass Class = MIResultClass.Done;
+        private Dictionary<string, MIValue> Results;
+    }
+
+    public class MIOutOfBandRecord
+    {
+        public MIOutOfBandRecord(MIOutOfBandRecordType type)
+        {
+            Type = type;
+        }
+
+        public override string ToString()
+        {
+            return null;
+        }
+
+        public MIOutOfBandRecordType Type;
+    }
+
+    public class MIToken
+    {
+        public MIToken(ulong number)
+        {
+            Number = number;
+        }
+
+        public override string ToString()
+        {
+            return Number.ToString();
+        }
+
+        public ulong Number;
+    }
+
+    public class MIListElement
+    {
+        public MIListElement(MIListElementType type)
+        {
+            ElementType = type;
+        }
+
+        public override string ToString()
+        {
+            return null;
+        }
+
+        public MIListElementType ElementType;
+    }
+
+    public class MIValue : MIListElement
+    {
+        public MIValue(MIValueType type)
+            : base(MIListElementType.Value)
+        {
+        }
+
+        public override string ToString()
+        {
+            return null;
+        }
+
+        public MIListElementType Type;
+    }
+
+    public class MITuple : MIValue, IEnumerable
+    {
+        public MITuple() : base(MIValueType.Tuple)
+        {
+            Results = new Dictionary<string, MIValue>();
+        }
+
+        public MITuple(MIResult[] results) : base(MIValueType.Tuple)
+        {
+            Results = new Dictionary<string, MIValue>();
+
+            if (results == null) {
+                return;
+            }
+
+            foreach (MIResult result in results) {
+                Results.Add(result.Variable, result.Value);
+            }
+        }
+
+        public override string ToString()
+        {
+            bool firstElement = true;
+            var sb = new StringBuilder("{");
+
+            foreach (KeyValuePair<string, MIValue> pair in Results)
+            {
+                if (firstElement) {
+                    firstElement = false;
+                } else {
+                    sb.Append(",");
+                }
+
+                sb.Append(pair.Key + "=" + pair.Value.ToString());
+            }
+
+            sb.Append("}");
+
+            return sb.ToString();
+        }
+
+        public void Add(string variable, MIValue val)
+        {
+            Results.Add(variable, val);
+        }
+
+        public void Add(string variable, string val)
+        {
+            Results.Add(variable, new MIConst(val));
+        }
+
+        public MIValue this[string Variable]
+        {
+            get {
+                return Results[Variable];
+            }
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return Results.GetEnumerator();
+        }
+
+        private Dictionary<string, MIValue>Results;
+    }
+
+    public class MIList : MIValue, IEnumerable
+    {
+        public MIList() : base(MIValueType.List)
+        {
+            Elements = new List<MIListElement>();
+        }
+
+        public MIList(List<MIListElement> elements, MIListElementType elementType)
+            : base(MIValueType.List)
+        {
+            ElementsType = elementType;
+            Elements = elements;
+        }
+
+        public MIList(MIValue[] values) : base(MIValueType.List)
+        {
+            ElementsType = MIListElementType.Value;
+
+            Elements = new List<MIListElement>(values);
+        }
+
+        public MIListElement this[int index]
+        {
+            get {
+                return Elements[index];
+            }
+        }
+
+        public void Add(string cstring)
+        {
+            if (Elements.Count == 0) {
+                ElementsType = MIListElementType.Value;
+            }
+
+            Elements.Add(new MIConst(cstring));
+        }
+
+        public void Add(MIListElement element)
+        {
+            if (Elements.Count == 0) {
+                ElementsType = element.ElementType;
+            }
+
+            if (ElementsType != element.ElementType) {
+                throw new Exception();
+            }
+
+            Elements.Add(element);
+        }
+
+        public IEnumerator GetEnumerator()
+        {
+            return Elements.GetEnumerator();
+        }
+
+        public override string ToString()
+        {
+            bool firstElement = true;
+            var sb = new StringBuilder("[");
+
+            foreach (MIListElement element in Elements) {
+                if (firstElement) {
+                    firstElement = false;
+                } else {
+                    sb.Append(",");
+                }
+
+                sb.Append(element.ToString());
+            }
+
+            sb.Append("]");
+
+            return sb.ToString();
+        }
+
+        public MIListElement[] ToArray()
+        {
+            return Elements.ToArray();
+        }
+
+        public int Count
+        {
+            get { return Elements.Count; }
+        }
+
+        MIListElementType ElementsType;
+        List<MIListElement> Elements;
+    }
+
+    public class MIConst : MIValue
+    {
+        public MIConst(string cstring) : base(MIValueType.Const)
+        {
+            CString = cstring;
+        }
+
+        public override string ToString()
+        {
+            return "\"" + CString + "\"";
+        }
+
+        public string CString;
+
+        // return c-string without escape sequences
+        // https://en.wikipedia.org/wiki/Escape_sequences_in_C
+        // throw exception for invalid c-string
+        public string String
+        {
+            get {
+                var sb = new StringBuilder();
+                try {
+                    for (int i = 0; i < CString.Length; ) {
+                        if (CString[i] == '\\') {
+                            char c;
+                            int hex;
+                            switch (CString[i + 1]) {
+                            case 'a':
+                                c = '\a'; i += 2; break;
+                            case 'b':
+                                c = '\b'; i += 2; break;
+                            case 'f':
+                                c = '\f'; i += 2; break;
+                            case 'n':
+                                c = '\n'; i += 2; break;
+                            case 'r':
+                                c = '\r'; i += 2; break;
+                            case 't':
+                                c = '\t'; i += 2; break;
+                            case 'v':
+                                c = '\v'; i += 2; break;
+                            case '\\':
+                                c = '\\'; i += 2; break;
+                            case '\'':
+                                c = '\''; i += 2; break;
+                            case '\"':
+                                c = '\"'; i += 2; break;
+                            case '?':
+                                c = '\u003f'; i += 2; break;
+                            case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
+                                int num2 = CString[i + 2] - '0';
+                                int num1 = CString[i + 3] - '0';
+                                int num0 = CString[i + 4] - '0';
+                                c = (char)(num2 * 64 + num1 * 8 + num0);
+                                i += 5;
+                                break;
+                            case 'e':
+                                c = '\u001b'; i += 2; break;
+                            case 'U':
+                                hex = Int32.Parse(CString.Substring(i + 2, i + 10),
+                                                  System.Globalization.NumberStyles.HexNumber);
+                                c = (char)hex;
+                                i += 11;
+                                break;
+                            case 'u':
+                                hex = Int32.Parse(CString.Substring(i + 2, i + 6),
+                                                  System.Globalization.NumberStyles.HexNumber);
+                                c = (char)hex;
+                                i += 7;
+                                break;
+                            default:
+                                throw new FormatException();
+                            }
+                            sb.Append(c);
+                        } else {
+                            sb.Append(CString[i]);
+                            i++;
+                        }
+                    }
+                }
+                catch {
+                    throw new FormatException("Invalid c-string");
+                }
+
+                return sb.ToString();
+            }
+        }
+
+        public int Int
+        {
+            get { return Int32.Parse(CString); }
+        }
+    }
+
+    public class MIResult : MIListElement
+    {
+        public MIResult(string variable, MIValue val) : base(MIListElementType.Result)
+        {
+            Variable = variable;
+            Value = val;
+        }
+
+        public MIResult(string variable, string cstring) : base(MIListElementType.Result)
+        {
+            Variable = variable;
+            Value = new MIConst(cstring);
+        }
+
+        public override string ToString()
+        {
+            return Variable + "=" + Value.ToString();
+        }
+
+        public string Variable;
+        public MIValue Value;
+    }
+
+    public class MIAsyncRecord : MIOutOfBandRecord
+    {
+        public MIAsyncRecord(MIToken token, MIAsyncRecordClass cl, MIAsyncOutput output)
+            : base(MIOutOfBandRecordType.Async)
+        {
+            Token = token;
+            Class = cl;
+            Output = output;
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+
+            if (Token != null) {
+                sb.Append(Token.ToString());
+            }
+
+            sb.Append(Class.ToString() + Output.ToString());
+
+            return sb.ToString();
+        }
+
+        public MIToken Token;
+        public MIAsyncRecordClass Class;
+        public MIAsyncOutput Output;
+    }
+
+    public class MIStreamRecord : MIOutOfBandRecord
+    {
+        public MIStreamRecord(MIStreamRecordClass cl, MIConst constant)
+            : base(MIOutOfBandRecordType.Stream)
+        {
+            Class = cl;
+            Const = constant;
+        }
+
+        public override string ToString()
+        {
+            return Class.ToString() + Const.ToString();
+        }
+
+        public MIStreamRecordClass Class;
+        public MIConst Const;
+    }
+
+    public class MIAsyncOutput
+    {
+        public MIAsyncOutput(MIAsyncOutputClass cl, MIResult[] results)
+        {
+            Class = cl;
+            Results = new Dictionary<string, MIValue>();
+
+            if (results == null) {
+                return;
+            }
+
+            foreach(MIResult result in results) {
+                Results.Add(result.Variable, result.Value);
+            }
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder(Class.ToString());
+
+            foreach (KeyValuePair<string, MIValue> result in Results) {
+                sb.Append("," + result.Value.ToString());
+            }
+
+            return sb.ToString();
+        }
+
+        public MIValue this[string variable]
+        {
+            get { return Results[variable]; }
+        }
+
+        public MIAsyncOutputClass Class;
+        private Dictionary<string, MIValue> Results;
+    }
+
+    public class MIResultClass
+    {
+        public static MIResultClass Done =
+            new MIResultClass("done");
+
+        public static MIResultClass Running =
+            new MIResultClass("running");
+
+        public static MIResultClass Connected =
+            new MIResultClass("connected");
+
+        public static MIResultClass Error =
+            new MIResultClass("error");
+
+        public static MIResultClass Exit =
+            new MIResultClass("exit");
+
+        public override string ToString()
+        {
+            return Representation;
+        }
+
+        private MIResultClass(string reprsentation)
+        {
+            Representation = reprsentation;
+        }
+
+        private string Representation;
+    }
+
+    public class MIAsyncOutputClass
+    {
+        public static MIAsyncOutputClass Stopped =
+            new MIAsyncOutputClass("stopped");
+
+        public static MIAsyncOutputClass Others(string representation)
+        {
+            return new MIAsyncOutputClass(representation);
+        }
+
+        public override string ToString()
+        {
+            return Represenation;
+        }
+
+        private MIAsyncOutputClass(string representation)
+        {
+            Represenation = representation;
+        }
+
+        private string Represenation;
+    }
+
+    public class MIAsyncRecordClass
+    {
+        public static MIAsyncRecordClass Exec =
+            new MIAsyncRecordClass("*");
+
+        public static MIAsyncRecordClass Status =
+            new MIAsyncRecordClass("+");
+
+        public static MIAsyncRecordClass Notify =
+            new MIAsyncRecordClass("=");
+
+        public override string ToString()
+        {
+            return Represenation;
+        }
+
+        private MIAsyncRecordClass(string represenation)
+        {
+            Represenation = represenation;
+        }
+
+        string Represenation;
+    }
+
+    public class MIStreamRecordClass
+    {
+        public override string ToString()
+        {
+            return Represenation;
+        }
+
+        public static MIStreamRecordClass Console =
+            new MIStreamRecordClass("~");
+
+        public static MIStreamRecordClass Target =
+            new MIStreamRecordClass("@");
+
+        public static MIStreamRecordClass Log =
+            new MIStreamRecordClass("&");
+
+        private MIStreamRecordClass(string representation)
+        {
+            Represenation = representation;
+        }
+
+        string Represenation;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MIParser.cs b/test-suite/NetcoreDbgTest/MI/MIParser.cs
new file mode 100644 (file)
index 0000000..ceee3aa
--- /dev/null
@@ -0,0 +1,353 @@
+using System;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+using NetcoreDbgTest.MI;
+
+namespace NetcoreDbgTestCore.MI
+{
+    public class MIParserException : NetcoreDbgTestCore.Exception
+    {
+    }
+
+    public class MIParser
+    {
+        public MIOutput ParseOutput(string[] output)
+        {
+            var outOfBandRecordList = new List<MIOutOfBandRecord>();
+            MIResultRecord resultRecord = null;
+            int i = 0;
+
+            while (IsOutOfBandRecord(output[i])) {
+                outOfBandRecordList.Add(ParseOutOfBandRecord(output[i]));
+                i++;
+            }
+
+            if (IsResultRecord(output[i])) {
+                resultRecord = ParseResultRecord(output[i]);
+                i++;
+            }
+
+            if (!IsEnd(output[i])) {
+                throw new MIParserException();
+            }
+
+            return new MIOutput(outOfBandRecordList.ToArray(), resultRecord);
+        }
+
+        private MIResultRecord ParseResultRecord(string response)
+        {
+            int endIndex;
+            var token = ParseToken(response, 0, out endIndex);
+
+            if (response[endIndex] != '^') {
+                throw new MIParserException();
+            }
+
+            var resultClass = ParseResultClass(response, endIndex + 1, out endIndex);
+            var results = new List<MIResult>();
+
+            while (endIndex != response.Length) {
+                if (response[endIndex] != ',') {
+                    throw new MIParserException();
+                }
+
+                results.Add(ParseResult(response, endIndex + 1, out endIndex));
+            }
+
+            return new MIResultRecord(token, resultClass, results.ToArray());
+        }
+
+        private MIOutOfBandRecord ParseOutOfBandRecord(string response)
+        {
+            MIOutOfBandRecord outOfBandRecord;
+            int endIndex;
+
+            if (IsStreamRecord(response)) {
+                outOfBandRecord = ParseStreamRecord(response, 0, out endIndex);
+            } else {
+                outOfBandRecord = ParseAsyncRecord(response, 0, out endIndex);
+            }
+
+            if (endIndex != response.Length) {
+                throw new MIParserException();
+            }
+
+            return outOfBandRecord;
+        }
+
+        private MIToken ParseToken(string response, int beginIndex, out int endIndex)
+        {
+            endIndex = beginIndex;
+
+            while (Char.IsDigit(response[endIndex])) {
+                endIndex++;
+            }
+
+            if (beginIndex == endIndex) {
+                return null;
+            }
+
+            return new MIToken(
+                Convert.ToUInt64(response.Substring(beginIndex, endIndex - beginIndex), 10)
+            );
+        }
+
+        private MIResultClass ParseResultClass(string response, int beginIndex, out int endIndex)
+        {
+            var resClasses = new MIResultClass[] {
+                MIResultClass.Done,
+                MIResultClass.Running,
+                MIResultClass.Connected,
+                MIResultClass.Error,
+                MIResultClass.Exit,
+            };
+
+            foreach (MIResultClass resClass in resClasses)
+            {
+                string strClass = resClass.ToString();
+                int len = Math.Min(response.Length - beginIndex, strClass.Length);
+
+                if (String.Compare(response, beginIndex, strClass, 0, len) == 0) {
+                    endIndex = beginIndex + strClass.Length;
+                    return resClass;
+                }
+            }
+
+            throw new MIParserException();
+        }
+
+        private MIResult ParseResult(string response, int beginIndex, out int endIndex)
+        {
+            endIndex = response.IndexOf('=', beginIndex);
+            string variable = response.Substring(beginIndex, endIndex - beginIndex);
+            MIValue miValue = ParseValue(response, endIndex + 1, out endIndex);
+
+            return new MIResult(variable, miValue);
+        }
+
+        private MIValue ParseValue(string response, int beginIndex, out int endIndex)
+        {
+            if (response[beginIndex] == '{') {
+                 return ParseTuple(response, beginIndex, out endIndex);
+            } else if (response[beginIndex] == '[') {
+                return ParseList(response, beginIndex, out endIndex);
+            } else if (response[beginIndex] == '"') {
+                return ParseConst(response, beginIndex, out endIndex);
+            }
+
+            throw new MIParserException();
+        }
+
+        private MITuple ParseTuple(string response, int beginIndex, out int endIndex)
+        {
+            beginIndex++; // eat '{'
+
+            if (response[beginIndex] == '}') {
+                endIndex = beginIndex + 1;
+                return new MITuple(null);
+            }
+
+            var results = new List<MIResult>();
+            results.Add(ParseResult(response, beginIndex, out endIndex));
+
+            while (response[endIndex] == ',') {
+                results.Add(ParseResult(response, endIndex + 1, out endIndex));
+            }
+
+            if (response[endIndex] == '}') {
+                endIndex++;
+                return new MITuple(results.ToArray());
+            }
+
+            throw new MIParserException();
+        }
+
+        private MIListElement ParseListElement(MIListElementType type,
+                                     string response,
+                                     int beginIndex,
+                                     out int endIndex)
+        {
+            switch (type) {
+            case MIListElementType.Value:
+                return ParseValue(response, beginIndex, out endIndex);
+            case MIListElementType.Result:
+                return ParseResult(response, beginIndex, out endIndex);
+            }
+
+            throw new MIParserException();
+        }
+
+        private MIList ParseList(string response, int beginIndex, out int endIndex)
+        {
+            var elements = new List<MIListElement>();
+            MIListElementType type;
+
+            beginIndex++; // eat '['
+
+            if (response[beginIndex] == ']') {
+                endIndex = beginIndex + 1;
+                // Element type of empty list can be either
+                return new MIList(elements, MIListElementType.Value);
+            }
+
+            if (response[beginIndex] == '{' ||
+                response[beginIndex] == '[' ||
+                response[beginIndex] == '"'
+            ) {
+                type = MIListElementType.Value;
+            } else {
+                type = MIListElementType.Result;
+            }
+
+            elements.Add(ParseListElement(type, response, beginIndex, out endIndex));
+
+            while (response[endIndex] == ',') {
+                elements.Add(ParseListElement(type, response, endIndex + 1, out endIndex));
+            }
+
+            if (response[endIndex] == ']') {
+                endIndex++;
+                return new MIList(elements, type);
+            }
+
+            throw new MIParserException();
+        }
+
+        private MIConst ParseConst(string response, int beginIndex, out int endIndex)
+        {
+            for (endIndex = beginIndex + 1; endIndex < response.Length; endIndex++) {
+                if (response[endIndex] == '"' && response[endIndex - 1] != '\\') {
+                    break;
+                }
+            }
+
+            var cstring = response.Substring(beginIndex + 1, endIndex - beginIndex - 1);
+
+            endIndex++;
+
+            return new MIConst(cstring);
+        }
+
+        private MIOutOfBandRecord ParseAsyncRecord(string response, int beginIndex, out int endIndex)
+        {
+            MIToken token = ParseToken(response, beginIndex, out endIndex);
+            MIAsyncRecordClass asyncRecordClass = ParseAsyncRecordClass(response, endIndex, out endIndex);
+            MIAsyncOutput asyncOutput = ParseAsyncOutput(response, endIndex, out endIndex);
+
+            return new MIAsyncRecord(token, asyncRecordClass, asyncOutput);
+        }
+
+        private MIAsyncRecordClass ParseAsyncRecordClass(string response, int beginIndex, out int endIndex)
+        {
+            endIndex = beginIndex + 1;
+
+            switch (response[beginIndex]) {
+            case '*': return MIAsyncRecordClass.Exec;
+            case '+': return MIAsyncRecordClass.Status;
+            case '=': return MIAsyncRecordClass.Notify;
+            }
+
+            throw new MIParserException();
+        }
+
+        private MIAsyncOutput ParseAsyncOutput(string response, int beginIndex, out int endIndex)
+        {
+            MIAsyncOutputClass asyncClass = ParseAsyncOutputClass(response, beginIndex, out endIndex);
+
+            List<MIResult>results = new List<MIResult>();
+
+            while (endIndex != response.Length) {
+                if (response[endIndex] != ',') {
+                    break;
+                }
+
+                results.Add(ParseResult(response, endIndex + 1, out endIndex));
+            }
+
+            return new MIAsyncOutput(asyncClass, results.ToArray());
+        }
+
+        private MIAsyncOutputClass ParseAsyncOutputClass(string response, int beginIndex, out int endIndex)
+        {
+            endIndex = beginIndex;
+
+            while (endIndex < response.Length) {
+                if (response[endIndex] == ',') {
+                    break;
+                }
+                endIndex++;
+            }
+
+            string strClass = response.Substring(beginIndex, endIndex - beginIndex);
+
+            if (strClass == "stopped") {
+                return MIAsyncOutputClass.Stopped;
+            }
+
+            return MIAsyncOutputClass.Others(strClass);
+        }
+
+        private MIStreamRecord ParseStreamRecord(string response, int beginIndex, out int endIndex)
+        {
+            MIStreamRecordClass streamRecordClass =
+                ParseStreamRecordClass(response, beginIndex, out endIndex);
+            MIConst constant = ParseConst(response, endIndex, out endIndex);
+
+            return new MIStreamRecord(streamRecordClass, constant);
+        }
+
+        private MIStreamRecordClass ParseStreamRecordClass(string response, int beginIndex, out int endIndex)
+        {
+            endIndex = beginIndex + 1;
+
+            switch (response[beginIndex]) {
+            case '~': return MIStreamRecordClass.Console;
+            case '@': return MIStreamRecordClass.Target;
+            case '&': return MIStreamRecordClass.Log;
+            }
+
+            throw new MIParserException();
+        }
+
+        private bool IsOutOfBandRecord(string response)
+        {
+            return IsStreamRecord(response) ||
+                   IsAsyncRecord(response);
+        }
+
+        private bool IsStreamRecord(string response)
+        {
+            return response[0] == '~' ||
+                   response[0] == '@' ||
+                   response[0] == '&';
+        }
+
+        private bool IsAsyncRecord(string response)
+        {
+            int i = 0;
+            while (Char.IsDigit(response[i])) {
+                i++;
+            }
+
+            return response[i] == '*' ||
+                   response[i] == '+' ||
+                   response[i] == '=';
+        }
+
+        private bool IsResultRecord(string response)
+        {
+            int i = 0;
+            while (Char.IsDigit(response[i])) {
+                i++;
+            }
+
+            return response[i] == '^';
+        }
+
+        private bool IsEnd(string response)
+        {
+            return response == "(gdb)";
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/MI/MITcpDebuggerClient.cs b/test-suite/NetcoreDbgTest/MI/MITcpDebuggerClient.cs
new file mode 100644 (file)
index 0000000..d826a31
--- /dev/null
@@ -0,0 +1,108 @@
+using System.Net.Sockets;
+using System.Text;
+
+namespace NetcoreDbgTestCore
+{
+    namespace MI
+    {
+        public class MITcpDebuggerClient : DebuggerClient
+        {
+            public MITcpDebuggerClient(string addr, int port) : base(ProtocolType.MI)
+            {
+                client = new TcpClient(addr, port);
+                stream = client.GetStream();
+                PendingOutput = "";
+            }
+
+            public override bool DoHandshake(int timeout)
+            {
+                string[] output = ReceiveOutputLines(timeout);
+
+                // output must consist of one line "(gdb)"
+                return output != null && output.Length == 1;
+            }
+
+            public override bool Send(string cmd)
+            {
+                SendCommandLine(cmd);
+
+                return true;
+            }
+
+            public override string[] Receive(int timeout)
+            {
+                return ReceiveOutputLines(timeout);
+            }
+
+            public override void Close()
+            {
+                stream.Close();
+                client.Close();
+            }
+
+            void SendCommandLine(string str)
+            {
+                byte[] bytes = Encoding.ASCII.GetBytes(str + "\n");
+                stream.Write(bytes, 0, bytes.Length);
+            }
+
+            string[] ReceiveOutputLines(int timeout)
+            {
+                int lenOfGdb = "(gdb)".Length;
+                var sb = new StringBuilder(PendingOutput);
+                int indexOfGdb = PendingOutput.IndexOf("(gdb)");
+
+                while (indexOfGdb == -1) {
+                    string availableData = LoadAvailableData(timeout);
+
+                    if (availableData == null) {
+                        return null;
+                    }
+
+                    sb.Append(availableData);
+                    PendingOutput = sb.ToString();
+                    indexOfGdb = PendingOutput.IndexOf("(gdb)");
+                }
+
+                var packedOutputLines = PendingOutput.Substring(0, indexOfGdb + lenOfGdb);
+                PendingOutput = PendingOutput.Substring(indexOfGdb + lenOfGdb + 1);
+
+                return packedOutputLines.TrimEnd('\r', '\n').Split("\n");
+            }
+
+            string LoadAvailableData(int timeout)
+            {
+                byte[] recvBuffer = new byte[64];
+                StringBuilder sb = new StringBuilder();
+                int recvCount = 0;
+                string response;
+
+                stream.ReadTimeout = timeout;
+
+                try {
+                    do {
+                        int readCount = stream.Read(recvBuffer, 0, recvBuffer.Length);
+                        response = Encoding.UTF8.GetString(recvBuffer, 0, readCount);
+                        sb.Append(response);
+                        recvCount += readCount;
+                    } while (stream.DataAvailable);
+                }
+
+                catch {
+                }
+
+                stream.ReadTimeout = -1;
+
+                if (recvCount == 0) {
+                    return null;
+                }
+
+                return sb.ToString();
+            }
+
+            TcpClient client;
+            NetworkStream stream;
+            string PendingOutput;
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/NetcoreDbgTest.csproj b/test-suite/NetcoreDbgTest/NetcoreDbgTest.csproj
new file mode 100644 (file)
index 0000000..802eccd
--- /dev/null
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis" Version="2.10.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.10.0" />
+    <PackageReference Include="Xunit" Version="2.4.1" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
+  </ItemGroup>
+</Project>
diff --git a/test-suite/NetcoreDbgTest/Script.cs b/test-suite/NetcoreDbgTest/Script.cs
new file mode 100644 (file)
index 0000000..0506a1f
--- /dev/null
@@ -0,0 +1,7 @@
+namespace NetcoreDbgTest
+{
+    namespace Script
+    {
+        // Declarations defined by user in test script
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeBreakpoint.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeBreakpoint.cs
new file mode 100644 (file)
index 0000000..8d119e9
--- /dev/null
@@ -0,0 +1,20 @@
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTest
+{
+    namespace VSCode
+    {
+        class VSCodeLineBreakpoint : LineBreakpoint
+        {
+            public VSCodeLineBreakpoint(string name, string srcName, int lineNum)
+                : base(name, srcName, lineNum, ProtocolType.VSCode)
+            {
+            }
+
+            public override string ToString()
+            {
+                return System.String.Format("{0}:{1}", FileName, NumLine);
+            }
+        }
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeDebugger.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeDebugger.cs
new file mode 100644 (file)
index 0000000..d02856d
--- /dev/null
@@ -0,0 +1,111 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.VSCode;
+
+using Newtonsoft.Json;
+
+namespace NetcoreDbgTest.VSCode
+{
+    public class VSCodeResult : Tuple<bool, string>
+    {
+        public VSCodeResult(bool Success, string ResponseStr)
+            :base(Success, ResponseStr)
+        {
+        }
+
+        public bool Success { get{ return this.Item1; } }
+        public string ResponseStr { get{ return this.Item2; } }
+    }
+
+    public class VSCodeDebugger
+    {
+        public bool isResponseContainProperty(string stringJSON, string testField, string testValue)
+        {
+            JsonTextReader reader = new JsonTextReader(new StringReader(stringJSON));
+            while (reader.Read()) {
+                if (reader.Value != null
+                    && reader.TokenType.ToString() == "PropertyName"
+                    && reader.Value.ToString() == testField
+                    && reader.Read()
+                    && reader.Value != null
+                    && reader.Value.ToString() == testValue) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public object GetResponsePropertyValue(string stringJSON, string testField)
+        {
+            JsonTextReader reader = new JsonTextReader(new StringReader(stringJSON));
+            while (reader.Read()) {
+                if (reader.Value != null
+                    && reader.TokenType.ToString() == "PropertyName"
+                    && reader.Value.ToString() == testField
+                    && reader.Read()) {
+                    return reader.Value;
+                }
+            }
+
+            return null;
+        }
+        public VSCodeResult Request(Request command, int timeout = -1)
+        {
+            string stringJSON = JsonConvert.SerializeObject(command,
+                                                            Formatting.None,
+                                                            new JsonSerializerSettings { 
+                                                                NullValueHandling = NullValueHandling.Ignore});
+
+            Logger.LogLine("-> (C) " + stringJSON);
+            Int64 RequestSeq = (Int64)GetResponsePropertyValue(stringJSON, "seq");
+
+            if (!Debuggee.DebuggerClient.Send(stringJSON)) {
+                throw new DebuggerNotResponsesException();
+            }
+
+            while (true) {
+                string[] response = Debuggee.DebuggerClient.Receive(timeout);
+                if (response == null) {
+                    throw new DebuggerNotResponsesException();
+                }
+                string line = response[0];
+
+                if (isResponseContainProperty(line, "type", "response")
+                    && (Int64)GetResponsePropertyValue(line, "request_seq") == RequestSeq) {
+                    Logger.LogLine("<- (R) " + line);
+                    return new VSCodeResult((bool)GetResponsePropertyValue(line, "success"), line);
+                } else {
+                    Logger.LogLine("<- (E) " + line);
+                    EventList.Add(line);
+                }
+            }
+        }
+
+        public string Receive(int timeout = -1)
+        {
+            while (true) {
+                string[] response = Debuggee.DebuggerClient.Receive(timeout);
+                if (response == null) {
+                    throw new DebuggerNotResponsesException();
+                }
+                string line = response[0];
+
+                Logger.LogLine("<- (E) " + line);
+                EventList.Add(line);
+
+                foreach (var Event in StopEvents) {
+                    if (isResponseContainProperty(line, "event", Event)) {
+                        return line;
+                    }
+                }
+            }
+        }
+
+        public List<string> EventList = new List<string>();
+        string[] StopEvents = {"stopped",
+                               "terminated"};
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeLocaleDebuggerClient.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeLocaleDebuggerClient.cs
new file mode 100644 (file)
index 0000000..5fd7646
--- /dev/null
@@ -0,0 +1,122 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Threading;
+
+using NetcoreDbgTestCore;
+
+namespace NetcoreDbgTestCore.VSCode
+{
+    public class VSCodeLocalDebuggerClient : DebuggerClient
+    {
+        public VSCodeLocalDebuggerClient(StreamWriter input, StreamReader output)
+            : base(ProtocolType.VSCode)
+        {
+            DebuggerInput = input;
+            DebuggerOutput = output;
+            GetInput = new AutoResetEvent(false);
+            GotInput = new AutoResetEvent(false);
+            InputThread = new Thread(ReaderThread);
+            InputThread.IsBackground = true;
+            InputThread.Start();
+        }
+
+        public override bool DoHandshake(int timeout)
+        {
+            return true;
+        }
+
+        public override bool Send(string command)
+        {
+            DebuggerInput.Write(CONTENT_LENGTH + command.Length.ToString() + TWO_CRLF + command);
+            DebuggerInput.Flush();
+
+            return true;
+        }
+
+        public override string[] Receive(int timeout)
+        {
+            string line = ReceiveOutputLine(timeout);
+            if (line == null) {
+                return null;
+            }
+            return new string[1]{line};
+        }
+
+        public override void Close()
+        {
+            DebuggerInput.Close();
+            DebuggerOutput.Close();
+        }
+
+        string ReadData()
+        {
+            string header = "";
+
+            while (true) {
+                // Read until "\r\n\r\n"
+                int res = DebuggerOutput.Read();
+                if (res < 0) {
+                    return null;
+                }
+
+                header += (char)res;
+
+                if (header.Length < TWO_CRLF.Length) {
+                    continue;
+                }
+
+                if (header.Substring(header.Length - TWO_CRLF.Length, TWO_CRLF.Length) != TWO_CRLF) {
+                    continue;
+                }
+
+                // Extract Content-Length
+                int lengthIndex = header.IndexOf(CONTENT_LENGTH);
+                if (lengthIndex == -1) {
+                    continue;
+                }
+
+                int contentLength = Int32.Parse(header.Substring(lengthIndex + CONTENT_LENGTH.Length));
+
+                char[] result = new char[contentLength];
+                if (DebuggerOutput.Read(result, 0, contentLength) == -1) {
+                    return null;
+                }
+
+                return new string(result);
+            }
+            // unreachable
+        }
+        void ReaderThread()
+        {
+            while (true) {
+                GetInput.WaitOne();
+                InputString = ReadData();
+                GotInput.Set();
+            }
+        }
+
+        string ReceiveOutputLine(int timeout)
+        {
+            GetInput.Set();
+            bool success = GotInput.WaitOne(timeout);
+            if (!success) {
+                throw new DebuggerNotResponsesException();
+            }
+
+            if (InputString == null) {
+                return null;
+            }
+
+            return InputString;
+        }
+
+        StreamWriter DebuggerInput;
+        StreamReader DebuggerOutput;
+        Thread InputThread;
+        AutoResetEvent GetInput, GotInput;
+        string InputString;
+        static string TWO_CRLF = "\r\n\r\n";
+        static string CONTENT_LENGTH = "Content-Length: ";
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocol.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocol.cs
new file mode 100644 (file)
index 0000000..46563ce
--- /dev/null
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+    // https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json
+    // https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts
+    public class ProtocolMessage {
+        public int seq;
+        public string type;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolEvent.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolEvent.cs
new file mode 100644 (file)
index 0000000..136e931
--- /dev/null
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+    public class Event : ProtocolMessage {
+    }
+
+    public class ThreadEvent : Event {
+        public ThreadEventBody body;
+    }
+
+    public class ThreadEventBody {
+        public string reason;
+        public int threadId;
+    }
+
+    public class StoppedEvent : Event {
+        public StoppedEventBody body;
+    }
+
+    public class StoppedEventBody {
+        public string reason;
+        public string description;
+        public int ?threadId;
+        public bool ?preserveFocusHint;
+        public string text;
+        public bool ?allThreadsStopped;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolRequest.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolRequest.cs
new file mode 100644 (file)
index 0000000..0c33700
--- /dev/null
@@ -0,0 +1,316 @@
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+    public class Request : ProtocolMessage {
+        public Request()
+        {
+            seq = RequestSeq++;
+            type = "request";
+        }
+        public string command;
+        static public int RequestSeq = 1;
+    }
+
+
+    public class InitializeRequest : Request {
+        public InitializeRequest()
+        {
+            command = "initialize";
+        }
+        public InitializeRequestArguments arguments = new InitializeRequestArguments();
+    }
+
+    public class InitializeRequestArguments {
+        public string clientID;
+        public string clientName;
+        public string adapterID;
+        public string locale;
+        public bool ?linesStartAt1;
+        public bool ?columnsStartAt1;
+        public string pathFormat;
+        public bool ?supportsVariableType;
+        public bool ?supportsVariablePaging;
+        public bool ?supportsRunInTerminalRequest;
+    }
+
+    public class LaunchRequest : Request {
+        public LaunchRequest()
+        {
+            command = "launch";
+        }
+        public LaunchRequestArguments arguments = new LaunchRequestArguments();
+    }
+
+    public class LaunchRequestArguments {
+        public string name;
+        public string type;
+        public string preLaunchTask;
+        public string program;
+        public List<string> args;
+        public string cwd;
+        public string console;
+        public bool stopAtEntry;
+        public string internalConsoleOptions;
+        public string __sessionId;
+    }
+
+    public class AttachRequest : Request {
+        public AttachRequest()
+        {
+            command = "attach";
+        }
+        public AttachRequestArguments arguments = new AttachRequestArguments();
+    }
+
+    public class AttachRequestArguments {
+        public int processId;
+    }
+
+    public class ConfigurationDoneRequest : Request {
+        public ConfigurationDoneRequest()
+        {
+            command = "configurationDone";
+        }
+        public ConfigurationDoneArguments arguments;
+    }
+
+    public class ConfigurationDoneArguments {
+    }
+
+    public class ContinueRequest : Request {
+        public ContinueRequest()
+        {
+            command = "continue";
+        }
+        public ContinueArguments arguments = new ContinueArguments();
+    }
+
+    public class ContinueArguments {
+        public int threadId;
+    }
+
+    public class DisconnectRequest : Request {
+        public DisconnectRequest()
+        {
+            command = "disconnect";
+        }
+        public DisconnectArguments arguments;
+    }
+
+    public class DisconnectArguments {
+        public bool ?restart;
+        public bool ?terminateDebuggee;
+    }
+
+    public class SetBreakpointsRequest : Request {
+        public SetBreakpointsRequest()
+        {
+            command = "setBreakpoints";
+        }
+        public SetBreakpointsArguments arguments = new SetBreakpointsArguments();
+    }
+
+    public class SetBreakpointsArguments {
+        public Source source = new Source();
+        public List<SourceBreakpoint> breakpoints = new List<SourceBreakpoint>();
+        public List<int> lines = new List<int>();
+        public bool ?sourceModified;
+    }
+
+    public class SourceBreakpoint {
+       public SourceBreakpoint(int bpLine, string Condition = null)
+       {
+            line = bpLine;
+            condition = Condition;
+       }
+        public int line;
+        public int ?column;
+        public string condition;
+        public string hitCondition;
+        public string logMessage;
+    }
+
+    public class Source {
+        public string name;
+        public string path;
+        public int ?sourceReference;
+        public string presentationHint; // "normal" | "emphasize" | "deemphasize"
+        public string origin;
+        public List<Source> sources = new List<Source>();
+        public dynamic adapterData = null;
+        public List<Checksum> checksums = new List<Checksum>();
+    }
+
+    public class Checksum {
+        public string algorithm; // "MD5" | "SHA1" | "SHA256" | "timestamp"
+        public string checksum;
+    }
+
+    public class SetFunctionBreakpointsRequest : Request {
+        public SetFunctionBreakpointsRequest()
+        {
+            command = "setFunctionBreakpoints";
+        }
+        public SetFunctionBreakpointsArguments arguments = new SetFunctionBreakpointsArguments();
+    }
+
+    public class SetFunctionBreakpointsArguments {
+        public List<FunctionBreakpoint> breakpoints = new List<FunctionBreakpoint>();
+    }
+
+    public class FunctionBreakpoint {
+        public FunctionBreakpoint(string funcName, string Condition = null)
+        {
+            name = funcName;
+            condition = Condition;
+        }
+        public string name;
+        public string condition;
+        public string hitCondition;
+    }
+
+    public class StackTraceRequest : Request {
+        public StackTraceRequest()
+        {
+            command = "stackTrace";
+        }
+        public StackTraceArguments arguments = new StackTraceArguments();
+    }
+
+    public class StackTraceArguments {
+        public int threadId;
+        public int ?startFrame;
+        public int ?levels;
+        public StackFrameFormat format;
+    }
+
+    public class ValueFormat {
+        public bool ?hex;
+    }
+
+    public class StackFrameFormat : ValueFormat {
+        public bool ?parameters;
+        public bool ?parameterTypes;
+        public bool ?parameterNames;
+        public bool ?parameterValues;
+        public bool ?line;
+        public bool ?module;
+        public bool ?includeAll;
+    }
+
+    public class PauseRequest : Request {
+        public PauseRequest()
+        {
+            command = "pause";
+        }
+        public PauseArguments arguments = new PauseArguments();
+    }
+
+    public class PauseArguments {
+        public int threadId;
+    }
+
+    public class ThreadsRequest : Request {
+        public ThreadsRequest()
+        {
+            command = "threads";
+        }
+    }
+
+    public class ScopesRequest : Request {
+        public ScopesRequest()
+        {
+            command = "scopes";
+        }
+        public ScopesArguments arguments = new ScopesArguments();
+    }
+
+    public class ScopesArguments {
+        public Int64 frameId;
+    }
+
+    public class VariablesRequest : Request {
+        public VariablesRequest()
+        {
+            command = "variables";
+        }
+        public VariablesArguments arguments = new VariablesArguments();
+    }
+
+    public class VariablesArguments {
+        public int variablesReference;
+        public string filter; // "indexed" | "named"
+        public int ?start;
+        public int ?count;
+        public ValueFormat format;
+    }
+
+    public class EvaluateRequest : Request {
+        public EvaluateRequest()
+        {
+            command = "evaluate";
+        }
+               public EvaluateArguments arguments = new EvaluateArguments();
+    }
+
+    public class EvaluateArguments {
+        public string expression;
+        public Int64 ?frameId;
+        public string context;
+        public ValueFormat format;
+    }
+
+    public class SetVariableRequest : Request {
+        public SetVariableRequest()
+        {
+            command = "setVariable";
+        }
+        public SetVariableArguments arguments = new SetVariableArguments();
+    }
+
+    public class SetVariableArguments {
+        public int variablesReference;
+        public string name;
+        public string value;
+        public ValueFormat format;
+    }
+
+    public class NextRequest : Request {
+        public NextRequest()
+        {
+            command = "next";
+        }
+        public NextArguments arguments = new NextArguments();
+    }
+
+    public class NextArguments {
+        public int threadId;
+    }
+
+    public class StepInRequest : Request {
+        public StepInRequest()
+        {
+            command = "stepIn";
+        }
+        public StepInArguments arguments = new StepInArguments();
+    }
+
+    public class StepInArguments {
+        public int threadId;
+        public int ?targetId;
+    }
+
+    public class StepOutRequest : Request {
+        public StepOutRequest()
+        {
+            command = "stepOut";
+        }
+        public StepOutArguments arguments = new StepOutArguments();
+    }
+
+    public class StepOutArguments {
+        public int threadId;
+    }
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolResponse.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeProtocolResponse.cs
new file mode 100644 (file)
index 0000000..cb8f882
--- /dev/null
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+
+namespace NetcoreDbgTest.VSCode
+{
+    public class Response : ProtocolMessage {
+        public int request_seq;
+        public bool success;
+        public string command;
+        public string message;
+    }
+
+    public class StackTraceResponse : Response {
+        public StackTraceResponseBody body;
+    }
+
+    public class StackTraceResponseBody {
+        public List<StackFrame> stackFrames;
+        public int ?totalFrames;
+    };
+
+    public class StackFrame {
+        public Int64 id;
+        public string name;
+        public Source source;
+        public int line;
+        public int column;
+        public int ?endLine;
+        public int ?endColumn;
+        public dynamic moduleId = null;
+        public string presentationHint; // "normal" | "label" | "subtle"
+    }
+
+    public class ThreadsResponse : Response {
+        public ThreadsResponseBody body;
+    }
+
+    public class ThreadsResponseBody {
+        public List<Thread> threads;
+    };
+
+    public class Thread {
+        public int id;
+        public string name;
+    }
+
+    public class ScopesResponse : Response {
+        public ScopesResponseBody body;
+    }
+
+    public class ScopesResponseBody {
+        public List<Scope> scopes;
+    };
+
+    public class Scope {
+        public string name;
+        public int ?variablesReference;
+        public int ?namedVariables;
+        public int ?indexedVariables;
+        public bool ?expensive;
+        public Source source;
+        public int ?line;
+        public int ?column;
+        public int ?endLine;
+        public int ?endColumn;
+    }
+
+    public class VariablesResponse : Response {
+        public VariablesResponseBody body;
+    }
+
+    public class VariablesResponseBody {
+        public List<Variable> variables;
+    };
+
+    public class Variable {
+        public string name;
+        public string value;
+        public string type;
+        public VariablePresentationHint presentationHint;
+        public string evaluateName;
+        public int variablesReference;
+        public int ?namedVariables;
+        public int ?indexedVariables;
+    }
+
+    public class VariablePresentationHint {
+        public string kind;
+        public List<string> attributes;
+        public string visibility;
+    }
+
+    public class EvaluateResponse : Response {
+        public EvaluateResponseBody body;
+    }
+
+    public class EvaluateResponseBody {
+        public string result;
+        public string type;
+        public VariablePresentationHint presentationHint;
+        public int variablesReference;
+        public int ?namedVariables;
+        public int ?indexedVariables;
+    };
+
+    public class SetVariableResponse : Response {
+        public SetVariableResponseBody body;
+    }
+
+    public class SetVariableResponseBody {
+        public string value;
+        public string type;
+        public int ?variablesReference;
+        public int ?namedVariables;
+        public int ?indexedVariables;
+    };
+}
diff --git a/test-suite/NetcoreDbgTest/VSCode/VSCodeTcpDebuggerClient.cs b/test-suite/NetcoreDbgTest/VSCode/VSCodeTcpDebuggerClient.cs
new file mode 100644 (file)
index 0000000..8700562
--- /dev/null
@@ -0,0 +1,92 @@
+using System;
+using System.Net.Sockets;
+using System.Text;
+
+namespace NetcoreDbgTestCore
+{
+    namespace VSCode
+    {
+        public class VSCodeTcpDebuggerClient : DebuggerClient
+        {
+            public VSCodeTcpDebuggerClient(string addr, int port) : base(ProtocolType.VSCode)
+            {
+                client = new TcpClient(addr, port);
+
+                stream = client.GetStream();
+            }
+
+            public override bool DoHandshake(int timeout)
+            {
+                return true;
+            }
+
+            public override bool Send(string cmd)
+            {
+                SendCommandLine(CONTENT_LENGTH + cmd.Length.ToString() + TWO_CRLF + cmd);
+
+                return true;
+            }
+
+            public override string[] Receive(int timeout)
+            {
+                string line = ReceiveOutputLine(timeout);
+                if (line == null) {
+                    return null;
+                }
+                return new string[1]{line};
+            }
+
+            public override void Close()
+            {
+                stream.Close();
+                client.Close();
+            }
+
+            void SendCommandLine(string str)
+            {
+                byte[] bytes = Encoding.UTF8.GetBytes(str);
+                stream.Write(bytes, 0, bytes.Length);
+            }
+
+            string ReceiveOutputLine(int timeout)
+            {
+                string header = "";
+                byte[] recvBuffer = new byte[1];
+
+                while (true) {
+                    // Read until "\r\n\r\n"
+                    int readCount = stream.Read(recvBuffer, 0, recvBuffer.Length);
+                    header += Encoding.ASCII.GetString(recvBuffer, 0, readCount);
+
+                    if (header.Length < TWO_CRLF.Length) {
+                       continue;
+                    }
+
+                    if (header.Substring(header.Length - TWO_CRLF.Length, TWO_CRLF.Length) != TWO_CRLF) {
+                        continue;
+                    }
+
+                    // Extract Content-Length
+                    int lengthIndex = header.IndexOf(CONTENT_LENGTH);
+                    if (lengthIndex == -1) {
+                        continue;
+                    }
+
+                    int contentLength = Int32.Parse(header.Substring(lengthIndex + CONTENT_LENGTH.Length));
+                    byte[] result = new byte[contentLength];
+                    if (stream.Read(result, 0, contentLength) == -1) {
+                        return null;
+                    }
+
+                    return System.Text.Encoding.UTF8.GetString(result);
+                }
+                // unreachable
+            }
+
+            TcpClient client;
+            NetworkStream stream;
+            static string TWO_CRLF = "\r\n\r\n";
+            static string CONTENT_LENGTH = "Content-Length: ";
+        }
+    }
+}
diff --git a/test-suite/README.md b/test-suite/README.md
new file mode 100644 (file)
index 0000000..0919f9c
--- /dev/null
@@ -0,0 +1,109 @@
+# How to launch MIExampleTest for tcp netcoredbg server
+
+- launch target and install netcoredbg;
+
+- launch netcoredbg as tcp server on target via sdb shell:
+```
+    $ sdb shell nohup <path-to-netcoredbg> --server
+```
+
+- forward tcp port from target to host:
+```
+    $ sdb forward tcp:4712 tcp:4711
+```
+
+- move to test-suite directory;
+
+- build MIExampleTest target assembly and TestRunner:
+```
+    $ dotnet build MIExampleTest
+    $ dotnet build TestRunner
+```
+or you can build solution which consists of TestRunner and all tests:
+```
+    $ dotnet build
+```
+
+- push dll and pdb files into target /tmp/ disrectory:
+```
+    $ sdb push MIExampleTest/bin/Debug/netcoreapp2.1/MIExampleTest{pdb,dll} /tmp/
+```
+
+- change smack permissions:
+```
+    $ sdb shell chsmack -a "_" /tmp/MIExampleTest.{pdb,dll}
+```
+
+- launch TestRunner with tcp client (repsonses will be parsed only as GDB/MI outputs):
+
+```
+    $ dotnet run --project TestRunner -- \
+        --tcp localhost 4712 \
+        --dotnet /usr/share/dotnet/corerun \
+        --test MIExampleTest \
+        --sources MIExampleTest/Program.cs \
+        --assembly /tmp/MIExampleTest.dll
+```
+
+- expect successfull passing of test.
+
+Also after preparing sdb target with netcoredbg you can run follow script
+to make all necessary steps.
+
+- On Linux:
+```
+    launch all tests:
+    $ ./sdb_run_tests.sh
+    or
+    $ ./sdb_run_tests.sh <test-name> [<test-name>]
+```
+- On Windows:
+```
+    launch all tests:
+    > powershell.exe -executionpolicy bypass -File sdb_run_tests.ps1
+    or
+    > powershell.exe -executionpolicy bypass -File sdb_run_tests.ps1 <test-name> [<test-name>]
+```
+
+# How to launch tests locally
+
+- On Linux:
+```
+    launch all tests:
+    $ ./run_tests.sh
+    or
+    $ ./run_tests.sh <test-name> [<test-name>]
+    or
+    $ NETCOREDBG=<path-to-netcoredbg> ./run_tests.sh <test-name> [<test-name>]
+```
+
+- On Windows:
+```
+    launch all tests:
+    $ powershell.exe -executionpolicy bypass -File run_tests.ps1
+    or
+    $ powershell.exe -executionpolicy bypass -File run_tests.ps1 <test-name> [<test-name>]
+```
+
+# How to add new test
+
+- move to test-suite directory;
+
+- create new project in test-suite folder:
+```
+    $ dotnet new console -o NewTest
+```
+
+- add reference to NetcoreDbgTest library:
+```
+    $ dotnet add NewTest/NewTest.csproj reference NetcoreDbgTest/NetcoreDbgTest.csproj
+```
+
+- add project NewTest to solution:
+```
+    $ dotnet sln add NewTest/NewTest.csproj
+```
+
+- add test name into ALL_TEST_NAMES list in "run_tests.sh", "run_tests.ps1", "sdb_run_tests.sh" and "sdb_run_tests.ps1" scripts;
+
+- in MIExampleTest implemented small scenario of NetCoreDbgTest library using.
diff --git a/test-suite/TestRunner/TestRunner.cs b/test-suite/TestRunner/TestRunner.cs
new file mode 100644 (file)
index 0000000..c8be0c0
--- /dev/null
@@ -0,0 +1,373 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+using LocalDebugger;
+using NetcoreDbgTestCore;
+using NetcoreDbgTestCore.MI;
+using NetcoreDbgTestCore.VSCode;
+
+namespace TestRunner
+{
+    class Program
+    {
+        public static int Main(string[] args)
+        {
+            var cli = new CLInterface(args);
+            DebuggerClient debugger = null;
+            DebuggeeScript script = null;
+            LocalDebuggerProcess localDebugger = null;
+
+            if (cli.NeedHelp) {
+                cli.PrintHelp();
+                return 1;
+            }
+
+            if (cli.ClientInfo == null) {
+                Console.Error.WriteLine("Please define client type");
+                return 1;
+            }
+
+            try {
+                switch (cli.Protocol) {
+                case ProtocolType.MI:
+                    switch (cli.ClientInfo.Type) {
+                    case ClientType.Local:
+                        var localClientInfo = (LocalClientInfo)cli.ClientInfo;
+                        localDebugger = new LocalDebuggerProcess(
+                                            localClientInfo.DebuggerPath, @" --interpreter=mi");
+                        localDebugger.Start();
+
+                        debugger = new MILocalDebuggerClient(localDebugger.Input,
+                                                             localDebugger.Output);
+                        break;
+                    case ClientType.Tcp:
+                        var tcpClientInfo = (TcpClientInfo)cli.ClientInfo;
+                        debugger = new MITcpDebuggerClient(tcpClientInfo.Addr,
+                                                           tcpClientInfo.Port);
+                        break;
+                    default:
+                            Console.Error.WriteLine("Only tcp and local debuggers are supported now");
+                            return 1;
+                    }
+
+                    break;
+
+                case ProtocolType.VSCode:
+                    switch (cli.ClientInfo.Type) {
+                    case ClientType.Local:
+                        var localClientInfo = (LocalClientInfo)cli.ClientInfo;
+                        localDebugger = new LocalDebuggerProcess(
+                                            localClientInfo.DebuggerPath, @" --interpreter=vscode");
+                        localDebugger.Start();
+
+                        debugger = new VSCodeLocalDebuggerClient(localDebugger.Input,
+                                                                 localDebugger.Output);
+                        break;
+                    case ClientType.Tcp:
+                        var tcpClientInfo = (TcpClientInfo)cli.ClientInfo;
+                        debugger = new VSCodeTcpDebuggerClient(tcpClientInfo.Addr,
+                                                               tcpClientInfo.Port);
+                        break;
+                    default:
+                            Console.Error.WriteLine("Only tcp and local debuggers are supported now");
+                            return 1;
+                    }
+
+                    break;
+
+                default:
+                    Console.Error.WriteLine("Only GDB/MI and VSCode protocols is supported now");
+                    return 1;
+                }
+            }
+            catch {
+                Console.Error.WriteLine("Can't create debugger client");
+                return 1;
+            }
+
+            if (!debugger.DoHandshake(200)) {
+                Console.Error.WriteLine("Handshake is failed");
+                if (localDebugger != null) {
+                    localDebugger.Close();
+                }
+                return 1;
+            }
+
+            try {
+                script = new DebuggeeScript(cli.Environment.SourceFilesPath, debugger.Protocol);
+            }
+            catch (ScriptNotBuiltException e) {
+                Console.Error.WriteLine("Script is not built:");
+                Console.Error.WriteLine(e.ToString());
+                debugger.Close();
+                if (localDebugger != null) {
+                    localDebugger.Close();
+                }
+                return 1;
+            }
+
+            try {
+                Debuggee.Run(script, debugger, cli.Environment);
+                Console.WriteLine("Success: Test case \"{0}\" is passed!!!",
+                                  cli.Environment.TestName);
+            }
+            catch (System.Exception e) {
+                Console.Error.WriteLine("Script running is failed. Got exception:\n" + e.ToString());
+                return 1;
+            }
+
+            debugger.Close();
+            return 0;
+        }
+    }
+
+    enum ClientType
+    {
+        Local,
+        Tcp,
+    }
+
+    class CLInterface
+    {
+        public CLInterface(string[] args)
+        {
+            Environment = new NetcoreDbgTestCore.Environment();
+
+            if (args.Length == 0) {
+                NeedHelp = true;
+                return;
+            }
+
+            if (args.Length == 1 && (args[0] == "-h" || args[0] == "--help")) {
+                NeedHelp = true;
+                return;
+            }
+
+            int i = 0;
+            while (i < args.Length && !NeedHelp) {
+                switch (args[i]) {
+                case "--tcp":
+                    if (i + 2 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    try {
+                        ClientInfo = new TcpClientInfo(args[i + 1],
+                                                       args[i + 2]);
+                    }
+                    catch {
+                        NeedHelp = true;
+                        break;
+                    }
+                    i += 3;
+
+                    break;
+                case "--local":
+                    if (i + 1 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    try {
+                        string debuggerPath = Path.GetFullPath(args[i + 1]);
+                        ClientInfo = new LocalClientInfo(debuggerPath);
+                    }
+                    catch {
+                        NeedHelp = true;
+                        break;
+                    }
+                    i += 2;
+
+                    break;
+                case "--proto":
+                    if (i + 1 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    switch (args[i + 1]) {
+                    case "mi":
+                        Protocol = ProtocolType.MI;
+                        break;
+                    case "vscode":
+                        Protocol = ProtocolType.VSCode;
+                        break;
+                    default:
+                        Protocol = ProtocolType.None;
+                        break;
+                    }
+                    i += 2;
+
+                    break;
+                case "--test":
+                    if (i + 2 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    try {
+                        Environment.TestName = args[i + 1];
+                    }
+                    catch {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    i += 2;
+
+                    break;
+                case "--sources":
+                    if (i + 1 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    try {
+                        Environment.SourceFilesPath = Path.GetFullPath(args[i + 1]);
+                        if (Environment.SourceFilesPath[Environment.SourceFilesPath.Length - 1] == ';') {
+                            Environment.SourceFilesPath =
+                                Environment.SourceFilesPath.Remove(Environment.SourceFilesPath.Length - 1);
+                        }
+                    }
+                    catch {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    i += 2;
+
+                    break;
+                case "--assembly":
+                    if (i + 1 >= args.Length) {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    Environment.TargetAssemblyPath = args[i + 1];
+
+                    i += 2;
+
+                    break;
+                case "--dotnet":
+                    if (i + 1 >= args.Length) {
+                        NeedHelp = true;
+                    }
+
+                    try {
+                        Path.GetFullPath(args[i + 1]);
+                    }
+                    catch {
+                        NeedHelp = true;
+                        break;
+                    }
+
+                    Environment.CorerunPath = args[i + 1];
+
+                    i += 2;
+
+                    break;
+                default:
+                    NeedHelp = true;
+                    break;
+                }
+            }
+
+            if (ClientInfo != null
+                && ClientInfo.Type == ClientType.Local
+                && !File.Exists(Environment.TargetAssemblyPath)) {
+                Console.Error.WriteLine("Provided assembly path is invalid");
+                throw new System.Exception();
+            }
+
+            if (NeedHelp) {
+                return;
+            }
+        }
+
+        public void PrintHelp()
+        {
+            Console.Error.WriteLine(
+@"usage: dotnet run {-h|--help|[OPTIONS] TESTS}
+options:
+    --dotnet dotnet-path    Set dotnet path(default: dotnet-path=""dotnet"")
+    --proto protocol        Set protocol(default: protocol=mi)
+    --tcp server port       Create TCP client for debugger
+    --local debugger-path   Create launch debugger locally and create client
+    --test name             Test name
+    --sources path[;path]   Semicolon separated paths to source files
+    --assembly path         Path to target assambly file
+    
+    
+    ");
+        }
+
+        public bool NeedHelp = false;
+        public ProtocolType Protocol = ProtocolType.MI;
+        public NetcoreDbgTestCore.Environment Environment;
+        public ClientInfo ClientInfo;
+    }
+
+    class ClientInfo
+    {
+        public ClientType Type;
+    }
+
+    class TcpClientInfo : ClientInfo
+    {
+        public TcpClientInfo(string addr, string port)
+        {
+            Type = ClientType.Tcp;
+
+            if (!IsIpAddressValid(addr)) {
+                Console.Error.WriteLine("IP address is invalid");
+                throw new System.Exception();
+            }
+
+            Addr = addr;
+            Port = Int32.Parse(port);
+        }
+
+        private bool IsIpAddressValid(string addr)
+        {
+            try {
+                IPAddress[] hostIPs = Dns.GetHostAddresses(addr);
+                IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
+
+                foreach (IPAddress hostIP in hostIPs) {
+                    if (IPAddress.IsLoopback(hostIP)) {
+                        return true;
+                    }
+                    foreach (IPAddress localIP in localIPs) {
+                        if (hostIP.Equals(localIP)) {
+                            return true;
+                        }
+                    }
+                }
+
+                IPAddress address = IPAddress.Parse(addr);
+                return true;
+            }
+            catch {
+            }
+
+            return false;
+        }
+        public string Addr;
+        public int Port;
+    }
+
+    class LocalClientInfo : ClientInfo
+    {
+        public LocalClientInfo(string debuggerPath)
+        {
+            Type = ClientType.Local;
+            DebuggerPath = debuggerPath;
+        }
+
+        public string DebuggerPath;
+    }
+}
diff --git a/test-suite/TestRunner/TestRunner.csproj b/test-suite/TestRunner/TestRunner.csproj
new file mode 100644 (file)
index 0000000..cbb1855
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+    <ProjectReference Include="..\LocalDebugger\LocalDebugger.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/test-suite/VSCodeExampleTest/Program.cs b/test-suite/VSCodeExampleTest/Program.cs
new file mode 100644 (file)
index 0000000..0454e29
--- /dev/null
@@ -0,0 +1,183 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+using NetcoreDbgTest;
+using NetcoreDbgTest.VSCode;
+using NetcoreDbgTest.Script;
+
+using Xunit;
+using Newtonsoft.Json;
+
+namespace NetcoreDbgTest.Script
+{
+    // Context includes methods and constants which
+    // will be move to debugger API
+    class Context
+    {
+        public static void PrepareStart()
+        {
+            InitializeRequest initializeRequest = new InitializeRequest();
+            initializeRequest.arguments.clientID = "vscode";
+            initializeRequest.arguments.clientName = "Visual Studio Code";
+            initializeRequest.arguments.adapterID = "coreclr";
+            initializeRequest.arguments.pathFormat = "path";
+            initializeRequest.arguments.linesStartAt1 = true;
+            initializeRequest.arguments.columnsStartAt1 = true;
+            initializeRequest.arguments.supportsVariableType = true;
+            initializeRequest.arguments.supportsVariablePaging = true;
+            initializeRequest.arguments.supportsRunInTerminalRequest = true;
+            initializeRequest.arguments.locale = "en-us";
+            Assert.True(VSCodeDebugger.Request(initializeRequest).Success);
+
+            LaunchRequest launchRequest = new LaunchRequest();
+            launchRequest.arguments.name = ".NET Core Launch (console) with pipeline";
+            launchRequest.arguments.type = "coreclr";
+            launchRequest.arguments.preLaunchTask = "build";
+            launchRequest.arguments.program = DebuggeeInfo.TargetAssemblyPath;
+            // NOTE this code works only with one source file
+            launchRequest.arguments.cwd = Directory.GetParent(DebuggeeInfo.SourceFilesPath).FullName;
+            launchRequest.arguments.console = "internalConsole";
+            launchRequest.arguments.stopAtEntry = true;
+            launchRequest.arguments.internalConsoleOptions = "openOnSessionStart";
+            launchRequest.arguments.__sessionId = Guid.NewGuid().ToString();
+            Assert.True(VSCodeDebugger.Request(launchRequest).Success);
+        }
+
+        public static void PrepareEnd()
+        {
+            ConfigurationDoneRequest configurationDoneRequest = new ConfigurationDoneRequest();
+            Assert.True(VSCodeDebugger.Request(configurationDoneRequest).Success);
+        }
+
+        public static void WasEntryPointHit()
+        {
+            string resJSON = VSCodeDebugger.Receive(-1);
+            Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped")
+                        && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "entry"));
+
+            foreach (var Event in VSCodeDebugger.EventList) {
+                if (VSCodeDebugger.isResponseContainProperty(Event, "event", "stopped")
+                    && VSCodeDebugger.isResponseContainProperty(Event, "reason", "entry")) {
+                    threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(Event, "threadId"));
+                    break;
+                }
+            }
+        }
+
+        public static void WasExit()
+        {
+            string resJSON = VSCodeDebugger.Receive(-1);
+            Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "terminated"));
+
+            DisconnectRequest disconnectRequest = new DisconnectRequest();
+            disconnectRequest.arguments = new DisconnectArguments();
+            disconnectRequest.arguments.restart = false;
+            Assert.True(VSCodeDebugger.Request(disconnectRequest).Success);
+        }
+
+        public static void AddBreakpoint(string bpName, string Condition = null)
+        {
+            Breakpoint bp = DebuggeeInfo.Breakpoints[bpName];
+            Assert.Equal(BreakpointType.Line, bp.Type);
+            var lbp = (LineBreakpoint)bp;
+
+            BreakpointSourceName = lbp.FileName;
+            BreakpointList.Add(new SourceBreakpoint(lbp.NumLine, Condition));
+            BreakpointLines.Add(lbp.NumLine);
+        }
+
+        public static void SetBreakpoints()
+        {
+            SetBreakpointsRequest setBreakpointsRequest = new SetBreakpointsRequest();
+            setBreakpointsRequest.arguments.source.name = BreakpointSourceName;
+            // NOTE this code works only with one source file
+            setBreakpointsRequest.arguments.source.path = DebuggeeInfo.SourceFilesPath;
+            setBreakpointsRequest.arguments.lines.AddRange(BreakpointLines);
+            setBreakpointsRequest.arguments.breakpoints.AddRange(BreakpointList);
+            setBreakpointsRequest.arguments.sourceModified = false;
+            Assert.True(VSCodeDebugger.Request(setBreakpointsRequest).Success);
+        }
+
+        public static void WasBreakpointHit(Breakpoint breakpoint)
+        {
+            string resJSON = VSCodeDebugger.Receive(-1);
+            Assert.True(VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped")
+                        && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint"));
+
+            foreach (var Event in VSCodeDebugger.EventList) {
+                if (VSCodeDebugger.isResponseContainProperty(Event, "event", "stopped")
+                    && VSCodeDebugger.isResponseContainProperty(Event, "reason", "breakpoint")) {
+                    threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(Event, "threadId"));
+                }
+            }
+
+            StackTraceRequest stackTraceRequest = new StackTraceRequest();
+            stackTraceRequest.arguments.threadId = threadId;
+            stackTraceRequest.arguments.startFrame = 0;
+            stackTraceRequest.arguments.levels = 20;
+            var ret = VSCodeDebugger.Request(stackTraceRequest);
+            Assert.True(ret.Success);
+
+            Assert.Equal(BreakpointType.Line, breakpoint.Type);
+            var lbp = (LineBreakpoint)breakpoint;
+
+            StackTraceResponse stackTraceResponse =
+                JsonConvert.DeserializeObject<StackTraceResponse>(ret.ResponseStr);
+
+            foreach (var Frame in stackTraceResponse.body.stackFrames) {
+                if (Frame.line == lbp.NumLine
+                    && Frame.source.name == lbp.FileName
+                    // NOTE this code works only with one source file
+                    && Frame.source.path == DebuggeeInfo.SourceFilesPath)
+                    return;
+            }
+
+            throw new NetcoreDbgTestCore.ResultNotSuccessException();
+        }
+
+        public static void Continue()
+        {
+            ContinueRequest continueRequest = new ContinueRequest();
+            continueRequest.arguments.threadId = threadId;
+            Assert.True(VSCodeDebugger.Request(continueRequest).Success);
+        }
+
+        static VSCodeDebugger VSCodeDebugger = new VSCodeDebugger();
+        static int threadId = -1;
+        public static string BreakpointSourceName;
+        public static List<SourceBreakpoint> BreakpointList = new List<SourceBreakpoint>();
+        public static List<int> BreakpointLines = new List<int>();
+    }
+}
+
+namespace VSCodeExampleTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // first checkpoint (initialization) must provide "init" as id
+            Label.Checkpoint("init", "bp_test", () => {
+                Context.PrepareStart();
+                Context.AddBreakpoint("bp");
+                Context.SetBreakpoints();
+                Context.PrepareEnd();
+                Context.WasEntryPointHit();
+                Context.Continue();
+            });
+
+            Console.WriteLine("A breakpoint \"bp\" is set on this line"); Label.Breakpoint("bp");
+
+            Label.Checkpoint("bp_test", "finish", () => {
+                Context.WasBreakpointHit(DebuggeeInfo.Breakpoints["bp"]);
+                Context.Continue();
+            });
+
+            // last checkpoint must provide "finish" as id or empty string ("") as next checkpoint id
+            Label.Checkpoint("finish", "", () => {
+                Context.WasExit();
+            });
+        }
+    }
+}
diff --git a/test-suite/VSCodeExampleTest/VSCodeExampleTest.csproj b/test-suite/VSCodeExampleTest/VSCodeExampleTest.csproj
new file mode 100644 (file)
index 0000000..ae80ae2
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />
+  </ItemGroup>
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+  </PropertyGroup>
+
+</Project>
diff --git a/test-suite/run_tests.ps1 b/test-suite/run_tests.ps1
new file mode 100644 (file)
index 0000000..e3f70bd
--- /dev/null
@@ -0,0 +1,62 @@
+$ALL_TEST_NAMES = @(
+    "MIExampleTest"
+    "VSCodeExampleTest"
+)
+
+$TEST_NAMES = $args
+
+if ($NETCOREDBG.count -eq 0) {
+    $NETCOREDBG = "../bin/netcoredbg.exe"
+}
+
+if ($TEST_NAMES.count -eq 0) {
+    $TEST_NAMES = $ALL_TEST_NAMES
+}
+
+# Prepare
+dotnet build TestRunner
+
+$test_pass = 0
+$test_fail = 0
+$test_list = ""
+
+# Build, push and run tests
+foreach ($TEST_NAME in $TEST_NAMES) {
+    dotnet build $TEST_NAME
+
+    $SOURCE_FILE_LIST = (Get-ChildItem -Path $TEST_NAME -Recurse *.cs |
+        ? { $_.FullName -inotmatch "$TEST_NAME\\obj" }).Name
+
+    $SOURCE_FILES = ""
+    foreach ($SOURCE_FILE in $SOURCE_FILE_LIST) {
+        $SOURCE_FILES += $TEST_NAME + "/" + $SOURCE_FILE + ";"
+    }
+
+    $PROTO = "mi"
+    if ($TEST_NAME.StartsWith("VSCode")) {
+        $PROTO = "vscode"
+    }
+
+    dotnet run --project TestRunner -- `
+        --local $NETCOREDBG `
+        --proto $PROTO `
+        --test $TEST_NAME `
+        --sources $SOURCE_FILES `
+        --assembly $TEST_NAME/bin/Debug/netcoreapp2.1/$TEST_NAME.dll
+
+
+    if($?)
+    {
+        $test_pass++
+        $test_list = "$test_list$TEST_NAME ... passed`n"
+    }
+    else
+    {
+        $test_fail++
+        $test_list = "$test_list$TEST_NAME ... failed`n"
+    }
+}
+
+Write-Host ""
+Write-Host $test_list
+Write-Host "Total tests: $($test_pass + $test_fail). Passed: $test_pass. Failed: $test_fail."
diff --git a/test-suite/run_tests.sh b/test-suite/run_tests.sh
new file mode 100755 (executable)
index 0000000..bd9b6f9
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+ALL_TEST_NAMES=(
+    "MIExampleTest"
+    "VSCodeExampleTest"
+)
+
+TEST_NAMES="$@"
+
+if [[ -z $NETCOREDBG ]]; then
+    NETCOREDBG="../bin/netcoredbg"
+fi
+
+if [[ -z $TEST_NAMES ]]; then
+    TEST_NAMES="${ALL_TEST_NAMES[@]}"
+fi
+
+dotnet build TestRunner
+
+test_pass=0
+test_fail=0
+test_list=""
+
+for TEST_NAME in $TEST_NAMES; do
+    dotnet build $TEST_NAME
+
+    SOURCE_FILES=$(find $TEST_NAME \! -path "$TEST_NAME/obj/*" -type f -name "*.cs" -printf '%p;')
+
+    PROTO="mi"
+    if  [[ $TEST_NAME == VSCode* ]] ;
+    then
+        PROTO="vscode"
+    fi
+
+    dotnet run --project TestRunner -- \
+        --local $NETCOREDBG \
+        --proto $PROTO \
+        --test $TEST_NAME \
+        --sources "$SOURCE_FILES" \
+        --assembly $TEST_NAME/bin/Debug/netcoreapp2.1/$TEST_NAME.dll
+
+    if [ "$?" -ne "0" ]; then
+        test_fail=$(($test_fail + 1))
+        test_list="$test_list$TEST_NAME ... failed\n"
+    else
+        test_pass=$(($test_pass + 1))
+        test_list="$test_list$TEST_NAME ... passed\n"
+    fi
+done
+
+echo ""
+echo -e $test_list
+echo "Total tests: $(($test_pass + $test_fail)). Passed: $test_pass. Failed: $test_fail."
diff --git a/test-suite/sdb_run_tests.ps1 b/test-suite/sdb_run_tests.ps1
new file mode 100644 (file)
index 0000000..4c2f02b
--- /dev/null
@@ -0,0 +1,86 @@
+# Please prepare sdb target with netcoredbg before start
+
+$NETCOREDBG = "/home/owner/share/tmp/sdk_tools/netcoredbg/netcoredbg"
+
+$ALL_TEST_NAMES = @(
+    "MIExampleTest"
+    "VSCodeExampleTest"
+)
+
+$TEST_NAMES = $args
+
+if ($TEST_NAMES.count -eq 0) {
+    $TEST_NAMES = $ALL_TEST_NAMES
+}
+
+# Prepare
+dotnet build TestRunner
+
+sdb root on
+sdb forward tcp:4712 tcp:4711
+
+$test_pass = 0
+$test_fail = 0
+$test_list = ""
+
+# Build, push and run tests
+foreach ($TEST_NAME in $TEST_NAMES) {
+    dotnet build $TEST_NAME
+
+    sdb push $TEST_NAME\bin\Debug\netcoreapp2.1\$TEST_NAME.dll /tmp/
+    sdb push $TEST_NAME\bin\Debug\netcoreapp2.1\$TEST_NAME.pdb /tmp/
+
+    $SOURCE_FILE_LIST = (Get-ChildItem -Path $TEST_NAME -Recurse *.cs |
+        ? { $_.FullName -inotmatch "$TEST_NAME\\obj" }).Name
+
+    $SOURCE_FILES = ""
+    foreach ($SOURCE_FILE in $SOURCE_FILE_LIST) {
+        $SOURCE_FILES += $TEST_NAME + "/" + $SOURCE_FILE + ";"
+    }
+
+    if ($TEST_NAME.StartsWith("VSCode")) {
+        $PROTO = "vscode"
+
+        # change $HOME to /tmp in order to prevent /root/nohup.out creation
+        sdb shell HOME=/tmp nohup $NETCOREDBG --server --interpreter=$PROTO -- `
+            /usr/bin/dotnet-launcher /tmp/$TEST_NAME.dll
+
+        dotnet run --project TestRunner -- `
+            --tcp localhost 4712 `
+            --proto $PROTO `
+            --test $TEST_NAME `
+            --sources $SOURCE_FILES
+    } else {
+        $PROTO = "mi"
+
+        # change $HOME to /tmp in order to prevent /root/nohup.out creation
+        sdb shell HOME=/tmp nohup $NETCOREDBG --server --interpreter=$PROTO
+
+        dotnet run --project TestRunner -- `
+            --tcp localhost 4712 `
+            --dotnet /usr/bin/dotnet-launcher `
+            --proto $PROTO `
+            --test $TEST_NAME `
+            --sources $SOURCE_FILES `
+            --assembly /tmp/$TEST_NAME.dll
+    }
+
+
+    if($?)
+    {
+        $test_pass++
+        $test_list = "$test_list$TEST_NAME ... passed`n"
+    }
+    else
+    {
+        $test_fail++
+        $test_list = "$test_list$TEST_NAME ... failed`n"
+    }
+}
+
+# Leave
+sdb root off
+
+Write-Host ""
+Write-Host $test_list
+Write-Host "Total tests: $($test_pass + $test_fail). Passed: $test_pass. Failed: $test_fail."
diff --git a/test-suite/sdb_run_tests.sh b/test-suite/sdb_run_tests.sh
new file mode 100755 (executable)
index 0000000..31cabda
--- /dev/null
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+ALL_TEST_NAMES=(
+    "MIExampleTest"
+    "VSCodeExampleTest"
+)
+
+TEST_NAMES="$@"
+
+if [[ -z $TEST_NAMES ]]; then
+    TEST_NAMES="${ALL_TEST_NAMES[@]}"
+fi
+
+
+SDB=${SDB:-sdb}
+
+GBSROOT=$HOME/GBS-ROOT
+TOOLS_ABS_PATH=/home/owner/share/tmp/sdk_tools
+
+SCRIPTDIR=$(dirname $(readlink -f $0))
+
+# Detect target arch
+
+if   $SDB shell lscpu | grep -q armv7l; then ARCH=armv7l; 
+elif $SDB shell lscpu | grep -q i686;   then ARCH=i686;
+else echo "Unknown target architecture"; exit 1; fi
+
+# The following command assumes that GBS build was performed on a clean system (or in Docker),
+# which means only one such file exists.
+RPMFILE=$(find $GBSROOT/local/repos/ -type f -name netcoredbg-[0-9]\*$ARCH.rpm -print -quit)
+
+# Repackage RPM file as TGZ
+
+if [ ! -f "$RPMFILE" ]; then echo "Debugger RPM not found"; exit 1; fi
+PKGNAME=`rpm -q --qf "%{n}" -p $RPMFILE`
+PKGVERSION=`rpm -q --qf "%{v}" -p $RPMFILE`
+PKGARCH=`rpm -q --qf "%{arch}" -p $RPMFILE`
+TARGZNAME=$PKGNAME-$PKGVERSION-$PKGARCH.tar.gz
+if [ -d "$SCRIPTDIR/unpacked" ]; then rm -rf "$SCRIPTDIR/unpacked"; fi
+mkdir "$SCRIPTDIR/unpacked" && cd "$SCRIPTDIR/unpacked"
+rpm2cpio "$RPMFILE" | cpio -idmv
+touch .$TOOLS_ABS_PATH/$PKGNAME/version-$PKGVERSION
+tar cfz ../$TARGZNAME --owner=root --group=root -C .$TOOLS_ABS_PATH .
+cd ..
+
+# Upload TGZ to target and unpack
+
+REMOTETESTDIR=$TOOLS_ABS_PATH/netcoredbg-tests
+
+$SDB root on
+$SDB shell rm -rf "$TOOLS_ABS_PATH/netcoredbg"
+$SDB shell mkdir -p $TOOLS_ABS_PATH/on-demand
+$SDB push $TARGZNAME $TOOLS_ABS_PATH/on-demand
+$SDB shell "cd $TOOLS_ABS_PATH && tar xf $TOOLS_ABS_PATH/on-demand/$(basename $TARGZNAME)"
+$SDB shell rm -rf "$REMOTETESTDIR"
+$SDB shell mkdir $REMOTETESTDIR
+
+NETCOREDBG=$TOOLS_ABS_PATH/netcoredbg/netcoredbg
+
+# Prepare
+dotnet build $SCRIPTDIR/TestRunner
+sdb forward tcp:4712 tcp:4711
+
+test_pass=0
+test_fail=0
+test_list=""
+
+# Build, push and run tests
+for TEST_NAME in $TEST_NAMES; do
+    HOSTTESTDIR=$SCRIPTDIR/$TEST_NAME
+    dotnet build $HOSTTESTDIR
+    sdb push $HOSTTESTDIR/bin/Debug/netcoreapp2.1/$TEST_NAME.{dll,pdb} $REMOTETESTDIR
+
+    SOURCE_FILES=$(find $HOSTTESTDIR \! -path "$HOSTTESTDIR/obj/*" -type f -name "*.cs" -printf '%p;')
+
+    if  [[ $TEST_NAME == VSCode* ]] ;
+    then
+        PROTO="vscode"
+
+        # change $HOME to $REMOTETESTDIR in order to prevent /root/nohup.out creation
+        sdb shell HOME=$REMOTETESTDIR nohup $NETCOREDBG --server --interpreter=$PROTO -- \
+            /usr/bin/dotnet-launcher $REMOTETESTDIR/$TEST_NAME.dll
+
+        dotnet run --project TestRunner -- \
+            --tcp localhost 4712 \
+            --proto $PROTO \
+            --test $TEST_NAME \
+            --sources $SOURCE_FILES
+    else
+        PROTO="mi"
+
+        # change $HOME to /tmp in order to prevent /root/nohup.out creation
+        sdb shell HOME=$REMOTETESTDIR nohup $NETCOREDBG --server --interpreter=$PROTO
+
+        dotnet run --project TestRunner -- \
+            --tcp localhost 4712 \
+            --dotnet /usr/bin/dotnet-launcher \
+            --proto $PROTO \
+            --test $TEST_NAME \
+            --sources "$SOURCE_FILES" \
+            --assembly $REMOTETESTDIR/$TEST_NAME.dll
+    fi
+
+
+    if [ "$?" -ne "0" ]; then
+        test_fail=$(($test_fail + 1))
+        test_list="$test_list$TEST_NAME ... failed\n"
+    else
+        test_pass=$(($test_pass + 1))
+        test_list="$test_list$TEST_NAME ... passed\n"
+    fi
+done
+
+# Leave
+sdb root off
+
+echo ""
+echo -e $test_list
+echo "Total tests: $(($test_pass + $test_fail)). Passed: $test_pass. Failed: $test_fail."
diff --git a/test-suite/test-suite.sln b/test-suite/test-suite.sln
new file mode 100644 (file)
index 0000000..9df41dd
--- /dev/null
@@ -0,0 +1,76 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetcoreDbgTest", "NetcoreDbgTest\NetcoreDbgTest.csproj", "{D6915FE2-2E22-4924-8A98-AC7C7C697691}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunner", "TestRunner\TestRunner.csproj", "{851C3938-D4E2-46A3-B3F0-80F9E1A52B78}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MIExampleTest", "MIExampleTest\MIExampleTest.csproj", "{F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VSCodeExampleTest", "VSCodeExampleTest\VSCodeExampleTest.csproj", "{337B41D3-6674-4628-B309-7723DAFE119D}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Debug|x64 = Debug|x64
+               Debug|x86 = Debug|x86
+               Release|Any CPU = Release|Any CPU
+               Release|x64 = Release|x64
+               Release|x86 = Release|x86
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x64.Build.0 = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Debug|x86.Build.0 = Debug|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|Any CPU.Build.0 = Release|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x64.ActiveCfg = Release|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x64.Build.0 = Release|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x86.ActiveCfg = Release|Any CPU
+               {D6915FE2-2E22-4924-8A98-AC7C7C697691}.Release|x86.Build.0 = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x64.Build.0 = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Debug|x86.Build.0 = Debug|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|Any CPU.Build.0 = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x64.ActiveCfg = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x64.Build.0 = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x86.ActiveCfg = Release|Any CPU
+               {851C3938-D4E2-46A3-B3F0-80F9E1A52B78}.Release|x86.Build.0 = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x64.Build.0 = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Debug|x86.Build.0 = Debug|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|Any CPU.Build.0 = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x64.ActiveCfg = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x64.Build.0 = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x86.ActiveCfg = Release|Any CPU
+               {F2A7FC35-7ACE-4C93-A191-4579E0E7B9D1}.Release|x86.Build.0 = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x64.Build.0 = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Debug|x86.Build.0 = Debug|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|Any CPU.Build.0 = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x64.ActiveCfg = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x64.Build.0 = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x86.ActiveCfg = Release|Any CPU
+               {337B41D3-6674-4628-B309-7723DAFE119D}.Release|x86.Build.0 = Release|Any CPU
+       EndGlobalSection
+EndGlobal