[gh-88] Added limit on reporting of multiple iterations.
This commit is contained in:
Родитель
33e77cc9cd
Коммит
c52b7b72b7
|
@ -9,32 +9,52 @@ namespace SharpLab.Runtime.Internal {
|
|||
public static class Flow {
|
||||
private const int MaxReportLength = 20;
|
||||
private const int MaxReportItemCount = 3;
|
||||
private const int MaxReportStepNotesPerLineCount = 3;
|
||||
|
||||
private static readonly List<Line> _lines = new List<Line>();
|
||||
public static IReadOnlyList<Line> Lines => _lines;
|
||||
private static readonly IDictionary<int, int> _stepNotesCountPerLine = new Dictionary<int, int>();
|
||||
private static readonly List<Step> _steps = new List<Step>();
|
||||
public static IReadOnlyList<Step> Steps => _steps;
|
||||
|
||||
public static void ReportLineStart(int lineNumber) {
|
||||
_lines.Add(new Line(lineNumber));
|
||||
_steps.Add(new Step(lineNumber));
|
||||
}
|
||||
|
||||
public static void ReportVariable<T>(string name, T value) {
|
||||
var line = _lines[_lines.Count - 1];
|
||||
var notes = line.Notes;
|
||||
var step = _steps[_steps.Count - 1];
|
||||
if (!_stepNotesCountPerLine.TryGetValue(step.LineNumber, out int countPerLine))
|
||||
countPerLine = 0;
|
||||
countPerLine += 1;
|
||||
_stepNotesCountPerLine[step.LineNumber] = countPerLine;
|
||||
|
||||
if (countPerLine == MaxReportStepNotesPerLineCount + 1) {
|
||||
if (step.Notes != null) // already has "…"
|
||||
return;
|
||||
step.Notes = new StringBuilder("…");
|
||||
_steps[_steps.Count - 1] = step;
|
||||
return;
|
||||
}
|
||||
|
||||
if (countPerLine > MaxReportStepNotesPerLineCount + 1)
|
||||
return;
|
||||
|
||||
var notes = step.Notes;
|
||||
if (notes == null) {
|
||||
notes = new StringBuilder();
|
||||
step.Notes = notes;
|
||||
}
|
||||
|
||||
if (notes.Length > 0)
|
||||
notes.Append(", ");
|
||||
notes.Append(name).Append(": ");
|
||||
AppendValue(notes, value);
|
||||
_lines[_lines.Count - 1] = line;
|
||||
// Have to reassign in case we set Notes
|
||||
_steps[_steps.Count - 1] = step;
|
||||
}
|
||||
|
||||
public static void ReportException(object exception) {
|
||||
var line = _lines[_lines.Count - 1];
|
||||
line.Exception = exception;
|
||||
_lines[_lines.Count - 1] = line;
|
||||
}
|
||||
|
||||
private static bool HadException() {
|
||||
return Marshal.GetExceptionPointers() != IntPtr.Zero || Marshal.GetExceptionCode() != 0;
|
||||
var step = _steps[_steps.Count - 1];
|
||||
step.Exception = exception;
|
||||
_steps[_steps.Count - 1] = step;
|
||||
}
|
||||
|
||||
private static StringBuilder AppendValue<T>(StringBuilder builder, T value) {
|
||||
|
@ -84,26 +104,16 @@ namespace SharpLab.Runtime.Internal {
|
|||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Line {
|
||||
private StringBuilder _notes;
|
||||
|
||||
public Line(int number) {
|
||||
Number = number;
|
||||
_notes = null;
|
||||
public struct Step {
|
||||
public Step(int lineNumber) {
|
||||
LineNumber = lineNumber;
|
||||
Notes = null;
|
||||
Exception = null;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
public int LineNumber { get; }
|
||||
public object Exception { get; internal set; }
|
||||
|
||||
public bool HasNotes => _notes != null;
|
||||
public StringBuilder Notes {
|
||||
get {
|
||||
if (_notes == null)
|
||||
_notes = new StringBuilder();
|
||||
return _notes;
|
||||
}
|
||||
}
|
||||
public StringBuilder Notes { get; internal set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,18 @@ using SharpLab.Runtime.Internal;
|
|||
namespace SharpLab.Server.Execution {
|
||||
[Serializable]
|
||||
public class ExecutionResult {
|
||||
public ExecutionResult(string returnValue, IReadOnlyList<Flow.Line> flow) {
|
||||
public ExecutionResult(string returnValue, IReadOnlyList<Flow.Step> flow) {
|
||||
ReturnValue = returnValue;
|
||||
Flow = flow;
|
||||
}
|
||||
|
||||
public ExecutionResult(Exception exception, IReadOnlyList<Flow.Line> flow) {
|
||||
public ExecutionResult(Exception exception, IReadOnlyList<Flow.Step> flow) {
|
||||
Exception = exception;
|
||||
Flow = flow;
|
||||
}
|
||||
|
||||
public string ReturnValue { get; }
|
||||
public Exception Exception { get; }
|
||||
public IReadOnlyList<Flow.Line> Flow { get; }
|
||||
public IReadOnlyList<Flow.Step> Flow { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,25 +62,25 @@ namespace SharpLab.Server.Execution {
|
|||
writer.WriteProperty("exception", result.Exception.ToString());
|
||||
}
|
||||
writer.WritePropertyStartArray("flow");
|
||||
foreach (var line in result.Flow) {
|
||||
SerializeFlowLine(line, writer);
|
||||
foreach (var step in result.Flow) {
|
||||
SerializeFlowStep(step, writer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private void SerializeFlowLine(Flow.Line line, IFastJsonWriter writer) {
|
||||
if (!line.HasNotes && line.Exception == null) {
|
||||
writer.WriteValue(line.Number);
|
||||
private void SerializeFlowStep(Flow.Step step, IFastJsonWriter writer) {
|
||||
if (step.Notes == null && step.Exception == null) {
|
||||
writer.WriteValue(step.LineNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteProperty("line", line.Number);
|
||||
if (line.HasNotes)
|
||||
writer.WriteProperty("notes", line.Notes);
|
||||
if (line.Exception != null)
|
||||
writer.WriteProperty("exception", line.Exception.GetType().Name);
|
||||
writer.WriteProperty("line", step.LineNumber);
|
||||
if (step.Notes != null)
|
||||
writer.WriteProperty("notes", step.Notes);
|
||||
if (step.Exception != null)
|
||||
writer.WriteProperty("exception", step.Exception.GetType().Name);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ namespace SharpLab.Server.Execution {
|
|||
|
||||
using (guardToken.Scope()) {
|
||||
var result = m.Invoke(Activator.CreateInstance(c), null);
|
||||
return new ExecutionResult(result?.ToString(), Flow.Lines);
|
||||
return new ExecutionResult(result?.ToString(), Flow.Steps);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
@ -101,7 +101,7 @@ namespace SharpLab.Server.Execution {
|
|||
ex = invocationEx.InnerException;
|
||||
|
||||
Flow.ReportException(ex);
|
||||
return new ExecutionResult(ex, Flow.Lines);
|
||||
return new ExecutionResult(ex, Flow.Steps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using AshMind.Extensions;
|
||||
|
@ -8,7 +9,9 @@ using MirrorSharp;
|
|||
using MirrorSharp.Testing;
|
||||
using SharpLab.Server;
|
||||
using SharpLab.Server.MirrorSharp.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
|
||||
namespace SharpLab.Tests {
|
||||
public class ExecutionTests {
|
||||
|
@ -25,12 +28,34 @@ namespace SharpLab.Tests {
|
|||
|
||||
var result = await driver.SendSlowUpdateAsync<ExecutionResultData>();
|
||||
var errors = result.JoinErrors();
|
||||
var lines = result.ExtensionResult.Flow
|
||||
.Select(f => new { Line = (f as JObject)?.Value<int>("line") ?? f.Value<int>(), Exception = (f as JObject)?.Value<string>("exception") })
|
||||
var steps = result.ExtensionResult.Flow
|
||||
.Select(s => new { s.Line, s.Exception })
|
||||
.ToArray();
|
||||
|
||||
Assert.True(errors.IsNullOrEmpty(), errors);
|
||||
Assert.Contains(new { Line = expectedLineNumber, Exception = expectedExceptionTypeName }, lines);
|
||||
Assert.Contains(new { Line = expectedLineNumber, Exception = expectedExceptionTypeName }, steps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SlowUpdate_ReportsLimitedNumberOfNotesPerLine() {
|
||||
var driver = await NewTestDriverAsync(LoadCodeFromResource("Loops.For.10Iterations.cs"));
|
||||
|
||||
var result = await driver.SendSlowUpdateAsync<ExecutionResultData>();
|
||||
var errors = result.JoinErrors();
|
||||
|
||||
var notes = string.Join(
|
||||
", ",
|
||||
result.ExtensionResult.Flow
|
||||
.Where(s => s.Line == 3 && s.Notes != null)
|
||||
.Select(s => s.Notes)
|
||||
);
|
||||
|
||||
Assert.True(errors.IsNullOrEmpty(), errors);
|
||||
Assert.Equal("i: 0, i: 1, i: 2, …", notes);
|
||||
}
|
||||
|
||||
private static int LineNumberFromFlowStep(JToken step) {
|
||||
return (step as JObject)?.Value<int>("line") ?? step.Value<int>();
|
||||
}
|
||||
|
||||
private static string LoadCodeFromResource(string resourceName) {
|
||||
|
@ -48,7 +73,34 @@ namespace SharpLab.Tests {
|
|||
|
||||
private class ExecutionResultData {
|
||||
public string Exception { get; set; }
|
||||
public IList<JToken> Flow { get; } = new List<JToken>();
|
||||
}
|
||||
[JsonIgnore]
|
||||
public IList<FlowStepData> Flow { get; } = new List<FlowStepData>();
|
||||
[JsonProperty("flow")]
|
||||
private IList<JToken> FlowRaw { get; } = new List<JToken>();
|
||||
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context) {
|
||||
foreach (var token in FlowRaw) {
|
||||
Flow.Add(ParseStepData(token));
|
||||
}
|
||||
}
|
||||
|
||||
private FlowStepData ParseStepData(JToken token) {
|
||||
if (token is JValue value)
|
||||
return new FlowStepData { Line = value.Value<int>() };
|
||||
|
||||
return new FlowStepData {
|
||||
Line = token.Value<int>("line"),
|
||||
Exception = token.Value<string>("exception"),
|
||||
Notes = token.Value<string>("notes")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class FlowStepData {
|
||||
public int Line { get; set; }
|
||||
public string Exception { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
public class C {
|
||||
public void M() {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче