[gh-88] Added limit on reporting of multiple iterations.

This commit is contained in:
Andrey Shchekin 2017-07-22 11:13:30 +12:00
Родитель 33e77cc9cd
Коммит c52b7b72b7
5 изменённых файлов: 117 добавлений и 49 удалений

Просмотреть файл

@ -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++) {
}
}
}