diff --git a/src/MIDebugEngine/AD7.Impl/AD7Events.cs b/src/MIDebugEngine/AD7.Impl/AD7Events.cs index 022d24908..961627246 100644 --- a/src/MIDebugEngine/AD7.Impl/AD7Events.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Events.cs @@ -541,9 +541,9 @@ internal sealed class AD7BreakpointEvent : AD7StoppingEvent, IDebugBreakpointEve { public const string IID = "501C1E21-C557-48B8-BA30-A1EAB0BC4A74"; - private IEnumDebugBoundBreakpoints2 _boundBreakpoints; + IDebugBoundBreakpoint2[] _boundBreakpoints; - public AD7BreakpointEvent(IEnumDebugBoundBreakpoints2 boundBreakpoints) + public AD7BreakpointEvent(IDebugBoundBreakpoint2[] boundBreakpoints) { _boundBreakpoints = boundBreakpoints; } @@ -552,7 +552,7 @@ public AD7BreakpointEvent(IEnumDebugBoundBreakpoints2 boundBreakpoints) int IDebugBreakpointEvent2.EnumBreakpoints(out IEnumDebugBoundBreakpoints2 ppEnum) { - ppEnum = _boundBreakpoints; + ppEnum = new AD7BoundBreakpointsEnum(_boundBreakpoints); return Constants.S_OK; } diff --git a/src/MIDebugEngine/Engine.Impl/EngineCallback.cs b/src/MIDebugEngine/Engine.Impl/EngineCallback.cs index e6813ffde..d87a541e8 100644 --- a/src/MIDebugEngine/Engine.Impl/EngineCallback.cs +++ b/src/MIDebugEngine/Engine.Impl/EngineCallback.cs @@ -199,9 +199,7 @@ public void OnBreakpoint(DebuggedThread thread, ReadOnlyCollection clien // should notify each bound breakpoint that it has been hit and evaluate conditions here. // The sample engine does not support these features. - AD7BoundBreakpointsEnum boundBreakpointsEnum = new AD7BoundBreakpointsEnum(boundBreakpoints); - - AD7BreakpointEvent eventObject = new AD7BreakpointEvent(boundBreakpointsEnum); + AD7BreakpointEvent eventObject = new AD7BreakpointEvent(boundBreakpoints); AD7Thread ad7Thread = (AD7Thread)thread.Client; Send(eventObject, AD7BreakpointEvent.IID, ad7Thread); diff --git a/test/CppTests/Tests/BreakpointTests.cs b/test/CppTests/Tests/BreakpointTests.cs index 40a99c9e8..daee25269 100644 --- a/test/CppTests/Tests/BreakpointTests.cs +++ b/test/CppTests/Tests/BreakpointTests.cs @@ -154,6 +154,49 @@ public void FunctionBreakpointsBasic(ITestSettings settings) } } + [Theory] + [DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))] + [RequiresTestSettings] + public void LineLogBreakpointsBasic(ITestSettings settings) + { + this.TestPurpose("Tests basic operation of line breakpoints with a LogPoint"); + this.WriteSettings(settings); + + IDebuggee debuggee = SinkHelper.Open(this, settings.CompilerSettings, DebuggeeMonikers.KitchenSink.Breakpoint); + + using (IDebuggerRunner runner = CreateDebugAdapterRunner(settings)) + { + this.Comment("Configure launch"); + runner.Launch(settings.DebuggerSettings, debuggee, "-fCalling"); + + // These keep track of all the breakpoints in a source file + SourceBreakpoints callingBreakpoints = debuggee.Breakpoints(SinkHelper.Calling, 48); + + this.Comment("Set initial breakpoints"); + runner.SetBreakpoints(callingBreakpoints); + + this.Comment("Launch and run until first breakpoint"); + runner.Expects.HitBreakpointEvent(SinkHelper.Calling, 48) + .AfterConfigurationDone(); + + string logMessage = "Log Message"; + + this.Comment("Set a logpoint while in break mode"); + callingBreakpoints.Add(52, null, logMessage); + runner.SetBreakpoints(callingBreakpoints); + + this.Comment("Continue til end with newly-added logpoint"); + // ignoringResponseOrder: true here since sometimes the ContinuedResponse occurs after the OutputEvent and + // DAR does not look at previous messages unless marked ignoreResponseOrder. + runner.Expects.OutputEvent("^" + logMessage + "\\b", CategoryValue.Console, ignoreResponseOrder: true) + .ExitedEvent() + .TerminatedEvent() + .AfterContinue(); + + runner.DisconnectAndVerify(); + } + } + [Theory] [DependsOnTest(nameof(CompileKitchenSinkForBreakpointTests))] [RequiresTestSettings] diff --git a/test/DebugAdapterRunner/DebugAdapterCommand.cs b/test/DebugAdapterRunner/DebugAdapterCommand.cs index 529d11b98..e7f091a97 100644 --- a/test/DebugAdapterRunner/DebugAdapterCommand.cs +++ b/test/DebugAdapterRunner/DebugAdapterCommand.cs @@ -130,6 +130,19 @@ private byte[] ReadBlockFromStream(Stream stream, Process debugAdapter, int leng return messageBuffer; } + private struct ResponsePair + { + /// + /// Boolean to indicate if this response has a match. + /// + public bool FoundMatch { get; set; } + + /// + /// The response + /// + public object Response { get; set; } + } + public override void Run(DebugAdapterRunner runner) { // Send the request @@ -138,12 +151,44 @@ public override void Run(DebugAdapterRunner runner) runner.DebugAdapter.StandardInput.Write(request); // Process + validate responses - List responseList = new List(); + List responseList = new List(); int currentExpectedResponseIndex = 0; + int previousExpectedResponseIndex = 0; // Loop until we have received as many expected responses as expected while (currentExpectedResponseIndex < this.ExpectedResponses.Count) { + // Check if previous messages contained the expected response + if (previousExpectedResponseIndex != currentExpectedResponseIndex) + { + DebugAdapterResponse expected = this.ExpectedResponses[currentExpectedResponseIndex]; + // Only search responses in history list if we can ignore the response order. + if (expected.IgnoreResponseOrder) + { + for (int i = 0; i < responseList.Count; i++) + { + ResponsePair responsePair = responseList[i]; + // Make sure we have not seen this response and check to see if it the response we are expecting. + if (!responsePair.FoundMatch && Utils.CompareObjects(expected.Response, responsePair.Response, expected.IgnoreOrder)) + { + expected.Match = responsePair.Response; + responsePair.FoundMatch = true; + break; + } + } + + // We found an expected response from a previous response. + // Continue to next expectedResponse. + if (expected.Match != null) + { + currentExpectedResponseIndex++; + continue; + } + } + } + + previousExpectedResponseIndex = currentExpectedResponseIndex; + string receivedMessage = null; Exception getMessageExeception = null; try @@ -200,7 +245,19 @@ public override void Run(DebugAdapterRunner runner) messageStart = "Exception while reading message from debug adpter. " + getMessageExeception.Message; } - string expectedResponseText = JsonConvert.SerializeObject(this.ExpectedResponses[currentExpectedResponseIndex].Response); + string expectedResponseText = string.Empty; + for (int i = 0; i < ExpectedResponses.Count; i++) + { + string status; + if (i < currentExpectedResponseIndex) + status = "Found"; + else if (i == currentExpectedResponseIndex) + status = "Not Found"; + else + status = "Not searched yet"; + expectedResponseText += string.Format(CultureInfo.CurrentCulture, "{0}. {1}: {2}\n", (i + 1), status, JsonConvert.SerializeObject(ExpectedResponses[i].Response)); + } + string actualResponseText = string.Empty; for (int i = 0; i < responseList.Count; i++) @@ -208,7 +265,7 @@ public override void Run(DebugAdapterRunner runner) actualResponseText += string.Format(CultureInfo.CurrentCulture, "{0}. {1}\n", (i + 1), JsonConvert.SerializeObject(responseList[i])); } - string errorMessage = string.Format(CultureInfo.CurrentCulture, "{0}\nExpected = {1}\nActual Responses =\n{2}", + string errorMessage = string.Format(CultureInfo.CurrentCulture, "{0}\nExpected =\n{1}\nActual Responses =\n{2}", messageStart, expectedResponseText, actualResponseText); throw new DARException(errorMessage); @@ -221,7 +278,6 @@ public override void Run(DebugAdapterRunner runner) if (dispatcherMessage.type == "event") { DispatcherEvent dispatcherEvent = JsonConvert.DeserializeObject(receivedMessage); - responseList.Add(dispatcherEvent); if (dispatcherEvent.eventType == "stopped") { @@ -234,11 +290,16 @@ public override void Run(DebugAdapterRunner runner) expected.Match = dispatcherEvent; currentExpectedResponseIndex++; } + + responseList.Add(new ResponsePair() + { + FoundMatch = expected.Match != null, + Response = dispatcherEvent + }); } else if (dispatcherMessage.type == "response") { DispatcherResponse dispatcherResponse = JsonConvert.DeserializeObject(receivedMessage); - responseList.Add(dispatcherResponse); var expected = this.ExpectedResponses[currentExpectedResponseIndex]; if (Utils.CompareObjects(expected.Response, dispatcherResponse, expected.IgnoreOrder)) @@ -246,6 +307,12 @@ public override void Run(DebugAdapterRunner runner) expected.Match = dispatcherResponse; currentExpectedResponseIndex++; } + + responseList.Add(new ResponsePair() + { + FoundMatch = expected.Match != null, + Response = dispatcherResponse + }); } else if (dispatcherMessage.type == "request") { diff --git a/test/DebugAdapterRunner/DebugAdapterResponse.cs b/test/DebugAdapterRunner/DebugAdapterResponse.cs index 0d2346974..531a3c32a 100644 --- a/test/DebugAdapterRunner/DebugAdapterResponse.cs +++ b/test/DebugAdapterRunner/DebugAdapterResponse.cs @@ -16,11 +16,14 @@ public class DebugAdapterResponse public dynamic Match { get; internal set; } public bool IgnoreOrder { get; private set; } - public DebugAdapterResponse(object response, bool ignoreOrder = false) + public bool IgnoreResponseOrder { get; private set; } + + public DebugAdapterResponse(object response, bool ignoreOrder = false, bool ignoreResponseOrder = false) { Response = response; Match = null; IgnoreOrder = ignoreOrder; + IgnoreResponseOrder = ignoreResponseOrder; } } } diff --git a/test/DebuggerTesting/OpenDebug/Commands/Command.cs b/test/DebuggerTesting/OpenDebug/Commands/Command.cs index a81cd2d3d..d2ff16008 100644 --- a/test/DebuggerTesting/OpenDebug/Commands/Command.cs +++ b/test/DebuggerTesting/OpenDebug/Commands/Command.cs @@ -199,7 +199,7 @@ private void Run(DarRunner darRunner, ILoggingComponent log, params IEvent[] exp // Create a DAR Response from an expected response private static DebugAdapterResponse GetDarResponse(IResponse response) { - return new DebugAdapterResponse(response.DynamicResponse, response.IgnoreOrder); + return new DebugAdapterResponse(response.DynamicResponse, response.IgnoreOrder, response.IgnoreResponseOrder); } #region ActualResponse diff --git a/test/DebuggerTesting/OpenDebug/Commands/SetBreakpointsCommand.cs b/test/DebuggerTesting/OpenDebug/Commands/SetBreakpointsCommand.cs index d647327ca..d16f9fabd 100644 --- a/test/DebuggerTesting/OpenDebug/Commands/SetBreakpointsCommand.cs +++ b/test/DebuggerTesting/OpenDebug/Commands/SetBreakpointsCommand.cs @@ -18,10 +18,12 @@ public sealed class SetBreakpointsCommandArgs : JsonValue { public sealed class SourceBreakpoint { - public SourceBreakpoint(int line, int? column, string condition) + public SourceBreakpoint(int line, int? column, string condition, string logMessage) { this.line = line; + this.column = column; this.condition = condition; + this.logMessage = logMessage; } public int line; @@ -31,6 +33,9 @@ public SourceBreakpoint(int line, int? column, string condition) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public string condition; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string logMessage; } public Source source = new Source(); @@ -58,7 +63,7 @@ public SourceBreakpoints(string sourceRoot, string relativePath) { Parameter.ThrowIfNull(sourceRoot, nameof(sourceRoot)); Parameter.ThrowIfNull(relativePath, nameof(relativePath)); - this.Breakpoints = new Dictionary(); + this.Breakpoints = new Dictionary(); this.RelativePath = relativePath; this.FullPath = Path.Combine(sourceRoot, relativePath); } @@ -67,11 +72,11 @@ public SourceBreakpoints(string sourceRoot, string relativePath) #region Add/Remove - public SourceBreakpoints Add(int lineNumber, string condition = null) + public SourceBreakpoints Add(int lineNumber, string condition = null, string logMessage = null) { if (this.Breakpoints.ContainsKey(lineNumber)) throw new RunnerException("Breakpoint line {0} already added to file {1}.", lineNumber, this.RelativePath); - this.Breakpoints.Add(lineNumber, condition); + this.Breakpoints.Add(lineNumber, new SetBreakpointsCommandArgs.SourceBreakpoint(lineNumber, null, condition, logMessage)); return this; } @@ -97,7 +102,7 @@ public SourceBreakpoints Remove(int lineNumber) /// Keep the breakpoint info in a dictionary indexed by line number. /// Store the condition as the value. /// - public IDictionary Breakpoints { get; private set; } + public IDictionary Breakpoints { get; private set; } } #endregion @@ -112,10 +117,7 @@ public SetBreakpointsCommand(SourceBreakpoints sourceBreakpoints) : this() { this.Args.source.path = sourceBreakpoints.FullPath; - IDictionary breakpoints = sourceBreakpoints.Breakpoints; - this.Args.breakpoints = breakpoints.Select(x => - new SetBreakpointsCommandArgs.SourceBreakpoint(x.Key, null, x.Value) - ).ToArray(); + this.Args.breakpoints = sourceBreakpoints.Breakpoints.Select(x => x.Value).ToArray(); this.Args.lines = this.Args.breakpoints.Select(x => x.line).ToArray(); } diff --git a/test/DebuggerTesting/OpenDebug/Events/ConsoleEvent.cs b/test/DebuggerTesting/OpenDebug/Events/ConsoleEvent.cs deleted file mode 100644 index 300c61d59..000000000 --- a/test/DebuggerTesting/OpenDebug/Events/ConsoleEvent.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Newtonsoft.Json; - -namespace DebuggerTesting.OpenDebug.Events -{ - - #region ConsoleEventValue - - public sealed class ConsoleEventValue : EventValue - { - public sealed class Body - { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public string output; - - public string category; - } - - public Body body = new Body(); - } - - #endregion - - public class ConsoleEvent : Event - { - public ConsoleEvent(string text) : base("console") - { - this.ExpectedResponse.body.category = "console"; - this.ExpectedResponse.body.output = text; - } - } -} diff --git a/test/DebuggerTesting/OpenDebug/Events/OutputEvent.cs b/test/DebuggerTesting/OpenDebug/Events/OutputEvent.cs new file mode 100644 index 000000000..eba4a4c51 --- /dev/null +++ b/test/DebuggerTesting/OpenDebug/Events/OutputEvent.cs @@ -0,0 +1,44 @@ +// // Copyright (c) Microsoft. All rights reserved. +// // Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace DebuggerTesting.OpenDebug.Events +{ + public enum CategoryValue + { + Console = 0, + Stdout = 1, + Stderr = 2, + Telemetry = 3, + Unknown = Int32.MaxValue + } + + public sealed class OutputEventValue : EventValue + { + public sealed class Body + { + public string output; + + public string category; + } + + public Body body = new Body(); + } + + public sealed class OutputEvent : Event + { + public OutputEvent(string text, CategoryValue category, bool ignoreResponseOrder) : base("output") + { + this.IgnoreResponseOrder = ignoreResponseOrder; + this.ExpectedResponse.body.category = GetCategory(category); + this.ExpectedResponse.body.output = text; + } + + private static string GetCategory(CategoryValue category) + { + Parameter.ThrowIfIsInvalid(category, CategoryValue.Unknown, nameof(category)); + return category.ToString().ToLowerInvariant(); + } + } +} diff --git a/test/DebuggerTesting/OpenDebug/IResponse.cs b/test/DebuggerTesting/OpenDebug/IResponse.cs index c8b9ddb3f..4d7558d6d 100644 --- a/test/DebuggerTesting/OpenDebug/IResponse.cs +++ b/test/DebuggerTesting/OpenDebug/IResponse.cs @@ -17,5 +17,10 @@ public interface IResponse /// Set to true if the order of the items in the response is not important (like variable lists) /// bool IgnoreOrder { get; } + + /// + /// Set to true if the order of the response is not important (like output events) + /// + bool IgnoreResponseOrder { get; } } } diff --git a/test/DebuggerTesting/OpenDebug/Response.cs b/test/DebuggerTesting/OpenDebug/Response.cs index d56ad11dc..7b4921649 100644 --- a/test/DebuggerTesting/OpenDebug/Response.cs +++ b/test/DebuggerTesting/OpenDebug/Response.cs @@ -31,9 +31,11 @@ public Response() public bool IgnoreOrder { get; protected set; } - #endregion + public bool IgnoreResponseOrder { get; protected set; } - public T ExpectedResponse { get; protected set; } + #endregion + + public T ExpectedResponse { get; protected set; } public override string ToString() { diff --git a/test/DebuggerTesting/OpenDebug/RunBuilder.cs b/test/DebuggerTesting/OpenDebug/RunBuilder.cs index 6e0df5945..a5e4bd130 100644 --- a/test/DebuggerTesting/OpenDebug/RunBuilder.cs +++ b/test/DebuggerTesting/OpenDebug/RunBuilder.cs @@ -106,9 +106,9 @@ public static IRunBuilder FunctionBreakpointChangedEvent(this IRunBuilder runBui return runBuilder.Event(new BreakpointEvent(reason, startLine, endLine)); } - public static IRunBuilder ConsoleEvent(this IRunBuilder runBuilder, string text) + public static IRunBuilder OutputEvent(this IRunBuilder runBuilder, string text, CategoryValue category, bool ignoreResponseOrder = false) { - return runBuilder.Event(new ConsoleEvent(text)); + return runBuilder.Event(new OutputEvent(text, category, ignoreResponseOrder)); } #endregion