|
@@ -1,39 +1,42 @@
|
|
|
-using System.Diagnostics;
|
|
|
+#nullable enable
|
|
|
+using System.Diagnostics;
|
|
|
using System.Text;
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
namespace UnitTests.ConsoleDrivers;
|
|
|
+
|
|
|
public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
{
|
|
|
- AnsiResponseParser<int> _parser1 = new AnsiResponseParser<int> ();
|
|
|
- AnsiResponseParser _parser2 = new AnsiResponseParser ();
|
|
|
+ private readonly AnsiResponseParser<int> _parser1 = new ();
|
|
|
+ private readonly AnsiResponseParser _parser2 = new ();
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Used for the T value in batches that are passed to the AnsiResponseParser<int> (parser1)
|
|
|
+ /// Used for the T value in batches that are passed to the AnsiResponseParser<int> (parser1)
|
|
|
/// </summary>
|
|
|
- private int tIndex = 0;
|
|
|
+ private int _tIndex;
|
|
|
|
|
|
[Fact]
|
|
|
public void TestInputProcessing ()
|
|
|
{
|
|
|
- string ansiStream = "\u001b[<0;10;20M" + // ANSI escape for mouse move at (10, 20)
|
|
|
- "Hello" + // User types "Hello"
|
|
|
- "\u001b[0c"; // Device Attributes response (e.g., terminal identification i.e. DAR)
|
|
|
-
|
|
|
+ string ansiStream = "\u001b[<0;10;20M"
|
|
|
+ + // ANSI escape for mouse move at (10, 20)
|
|
|
+ "Hello"
|
|
|
+ + // User types "Hello"
|
|
|
+ "\u001b[0c"; // Device Attributes response (e.g., terminal identification i.e. DAR)
|
|
|
|
|
|
- string response1 = null;
|
|
|
- string response2 = null;
|
|
|
+ string? response1 = null;
|
|
|
+ string? response2 = null;
|
|
|
|
|
|
- int i = 0;
|
|
|
+ var i = 0;
|
|
|
|
|
|
// Imagine that we are expecting a DAR
|
|
|
- _parser1.ExpectResponse ("c",(s)=> response1 = s,null, false);
|
|
|
- _parser2.ExpectResponse ("c", (s) => response2 = s , null, false);
|
|
|
+ _parser1.ExpectResponse ("c", s => response1 = s, null, false);
|
|
|
+ _parser2.ExpectResponse ("c", s => response2 = s, null, false);
|
|
|
|
|
|
// First char is Escape which we must consume incase what follows is the DAR
|
|
|
AssertConsumed (ansiStream, ref i); // Esc
|
|
|
|
|
|
- for (int c = 0; c < "[<0;10;20".Length; c++)
|
|
|
+ for (var c = 0; c < "[<0;10;20".Length; c++)
|
|
|
{
|
|
|
AssertConsumed (ansiStream, ref i);
|
|
|
}
|
|
@@ -42,13 +45,13 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
AssertReleased (ansiStream, ref i, "\u001b[<0;10;20M");
|
|
|
|
|
|
// Regular user typing
|
|
|
- for (int c = 0; c < "Hello".Length; c++)
|
|
|
+ for (var c = 0; c < "Hello".Length; c++)
|
|
|
{
|
|
|
- AssertIgnored (ansiStream,"Hello"[c], ref i);
|
|
|
+ AssertIgnored (ansiStream, "Hello" [c], ref i);
|
|
|
}
|
|
|
|
|
|
// Now we have entered the actual DAR we should be consuming these
|
|
|
- for (int c = 0; c < "\u001b[0".Length; c++)
|
|
|
+ for (var c = 0; c < "\u001b[0".Length; c++)
|
|
|
{
|
|
|
AssertConsumed (ansiStream, ref i);
|
|
|
}
|
|
@@ -69,7 +72,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
[InlineData ("\u001b[0cHi\u001b[0c", "c", "\u001b[0c", "Hi\u001b[0c")]
|
|
|
[InlineData ("\u001b[<0;0;0MHe\u001b[3c", "c", "\u001b[3c", "\u001b[<0;0;0MHe")]
|
|
|
[InlineData ("\u001b[<0;1;2Da\u001b[0c\u001b[1c", "c", "\u001b[0c", "\u001b[<0;1;2Da\u001b[1c")]
|
|
|
- [InlineData ("\u001b[1;1M\u001b[3cAn", "c", "\u001b[3c", "\u001b[1;1MAn")]
|
|
|
+ [InlineData ("\u001b[1;1M\u001b[3cAn", "c", "\u001b[3c", "\u001b[1;1MAn")]
|
|
|
[InlineData ("hi\u001b[2c\u001b[<5;5;5m", "c", "\u001b[2c", "hi\u001b[<5;5;5m")]
|
|
|
[InlineData ("\u001b[3c\u001b[4c\u001b[<0;0;0MIn", "c", "\u001b[3c", "\u001b[4c\u001b[<0;0;0MIn")]
|
|
|
[InlineData ("\u001b[<1;2;3M\u001b[0c\u001b[<1;2;3M\u001b[2c", "c", "\u001b[0c", "\u001b[<1;2;3M\u001b[<1;2;3M\u001b[2c")]
|
|
@@ -84,8 +87,16 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
[InlineData ("\u001b[<1;1;1MJJ\u001b[9c", "c", "\u001b[9c", "\u001b[<1;1;1MJJ")] // Mixed text
|
|
|
[InlineData ("Be\u001b[0cAf", "c", "\u001b[0c", "BeAf")] // Escape in the middle of the string
|
|
|
[InlineData ("\u001b[<0;0;0M\u001b[2cNot e", "c", "\u001b[2c", "\u001b[<0;0;0MNot e")] // Unexpected sequence followed by text
|
|
|
- [InlineData ("Just te\u001b[<0;0;0M\u001b[3c\u001b[2c\u001b[4c", "c", "\u001b[3c", "Just te\u001b[<0;0;0M\u001b[2c\u001b[4c")] // Multiple unexpected responses
|
|
|
- [InlineData ("\u001b[1;2;3M\u001b[0c\u001b[2;2M\u001b[0;0;0MTe", "c", "\u001b[0c", "\u001b[1;2;3M\u001b[2;2M\u001b[0;0;0MTe")] // Multiple commands with responses
|
|
|
+ [InlineData (
|
|
|
+ "Just te\u001b[<0;0;0M\u001b[3c\u001b[2c\u001b[4c",
|
|
|
+ "c",
|
|
|
+ "\u001b[3c",
|
|
|
+ "Just te\u001b[<0;0;0M\u001b[2c\u001b[4c")] // Multiple unexpected responses
|
|
|
+ [InlineData (
|
|
|
+ "\u001b[1;2;3M\u001b[0c\u001b[2;2M\u001b[0;0;0MTe",
|
|
|
+ "c",
|
|
|
+ "\u001b[0c",
|
|
|
+ "\u001b[1;2;3M\u001b[2;2M\u001b[0;0;0MTe")] // Multiple commands with responses
|
|
|
[InlineData ("\u001b[<3;3;3Mabc\u001b[4cde", "c", "\u001b[4c", "\u001b[<3;3;3Mabcde")] // Escape sequences mixed with regular text
|
|
|
|
|
|
// Edge cases
|
|
@@ -94,58 +105,57 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
[InlineData ("Normal", "c", "", "Normal")] // No escape sequences
|
|
|
[InlineData ("\u001b[<0;0;0M", "c", "", "\u001b[<0;0;0M")] // Escape sequence only
|
|
|
[InlineData ("\u001b[1;2;3M\u001b[0c", "c", "\u001b[0c", "\u001b[1;2;3M")] // Last response consumed
|
|
|
-
|
|
|
[InlineData ("Inpu\u001b[0c\u001b[1;0;0M", "c", "\u001b[0c", "Inpu\u001b[1;0;0M")] // Single input followed by escape
|
|
|
[InlineData ("\u001b[2c\u001b[<5;6;7MDa", "c", "\u001b[2c", "\u001b[<5;6;7MDa")] // Multiple escape sequences followed by text
|
|
|
[InlineData ("\u001b[0cHi\u001b[1cGo", "c", "\u001b[0c", "Hi\u001b[1cGo")] // Normal text with multiple escape sequences
|
|
|
-
|
|
|
[InlineData ("\u001b[<1;1;1MTe", "c", "", "\u001b[<1;1;1MTe")]
|
|
|
+
|
|
|
// Add more test cases here...
|
|
|
- public void TestInputSequences (string ansiStream, string expectedTerminator, string expectedResponse, string expectedOutput)
|
|
|
+ public void TestInputSequences (string ansiStream, string? expectedTerminator, string expectedResponse, string expectedOutput)
|
|
|
{
|
|
|
var swGenBatches = Stopwatch.StartNew ();
|
|
|
- int tests = 0;
|
|
|
+ var tests = 0;
|
|
|
|
|
|
- var permutations = GetBatchPermutations (ansiStream,5).ToArray ();
|
|
|
+ string [] [] permutations = GetBatchPermutations (ansiStream, 5).ToArray ();
|
|
|
|
|
|
swGenBatches.Stop ();
|
|
|
var swRunTest = Stopwatch.StartNew ();
|
|
|
|
|
|
- foreach (var batchSet in permutations)
|
|
|
+ foreach (string [] batchSet in permutations)
|
|
|
{
|
|
|
- tIndex = 0;
|
|
|
- string response1 = string.Empty;
|
|
|
- string response2 = string.Empty;
|
|
|
+ _tIndex = 0;
|
|
|
+ var response1 = string.Empty;
|
|
|
+ var response2 = string.Empty;
|
|
|
|
|
|
// Register the expected response with the given terminator
|
|
|
_parser1.ExpectResponse (expectedTerminator, s => response1 = s, null, false);
|
|
|
_parser2.ExpectResponse (expectedTerminator, s => response2 = s, null, false);
|
|
|
|
|
|
// Process the input
|
|
|
- StringBuilder actualOutput1 = new StringBuilder ();
|
|
|
- StringBuilder actualOutput2 = new StringBuilder ();
|
|
|
+ var actualOutput1 = new StringBuilder ();
|
|
|
+ var actualOutput2 = new StringBuilder ();
|
|
|
|
|
|
- foreach (var batch in batchSet)
|
|
|
+ foreach (string batch in batchSet)
|
|
|
{
|
|
|
- var output1 = _parser1.ProcessInput (StringToBatch (batch));
|
|
|
+ IEnumerable<Tuple<char, int>> output1 = _parser1.ProcessInput (StringToBatch (batch));
|
|
|
actualOutput1.Append (BatchToString (output1));
|
|
|
|
|
|
- var output2 = _parser2.ProcessInput (batch);
|
|
|
+ string output2 = _parser2.ProcessInput (batch);
|
|
|
actualOutput2.Append (output2);
|
|
|
}
|
|
|
|
|
|
// Assert the final output minus the expected response
|
|
|
- Assert.Equal (expectedOutput, actualOutput1.ToString());
|
|
|
+ Assert.Equal (expectedOutput, actualOutput1.ToString ());
|
|
|
Assert.Equal (expectedResponse, response1);
|
|
|
Assert.Equal (expectedOutput, actualOutput2.ToString ());
|
|
|
Assert.Equal (expectedResponse, response2);
|
|
|
tests++;
|
|
|
}
|
|
|
|
|
|
- output.WriteLine ($"Tested {tests} in {swRunTest.ElapsedMilliseconds} ms (gen batches took {swGenBatches.ElapsedMilliseconds} ms)" );
|
|
|
+ output.WriteLine ($"Tested {tests} in {swRunTest.ElapsedMilliseconds} ms (gen batches took {swGenBatches.ElapsedMilliseconds} ms)");
|
|
|
}
|
|
|
|
|
|
- public static IEnumerable<object []> TestInputSequencesExact_Cases ()
|
|
|
+ public static IEnumerable<object? []> TestInputSequencesExact_Cases ()
|
|
|
{
|
|
|
yield return
|
|
|
[
|
|
@@ -153,7 +163,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
null,
|
|
|
new []
|
|
|
{
|
|
|
- new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty)
|
|
|
+ new StepExpectation ('\u001b', AnsiResponseParserState.ExpectingEscapeSequence, string.Empty)
|
|
|
}
|
|
|
];
|
|
|
|
|
@@ -163,13 +173,20 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
'c',
|
|
|
new []
|
|
|
{
|
|
|
- new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
|
|
|
- new StepExpectation ('H',AnsiResponseParserState.InResponse,string.Empty), // H is known terminator and not expected one so here we release both chars
|
|
|
- new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,"\u001bH"),
|
|
|
- new StepExpectation ('[',AnsiResponseParserState.InResponse,string.Empty),
|
|
|
- new StepExpectation ('0',AnsiResponseParserState.InResponse,string.Empty),
|
|
|
- new StepExpectation ('c',AnsiResponseParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
|
|
|
- new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
|
|
|
+ new StepExpectation ('\u001b', AnsiResponseParserState.ExpectingEscapeSequence, string.Empty),
|
|
|
+ new StepExpectation (
|
|
|
+ 'H',
|
|
|
+ AnsiResponseParserState.InResponse,
|
|
|
+ string.Empty), // H is known terminator and not expected one so here we release both chars
|
|
|
+ new StepExpectation ('\u001b', AnsiResponseParserState.ExpectingEscapeSequence, "\u001bH"),
|
|
|
+ new StepExpectation ('[', AnsiResponseParserState.InResponse, string.Empty),
|
|
|
+ new StepExpectation ('0', AnsiResponseParserState.InResponse, string.Empty),
|
|
|
+ new StepExpectation (
|
|
|
+ 'c',
|
|
|
+ AnsiResponseParserState.Normal,
|
|
|
+ string.Empty,
|
|
|
+ "\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
|
|
|
+ new StepExpectation ('\u001b', AnsiResponseParserState.ExpectingEscapeSequence, string.Empty)
|
|
|
}
|
|
|
];
|
|
|
}
|
|
@@ -177,24 +194,24 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
public class StepExpectation ()
|
|
|
{
|
|
|
/// <summary>
|
|
|
- /// The input character to feed into the parser at this step of the test
|
|
|
+ /// The input character to feed into the parser at this step of the test
|
|
|
/// </summary>
|
|
|
public char Input { get; }
|
|
|
|
|
|
/// <summary>
|
|
|
- /// What should the state of the parser be after the <see cref="Input"/>
|
|
|
- /// is fed in.
|
|
|
+ /// What should the state of the parser be after the <see cref="Input"/>
|
|
|
+ /// is fed in.
|
|
|
/// </summary>
|
|
|
public AnsiResponseParserState ExpectedStateAfterOperation { get; }
|
|
|
|
|
|
/// <summary>
|
|
|
- /// If this step should release one or more characters, put them here.
|
|
|
+ /// If this step should release one or more characters, put them here.
|
|
|
/// </summary>
|
|
|
public string ExpectedRelease { get; } = string.Empty;
|
|
|
|
|
|
/// <summary>
|
|
|
- /// If this step should result in a completing of detection of ANSI response
|
|
|
- /// then put the expected full response sequence here.
|
|
|
+ /// If this step should result in a completing of detection of ANSI response
|
|
|
+ /// then put the expected full response sequence here.
|
|
|
/// </summary>
|
|
|
public string ExpectedAnsiResponse { get; } = string.Empty;
|
|
|
|
|
@@ -202,35 +219,36 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
char input,
|
|
|
AnsiResponseParserState expectedStateAfterOperation,
|
|
|
string expectedRelease = "",
|
|
|
- string expectedAnsiResponse = "") : this ()
|
|
|
+ string expectedAnsiResponse = ""
|
|
|
+ ) : this ()
|
|
|
{
|
|
|
Input = input;
|
|
|
ExpectedStateAfterOperation = expectedStateAfterOperation;
|
|
|
ExpectedRelease = expectedRelease;
|
|
|
ExpectedAnsiResponse = expectedAnsiResponse;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- [MemberData(nameof(TestInputSequencesExact_Cases))]
|
|
|
+ [MemberData (nameof (TestInputSequencesExact_Cases))]
|
|
|
[Theory]
|
|
|
public void TestInputSequencesExact (string caseName, char? terminator, IEnumerable<StepExpectation> expectedStates)
|
|
|
{
|
|
|
output.WriteLine ("Running test case:" + caseName);
|
|
|
|
|
|
var parser = new AnsiResponseParser ();
|
|
|
- string response = null;
|
|
|
+ string? response = null;
|
|
|
|
|
|
if (terminator.HasValue)
|
|
|
{
|
|
|
- parser.ExpectResponse (terminator.Value.ToString (),(s)=> response = s,null, false);
|
|
|
+ parser.ExpectResponse (terminator.Value.ToString (), s => response = s, null, false);
|
|
|
}
|
|
|
- int step= 0;
|
|
|
- foreach (var state in expectedStates)
|
|
|
+
|
|
|
+ var step = 0;
|
|
|
+
|
|
|
+ foreach (StepExpectation state in expectedStates)
|
|
|
{
|
|
|
step++;
|
|
|
+
|
|
|
// If we expect the response to be detected at this step
|
|
|
if (!string.IsNullOrWhiteSpace (state.ExpectedAnsiResponse))
|
|
|
{
|
|
@@ -238,9 +256,9 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
Assert.Null (response);
|
|
|
}
|
|
|
|
|
|
- var actual = parser.ProcessInput (state.Input.ToString ());
|
|
|
+ string actual = parser.ProcessInput (state.Input.ToString ());
|
|
|
|
|
|
- Assert.Equal (state.ExpectedRelease,actual);
|
|
|
+ Assert.Equal (state.ExpectedRelease, actual);
|
|
|
Assert.Equal (state.ExpectedStateAfterOperation, parser.State);
|
|
|
|
|
|
// If we expect the response to be detected at this step
|
|
@@ -257,11 +275,11 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
[Fact]
|
|
|
public void ReleasesEscapeAfterTimeout ()
|
|
|
{
|
|
|
- string input = "\u001b";
|
|
|
- int i = 0;
|
|
|
+ var input = "\u001b";
|
|
|
+ var i = 0;
|
|
|
|
|
|
// Esc on its own looks like it might be an esc sequence so should be consumed
|
|
|
- AssertConsumed (input,ref i);
|
|
|
+ AssertConsumed (input, ref i);
|
|
|
|
|
|
// We should know when the state changed
|
|
|
Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser1.State);
|
|
@@ -273,24 +291,23 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
AssertManualReleaseIs (input);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
[Fact]
|
|
|
- public void TwoExcapesInARow ()
|
|
|
+ public void TwoEscapesInARow ()
|
|
|
{
|
|
|
// Example user presses Esc key then a DAR comes in
|
|
|
- string input = "\u001b\u001b";
|
|
|
- int i = 0;
|
|
|
+ var input = "\u001b\u001b";
|
|
|
+ var i = 0;
|
|
|
|
|
|
// First Esc gets grabbed
|
|
|
AssertConsumed (input, ref i);
|
|
|
|
|
|
// Upon getting the second Esc we should release the first
|
|
|
- AssertReleased (input, ref i, "\u001b",0);
|
|
|
+ AssertReleased (input, ref i, "\u001b", 0);
|
|
|
|
|
|
// Assume 50ms or something has passed, lets force release as no new content
|
|
|
|
|
|
// It should be the second escape that gets released (i.e. index 1)
|
|
|
- AssertManualReleaseIs ("\u001b",1);
|
|
|
+ AssertManualReleaseIs ("\u001b", 1);
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
@@ -298,19 +315,19 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
{
|
|
|
var p = new AnsiResponseParser ();
|
|
|
|
|
|
- string responseA = null;
|
|
|
- string responseB = null;
|
|
|
+ string? responseA = null;
|
|
|
+ string? responseB = null;
|
|
|
|
|
|
- p.ExpectResponse ("z",(r)=>responseA=r, null, false);
|
|
|
+ p.ExpectResponse ("z", r => responseA = r, null, false);
|
|
|
|
|
|
// Some time goes by without us seeing a response
|
|
|
p.StopExpecting ("z", false);
|
|
|
|
|
|
// Send our new request
|
|
|
- p.ExpectResponse ("z", (r) => responseB = r, null, false);
|
|
|
+ p.ExpectResponse ("z", r => responseB = r, null, false);
|
|
|
|
|
|
// Because we gave up on getting A, we should expect the response to be to our new request
|
|
|
- Assert.Empty(p.ProcessInput ("\u001b[<1;2z"));
|
|
|
+ Assert.Empty (p.ProcessInput ("\u001b[<1;2z"));
|
|
|
Assert.Null (responseA);
|
|
|
Assert.Equal ("\u001b[<1;2z", responseB);
|
|
|
|
|
@@ -323,7 +340,6 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
|
|
|
// We now have no outstanding requests (late or otherwise) so new ansi codes should just fall through
|
|
|
Assert.Equal ("\u001b[111z", p.ProcessInput ("\u001b[111z"));
|
|
|
-
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
@@ -331,57 +347,61 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
{
|
|
|
var p = new AnsiResponseParser ();
|
|
|
|
|
|
- int m = 0;
|
|
|
- int M = 1;
|
|
|
+ var m = 0;
|
|
|
+ var M = 1;
|
|
|
|
|
|
p.ExpectResponse ("m", _ => m++, null, true);
|
|
|
p.ExpectResponse ("M", _ => M++, null, true);
|
|
|
|
|
|
// Act - Feed input strings containing ANSI sequences
|
|
|
- p.ProcessInput ("\u001b[<0;10;10m"); // Should match and increment `m`
|
|
|
- p.ProcessInput ("\u001b[<0;20;20m"); // Should match and increment `m`
|
|
|
- p.ProcessInput ("\u001b[<0;30;30M"); // Should match and increment `M`
|
|
|
- p.ProcessInput ("\u001b[<0;40;40M"); // Should match and increment `M`
|
|
|
- p.ProcessInput ("\u001b[<0;50;50M"); // Should match and increment `M`
|
|
|
+ p.ProcessInput ("\u001b[<0;10;10m"); // Should match and increment `m`
|
|
|
+ p.ProcessInput ("\u001b[<0;20;20m"); // Should match and increment `m`
|
|
|
+ p.ProcessInput ("\u001b[<0;30;30M"); // Should match and increment `M`
|
|
|
+ p.ProcessInput ("\u001b[<0;40;40M"); // Should match and increment `M`
|
|
|
+ p.ProcessInput ("\u001b[<0;50;50M"); // Should match and increment `M`
|
|
|
|
|
|
// Assert - Verify that counters reflect the expected counts of each terminator
|
|
|
- Assert.Equal (2, m); // Expected two `m` responses
|
|
|
- Assert.Equal (4, M); // Expected three `M` responses plus the initial value of 1
|
|
|
+ Assert.Equal (2, m); // Expected two `m` responses
|
|
|
+ Assert.Equal (4, M); // Expected three `M` responses plus the initial value of 1
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
public void TestPersistentResponses_WithMetadata ()
|
|
|
{
|
|
|
- var p = new AnsiResponseParser<int> ();
|
|
|
+ AnsiResponseParser<int> p = new ();
|
|
|
|
|
|
- int m = 0;
|
|
|
+ // ReSharper disable once NotAccessedVariable
|
|
|
+ var m = 0;
|
|
|
|
|
|
- var result = new List<Tuple<char,int>> ();
|
|
|
+ List<Tuple<char, int>> result = new ();
|
|
|
|
|
|
- p.ExpectResponseT ("m", (r) =>
|
|
|
- {
|
|
|
- result = r.ToList ();
|
|
|
- m++;
|
|
|
- },
|
|
|
- null, true);
|
|
|
+ p.ExpectResponseT (
|
|
|
+ "m",
|
|
|
+ r =>
|
|
|
+ {
|
|
|
+ result = r.ToList ();
|
|
|
+ m++;
|
|
|
+ },
|
|
|
+ null,
|
|
|
+ true);
|
|
|
|
|
|
// Act - Feed input strings containing ANSI sequences
|
|
|
- p.ProcessInput (StringToBatch("\u001b[<0;10;10m")); // Should match and increment `m`
|
|
|
+ p.ProcessInput (StringToBatch ("\u001b[<0;10;10m")); // Should match and increment `m`
|
|
|
|
|
|
// Prepare expected result:
|
|
|
- var expected = new List<Tuple<char, int>>
|
|
|
+ List<Tuple<char, int>> expected = new()
|
|
|
{
|
|
|
- Tuple.Create('\u001b', 0), // Escape character
|
|
|
- Tuple.Create('[', 1),
|
|
|
- Tuple.Create('<', 2),
|
|
|
- Tuple.Create('0', 3),
|
|
|
- Tuple.Create(';', 4),
|
|
|
- Tuple.Create('1', 5),
|
|
|
- Tuple.Create('0', 6),
|
|
|
- Tuple.Create(';', 7),
|
|
|
- Tuple.Create('1', 8),
|
|
|
- Tuple.Create('0', 9),
|
|
|
- Tuple.Create('m', 10)
|
|
|
+ Tuple.Create ('\u001b', 0), // Escape character
|
|
|
+ Tuple.Create ('[', 1),
|
|
|
+ Tuple.Create ('<', 2),
|
|
|
+ Tuple.Create ('0', 3),
|
|
|
+ Tuple.Create (';', 4),
|
|
|
+ Tuple.Create ('1', 5),
|
|
|
+ Tuple.Create ('0', 6),
|
|
|
+ Tuple.Create (';', 7),
|
|
|
+ Tuple.Create ('1', 8),
|
|
|
+ Tuple.Create ('0', 9),
|
|
|
+ Tuple.Create ('m', 10)
|
|
|
};
|
|
|
|
|
|
Assert.Equal (expected.Count, result.Count); // Ensure the count is as expected
|
|
@@ -395,7 +415,6 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
_parser1.UnexpectedResponseHandler = _ => true;
|
|
|
_parser2.UnknownResponseHandler = _ => true;
|
|
|
|
|
|
-
|
|
|
AssertReleased (
|
|
|
"Just te\u001b[<0;0;0M\u001b[3c\u001b[2c\u001b[4cst",
|
|
|
"Just test",
|
|
@@ -414,19 +433,21 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
public void UnknownResponses_ParameterShouldMatch ()
|
|
|
{
|
|
|
// Track unknown responses passed to the UnexpectedResponseHandler
|
|
|
- var unknownResponses = new List<string> ();
|
|
|
+ List<string> unknownResponses = new ();
|
|
|
|
|
|
// Set up the UnexpectedResponseHandler to log each unknown response
|
|
|
_parser1.UnexpectedResponseHandler = r1 =>
|
|
|
- {
|
|
|
- unknownResponses.Add (BatchToString (r1));
|
|
|
- return true; // Return true to swallow unknown responses
|
|
|
- };
|
|
|
+ {
|
|
|
+ unknownResponses.Add (BatchToString (r1));
|
|
|
+
|
|
|
+ return true; // Return true to swallow unknown responses
|
|
|
+ };
|
|
|
|
|
|
_parser2.UnknownResponseHandler = r2 =>
|
|
|
{
|
|
|
// parsers should be agreeing on what these responses are!
|
|
|
- Assert.Equal(unknownResponses.Last(),r2);
|
|
|
+ Assert.Equal (unknownResponses.Last (), r2);
|
|
|
+
|
|
|
return true; // Return true to swallow unknown responses
|
|
|
};
|
|
|
|
|
@@ -436,7 +457,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
"Just test");
|
|
|
|
|
|
// Expected unknown responses (ANSI sequences that are unknown)
|
|
|
- var expectedUnknownResponses = new List<string>
|
|
|
+ List<string> expectedUnknownResponses = new()
|
|
|
{
|
|
|
"\u001b[<0;0;0M",
|
|
|
"\u001b[3c",
|
|
@@ -468,19 +489,20 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
List<MouseEventArgs> mouseEventArgs = new ();
|
|
|
|
|
|
parser.Mouse += (s, e) => mouseEventArgs.Add (e);
|
|
|
- parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
|
|
|
- var released = parser.ProcessInput ("a" + MOUSE_DOWN + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + MOUSE_UP + "sss");
|
|
|
+ parser.ExpectResponse ("c", dar => foundDar = dar, null, false);
|
|
|
+ string released = parser.ProcessInput ("a" + MOUSE_DOWN + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + MOUSE_UP + "sss");
|
|
|
|
|
|
Assert.Equal ("aasdfbbccsss", released);
|
|
|
|
|
|
Assert.Equal (2, mouseEventArgs.Count);
|
|
|
|
|
|
Assert.NotNull (foundDar);
|
|
|
- Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE,foundDar);
|
|
|
+ Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE, foundDar);
|
|
|
|
|
|
Assert.True (mouseEventArgs [0].IsPressed);
|
|
|
+
|
|
|
// Mouse positions in ANSI are 1 based so actual Terminal.Gui Screen positions are x-1,y-1
|
|
|
- Assert.Equal (11,mouseEventArgs [0].Position.X);
|
|
|
+ Assert.Equal (11, mouseEventArgs [0].Position.X);
|
|
|
Assert.Equal (31, mouseEventArgs [0].Position.Y);
|
|
|
|
|
|
Assert.True (mouseEventArgs [1].IsReleased);
|
|
@@ -488,11 +510,9 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
Assert.Equal (49, mouseEventArgs [1].Position.Y);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
[Fact]
|
|
|
public void ParserDetectsKeyboard ()
|
|
|
{
|
|
|
-
|
|
|
// ANSI escape sequence for cursor left
|
|
|
const string LEFT = "\u001b[D";
|
|
|
|
|
@@ -509,8 +529,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
List<Key> keys = new ();
|
|
|
|
|
|
parser.Keyboard += (s, e) => keys.Add (e);
|
|
|
- parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
|
|
|
- var released = parser.ProcessInput ("a" + LEFT + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + SHIFT_UP + "sss");
|
|
|
+ parser.ExpectResponse ("c", dar => foundDar = dar, null, false);
|
|
|
+ string released = parser.ProcessInput ("a" + LEFT + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + SHIFT_UP + "sss");
|
|
|
|
|
|
Assert.Equal ("aasdfbbccsss", released);
|
|
|
|
|
@@ -519,7 +539,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
Assert.NotNull (foundDar);
|
|
|
Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE, foundDar);
|
|
|
|
|
|
- Assert.Equal (Key.CursorLeft,keys [0]);
|
|
|
+ Assert.Equal (Key.CursorLeft, keys [0]);
|
|
|
Assert.Equal (Key.CursorUp.WithShift, keys [1]);
|
|
|
}
|
|
|
|
|
@@ -550,71 +570,81 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
Key.F4
|
|
|
];
|
|
|
|
|
|
-
|
|
|
// These are also F keys
|
|
|
- yield return [
|
|
|
- "\u001b[11~",
|
|
|
- Key.F1
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[12~",
|
|
|
- Key.F2
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[13~",
|
|
|
- Key.F3
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[14~",
|
|
|
- Key.F4
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[15~",
|
|
|
- Key.F5
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[17~",
|
|
|
- Key.F6
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[18~",
|
|
|
- Key.F7
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[19~",
|
|
|
- Key.F8
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[20~",
|
|
|
- Key.F9
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[21~",
|
|
|
- Key.F10
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[23~",
|
|
|
- Key.F11
|
|
|
- ];
|
|
|
-
|
|
|
- yield return [
|
|
|
- "\u001b[24~",
|
|
|
- Key.F12
|
|
|
- ];
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[11~",
|
|
|
+ Key.F1
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[12~",
|
|
|
+ Key.F2
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[13~",
|
|
|
+ Key.F3
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[14~",
|
|
|
+ Key.F4
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[15~",
|
|
|
+ Key.F5
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[17~",
|
|
|
+ Key.F6
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[18~",
|
|
|
+ Key.F7
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[19~",
|
|
|
+ Key.F8
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[20~",
|
|
|
+ Key.F9
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[21~",
|
|
|
+ Key.F10
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[23~",
|
|
|
+ Key.F11
|
|
|
+ ];
|
|
|
+
|
|
|
+ yield return
|
|
|
+ [
|
|
|
+ "\u001b[24~",
|
|
|
+ Key.F12
|
|
|
+ ];
|
|
|
}
|
|
|
|
|
|
[MemberData (nameof (ParserDetects_FunctionKeys_Cases))]
|
|
|
-
|
|
|
[Theory]
|
|
|
public void ParserDetects_FunctionKeys (string input, Key expectedKey)
|
|
|
{
|
|
@@ -625,20 +655,18 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
|
|
|
parser.Keyboard += (s, e) => keys.Add (e);
|
|
|
|
|
|
- foreach (var ch in input.ToCharArray ())
|
|
|
+ foreach (char ch in input)
|
|
|
{
|
|
|
- parser.ProcessInput (new (ch,1));
|
|
|
+ parser.ProcessInput (new (ch, 1));
|
|
|
}
|
|
|
- var k = Assert.Single (keys);
|
|
|
|
|
|
- Assert.Equal (k,expectedKey);
|
|
|
- }
|
|
|
+ Key k = Assert.Single (keys);
|
|
|
|
|
|
- private Tuple<char, int> [] StringToBatch (string batch)
|
|
|
- {
|
|
|
- return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();
|
|
|
+ Assert.Equal (k, expectedKey);
|
|
|
}
|
|
|
|
|
|
+ private Tuple<char, int> [] StringToBatch (string batch) { return batch.Select (k => Tuple.Create (k, _tIndex++)).ToArray (); }
|
|
|
+
|
|
|
public static IEnumerable<string []> GetBatchPermutations (string input, int maxDepth = 3)
|
|
|
{
|
|
|
// Call the recursive method to generate batches with an initial depth of 0
|
|
@@ -657,6 +685,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
if (start >= input.Length)
|
|
|
{
|
|
|
yield return new string [0];
|
|
|
+
|
|
|
yield break;
|
|
|
}
|
|
|
|
|
@@ -667,42 +696,44 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
string batch = input.Substring (start, i - start);
|
|
|
|
|
|
// Recursively get batches from the remaining substring, increasing the depth
|
|
|
- foreach (var remainingBatches in GenerateBatches (input, i, maxDepth, currentDepth + 1))
|
|
|
+ foreach (string [] remainingBatches in GenerateBatches (input, i, maxDepth, currentDepth + 1))
|
|
|
{
|
|
|
// Combine the current batch with the remaining batches
|
|
|
var result = new string [1 + remainingBatches.Length];
|
|
|
result [0] = batch;
|
|
|
Array.Copy (remainingBatches, 0, result, 1, remainingBatches.Length);
|
|
|
+
|
|
|
yield return result;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void AssertIgnored (string ansiStream,char expected, ref int i)
|
|
|
+ private void AssertIgnored (string ansiStream, char expected, ref int i)
|
|
|
{
|
|
|
- var c2 = ansiStream [i];
|
|
|
- var c1 = NextChar (ansiStream, ref i);
|
|
|
+ char c2 = ansiStream [i];
|
|
|
+ Tuple<char, int> [] c1 = NextChar (ansiStream, ref i);
|
|
|
|
|
|
// Parser does not grab this key (i.e. driver can continue with regular operations)
|
|
|
- Assert.Equal ( c1,_parser1.ProcessInput (c1));
|
|
|
- Assert.Equal (expected,c1.Single().Item1);
|
|
|
+ Assert.Equal (c1, _parser1.ProcessInput (c1));
|
|
|
+ Assert.Equal (expected, c1.Single ().Item1);
|
|
|
|
|
|
- Assert.Equal (c2, _parser2.ProcessInput (c2.ToString()).Single());
|
|
|
- Assert.Equal (expected, c2 );
|
|
|
+ Assert.Equal (c2, _parser2.ProcessInput (c2.ToString ()).Single ());
|
|
|
+ Assert.Equal (expected, c2);
|
|
|
}
|
|
|
+
|
|
|
private void AssertConsumed (string ansiStream, ref int i)
|
|
|
{
|
|
|
// Parser grabs this key
|
|
|
- var c2 = ansiStream [i];
|
|
|
- var c1 = NextChar (ansiStream, ref i);
|
|
|
+ char c2 = ansiStream [i];
|
|
|
+ Tuple<char, int> [] c1 = NextChar (ansiStream, ref i);
|
|
|
|
|
|
- Assert.Empty (_parser1.ProcessInput(c1));
|
|
|
- Assert.Empty (_parser2.ProcessInput (c2.ToString()));
|
|
|
+ Assert.Empty (_parser1.ProcessInput (c1));
|
|
|
+ Assert.Empty (_parser2.ProcessInput (c2.ToString ()));
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Overload that fully exhausts <paramref name="ansiStream"/> and asserts
|
|
|
- /// that the final released content across whole processing is <paramref name="expectedRelease"/>
|
|
|
+ /// Overload that fully exhausts <paramref name="ansiStream"/> and asserts
|
|
|
+ /// that the final released content across whole processing is <paramref name="expectedRelease"/>
|
|
|
/// </summary>
|
|
|
/// <param name="ansiStream"></param>
|
|
|
/// <param name="expectedRelease"></param>
|
|
@@ -710,28 +741,27 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
private void AssertReleased (string ansiStream, string expectedRelease, params int [] expectedTValues)
|
|
|
{
|
|
|
var sb = new StringBuilder ();
|
|
|
- var tValues = new List<int> ();
|
|
|
+ List<int> tValues = new ();
|
|
|
|
|
|
- int i = 0;
|
|
|
+ var i = 0;
|
|
|
|
|
|
while (i < ansiStream.Length)
|
|
|
{
|
|
|
- var c2 = ansiStream [i];
|
|
|
- var c1 = NextChar (ansiStream, ref i);
|
|
|
+ char c2 = ansiStream [i];
|
|
|
+ Tuple<char, int> [] c1 = NextChar (ansiStream, ref i);
|
|
|
|
|
|
- var released1 = _parser1.ProcessInput (c1).ToArray ();
|
|
|
- tValues.AddRange(released1.Select (kv => kv.Item2));
|
|
|
+ Tuple<char, int> [] released1 = _parser1.ProcessInput (c1).ToArray ();
|
|
|
+ tValues.AddRange (released1.Select (kv => kv.Item2));
|
|
|
|
|
|
-
|
|
|
- var released2 = _parser2.ProcessInput (c2.ToString ());
|
|
|
+ string released2 = _parser2.ProcessInput (c2.ToString ());
|
|
|
|
|
|
// Both parsers should have same chars so release chars consistently with each other
|
|
|
- Assert.Equal (BatchToString(released1),released2);
|
|
|
+ Assert.Equal (BatchToString (released1), released2);
|
|
|
|
|
|
sb.Append (released2);
|
|
|
}
|
|
|
|
|
|
- Assert.Equal (expectedRelease, sb.ToString());
|
|
|
+ Assert.Equal (expectedRelease, sb.ToString ());
|
|
|
|
|
|
if (expectedTValues.Length > 0)
|
|
|
{
|
|
@@ -740,46 +770,40 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Asserts that <paramref name="i"/> index of <see cref="ansiStream"/> when consumed will release
|
|
|
- /// <paramref name="expectedRelease"/>. Results in implicit increment of <paramref name="i"/>.
|
|
|
- /// <remarks>Note that this does NOT iteratively consume all the stream, only 1 char at <paramref name="i"/></remarks>
|
|
|
+ /// Asserts that <paramref name="i"/> index of <see cref="ansiStream"/> when consumed will release
|
|
|
+ /// <paramref name="expectedRelease"/>. Results in implicit increment of <paramref name="i"/>.
|
|
|
+ /// <remarks>Note that this does NOT iteratively consume all the stream, only 1 char at <paramref name="i"/></remarks>
|
|
|
/// </summary>
|
|
|
/// <param name="ansiStream"></param>
|
|
|
/// <param name="i"></param>
|
|
|
/// <param name="expectedRelease"></param>
|
|
|
/// <param name="expectedTValues"></param>
|
|
|
- private void AssertReleased (string ansiStream, ref int i, string expectedRelease, params int[] expectedTValues)
|
|
|
+ private void AssertReleased (string ansiStream, ref int i, string expectedRelease, params int [] expectedTValues)
|
|
|
{
|
|
|
- var c2 = ansiStream [i];
|
|
|
- var c1 = NextChar (ansiStream, ref i);
|
|
|
+ char c2 = ansiStream [i];
|
|
|
+ Tuple<char, int> [] c1 = NextChar (ansiStream, ref i);
|
|
|
|
|
|
// Parser realizes it has grabbed content that does not belong to an outstanding request
|
|
|
// Parser returns false to indicate to continue
|
|
|
- var released1 = _parser1.ProcessInput (c1).ToArray ();
|
|
|
+ Tuple<char, int> [] released1 = _parser1.ProcessInput (c1).ToArray ();
|
|
|
Assert.Equal (expectedRelease, BatchToString (released1));
|
|
|
|
|
|
if (expectedTValues.Length > 0)
|
|
|
{
|
|
|
- Assert.True (expectedTValues.SequenceEqual (released1.Select (kv=>kv.Item2)));
|
|
|
+ Assert.True (expectedTValues.SequenceEqual (released1.Select (kv => kv.Item2)));
|
|
|
}
|
|
|
|
|
|
Assert.Equal (expectedRelease, _parser2.ProcessInput (c2.ToString ()));
|
|
|
}
|
|
|
|
|
|
- private string BatchToString (IEnumerable<Tuple<char, int>> processInput)
|
|
|
- {
|
|
|
- return new string(processInput.Select (a=>a.Item1).ToArray ());
|
|
|
- }
|
|
|
+ private string BatchToString (IEnumerable<Tuple<char, int>> processInput) { return new (processInput.Select (a => a.Item1).ToArray ()); }
|
|
|
+
|
|
|
+ private Tuple<char, int> [] NextChar (string ansiStream, ref int i) { return StringToBatch (ansiStream [i++].ToString ()); }
|
|
|
|
|
|
- private Tuple<char,int>[] NextChar (string ansiStream, ref int i)
|
|
|
- {
|
|
|
- return StringToBatch(ansiStream [i++].ToString());
|
|
|
- }
|
|
|
private void AssertManualReleaseIs (string expectedRelease, params int [] expectedTValues)
|
|
|
{
|
|
|
-
|
|
|
// Consumer is responsible for determining this based on e.g. after 50ms
|
|
|
- var released1 = _parser1.Release ().ToArray ();
|
|
|
+ Tuple<char, int> [] released1 = _parser1.Release ().ToArray ();
|
|
|
Assert.Equal (expectedRelease, BatchToString (released1));
|
|
|
|
|
|
if (expectedTValues.Length > 0)
|