ported over wills debugger to latest branch
This commit is contained in:
Родитель
99e3e1fb14
Коммит
ab8ebb4c74
|
@ -104,6 +104,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Dialo
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.TriggerTrees.Tests", "tests\Microsoft.Bot.Builder.AI.TriggerTrees.Tests\Microsoft.Bot.Builder.AI.TriggerTrees.Tests.csproj", "{A9E5DD02-E633-46DC-B702-2ABA1AAC2851}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.Dialogs.Debugging", "libraries\Microsoft.Bot.Builder.Dialogs.Debugging\Microsoft.Bot.Builder.Dialogs.Debugging.csproj", "{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
|
||||
|
@ -471,6 +473,14 @@ Global
|
|||
{A9E5DD02-E633-46DC-B702-2ABA1AAC2851}.Documentation|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9E5DD02-E633-46DC-B702-2ABA1AAC2851}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9E5DD02-E633-46DC-B702-2ABA1AAC2851}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Documentation|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -524,6 +534,7 @@ Global
|
|||
{FB2EA804-158C-4654-AD60-A2105AC366FF} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
|
||||
{D5E70443-4BA2-42ED-992A-010268440B08} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{A9E5DD02-E633-46DC-B702-2ABA1AAC2851} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{84E3B6A2-42D9-498A-9CD2-1C5F5BE0D526} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}
|
||||
|
|
|
@ -146,7 +146,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Rules
|
|||
|
||||
protected void RegisterSourceLocation(string path, int lineNumber)
|
||||
{
|
||||
Debugger.SourceRegistry.Add(this, new Source.Range()
|
||||
DebugSupport.SourceRegistry.Add(this, new Source.Range()
|
||||
{
|
||||
Path = path,
|
||||
Start = new Source.Point() { LineIndex = lineNumber, CharIndex = 0 },
|
||||
|
|
|
@ -0,0 +1,468 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public sealed class DebugAdapter : DebugTransport, IMiddleware, DebugSupport.IDebugger
|
||||
{
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
private readonly Source.IRegistry registry;
|
||||
private readonly IBreakpoints breakpoints;
|
||||
|
||||
// lifetime scoped to IMiddleware.OnTurnAsync
|
||||
private readonly ConcurrentDictionary<ITurnContext, TurnThreadModel> threadByContext = new ConcurrentDictionary<ITurnContext, TurnThreadModel>();
|
||||
private readonly Identifier<IThreadModel> threads = new Identifier<IThreadModel>();
|
||||
// TODO: leaks - consider scoping or ConditionalWeakTable
|
||||
private readonly Identifier<FrameModel> frames = new Identifier<FrameModel>();
|
||||
// TODO: leaks - consider scoping or ConditionalWeakTable
|
||||
private readonly Identifier<VariableModel> variables = new Identifier<VariableModel>();
|
||||
|
||||
private interface IThreadModel
|
||||
{
|
||||
string Name { get; }
|
||||
IReadOnlyList<FrameModel> Frames { get; }
|
||||
RunModel Run { get; }
|
||||
}
|
||||
|
||||
private sealed class BotThreadModel : IThreadModel
|
||||
{
|
||||
public string Name => "Bot";
|
||||
public IReadOnlyList<FrameModel> Frames => Array.Empty<FrameModel>();
|
||||
public RunModel Run { get; } = new RunModel();
|
||||
}
|
||||
|
||||
private sealed class TurnThreadModel : IThreadModel
|
||||
{
|
||||
public TurnThreadModel(ITurnContext turnContext)
|
||||
{
|
||||
TurnContext = turnContext;
|
||||
}
|
||||
public string Name => TurnContext.Activity.Text;
|
||||
|
||||
public IReadOnlyList<FrameModel> Frames => Model.FramesFor(LastContext, LastItem, LastMore);
|
||||
public RunModel Run { get; } = new RunModel();
|
||||
public ITurnContext TurnContext { get; }
|
||||
public DialogContext LastContext { get; set; }
|
||||
public object LastItem { get; set; }
|
||||
public string LastMore { get; set; }
|
||||
}
|
||||
|
||||
public enum Phase { Started, Continue, Next, Step, Breakpoint, Pause, Exited };
|
||||
|
||||
public sealed class RunModel
|
||||
{
|
||||
public Phase? PhaseSent { get; set; }
|
||||
public Phase Phase { get; set; } = Phase.Started;
|
||||
public object Gate { get; } = new object();
|
||||
|
||||
public void Post(Phase what)
|
||||
{
|
||||
Monitor.Enter(Gate);
|
||||
try
|
||||
{
|
||||
Phase = what;
|
||||
Monitor.Pulse(Gate);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(Gate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int VariablesReference(VariableModel variable)
|
||||
{
|
||||
var value = variable.Value;
|
||||
if (Policy.ShowAsScalar(variable.Value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return variables.Add(variable);
|
||||
}
|
||||
|
||||
private readonly Task task;
|
||||
|
||||
public DebugAdapter(Source.IRegistry registry, IBreakpoints breakpoints, ILogger logger)
|
||||
: base(logger)
|
||||
{
|
||||
this.registry = registry ?? throw new ArgumentNullException(nameof(registry));
|
||||
this.breakpoints = breakpoints ?? throw new ArgumentNullException(nameof(breakpoints));
|
||||
this.task = ListenAsync(new IPEndPoint(IPAddress.Any, port: 4712), cancellationToken.Token);
|
||||
|
||||
threads.Add(new BotThreadModel());
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
this.cancellationToken.Cancel();
|
||||
using (this.cancellationToken)
|
||||
using (this.task)
|
||||
{
|
||||
await this.task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task IMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken)
|
||||
{
|
||||
//if (System.Diagnostics.Debugger.IsAttached)
|
||||
//{
|
||||
// var source = new CancellationTokenSource();
|
||||
// source.CancelAfter(TimeSpan.FromMinutes(2));
|
||||
// cancellationToken = source.Token;
|
||||
//}
|
||||
|
||||
var thread = new TurnThreadModel(turnContext);
|
||||
var threadId = threads.Add(thread);
|
||||
threadByContext.TryAdd(turnContext, thread);
|
||||
try
|
||||
{
|
||||
thread.Run.Post(Phase.Started);
|
||||
await UpdateThreadPhaseAsync(thread, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
DebugSupport.IDebugger trace = this;
|
||||
turnContext.TurnState.Add(trace);
|
||||
await next(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
thread.Run.Post(Phase.Exited);
|
||||
await UpdateThreadPhaseAsync(thread, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
threadByContext.TryRemove(turnContext, out var ignored);
|
||||
threads.Remove(thread);
|
||||
}
|
||||
}
|
||||
|
||||
async Task DebugSupport.IDebugger.StepAsync(DialogContext context, object item, string more, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await OutputAsync($"Step: {Policy.NameFor(item)} {more}", item, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await UpdateBreakpointsAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var thread = threadByContext[context.Context];
|
||||
thread.LastContext = context;
|
||||
thread.LastItem = item;
|
||||
thread.LastMore = more;
|
||||
|
||||
var run = thread.Run;
|
||||
if (breakpoints.IsBreakPoint(item))
|
||||
{
|
||||
run.Post(Phase.Breakpoint);
|
||||
}
|
||||
|
||||
// TODO: implement asynchronous condition variables
|
||||
Monitor.Enter(run.Gate);
|
||||
try
|
||||
{
|
||||
// TODO: remove synchronous waits
|
||||
UpdateThreadPhaseAsync(thread, item, cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
while (!(run.Phase == Phase.Started || run.Phase == Phase.Continue || run.Phase == Phase.Next))
|
||||
{
|
||||
Monitor.Wait(run.Gate);
|
||||
}
|
||||
|
||||
if (run.Phase == Phase.Started)
|
||||
{
|
||||
run.Phase = Phase.Continue;
|
||||
}
|
||||
|
||||
// TODO: remove synchronous waits
|
||||
UpdateThreadPhaseAsync(thread, item, cancellationToken).GetAwaiter().GetResult();
|
||||
|
||||
if (run.Phase == Phase.Next)
|
||||
{
|
||||
run.Phase = Phase.Step;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(run.Gate);
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
this.logger.LogError(error, error.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateBreakpointsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var breakpoints = this.breakpoints.ApplyUpdates();
|
||||
foreach (var breakpoint in breakpoints)
|
||||
{
|
||||
if (breakpoint.verified)
|
||||
{
|
||||
var item = this.breakpoints.ItemFor(breakpoint);
|
||||
await OutputAsync($"Set breakpoint at {Policy.NameFor(item)}", item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var body = new { reason = "changed", breakpoint };
|
||||
await SendAsync(Protocol.Event.From(NextSeq, "breakpoint", body), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateThreadPhaseAsync(IThreadModel thread, object item, CancellationToken cancellationToken)
|
||||
{
|
||||
var run = thread.Run;
|
||||
if (run.Phase == run.PhaseSent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var phase = run.Phase;
|
||||
var suffix = item != null ? $" at {Policy.NameFor(item)}" : string.Empty;
|
||||
var description = $"'{thread.Name}' is {phase}{suffix}";
|
||||
|
||||
await OutputAsync(description, item, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var threadId = this.threads[thread];
|
||||
|
||||
if (phase == Phase.Next)
|
||||
{
|
||||
phase = Phase.Continue;
|
||||
}
|
||||
|
||||
string reason = phase.ToString().ToLower();
|
||||
|
||||
if (phase == Phase.Started || phase == Phase.Exited)
|
||||
{
|
||||
await SendAsync(Protocol.Event.From(NextSeq, "thread", new { threadId, reason }), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (phase == Phase.Continue)
|
||||
{
|
||||
await SendAsync(Protocol.Event.From(NextSeq, "continue", new { threadId, allThreadsContinued = false }), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var body = new
|
||||
{
|
||||
reason,
|
||||
description,
|
||||
threadId,
|
||||
text = description,
|
||||
preserveFocusHint = false,
|
||||
allThreadsStopped = true,
|
||||
};
|
||||
|
||||
await SendAsync(Protocol.Event.From(NextSeq, "stopped", body), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
run.PhaseSent = run.Phase;
|
||||
}
|
||||
|
||||
private async Task SendAsync(Protocol.Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
var token = JToken.FromObject(message, new JsonSerializer() { NullValueHandling = NullValueHandling.Include });
|
||||
await SendAsync(token, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task OutputAsync(string text, object item, CancellationToken cancellationToken)
|
||||
{
|
||||
bool found = this.registry.TryGetValue(item, out var range);
|
||||
|
||||
var body = new
|
||||
{
|
||||
output = text + Environment.NewLine,
|
||||
source = found ? new Protocol.Source(range.Path) : null,
|
||||
line = found ? (int?)range.Start.LineIndex : null,
|
||||
};
|
||||
|
||||
await SendAsync(Protocol.Event.From(NextSeq, "output", body), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private int sequence = 0;
|
||||
private int NextSeq => Interlocked.Increment(ref sequence);
|
||||
|
||||
private async Task<Protocol.Message> DispatchAsync(Protocol.Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (message is Protocol.Request<Protocol.Initialize> initialize)
|
||||
{
|
||||
var body = new
|
||||
{
|
||||
supportsConfigurationDoneRequest = true,
|
||||
};
|
||||
var response = Protocol.Response.From(NextSeq, initialize, body);
|
||||
await SendAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
return Protocol.Event.From(NextSeq, "initialized", new { });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Launch> launch)
|
||||
{
|
||||
return Protocol.Response.From(NextSeq, launch, new { });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Attach> attach)
|
||||
{
|
||||
return Protocol.Response.From(NextSeq, attach, new { });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.SetBreakpoints> setBreakpoints)
|
||||
{
|
||||
var arguments = setBreakpoints.arguments;
|
||||
var file = Path.GetFileName(arguments.source.path);
|
||||
await OutputAsync($"Set breakpoints for {file}", null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var breakpoints = this.breakpoints.SetBreakpoints(arguments.source, arguments.breakpoints);
|
||||
foreach (var breakpoint in breakpoints)
|
||||
{
|
||||
if (breakpoint.verified)
|
||||
{
|
||||
var item = this.breakpoints.ItemFor(breakpoint);
|
||||
await OutputAsync($"Set breakpoint at {Policy.NameFor(item)}", item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Protocol.Response.From(NextSeq, setBreakpoints, new { breakpoints });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Threads> threads)
|
||||
{
|
||||
var body = new
|
||||
{
|
||||
threads = this.threads.Select(t => new { id = t.Key, name = t.Value.Name }).ToArray()
|
||||
};
|
||||
|
||||
return Protocol.Response.From(NextSeq, threads, body);
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.StackTrace> stackTrace)
|
||||
{
|
||||
var arguments = stackTrace.arguments;
|
||||
var thread = this.threads[arguments.threadId];
|
||||
|
||||
var frames = thread.Frames;
|
||||
var stackFrames = new List<Protocol.StackFrame>();
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
var stackFrame = new Protocol.StackFrame()
|
||||
{
|
||||
id = this.frames.Add(frame),
|
||||
name = frame.Name
|
||||
};
|
||||
|
||||
if (this.registry.TryGetValue(frame.Item, out var range))
|
||||
{
|
||||
stackFrame.source = new Protocol.Source(range.Path);
|
||||
stackFrame.line = range.Start.LineIndex;
|
||||
stackFrame.column = range.Start.CharIndex;
|
||||
stackFrame.endLine = range.After.LineIndex;
|
||||
stackFrame.endColumn = range.After.CharIndex;
|
||||
}
|
||||
|
||||
stackFrames.Add(stackFrame);
|
||||
}
|
||||
|
||||
return Protocol.Response.From(NextSeq, stackTrace, new { stackFrames });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Scopes> scopes)
|
||||
{
|
||||
var arguments = scopes.arguments;
|
||||
var frame = this.frames[arguments.frameId];
|
||||
const bool expensive = false;
|
||||
|
||||
var body = new
|
||||
{
|
||||
scopes = new[] { new { expensive, name = frame.Name, variablesReference = VariablesReference(frame.Scopes) } }
|
||||
};
|
||||
|
||||
return Protocol.Response.From(NextSeq, scopes, body);
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Variables> vars)
|
||||
{
|
||||
var arguments = vars.arguments;
|
||||
var variable = this.variables[arguments.variablesReference];
|
||||
var variables = Model.VariablesFor(variable);
|
||||
|
||||
var body = new
|
||||
{
|
||||
variables = variables.Select(v => new
|
||||
{
|
||||
name = v.Name,
|
||||
value = Policy.ScalarJsonValue(v.Value),
|
||||
variablesReference = VariablesReference(v)
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
return Protocol.Response.From(NextSeq, vars, body);
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Continue> cont)
|
||||
{
|
||||
bool found = this.threads.TryGetValue(cont.arguments.threadId, out var thread);
|
||||
if (found)
|
||||
{
|
||||
thread.Run.Post(Phase.Continue);
|
||||
}
|
||||
|
||||
return Protocol.Response.From(NextSeq, cont, new { allThreadsContinued = false });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Pause> pause)
|
||||
{
|
||||
bool found = this.threads.TryGetValue(pause.arguments.threadId, out var thread);
|
||||
if (found)
|
||||
{
|
||||
thread.Run.Post(Phase.Pause);
|
||||
}
|
||||
|
||||
return Protocol.Response.From(NextSeq, pause, new { });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Next> next)
|
||||
{
|
||||
bool found = this.threads.TryGetValue(next.arguments.threadId, out var thread);
|
||||
if (found)
|
||||
{
|
||||
thread.Run.Post(Phase.Next);
|
||||
}
|
||||
|
||||
return Protocol.Response.From(NextSeq, next, new { });
|
||||
}
|
||||
else if (message is Protocol.Request<Protocol.Disconnect> disconnect)
|
||||
{
|
||||
// possibly run all threads
|
||||
|
||||
return Protocol.Response.From(NextSeq, disconnect, new { });
|
||||
}
|
||||
else if (message is Protocol.Request request)
|
||||
{
|
||||
return Protocol.Response.From(NextSeq, request, new { });
|
||||
}
|
||||
else if (message is Protocol.Event @event)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task AcceptAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await ReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
var message = Protocol.Parse(token);
|
||||
var response = await DispatchAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
await SendAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
this.logger.LogError(error, error.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public abstract class DebugTransport
|
||||
{
|
||||
private readonly ReaderWriterLock connected = new ReaderWriterLock(writer: true);
|
||||
private readonly SemaphoreSlim readable = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim writable = new SemaphoreSlim(1, 1);
|
||||
private StreamReader reader;
|
||||
private StreamWriter writer;
|
||||
|
||||
private const string Prefix = @"Content-Length: ";
|
||||
private static readonly Encoding Encoding = Encoding.UTF8;
|
||||
|
||||
protected readonly ILogger logger;
|
||||
|
||||
protected DebugTransport(ILogger logger)
|
||||
{
|
||||
this.logger = logger ?? NullLogger.Instance;
|
||||
}
|
||||
|
||||
protected async Task ListenAsync(IPEndPoint point, CancellationToken cancellationToken)
|
||||
{
|
||||
var listener = new TcpListener(point);
|
||||
listener.Start();
|
||||
using (cancellationToken.Register(listener.Stop))
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = await listener.AcceptTcpClientAsync().ConfigureAwait(false))
|
||||
using (var stream = client.GetStream())
|
||||
using (reader = new StreamReader(stream, Encoding))
|
||||
using (writer = new StreamWriter(stream, Encoding))
|
||||
using (cancellationToken.Register(() =>
|
||||
{
|
||||
stream.Close();
|
||||
reader.Close();
|
||||
writer.Close();
|
||||
client.Close();
|
||||
}))
|
||||
{
|
||||
connected.ExitWrite();
|
||||
|
||||
try
|
||||
{
|
||||
await AcceptAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connected.EnterWriteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
this.logger.LogError(error, error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task AcceptAsync(CancellationToken cancellationToken);
|
||||
|
||||
protected async Task<JToken> ReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var acquired = await connected.TryEnterReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!acquired)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (await readable.WithWaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
if (line == null)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
var empty = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
if (empty.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (!line.StartsWith(Prefix))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var count = int.Parse(line.Substring(Prefix.Length));
|
||||
|
||||
var buffer = new char[count];
|
||||
int index = 0;
|
||||
while (index < count)
|
||||
{
|
||||
var bytes = await reader.ReadAsync(buffer, index, count - index).ConfigureAwait(false);
|
||||
index += bytes;
|
||||
}
|
||||
|
||||
var json = new string(buffer);
|
||||
var token = JToken.Parse(json);
|
||||
logger.LogTrace($"READ: {token.ToString(Formatting.None)}");
|
||||
return token;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connected.ExitReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SendAsync(JToken token, CancellationToken cancellationToken)
|
||||
{
|
||||
var acquired = await connected.TryEnterReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!acquired)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (await writable.WithWaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogTrace($"SEND: {token.ToString(Formatting.None)}");
|
||||
var json = token.ToString();
|
||||
var buffer = Encoding.GetBytes(json);
|
||||
var length = buffer.Length + writer.NewLine.Length;
|
||||
await writer.WriteLineAsync(Prefix + length).ConfigureAwait(false);
|
||||
await writer.WriteLineAsync().ConfigureAwait(false);
|
||||
await writer.WriteLineAsync(json).ConfigureAwait(false);
|
||||
await writer.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connected.ExitReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
public struct Releaser : IDisposable
|
||||
{
|
||||
public SemaphoreSlim Semaphore { get; }
|
||||
public Releaser(SemaphoreSlim semaphore)
|
||||
{
|
||||
Semaphore = semaphore ?? throw new ArgumentNullException(nameof(semaphore));
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Releaser> WithWaitAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken)
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
return new Releaser(semaphore);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public sealed class ReferenceEquality<T> : IEqualityComparer<T>
|
||||
{
|
||||
public static readonly IEqualityComparer<T> Instance = new ReferenceEquality<T>();
|
||||
private ReferenceEquality()
|
||||
{
|
||||
}
|
||||
bool IEqualityComparer<T>.Equals(T x, T y) => object.ReferenceEquals(x, y);
|
||||
int IEqualityComparer<T>.GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
|
||||
public sealed class Identifier<T> : IEnumerable<KeyValuePair<int, T>>
|
||||
{
|
||||
private readonly Dictionary<T, int> codeByItem = new Dictionary<T, int>(ReferenceEquality<T>.Instance);
|
||||
private readonly Dictionary<int, T> itemByCode = new Dictionary<int, T>();
|
||||
private readonly object gate = new object();
|
||||
private int last = 0;
|
||||
|
||||
public int Add(T item)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (!this.codeByItem.TryGetValue(item, out var code))
|
||||
{
|
||||
// avoid falsey values
|
||||
code = ++last;
|
||||
this.codeByItem.Add(item, code);
|
||||
this.itemByCode.Add(code, item);
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
public void Remove(T item)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
var code = this.codeByItem[item];
|
||||
this.itemByCode.Remove(code);
|
||||
this.codeByItem.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<int, T>> IEnumerable<KeyValuePair<int, T>>.GetEnumerator()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.itemByCode.ToList().GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<int, T>>)this).GetEnumerator();
|
||||
|
||||
public T this[int code]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.itemByCode[code];
|
||||
}
|
||||
}
|
||||
}
|
||||
public int this[T item]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.codeByItem[item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(int code, out T item)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.itemByCode.TryGetValue(code, out item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder.Dialogs\Microsoft.Bot.Builder.Dialogs.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public sealed class VariableModel
|
||||
{
|
||||
public VariableModel(string name, object value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
public string Name { get; }
|
||||
public object Value { get; }
|
||||
|
||||
public override string ToString() => $"{Name}={Value}";
|
||||
}
|
||||
|
||||
public sealed class FrameModel
|
||||
{
|
||||
public FrameModel(string name, object item, VariableModel scopes)
|
||||
{
|
||||
Name = name;
|
||||
Item = item;
|
||||
Scopes = scopes;
|
||||
}
|
||||
public string Name { get; }
|
||||
public object Item { get; }
|
||||
public VariableModel Scopes { get; }
|
||||
public override string ToString() => $"{Name}:{Item}";
|
||||
}
|
||||
|
||||
public static class Model
|
||||
{
|
||||
public static IReadOnlyList<FrameModel> FramesFor(DialogContext dialogContext, object item, string more)
|
||||
{
|
||||
VariableModel scope = null;
|
||||
|
||||
var frames = new List<FrameModel>();
|
||||
while (dialogContext != null)
|
||||
{
|
||||
foreach (var instance in dialogContext.Stack)
|
||||
{
|
||||
var state = dialogContext.State;
|
||||
var data = new
|
||||
{
|
||||
user = state.User,
|
||||
conversation = state.Conversation,
|
||||
dialog = dialogContext.ActiveDialog != null ? state.Dialog : null,
|
||||
turn = state.Turn,
|
||||
entities = state.Entities,
|
||||
tags = dialogContext.ActiveTags,
|
||||
};
|
||||
|
||||
scope = new VariableModel(nameof(data) + frames.Count, data);
|
||||
var dialog = dialogContext.FindDialog(instance.Id);
|
||||
var frame = new FrameModel(instance.Id, dialog, scope);
|
||||
frames.Add(frame);
|
||||
}
|
||||
|
||||
dialogContext = dialogContext.Parent;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
var name = $"{Policy.NameFor(item)}:{more}";
|
||||
frames.Insert(0, new FrameModel(name, item, scope));
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<VariableModel> VariablesFor(VariableModel variable)
|
||||
{
|
||||
var variables = new List<VariableModel>();
|
||||
var value = variable.Value;
|
||||
if (value is IReadOnlyDictionary<string, object> dictionary)
|
||||
{
|
||||
foreach (var kv in dictionary)
|
||||
{
|
||||
variables.Add(new VariableModel(kv.Key, kv.Value));
|
||||
}
|
||||
}
|
||||
else if (value is IEnumerable<object> items)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
variables.Add(new VariableModel(index.ToString(), item));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
else if (value != null)
|
||||
{
|
||||
var type = value.GetType();
|
||||
var properties = type.GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var index = property.GetIndexParameters();
|
||||
if (index.Length == 0)
|
||||
{
|
||||
variables.Add(new VariableModel(property.Name, property.GetValue(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables.RemoveAll(v => !Policy.ShowToDebugger(v.Value));
|
||||
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public static class Policy
|
||||
{
|
||||
public static string NameFor(object item) => item.GetType().Name;
|
||||
public static bool ShowToDebugger(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = value.GetType();
|
||||
return type != typeof(CancellationToken);
|
||||
}
|
||||
public static bool ShowAsScalar(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = value.GetType();
|
||||
if (type.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static string ScalarJsonValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ShowAsScalar(value))
|
||||
{
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
if (value is ICollection collection)
|
||||
{
|
||||
return $"Count = {collection.Count}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
// https://github.com/Microsoft/debug-adapter-protocol/blob/gh-pages/debugAdapterProtocol.json
|
||||
public static class Protocol
|
||||
{
|
||||
public abstract class Message
|
||||
{
|
||||
public int seq { get; set; }
|
||||
public string type { get; set; }
|
||||
[JsonExtensionData]
|
||||
public JObject Rest { get; set; }
|
||||
}
|
||||
|
||||
public class Request : Message
|
||||
{
|
||||
public string command { get; set; }
|
||||
public override string ToString() => command;
|
||||
}
|
||||
|
||||
public class Request<Arguments> : Request
|
||||
{
|
||||
public Arguments arguments { get; set; }
|
||||
}
|
||||
public class Attach
|
||||
{
|
||||
}
|
||||
public class Launch
|
||||
{
|
||||
}
|
||||
public class Initialize
|
||||
{
|
||||
public string clientID { get; set; }
|
||||
public string clientName { get; set; }
|
||||
public string adapterID { get; set; }
|
||||
public string pathFormat { get; set; }
|
||||
public bool linesStartAt1 { get; set; }
|
||||
public bool columnsStartAt1 { get; set; }
|
||||
public bool supportsVariableType { get; set; }
|
||||
public bool supportsVariablePaging { get; set; }
|
||||
public bool supportsRunInTerminalRequest { get; set; }
|
||||
public string locale { get; set; }
|
||||
}
|
||||
|
||||
public class SetBreakpoints
|
||||
{
|
||||
public Source source { get; set; }
|
||||
public SourceBreakpoint[] breakpoints { get; set; }
|
||||
public bool sourceModified { get; set; }
|
||||
}
|
||||
public class Threads
|
||||
{
|
||||
}
|
||||
public abstract class PerThread
|
||||
{
|
||||
public int threadId { get; set; }
|
||||
}
|
||||
public class StackTrace : PerThread
|
||||
{
|
||||
public int? startFrame { get; set; }
|
||||
public int? levels { get; set; }
|
||||
}
|
||||
public class Continue : PerThread
|
||||
{
|
||||
}
|
||||
public class Pause : PerThread
|
||||
{
|
||||
}
|
||||
public class Next : PerThread
|
||||
{
|
||||
}
|
||||
public class Scopes
|
||||
{
|
||||
public int frameId { get; set; }
|
||||
}
|
||||
public class Variables
|
||||
{
|
||||
public int variablesReference { get; set; }
|
||||
}
|
||||
public class ConfigurationDone
|
||||
{
|
||||
}
|
||||
public class Disconnect
|
||||
{
|
||||
public bool restart { get; set; }
|
||||
public bool terminateDebuggee { get; set; }
|
||||
}
|
||||
public class Event : Message
|
||||
{
|
||||
public Event(int seq, string @event)
|
||||
{
|
||||
this.seq = seq;
|
||||
this.type = "event";
|
||||
this.@event = @event;
|
||||
}
|
||||
public string @event { get; set; }
|
||||
public static Event<Body> From<Body>(int seq, string @event, Body body) => new Event<Body>(seq, @event) { body = body };
|
||||
}
|
||||
public class Event<Body> : Event
|
||||
{
|
||||
public Event(int seq, string @event)
|
||||
: base(seq, @event)
|
||||
{
|
||||
}
|
||||
public Body body { get; set; }
|
||||
}
|
||||
|
||||
public class Response : Message
|
||||
{
|
||||
public Response(int seq, Request request)
|
||||
{
|
||||
this.seq = seq;
|
||||
this.type = "response";
|
||||
this.request_seq = request.seq;
|
||||
this.success = true;
|
||||
this.command = request.command;
|
||||
}
|
||||
public int request_seq { get; set; }
|
||||
public bool success { get; set; }
|
||||
public string command { get; set; }
|
||||
public string message { get; set; }
|
||||
public static Response<Body> From<Body>(int seq, Request request, Body body) => new Response<Body>(seq, request) { body = body };
|
||||
}
|
||||
|
||||
public class Response<Body> : Response
|
||||
{
|
||||
public Response(int seq, Request request)
|
||||
: base(seq, request)
|
||||
{
|
||||
}
|
||||
public Body body { get; set; }
|
||||
}
|
||||
|
||||
public abstract class Reference
|
||||
{
|
||||
public int id { get; set; }
|
||||
}
|
||||
|
||||
public class Range : Reference
|
||||
{
|
||||
public Source source { get; set; }
|
||||
public int? line { get; set; }
|
||||
public int? column { get; set; }
|
||||
public int? endLine { get; set; }
|
||||
public int? endColumn { get; set; }
|
||||
}
|
||||
|
||||
public class Breakpoint : Range
|
||||
{
|
||||
public bool verified { get; set; }
|
||||
public string message { get; set; }
|
||||
}
|
||||
|
||||
public class StackFrame : Range
|
||||
{
|
||||
public string name { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Thread : Reference
|
||||
{
|
||||
public string name { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Source
|
||||
{
|
||||
public Source(string path)
|
||||
{
|
||||
this.name = Path.GetFileName(path);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public string name { get; set; }
|
||||
public string path { get; set; }
|
||||
}
|
||||
public sealed class SourceBreakpoint
|
||||
{
|
||||
public int line { get; set; }
|
||||
}
|
||||
|
||||
public static Message Parse(JToken token)
|
||||
{
|
||||
switch ((string)token["type"])
|
||||
{
|
||||
case "request":
|
||||
switch ((string)token["command"])
|
||||
{
|
||||
case "launch": return token.ToObject<Request<Launch>>();
|
||||
case "attach": return token.ToObject<Request<Attach>>();
|
||||
case "initialize": return token.ToObject<Request<Initialize>>();
|
||||
case "setBreakpoints": return token.ToObject<Request<SetBreakpoints>>();
|
||||
case "threads": return token.ToObject<Request<Threads>>();
|
||||
case "stackTrace": return token.ToObject<Request<StackTrace>>();
|
||||
case "scopes": return token.ToObject<Request<Scopes>>();
|
||||
case "variables": return token.ToObject<Request<Variables>>();
|
||||
case "continue": return token.ToObject<Request<Continue>>();
|
||||
case "pause": return token.ToObject<Request<Pause>>();
|
||||
case "next": return token.ToObject<Request<Next>>();
|
||||
case "stepIn": return token.ToObject<Request<Next>>();
|
||||
case "stepOut": return token.ToObject<Request<Next>>();
|
||||
case "configurationDone": return token.ToObject<Request<ConfigurationDone>>();
|
||||
case "disconnect": return token.ToObject<Request<Disconnect>>();
|
||||
case "setFunctionBreakpoints":
|
||||
case "setExceptionBreakpoints":
|
||||
default: return token.ToObject<Request>();
|
||||
}
|
||||
case "event":
|
||||
switch ((string)token["event"])
|
||||
{
|
||||
default: return token.ToObject<Event>();
|
||||
}
|
||||
default:
|
||||
return token.ToObject<Message>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public sealed class ReaderWriterLock : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim reader;
|
||||
private readonly SemaphoreSlim writer;
|
||||
private int readers = 0;
|
||||
|
||||
public ReaderWriterLock(bool writer = false)
|
||||
{
|
||||
this.reader = new SemaphoreSlim(1, 1);
|
||||
this.writer = new SemaphoreSlim(writer ? 0 : 1, 1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
reader.Dispose();
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
public async Task<bool> TryEnterReadAsync(CancellationToken token)
|
||||
{
|
||||
using (await reader.WithWaitAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
bool acquired = true;
|
||||
|
||||
if (readers == 0)
|
||||
{
|
||||
acquired = await writer.WaitAsync(TimeSpan.Zero).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (acquired)
|
||||
{
|
||||
++readers;
|
||||
}
|
||||
|
||||
return acquired;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExitReadAsync(CancellationToken token)
|
||||
{
|
||||
using (await reader.WithWaitAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
--readers;
|
||||
if (readers == 0)
|
||||
{
|
||||
writer.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnterWriteAsync(CancellationToken token)
|
||||
{
|
||||
await writer.WaitAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void ExitWrite()
|
||||
{
|
||||
writer.Release();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public interface IBreakpoints
|
||||
{
|
||||
bool IsBreakPoint(object item);
|
||||
object ItemFor(Protocol.Breakpoint breakpoint);
|
||||
IReadOnlyList<Protocol.Breakpoint> SetBreakpoints(Protocol.Source source, IReadOnlyList<Protocol.SourceBreakpoint> sourceBreakpoints);
|
||||
IReadOnlyList<Protocol.Breakpoint> ApplyUpdates();
|
||||
}
|
||||
|
||||
public sealed class SourceMap : Source.IRegistry, IBreakpoints
|
||||
{
|
||||
private readonly object gate = new object();
|
||||
private readonly Dictionary<object, Source.Range> sourceByItem = new Dictionary<object, Source.Range>(ReferenceEquality<object>.Instance);
|
||||
private bool dirty = true;
|
||||
|
||||
void Source.IRegistry.Add(object item, Source.Range range)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
sourceByItem[item] = range;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Source.IRegistry.TryGetValue(object item, out Source.Range range)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
return sourceByItem.TryGetValue(item, out range);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = default(Source.Range);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Row
|
||||
{
|
||||
public Row(Protocol.Source source, Protocol.SourceBreakpoint sourceBreakpoint, Protocol.Breakpoint breakpoint)
|
||||
{
|
||||
Source = source;
|
||||
SourceBreakpoint = sourceBreakpoint;
|
||||
Breakpoint = breakpoint;
|
||||
}
|
||||
public Protocol.Source Source { get; }
|
||||
public Protocol.SourceBreakpoint SourceBreakpoint { get; }
|
||||
public Protocol.Breakpoint Breakpoint { get; }
|
||||
public object item { get; set; }
|
||||
}
|
||||
|
||||
private readonly Identifier<Row> rows = new Identifier<Row>();
|
||||
private readonly HashSet<object> items = new HashSet<object>(ReferenceEquality<object>.Instance);
|
||||
|
||||
private IReadOnlyList<Protocol.Breakpoint> Update()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
items.Clear();
|
||||
|
||||
var changes = new List<Protocol.Breakpoint>();
|
||||
foreach (var kv in rows)
|
||||
{
|
||||
var row = kv.Value;
|
||||
|
||||
var options = from sourceItem in sourceByItem
|
||||
let source = sourceItem.Value
|
||||
where source.Path == row.Source.path
|
||||
where source.Start.LineIndex >= row.SourceBreakpoint.line
|
||||
let distance = Math.Abs(source.Start.LineIndex - row.SourceBreakpoint.line)
|
||||
orderby distance
|
||||
select sourceItem;
|
||||
|
||||
options = options.ToArray();
|
||||
|
||||
var best = options.FirstOrDefault();
|
||||
var itemNew = best.Key;
|
||||
var verifiedNew = itemNew != null;
|
||||
var lineNew = verifiedNew
|
||||
? best.Value.Start.LineIndex
|
||||
: row.SourceBreakpoint.line;
|
||||
|
||||
var itemOld = row.item;
|
||||
var verifiedOld = row.Breakpoint.verified;
|
||||
var lineOld = row.Breakpoint.line;
|
||||
|
||||
var changed = itemNew != itemOld
|
||||
|| verifiedNew != verifiedOld
|
||||
|| lineNew != lineOld;
|
||||
|
||||
if (changed)
|
||||
{
|
||||
changes.Add(row.Breakpoint);
|
||||
row.item = itemNew;
|
||||
row.Breakpoint.verified = verifiedNew;
|
||||
row.Breakpoint.line = lineNew;
|
||||
}
|
||||
|
||||
if (itemNew != null)
|
||||
{
|
||||
items.Add(itemNew);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<Protocol.Breakpoint> IBreakpoints.SetBreakpoints(Protocol.Source source, IReadOnlyList<Protocol.SourceBreakpoint> sourceBreakpoints)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
var path = source.path;
|
||||
foreach (var kv in rows)
|
||||
{
|
||||
var row = kv.Value;
|
||||
if (row.Source.path == path)
|
||||
{
|
||||
rows.Remove(row);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sourceBreakpoint in sourceBreakpoints)
|
||||
{
|
||||
var breakpoint = new Protocol.Breakpoint() { source = source };
|
||||
var row = new Row(source, sourceBreakpoint, breakpoint);
|
||||
breakpoint.id = this.rows.Add(row);
|
||||
}
|
||||
|
||||
return Update();
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<Protocol.Breakpoint> IBreakpoints.ApplyUpdates()
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
IReadOnlyList<Protocol.Breakpoint> updates = Array.Empty<Protocol.Breakpoint>();
|
||||
if (dirty)
|
||||
{
|
||||
updates = Update();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
}
|
||||
|
||||
bool IBreakpoints.IsBreakPoint(object item)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.items.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
object IBreakpoints.ItemFor(Protocol.Breakpoint breakpoint)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return this.rows[breakpoint.id].item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"comments": {
|
||||
"lineComment": "//",
|
||||
"blockComment": [ "/*", "*/" ]
|
||||
},
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
{
|
||||
"open": "{",
|
||||
"close": "}",
|
||||
"notIn": [ "string" ]
|
||||
},
|
||||
{ "open": "[", "close": "]", "notIn": ["string"] },
|
||||
{ "open": "(", "close": ")", "notIn": ["string"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string"] },
|
||||
{ "open": "/*", "close": "*/", "notIn": ["string"] },
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
|
||||
{ "open": "`", "close": "`", "notIn": ["string", "comment"] }
|
||||
]
|
||||
}
|
5
libraries/Microsoft.Bot.Builder.Dialogs.Debugging/vscode-dialog-debugger-0.0.1/package-lock.json
сгенерированный
Normal file
5
libraries/Microsoft.Bot.Builder.Dialogs.Debugging/vscode-dialog-debugger-0.0.1/package-lock.json
сгенерированный
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "dialog-debugger",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "dialog-debugger",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"engines": {
|
||||
"vscode": "^1.0.0"
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
"id": "dialog",
|
||||
"aliases": [
|
||||
"dialog",
|
||||
"DIALOG"
|
||||
],
|
||||
"extensions": [
|
||||
".dialog"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "dialog",
|
||||
"scopeName": "source.json",
|
||||
"path": "./syntaxes/JSON.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "dialog",
|
||||
"label": "Dialog Debugger",
|
||||
"languages": [
|
||||
"dialog"
|
||||
]
|
||||
}
|
||||
],
|
||||
"breakpoints": [
|
||||
{
|
||||
"language": "dialog"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been converted from https://github.com/Microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage",
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
|
||||
"name": "JSON (Javascript Next)",
|
||||
"scopeName": "source.json",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"array": {
|
||||
"begin": "\\[",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.begin.json"
|
||||
}
|
||||
},
|
||||
"end": "\\]",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.end.json"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.array.json",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": ",",
|
||||
"name": "punctuation.separator.array.json"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\]]",
|
||||
"name": "invalid.illegal.expected-array-separator.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"comments": {
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "/\\*\\*(?!/)",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.json"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.documentation.json"
|
||||
},
|
||||
{
|
||||
"begin": "/\\*",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.json"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.json"
|
||||
},
|
||||
{
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.comment.json"
|
||||
}
|
||||
},
|
||||
"match": "(//).*$\\n?",
|
||||
"name": "comment.line.double-slash.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constant": {
|
||||
"match": "\\b(?:true|false|null)\\b",
|
||||
"name": "constant.language.json"
|
||||
},
|
||||
"number": {
|
||||
"match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
|
||||
"name": "constant.numeric.json"
|
||||
},
|
||||
"object": {
|
||||
"begin": "\\{",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.begin.json"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.end.json"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.json",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the JSON object key",
|
||||
"include": "#objectkey"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
},
|
||||
{
|
||||
"begin": ":",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.separator.dictionary.key-value.json"
|
||||
}
|
||||
},
|
||||
"end": "(,)|(?=\\})",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.separator.dictionary.pair.json"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.value.json",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the JSON object value",
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s,]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\}]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"string": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.begin.json"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.end.json"
|
||||
}
|
||||
},
|
||||
"name": "string.quoted.double.json",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"objectkey": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.begin.json"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.end.json"
|
||||
}
|
||||
},
|
||||
"name": "string.json support.type.property-name.json",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stringcontent": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
|
||||
"name": "constant.character.escape.json"
|
||||
},
|
||||
{
|
||||
"match": "\\\\.",
|
||||
"name": "invalid.illegal.unrecognized-string-escape.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#constant"
|
||||
},
|
||||
{
|
||||
"include": "#number"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"include": "#array"
|
||||
},
|
||||
{
|
||||
"include": "#object"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ using static Microsoft.Bot.Builder.Dialogs.Debugging.Source;
|
|||
|
||||
namespace Microsoft.Bot.Builder.Dialogs.Debugging
|
||||
{
|
||||
public static partial class Debugger
|
||||
public static partial class DebugSupport
|
||||
{
|
||||
public static Source.IRegistry SourceRegistry { get; set; } = new NullRegistry();
|
||||
|
|
@ -195,7 +195,7 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
{
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
Debugger.SourceRegistry.Add(this, new Source.Range()
|
||||
DebugSupport.SourceRegistry.Add(this, new Source.Range()
|
||||
{
|
||||
Path = path,
|
||||
Start = new Source.Point() { LineIndex = lineNumber, CharIndex = 0 },
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
|
@ -28,6 +29,13 @@
|
|||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"type": "dialog",
|
||||
"request": "attach",
|
||||
"trace": true,
|
||||
"name": "Attach to Dialog",
|
||||
"debugServer": 4712
|
||||
}
|
||||
]
|
||||
}
|
|
@ -60,6 +60,7 @@
|
|||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.AI.LanguageGeneration\Microsoft.Bot.Builder.AI.LanguageGeneration.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs.Adaptive\Microsoft.Bot.Builder.Dialogs.Adaptive.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs.Composition\Microsoft.Bot.Builder.Dialogs.Composition.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs.Debugging\Microsoft.Bot.Builder.Dialogs.Debugging.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs.Declarative\Microsoft.Bot.Builder.Dialogs.Declarative.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs\Microsoft.Bot.Builder.Dialogs.csproj" />
|
||||
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder\Microsoft.Bot.Builder.csproj" />
|
||||
|
|
|
@ -3,25 +3,25 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.ApplicationInsights.Extensibility;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.AI.LanguageGeneration;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Dialogs.Declarative;
|
||||
using Microsoft.Bot.Builder.Dialogs.Debugging;
|
||||
using Microsoft.Bot.Builder.Dialogs.Declarative.Resources;
|
||||
using Microsoft.Bot.Builder.Dialogs.Declarative.Types;
|
||||
using Microsoft.Bot.Builder.Integration;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Bot.Builder.TestBot.Json.Recognizers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Bot.Builder.Dialogs.Debugging;
|
||||
using Microsoft.Bot.Builder.Dialogs.Declarative.Resources;
|
||||
using Microsoft.Bot.Builder;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System.Linq;
|
||||
using Microsoft.ApplicationInsights.Extensibility;
|
||||
using Microsoft.Extensions.Logging.Debug;
|
||||
|
||||
namespace Microsoft.Bot.Builder.TestBot.Json
|
||||
{
|
||||
|
@ -60,6 +60,19 @@ namespace Microsoft.Bot.Builder.TestBot.Json
|
|||
{
|
||||
TelemetryConfiguration.Active.DisableTelemetry = true;
|
||||
}
|
||||
|
||||
// hook up debugging support
|
||||
var sourceMap = new SourceMap();
|
||||
DebugAdapter debugAdapter = null;
|
||||
bool enableDebugger = true;
|
||||
if (enableDebugger)
|
||||
{
|
||||
// by setting the source registry all dialogs will register themselves to be debugged as execution flows
|
||||
DebugSupport.SourceRegistry = sourceMap;
|
||||
debugAdapter = new DebugAdapter(sourceMap, sourceMap, new DebugLogger(nameof(DebugAdapter)));
|
||||
}
|
||||
|
||||
// m
|
||||
services.AddSingleton<IConfiguration>(this.Configuration);
|
||||
|
||||
IStorage dataStore = new MemoryStorage();
|
||||
|
@ -80,7 +93,7 @@ namespace Microsoft.Bot.Builder.TestBot.Json
|
|||
(IServiceProvider sp) =>
|
||||
{
|
||||
// declarative Adaptive dialogs bot sample
|
||||
return new TestBot(accessors, resourceExplorer, Source.NullRegistry.Instance);
|
||||
return new TestBot(accessors, resourceExplorer, DebugSupport.SourceRegistry);
|
||||
|
||||
// LG bot sample
|
||||
// return new TestBotLG(accessors);
|
||||
|
@ -96,6 +109,11 @@ namespace Microsoft.Bot.Builder.TestBot.Json
|
|||
options.Middleware.Add(new RegisterClassMiddleware<IStorage>(dataStore));
|
||||
options.Middleware.Add(new RegisterClassMiddleware<ResourceExplorer>(resourceExplorer));
|
||||
|
||||
if (debugAdapter != null)
|
||||
{
|
||||
options.Middleware.Add(debugAdapter);
|
||||
}
|
||||
|
||||
var lg = new LGLanguageGenerator(resourceExplorer);
|
||||
options.Middleware.Add(new RegisterClassMiddleware<ILanguageGenerator>(lg));
|
||||
options.Middleware.Add(new RegisterClassMiddleware<IMessageActivityGenerator>(new TextMessageActivityGenerator(lg)));
|
||||
|
|
|
@ -234,7 +234,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Loader.Tests
|
|||
string projPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, $@"..\..\..\..\..\samples\Microsoft.Bot.Builder.TestBot.Json\Microsoft.Bot.Builder.TestBot.Json.csproj"));
|
||||
var resourceExplorer = ResourceExplorer.LoadProject(projPath);
|
||||
|
||||
var dialog = DeclarativeTypeLoader.Load<IDialog>(path, resourceExplorer, Source.NullRegistry.Instance);
|
||||
var dialog = DeclarativeTypeLoader.Load<IDialog>(path, resourceExplorer, DebugSupport.SourceRegistry);
|
||||
|
||||
IStorage dataStore = new MemoryStorage();
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче