Add new tests for console, function, system, variable, and code flow statements

This commit is contained in:
Stone_Red
2026-03-04 14:49:54 +01:00
parent 36d8735fbb
commit a5f521912e
9 changed files with 899 additions and 56 deletions
@@ -0,0 +1,80 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class CodeFlowStatementsTests
{
[TestMethod]
public void ExitStopsExecutionTest()
{
List<string> lines =
[
"let result = before",
"exit",
"let result = after",
"${result}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Planned termination by code");
}
[TestMethod]
public void AbortAllStopsExecutionTest()
{
List<string> lines =
[
"abort_all",
"let result = after",
"${result}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Canceling all tasks");
}
[TestMethod]
public void ThrowTerminatesWithErrorFlagTest()
{
List<string> lines =
[
"throw bad"
];
YesNtAssert.ContainsTerminationMessage(lines, "with the message: bad");
}
[TestMethod]
public void ErrorTerminatesWithMessageTest()
{
List<string> lines =
[
"error soft"
];
YesNtAssert.ContainsTerminationMessage(lines, "with the message: soft");
}
[TestMethod]
public void MissingLabelFailsTest()
{
List<string> lines =
[
"goto nowhere"
];
YesNtAssert.ContainsTerminationMessage(lines, "Label \"nowhere\" not found");
}
[TestMethod]
public void MissingFunctionFailsTest()
{
List<string> lines =
[
"call nowhere"
];
YesNtAssert.ContainsTerminationMessage(lines, "Function \"nowhere\" not found");
}
}
@@ -136,4 +136,136 @@ public class CodeFlowTests
YesNtAssert.IsLastLineEqual(lines, "4");
}
[TestMethod]
public void IfElseTrueSkipsElseBranchTest()
{
List<string> lines =
[
"let result = 0",
"if 2 > 1:",
"let result = 1",
"else:",
"let result = 2",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void IfWithoutElseFalseSkipsBodyTest()
{
List<string> lines =
[
"let result = 5",
"if 1 == 2:",
"let result = 1",
"end_if",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "5");
}
[TestMethod]
public void IfGotoTrueTest()
{
List<string> lines =
[
"let result = 0",
"if 1 == 1 goto done",
"let result = 2",
"label done:",
"let result = ${result} + 1 calc",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1");
}
[TestMethod]
public void IfCallTrueTest()
{
List<string> lines =
[
"func set_result:",
"global result = ok",
"return",
"if 1 == 1 call set_result",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
[TestMethod]
public void LabelWithoutColonFailsTest()
{
List<string> lines =
[
"label loop"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void FunctionWithoutColonFailsTest()
{
List<string> lines =
[
"func missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void MissingEndIfFailsTest()
{
List<string> lines =
[
"if 1 == 2:",
"let result = 1"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found");
}
[TestMethod]
public void ElseWithoutIfFailsTest()
{
List<string> lines =
[
"else:"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_if found");
}
[TestMethod]
public void MissingEndWhileFailsTest()
{
List<string> lines =
[
"let i = 0",
"while ${i} > 1:",
"let i = ${i} + 1 calc"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching end_while found");
}
[TestMethod]
public void EndWhileWithoutWhileFailsTest()
{
List<string> lines =
[
"end_while"
];
YesNtAssert.ContainsTerminationMessage(lines, "No matching while found");
}
}
@@ -0,0 +1,110 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using YesNt.Interpreter.Runtime;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class ConsoleStatementsTests
{
private static readonly object ConsoleLock = new object();
[TestMethod]
public void PrintLineWritesOutputTest()
{
List<string> lines =
[
"print_line hello"
];
YesNtAssert.ContainsDebugOutput(lines, "hello");
}
[TestMethod]
public void PrintWritesOutputTest()
{
List<string> lines =
[
"print hello"
];
YesNtAssert.ContainsDebugOutput(lines, "hello");
}
[TestMethod]
public void ClearThrowsInNonInteractiveConsoleTest()
{
List<string> lines =
[
"clear"
];
_ = Assert.ThrowsException<IOException>(() => YesNtAssert.GetLastLine(lines));
}
[TestMethod]
public void ReadLineReplacesTokenTest()
{
lock (ConsoleLock)
{
TextReader originalIn = Console.In;
try
{
Console.SetIn(new StringReader("typed value" + Environment.NewLine));
List<string> lines =
[
"let value = %read_line",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "typed value");
}
finally
{
Console.SetIn(originalIn);
}
}
}
[TestMethod]
public void ReadKeyCanBeInterruptedByStopTest()
{
YesNtInterpreter interpreter = new YesNtInterpreter();
interpreter.Initialize();
AutoResetEvent onDone = new AutoResetEvent(false);
StringBuilder output = new StringBuilder();
interpreter.OnDebugOutput += (s) => _ = output.Append(s);
interpreter.OnLineExecuted += (e) =>
{
if (e is null)
{
_ = onDone.Set();
}
};
List<string> lines =
[
"let value = %read_key"
];
_ = Task.Run(() => interpreter.Execute(lines, true));
Thread.Sleep(100);
interpreter.Stop();
_ = onDone.WaitOne(TimeSpan.FromSeconds(3));
StringAssert.Contains(output.ToString(), "Terminated by external process");
}
}
@@ -0,0 +1,147 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class FunctionStatementsTests
{
[TestMethod]
public void FunctionCallWithInParameterTest()
{
List<string> lines =
[
"goto main",
"func echo:",
"global result = %in",
"return",
"label main:",
"call echo with hello",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "hello");
}
[TestMethod]
public void HasInAndHasOutTokensTest()
{
List<string> lines =
[
"goto main",
"func probe:",
"global hasInBefore = %has_in",
"let consume = %in",
"global hasInAfter = %has_in",
"push_out ${hasInBefore}",
"push_out ${hasInAfter}",
"return",
"label main:",
"call probe with x",
"let hasOut = %has_out",
"${hasOut}"
];
YesNtAssert.IsLastLineEqual(lines, "True");
}
[TestMethod]
public void OutParameterReadTest()
{
List<string> lines =
[
"goto main",
"func make:",
"push_out out_value",
"return",
"label main:",
"call make with anything",
"let value = %out",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "out_value");
}
[TestMethod]
public void OutParameterWithoutValueFailsTest()
{
List<string> lines =
[
"let x = %out"
];
YesNtAssert.ContainsTerminationMessage(lines, "No out argument in stack");
}
[TestMethod]
public void InParameterOutsideFunctionFailsTest()
{
List<string> lines =
[
"let x = %in"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void ReturnOutsideFunctionFailsTest()
{
List<string> lines =
[
"return"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void PushOutOutsideFunctionFailsTest()
{
List<string> lines =
[
"push_out value"
];
YesNtAssert.ContainsTerminationMessage(lines, "Statement not allowed outside of function");
}
[TestMethod]
public void FunctionWithoutColonFailsTest()
{
List<string> lines =
[
"func missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void NestedFunctionDefinitionFailsTest()
{
List<string> lines =
[
"func outer:",
"func inner:",
"return"
];
YesNtAssert.ContainsTerminationMessage(lines, "Nested functions are not allowed");
}
[TestMethod]
public void ClearCallStackRunsTest()
{
List<string> lines =
[
"clear_call_stack",
"let result = ok",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
}
@@ -0,0 +1,107 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class PredefinedVariableStatementsTests
{
[TestMethod]
public void TimeTokenProducesUnixTimestampTest()
{
List<string> lines =
[
"%time"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(long.TryParse(value, out long parsed));
long now = DateTimeOffset.Now.ToUnixTimeSeconds();
Assert.IsTrue(Math.Abs(now - parsed) < 10);
}
[TestMethod]
public void OsTokenProducesValueTest()
{
List<string> lines =
[
"%os"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsFalse(string.IsNullOrWhiteSpace(value));
}
[TestMethod]
public void CpuTokenProducesValueTest()
{
List<string> lines =
[
"%cpu"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsFalse(string.IsNullOrWhiteSpace(value));
}
[TestMethod]
public void Is64TokenProducesBooleanTest()
{
List<string> lines =
[
"%is64"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.AreEqual(Environment.Is64BitOperatingSystem.ToString(), value);
}
[TestMethod]
public void PiTokenProducesPiTest()
{
List<string> lines =
[
"%pi"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(double.TryParse(value, out double parsed));
Assert.IsTrue(Math.Abs(parsed - Math.PI) < 0.001d);
}
[TestMethod]
public void RandTokenProducesIntegerTest()
{
List<string> lines =
[
"%rand"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
Assert.IsTrue(int.TryParse(value, out int parsed));
Assert.IsTrue(parsed >= 32767);
}
[TestMethod]
public void MultipleRandTokensAreReplacedTest()
{
List<string> lines =
[
"%rand %rand"
];
string? value = YesNtAssert.GetLastLine(lines);
Assert.IsNotNull(value);
string[] parts = value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
Assert.AreEqual(2, parts.Length);
Assert.IsTrue(int.TryParse(parts[0], out _));
Assert.IsTrue(int.TryParse(parts[1], out _));
}
}
@@ -1,4 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace YesNt.Interpreter.Tests;
@@ -40,4 +44,98 @@ public class ProcessingStatementsTests
{
YesNtAssert.IsLineEqual("2 ^ 3 calc", "8");
}
[TestMethod]
public void EvalDecodesSafeStringTest()
{
YesNtAssert.IsLineEqual("hello~nliworld eval", "hello\nworld");
}
[TestMethod]
public void SleepInvalidValueFailsTest()
{
List<string> lines =
[
"sleep nope"
];
YesNtAssert.ContainsTerminationMessage(lines, "\"nope\" is not a valid time-out value");
}
[TestMethod]
public void SleepRunsAndContinuesTest()
{
List<string> lines =
[
"sleep 5",
"let result = ok",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "ok");
}
[TestMethod]
public void LengthPushesOutParameterTest()
{
List<string> lines =
[
"length hello",
"let value = %out",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "5");
}
[TestMethod]
public void ImportLoadsScriptTest()
{
string tempFile = Path.Combine(Path.GetTempPath(), $"yesnt-import-{Guid.NewGuid():N}.ynt");
try
{
File.WriteAllText(tempFile, "let imported = yes");
List<string> lines =
[
$"import {tempFile}",
"${imported}"
];
YesNtAssert.IsLastLineEqual(lines, "yes");
}
finally
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
}
[TestMethod]
public void ImportMissingFileFailsTest()
{
List<string> lines =
[
$"import {Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))}.ynt"
];
YesNtAssert.ContainsTerminationMessage(lines, "Could not find file");
}
[TestMethod]
public void TaskCanUpdateGlobalVariableTest()
{
List<string> lines =
[
"global result = 0",
"global result = 1 task",
"sleep 50",
"${result}"
];
YesNtAssert.IsLastLineEqual(lines, "1", timeout: 3000);
}
}
@@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class SystemStatementsTests
{
[TestMethod]
public void ExecWithArgsRunsProcessTest()
{
List<string> lines =
[
"exec cmd with /c,echo yesnt",
"let exitCode = %out",
"${exitCode}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void ExecWithInStackArgsRunsProcessTest()
{
List<string> lines =
[
"push_in /c",
"push_in echo yesnt",
"exec cmd",
"let exitCode = %out",
"${exitCode}"
];
YesNtAssert.IsLastLineEqual(lines, "0");
}
[TestMethod]
public void ExecInvalidProgramFailsTest()
{
List<string> lines =
[
"exec does_not_exist_abc_xyz"
];
YesNtAssert.ContainsTerminationMessage(lines, "Failed to start \"does_not_exist_abc_xyz\"");
}
}
@@ -0,0 +1,104 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace YesNt.Interpreter.Tests;
[TestClass]
public class VariableStatementsTests
{
[TestMethod]
public void LetAndReadVariableTest()
{
List<string> lines =
[
"let value = hi",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "hi");
}
[TestMethod]
public void GlobalVariableReadTest()
{
List<string> lines =
[
"global value = hi",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "hi");
}
[TestMethod]
public void LocalVariableOverridesGlobalTest()
{
List<string> lines =
[
"global value = global",
"let value = local",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "local");
}
[TestMethod]
public void DeleteLocalVariableTest()
{
List<string> lines =
[
"let value = a",
"delete value",
"global value = b",
"${value}"
];
YesNtAssert.IsLastLineEqual(lines, "b");
}
[TestMethod]
public void DeleteMissingVariableFailsTest()
{
List<string> lines =
[
"delete missing"
];
YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found");
}
[TestMethod]
public void LetInvalidSyntaxFailsTest()
{
List<string> lines =
[
"let a"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid statement");
}
[TestMethod]
public void LetInvalidNameFailsTest()
{
List<string> lines =
[
"let a b = 1"
];
YesNtAssert.ContainsTerminationMessage(lines, "Invalid Syntax");
}
[TestMethod]
public void MissingVariableReferenceFailsTest()
{
List<string> lines =
[
"${missing}"
];
YesNtAssert.ContainsTerminationMessage(lines, "Variable \"missing\" not found");
}
}
+60 -43
View File
@@ -1,7 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using System.Threading;
using YesNt.Interpreter.Runtime;
@@ -10,76 +12,91 @@ namespace YesNt.Interpreter.Tests;
internal static class YesNtAssert
{
private static readonly YesNtInterpreter yesNtInterpreter = new YesNtInterpreter();
static YesNtAssert()
{
yesNtInterpreter.Initialize();
}
public static void IsLastLineEqual(List<string> lines, string expected, int timeout = 1000)
{
AutoResetEvent onDone = new AutoResetEvent(false);
DebugEventArgs debugEventArgs = new DebugEventArgs();
yesNtInterpreter.OnLineExecuted += (er) =>
{
debugEventArgs = er ?? debugEventArgs;
if (er is null)
{
_ = onDone.Set();
}
};
yesNtInterpreter.Execute(lines, true);
_ = onDone.WaitOne(TimeSpan.FromSeconds(timeout));
Assert.AreEqual(expected, debugEventArgs.CurrentLine);
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreEqual(expected, debugEventArgs?.CurrentLine);
}
public static void IsLineEqual(string line, string expected, int timeout = 1000)
{
AutoResetEvent onDone = new AutoResetEvent(false);
List<string> lines =
[
line
];
DebugEventArgs debugEventArgs = new DebugEventArgs();
yesNtInterpreter.OnLineExecuted += (er) =>
{
debugEventArgs = er ?? debugEventArgs;
_ = onDone.Set();
};
yesNtInterpreter.Execute(lines, true);
_ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout));
Assert.AreEqual(expected, debugEventArgs.CurrentLine);
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreEqual(expected, debugEventArgs?.CurrentLine);
}
public static void IsLineNotEqual(string line, string expected, int timeout = 1000)
{
AutoResetEvent onDone = new AutoResetEvent(false);
List<string> lines =
[
line
];
DebugEventArgs debugEventArgs = new DebugEventArgs();
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
Assert.AreNotEqual(expected, debugEventArgs?.CurrentLine);
}
public static void ContainsTerminationMessage(List<string> lines, string expectedMessageFragment, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout);
StringAssert.Contains(debugOutput, expectedMessageFragment);
}
public static void ContainsDebugOutput(List<string> lines, string expectedFragment, int timeout = 1000)
{
(_, string debugOutput) = ExecuteAndCapture(lines, timeout);
StringAssert.Contains(debugOutput, expectedFragment);
}
public static string? GetLastLine(List<string> lines, int timeout = 1000)
{
(DebugEventArgs? debugEventArgs, _) = ExecuteAndCapture(lines, timeout);
return debugEventArgs?.CurrentLine;
}
public static void LastLineMatches(List<string> lines, string pattern, int timeout = 1000)
{
string? value = GetLastLine(lines, timeout);
Assert.IsNotNull(value);
StringAssert.Matches(value, new Regex(pattern));
}
private static (DebugEventArgs? LastDebugEvent, string DebugOutput) ExecuteAndCapture(List<string> lines, int timeout)
{
YesNtInterpreter yesNtInterpreter = new YesNtInterpreter();
yesNtInterpreter.Initialize();
AutoResetEvent onDone = new AutoResetEvent(false);
DebugEventArgs? debugEventArgs = null;
StringBuilder outputBuilder = new StringBuilder();
yesNtInterpreter.OnLineExecuted += (er) =>
{
debugEventArgs = er ?? debugEventArgs;
if (er is not null)
{
debugEventArgs = er;
}
else
{
_ = onDone.Set();
}
};
yesNtInterpreter.OnDebugOutput += (s) =>
{
_ = outputBuilder.Append(s);
};
yesNtInterpreter.Execute(lines, true);
_ = onDone.WaitOne(TimeSpan.FromMilliseconds(timeout));
Assert.AreNotEqual(expected, debugEventArgs.CurrentLine);
return (debugEventArgs, outputBuilder.ToString());
}
}