mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Robust.UnitTesting was both ALL tests for RT, and also API surface for content tests. Tests are now split into separate projects as appropriate, and the API side has also been split off.
515 lines
26 KiB
C#
515 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Toolshed;
|
|
using Robust.Shared.Toolshed.Commands.Generic;
|
|
using Robust.Shared.Toolshed.Syntax;
|
|
using Robust.Shared.Toolshed.TypeParsers;
|
|
using Robust.Shared.Toolshed.TypeParsers.Math;
|
|
|
|
namespace Robust.UnitTesting.Shared.Toolshed;
|
|
|
|
/// <summary>
|
|
/// Collection of miscellaneous toolshed command tests.
|
|
/// Several of these were just added ad hoc as bugs arose.
|
|
/// </summary>
|
|
internal sealed class ToolshedTests : ToolshedTest
|
|
{
|
|
// TODO Robust.UnitTesting
|
|
// split these into separate [TestCase]s when we have pooling.
|
|
// Or some other way to avoid starting a server per command.
|
|
[Test]
|
|
public async Task TestMiscCommands()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
AssertResult("i 5 iota reduce { max $value }", 5);
|
|
AssertResult("i 5 iota sum", 15);
|
|
AssertResult("i 5 iota map { + 1 }", new[] {2, 3, 4, 5, 6});
|
|
AssertResult("f 5 iota map { iota sum emplace { f 2 pow $value } }", new[] {2.0f, 8.0f, 64.0f, 1024.0f, 32768.0f});
|
|
AssertResult("f 5 iota map {iota sum emplace {f 2 pow $value}}", new[] {2.0f, 8.0f, 64.0f, 1024.0f, 32768.0f});
|
|
AssertResult("i 0 map { + 1 }", new [] { 1 });
|
|
AssertResult("i 1 to 1", new [] { 1 });
|
|
AssertResult("f 1 to 1", new [] { 1 });
|
|
AssertResult("i -2 to 2 map { + 1 }", new [] { -1, 0, 1, 2, 3 });
|
|
AssertResult("f -2 to 2 map { + 1 }", new [] { -1, 0, 1, 2, 3 });
|
|
AssertResult("f 1 to 1", new [] { 1 });
|
|
AssertResult("i 2 + 2", 4);
|
|
AssertResult("i 2 + { i 2 }", 4);
|
|
AssertResult("i 2 + 2 * 2", 8);
|
|
AssertResult("i 2 + { i 2 * 2 }", 6);
|
|
AssertResult("i 3 iota max { i 3 iota }", new[] {1, 2, 3 });
|
|
AssertResult("i 1 iota iterate { take 1 } 3", new[] { new[]{1}, [1], [1] });
|
|
AssertResult("i 3 iota iterate { take 2 } 3", new[] { new[]{1, 2}, [1, 2], [1, 2] });
|
|
|
|
ParseError<OutOfInputError>("");
|
|
ParseError<OutOfInputError>(" ");
|
|
ParseError<NotValidCommandError>("{");
|
|
ParseError<NotValidCommandError>("}");
|
|
ParseError<NotValidCommandError>("{}");
|
|
ParseError<NotValidCommandError>(";");
|
|
ParseError<EmptyCommandRun>("i 2 + { }");
|
|
ParseError<MustBeVarOrBlock>("i 3 iota max 3");
|
|
ParseError<WrongCommandReturn>("i 1 iota iterate { average } 2");
|
|
|
|
// Meta-test: check that ParseError() fails if the parse actually succeeds without generating an error
|
|
Assert.Throws<AssertionException>(() => ParseError<OutOfInputError>("i 2"));
|
|
Assert.That(ExpectedErrors.Count, Is.EqualTo(1));
|
|
ExpectedErrors.Clear();
|
|
|
|
// Check that the CommandImplementationAttibute is optional when the type only defines one method.
|
|
AssertResult("testimplicitimpl", 1);
|
|
AssertResult("testexplicitimpl", 2);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestTypeArgs()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
AssertResult("testtypearg int", "Int32");
|
|
AssertResult("testmultitypearg float string -1", "Single, String, -1");
|
|
|
|
ParseError<ExpectedTypeArgumentError>("testtypearg");
|
|
ParseError<ExpectedTypeArgumentError>("testtypearg ");
|
|
ParseError<UnknownType>("testtypearg invalidType");
|
|
ParseError<ExpectedTypeArgumentError>("testmultitypearg int");
|
|
ParseError<ExpectedTypeArgumentError>("testmultitypearg int ");
|
|
ParseError<UnknownType>("testmultitypearg int invalidType");
|
|
ParseError<ExpectedArgumentError>("testmultitypearg int float");
|
|
ParseError<ExpectedArgumentError>("testmultitypearg int float ");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestCommandTerminator()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
// Terminators can be used to chain together commands that output void
|
|
AssertResult("testvoid", null);
|
|
ParseError<EndOfCommandError>("testvoid testvoid");
|
|
AssertResult("testvoid;testvoid", null);
|
|
AssertResult("testvoid; testvoid", null);
|
|
AssertResult("testvoid ; testvoid", null);
|
|
AssertResult("testvoid ;; ; testvoid", null);
|
|
|
|
// Terminators allow commands that output data to be chained with commands that take no inputs
|
|
AssertResult("testint", 1);
|
|
AssertResult("testint; testint", 1);
|
|
AssertResult("testvoid; testint", 1);
|
|
AssertResult("testint; testvoid", null);
|
|
|
|
// Terminators can interrupt argument parsing
|
|
AssertResult("testintstrarg 1 \"A\"", 1);
|
|
AssertResult("testintstrarg 1 \"A\"; testvoid", null);
|
|
ParseError<ExpectedArgumentError>("testintstrarg 1;");
|
|
ParseError<ExpectedArgumentError>("testintstrarg 1; \"A\"");
|
|
ParseError<ExpectedArgumentError>("testintstrarg 1; testvoid");
|
|
ParseError<ExpectedArgumentError>("testintstrarg 1; \"A\"; testvoid");
|
|
|
|
// Terminators can interrupt type-argument parsing
|
|
AssertResult("testmultitypearg float string -1", "Single, String, -1");
|
|
AssertResult("testmultitypearg float string -1; testvoid", null);
|
|
ParseError<ExpectedTypeArgumentError>("testmultitypearg float;");
|
|
ParseError<ExpectedTypeArgumentError>("testmultitypearg float; string -1");
|
|
ParseError<ExpectedTypeArgumentError>("testmultitypearg float; testvoid");
|
|
ParseError<ExpectedArgumentError>("testmultitypearg float string; testvoid");
|
|
|
|
// Terminators don't actually discard the final output type if it is the end of the command.;
|
|
AssertResult("testint;", 1);
|
|
AssertResult("testint; testint;", 1);
|
|
ParseError<WrongCommandReturn>("i 2 + { i 2; }");
|
|
ParseError<WrongCommandReturn>("i 2 + { i 2; ; } ;; ;");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestVariables()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
AssertResult("i 2 => $x", 2);
|
|
AssertResult("val int $x", 2);
|
|
AssertResult("var $x", 2);
|
|
AssertResult("i 2 + $x", 4);
|
|
AssertResult("i 2 + $x + 2 ", 6);
|
|
AssertResult("i 2 + { i 2 * $x }", 6);
|
|
AssertResult("i 5 iota emplace { val int $value }", new[] {1, 2, 3, 4, 5});
|
|
AssertResult("i 5 iota emplace { var $value }", new[] {1, 2, 3, 4, 5});
|
|
AssertResult("i 5 iota emplace { i 1 + $value }", new[] {2, 3, 4, 5, 6});
|
|
AssertResult("i 5 iota reduce { + $value }", 15);
|
|
});
|
|
}
|
|
|
|
[Test, TestOf(typeof(EmplaceCommand))]
|
|
public async Task TestEmplace()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
|
|
var ent = Server.EntMan.Spawn();
|
|
Server.System<MetaDataSystem>().SetEntityName(ent, "Foo");
|
|
|
|
AssertResult($"ent e{ent.Id} emplace {{ val EntityUid $value }}", ent);
|
|
AssertResult($"ent e{ent.Id} emplace {{ var $value }}", ent);
|
|
AssertResult($"ent e{ent.Id} emplace {{ f 2 + $wx }}", 2.0f);
|
|
AssertResult($"ent e{ent.Id} emplace {{ var $name }}", "Foo");
|
|
AssertResult($"player:list emplace {{ var $value }} ", Array.Empty<ICommonSession>());
|
|
AssertResult($"player:list emplace {{ var $ent }} ", Array.Empty<EntityUid>());
|
|
|
|
AssertCompletionSingle($"ent e{ent.Id} emplace ", "{");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ ", "val", "var", "f");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ val EntityUi", "EntityUi");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ val EntityUid", "EntityUid");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ val EntityUid $", "$value");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ val EntityUid $val", "$value");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ var ", "$value", "$wx", "$paused");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ var $", "$value", "$wx", "$paused");
|
|
AssertCompletionContains($"ent e{ent.Id} emplace {{ var $val", "$value");
|
|
AssertCompletionSingle($"ent e{ent.Id} emplace {{ var $value ", "}");
|
|
|
|
// Intentionally misspelled variable name
|
|
AssertCompletionEmpty($"ent e{ent.Id} emplace {{ var $valuie ");
|
|
|
|
AssertResult($"ent e{ent.Id} emplace {{ delete $value; i 5 }}", 5 );
|
|
Assert.That(Server.EntMan.Deleted(ent));
|
|
|
|
AssertCompletionContains("i 2 emplace { var ", "$value");
|
|
AssertCompletionInvalid("i 2 emplace { var ", "wx");
|
|
AssertCompletionContains("player:list emplace { var ", "$value", "$ent", "$paused");
|
|
AssertCompletionInvalid("player:list emplace { var ", "wx");
|
|
AssertCompletionContains("i 1 emplace { var $value empla", "emplace");
|
|
AssertCompletionSingle("i 1 emplace { var $value emplace ", "{");
|
|
AssertCompletionContains("player:list emplace { var $ent emplace { var $", "$value", "$wx", "$paused");
|
|
|
|
ParseError<NotValidCommandError>("i 1 emplace {{");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestTerminators()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
// Baseline check that these commands work:
|
|
AssertResult("i 1", 1);
|
|
AssertResult("i 1 + 1", 2);
|
|
AssertResult("i { i 1 }", 1);
|
|
|
|
// Trailing terminators have no clear effect.
|
|
AssertResult("i 1;", 1);
|
|
AssertResult("i { i 1 };", 1);
|
|
|
|
// Simple explicit piping works
|
|
AssertResult("i 1 | + 1", 2);
|
|
|
|
// Explicit pipes imply a command is expected. Ending a command or a block after a pipe should error.
|
|
ParseError<OutOfInputError>("i 1 |");
|
|
ParseError<UnexpectedCloseBrace>("i { i 1 | }");
|
|
|
|
// A terminator inside a block or command run doesn't pipe anything;
|
|
ParseError<NoImplementationError>("i 1 ; + 1");
|
|
ParseError<WrongCommandReturn>("i { i 1 ; }");
|
|
|
|
// Check double terminators
|
|
// A starting terminators/pipes will try to be parsed as a command.
|
|
ParseError<NotValidCommandError>("|");
|
|
ParseError<NotValidCommandError>(";");
|
|
ParseError<NotValidCommandError>(";;");
|
|
ParseError<NotValidCommandError>("||");
|
|
ParseError<NotValidCommandError>("|;");
|
|
ParseError<NotValidCommandError>(";|");
|
|
AssertResult("i 1 ;;", 1);
|
|
|
|
// Consecutive pipes will try to parse the second one as the command, which will not succeed.
|
|
ParseError<NotValidCommandError>("i 1 ||");
|
|
ParseError<NotValidCommandError>("i 1 |;");
|
|
ParseError<NotValidCommandError>("i 1 ;|");
|
|
AssertResult("i 1 ;; i 1", 1);
|
|
ParseError<NotValidCommandError>("i 1 || i 1");
|
|
ParseError<NotValidCommandError>("i 1 |; i 1");
|
|
ParseError<NotValidCommandError>("i 1 ;| i 1");
|
|
ParseError<NoImplementationError>("i 1 ;; + 1");
|
|
ParseError<NotValidCommandError>("i 1 || + 1");
|
|
ParseError<NotValidCommandError>("i 1 |; + 1");
|
|
ParseError<NotValidCommandError>("i 1 ;| + 1");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestOptionalArgs()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
// Check that straightforward optional args work.
|
|
ParseError<ExpectedArgumentError>("testoptionalargs ");
|
|
AssertResult("testoptionalargs 1", new[] {1, 0, 1});
|
|
AssertResult("testoptionalargs 1 2", new[] {1, 2, 1});
|
|
AssertResult("testoptionalargs 1 2 3", new[] {1, 2, 3});
|
|
AssertResult("testoptionalargs 1 2 3 append 4", new[] {1, 2, 3, 4});
|
|
ParseError<UnknownCommandError>("testoptionalargs 1 2 3 4");
|
|
ParseError<InvalidNumber<int>>("testoptionalargs 1 append 4");
|
|
ParseError<InvalidNumber<int>>("testoptionalargs 1 2 append 4");
|
|
|
|
// Check that semicolon terminators interrupt optional args
|
|
ParseError<ExpectedArgumentError>("testoptionalargs ;");
|
|
AssertResult("testoptionalargs 1;", new[] {1, 0, 1});
|
|
AssertResult("testoptionalargs 1 2;", new[] {1, 2, 1});
|
|
AssertResult("testoptionalargs 1 2 3;", new[] {1, 2, 3});
|
|
ParseError<UnknownCommandError>("testoptionalargs 1 2 3; 4");
|
|
AssertResult("testoptionalargs 1 2; i 3", 3);
|
|
AssertResult("testoptionalargs 1 2 3; i 4", 4);
|
|
|
|
// Check that explicit pipes interrupt optional args
|
|
ParseError<ExpectedArgumentError>("testoptionalargs |");
|
|
ParseError<OutOfInputError>("testoptionalargs 1 |");
|
|
AssertResult("testoptionalargs 1 | append 4", new[] {1, 0, 1, 4});
|
|
AssertResult("testoptionalargs 1 2 | append 4", new[] {1, 2, 1, 4});
|
|
AssertResult("testoptionalargs 1 2 3 | append 4", new[] {1, 2, 3, 4});
|
|
|
|
// Check that variables and blocks can be used to specify optional args;
|
|
AssertResult("i -1 => $i", -1);
|
|
AssertResult("testoptionalargs 1 $i", new[] {1, -1, 1});
|
|
AssertResult("testoptionalargs 1 $i 2", new[] {1, -1, 2});
|
|
AssertResult("testoptionalargs 1 { i -1 }", new[] {1, -1, 1});
|
|
AssertResult("testoptionalargs 1 { i -1 } 2", new[] {1, -1, 2});
|
|
|
|
// Repeat the above groups of tests, but within a command block.
|
|
// I.e., wrap the commands in "i 1 join { <old command> }" to prepend "1" to the results.
|
|
|
|
// This first block also effectively checks that closing braces can interrupt optional args
|
|
ParseError<ExpectedArgumentError>("i 1 join { testoptionalargs } ");
|
|
AssertResult("i 1 join { testoptionalargs 1 } ", new[] {1, 1, 0, 1});
|
|
AssertResult("i 1 join { testoptionalargs 1 2 }", new[] {1, 1, 2, 1});
|
|
AssertResult("i 1 join { testoptionalargs 1 2 3 }", new[] {1, 1, 2, 3});
|
|
AssertResult("i 1 join { testoptionalargs 1 2 3 append 4 }", new[] {1, 1, 2, 3, 4});
|
|
ParseError<UnknownCommandError>("testoptionalargs 1 2 3 4 }");
|
|
ParseError<InvalidNumber<int>>("testoptionalargs 1 2 i 3 }");
|
|
ParseError<NoImplementationError>("testoptionalargs 1 2 3 i 4 }");
|
|
|
|
ParseError<ExpectedArgumentError>("i 1 join { testoptionalargs | }");
|
|
ParseError<UnexpectedCloseBrace>("i 1 join { testoptionalargs 1 | }");
|
|
AssertResult("i 1 join { testoptionalargs 1 | append 4 }", new[] {1, 1, 0, 1, 4});
|
|
AssertResult("i 1 join { testoptionalargs 1 2 | append 4 }", new[] {1, 1, 2, 1, 4});
|
|
AssertResult("i 1 join { testoptionalargs 1 2 3 | append 4 }", new[] {1, 1, 2, 3, 4});
|
|
|
|
AssertResult("i 1 join { testoptionalargs 1 $i }", new[] {1, 1, -1, 1});
|
|
AssertResult("i 1 join { testoptionalargs 1 $i 2 }", new[] {1, 1, -1, 2});
|
|
AssertResult("i 1 join { testoptionalargs 1 { i -1 } }", new[] {1, 1, -1, 1});
|
|
AssertResult("i 1 join { testoptionalargs 1 { i -1 } 2 }", new[] {1, 1, -1, 2});
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestParamsCollections()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
// Check that straightforward optional args work.
|
|
ParseError<ExpectedArgumentError>("testparamscollection");
|
|
AssertResult("testparamsonly", new int[] {});
|
|
AssertResult("testparamscollection 1", new[] {1, 0});
|
|
AssertResult("testparamscollection 1 2", new[] {1, 2});
|
|
AssertResult("testparamscollection 1 2 3", new[] {1, 2, 3});
|
|
AssertResult("testparamscollection 1 2 3 4", new[] {1, 2, 3, 4});
|
|
ParseError<InvalidNumber<int>>("testparamscollection 1 2 append 4");
|
|
ParseError<InvalidNumber<int>>("testparamscollection 1 2 3 append 4");
|
|
ParseError<InvalidNumber<int>>("testparamscollection 1 2 3 4 append 4");
|
|
|
|
// Check that semicolon terminators interrupt optional args
|
|
ParseError<ExpectedArgumentError>("testparamscollection ;");
|
|
AssertResult("testparamsonly;", new int[] { });
|
|
AssertResult("testparamscollection 1;", new[] {1, 0});
|
|
AssertResult("testparamscollection 1 2;", new[] {1, 2});
|
|
AssertResult("testparamscollection 1 2 3;", new[] {1, 2, 3});
|
|
AssertResult("testparamscollection 1 2 3 4;", new[] {1, 2, 3, 4});
|
|
AssertResult("testparamscollection 1 2; i 4", 4);
|
|
AssertResult("testparamscollection 1 2 3; i 4", 4);
|
|
AssertResult("testparamscollection 1 2 3 4; i 4", 4);
|
|
|
|
// Check that explicit pipes interrupt optional args
|
|
ParseError<ExpectedArgumentError>("testparamscollection |");
|
|
ParseError<OutOfInputError>("testparamsonly |");
|
|
ParseError<OutOfInputError>("testparamscollection 1 |");
|
|
ParseError<OutOfInputError>("testparamscollection 1 2 |");
|
|
ParseError<OutOfInputError>("testparamscollection 1 2 3 |");
|
|
ParseError<OutOfInputError>("testparamscollection 1 2 3 4 |");
|
|
AssertResult("testparamsonly | append 1", new[] {1});
|
|
AssertResult("testparamscollection 1 | append 1", new[] {1, 0, 1});
|
|
AssertResult("testparamscollection 1 2 | append 1", new[] {1, 2, 1});
|
|
AssertResult("testparamscollection 1 2 3 | append 1", new[] {1, 2, 3, 1});
|
|
AssertResult("testparamscollection 1 2 3 4 | append 1", new[] {1, 2, 3, 4, 1});
|
|
|
|
// Check that variables and blocks can be used to specify args inside params arrays;
|
|
AssertResult("i -1 => $i", -1);
|
|
AssertResult("testparamscollection 1 2 3 $i 5", new[] {1, 2, 3, -1, 5});
|
|
AssertResult("testparamscollection 1 2 3 { i -1 } 5", new[] {1, 2, 3, -1, 5});
|
|
|
|
// Check that closing braces interrupt optional args
|
|
AssertResult("i 1 join { testparamsonly }", new[] {1});
|
|
AssertResult("i 1 join { testparamscollection 1 }", new[] {1, 1, 0});
|
|
AssertResult("i 1 join { testparamscollection 1 2 }", new[] {1, 1, 2});
|
|
AssertResult("i 1 join { testparamscollection 1 2 3 }", new[] {1, 1, 2, 3});
|
|
AssertResult("i 1 join { testparamscollection 1 2 3 4 }", new[] {1, 1, 2, 3, 4});
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that the type of generic parameters can be correctly inferred from the piped-in value. I.e., when check
|
|
/// that if we pipe a <see cref="List{T}"/> into a command that takes an <see cref="IEnumerable{T}"/>, the value of
|
|
/// the generic parameter can be properly inferred.
|
|
/// </summary>
|
|
[Test]
|
|
[TestOf(typeof(TakesPipedTypeAsGenericAttribute))]
|
|
public async Task TestGenericPipeInference()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
// Pipe T[] -> T[]
|
|
AssertResult("testarray testarrayinfer 1", typeof(int));
|
|
|
|
// Pipe List<T> -> List<T>
|
|
AssertResult("testlist testlistinfer 1", typeof(int));
|
|
|
|
// Pipe T[] -> IEnumerable<T>
|
|
AssertResult("testarray testenumerableinfer 1", typeof(int));
|
|
|
|
// Pipe List<T> -> IEnumerable<T>
|
|
AssertResult("testlist testenumerableinfer 1", typeof(int));
|
|
|
|
// Pipe IEnumerable<T> -> IEnumerable<T>
|
|
AssertResult("testenumerable testenumerableinfer 1", typeof(int));
|
|
|
|
// Repeat but with nested types. i.e. extracting T when piping ProtoId<T> -> IEnumerable<ProtoId<T>>
|
|
AssertResult("testnestedarray testnestedarrayinfer", typeof(EntityCategoryPrototype));
|
|
AssertResult("testnestedlist testnestedlistinfer", typeof(EntityCategoryPrototype));
|
|
AssertResult("testnestedarray testnestedenumerableinfer", typeof(EntityCategoryPrototype));
|
|
AssertResult("testnestedlist testnestedenumerableinfer", typeof(EntityCategoryPrototype));
|
|
AssertResult("testnestedenumerable testnestedenumerableinfer", typeof(EntityCategoryPrototype));
|
|
|
|
// The map command used to work when the piped type was passed as an IEnumerable<T> directly, but would fail
|
|
// when given a List<T> or something else that implemented the interface.
|
|
// In particular, this would become relevant when using command variables (which store enumerables as a List<T>).
|
|
AssertResult("i 1 to 4 map { * 2 }", new[] {2, 4, 6, 8});
|
|
InvokeCommand("i 1 to 4 => $x", out _);
|
|
AssertResult("var $x map { * 2 }", new[] {2, 4, 6, 8});
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestCompletions()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
InvokeCommand($"i 1 => $x", out _);
|
|
|
|
// Valid/complete commands ending in a whitespace don't generate completions.
|
|
AssertCompletionEmpty($"i 1 ");
|
|
AssertCompletionEmpty($"i 1 => $x ");
|
|
AssertCompletionEmpty($"testvoid ");
|
|
|
|
// Without a whitespace, they will still suggest the hint for the command that is currently being typed.
|
|
AssertCompletionHint("i 1", "<value (Int32)>");
|
|
AssertCompletionSingle($"i 1 => $x", "$x");
|
|
AssertCompletionContains($"testvoid", "testvoid");
|
|
|
|
// If an error occurs while parsing something, but tha error is not at the end of the command, we should
|
|
// not generate completions. I.e., we don't want to mislead people into thinking a command is valid and is
|
|
// expecting additional arguments.
|
|
AssertCompletionHint("i a", "<value (Int32)>");
|
|
AssertCompletionEmpty("i a ");
|
|
AssertCompletionEmpty("i a 1");
|
|
AssertCompletionSingle("i $", "$x");
|
|
AssertCompletionEmpty("i $a ");
|
|
AssertCompletionSingle("var $", "$x");
|
|
AssertCompletionEmpty("var $a ");
|
|
|
|
// Test variable completion
|
|
AssertCompletionSingle($"i 1 + $", "$x");
|
|
AssertCompletionSingle($"i 1 + $x", "$x");
|
|
AssertCompletionEmpty($"i 1 + $x ");
|
|
|
|
// Completion suggestions are restricted based on the piped type.
|
|
AssertCompletionContains("", "i");
|
|
AssertCompletionInvalid("", "testpipedint");
|
|
AssertCompletionEmpty("i 5 ");
|
|
AssertCompletionContains("i 5 t", "testpipedint");
|
|
AssertCompletionInvalid("i 5 ", "i");
|
|
AssertCompletionInvalid("i 5; t", "testpipedint");
|
|
AssertCompletionEmpty("i 5; ");
|
|
AssertCompletionContains("i 5; i", "i");
|
|
|
|
// Check completions when typing out; var $x
|
|
AssertCompletionContains($"va", "val", "var", "vars");
|
|
AssertCompletionContains($"var", "var", "vars");
|
|
AssertCompletionSingle($"var ", "$x");
|
|
AssertCompletionSingle($"var $", "$x");
|
|
AssertCompletionSingle($"var $x", "$x");
|
|
AssertCompletionEmpty($"var $x ");
|
|
|
|
// Check completions when typing out: testintstrarg 1 "a"
|
|
AssertCompletionContains("testintstrarg", "testintstrarg");
|
|
AssertCompletionHint("testintstrarg ", "<i (Int32)>");
|
|
AssertCompletionHint("testintstrarg 1", "<i (Int32)>");
|
|
AssertCompletionSingle("testintstrarg 1 ", "\"");
|
|
AssertCompletionHint("testintstrarg 1 \"", "<str (String)>");
|
|
AssertCompletionHint("testintstrarg 1 \"a\"", "<str (String)>");
|
|
AssertCompletionEmpty("testintstrarg 1 \"a\" ");
|
|
AssertCompletionHint("testintstrarg 1 \"a\" + ", "<y (Int32)>");
|
|
|
|
AssertCompletionContains("i 5 iota reduce { ma", "max");
|
|
AssertCompletionContains("i 5 iota reduce { max $", "$x", "$value");
|
|
|
|
AssertCompletionEmpty("notarealcommand ");
|
|
AssertCompletionEmpty("i 2 + { notarealcommand ");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task TestCustomParser()
|
|
{
|
|
await Server.WaitAssertion(() =>
|
|
{
|
|
InvokeCommand("i -1 => $x", out _);
|
|
|
|
// custom parsers still result in argument expectation erroprs
|
|
ParseError<ExpectedArgumentError>("testcustomparser");
|
|
ParseError<ExpectedArgumentError>("testcustomparser ");
|
|
|
|
// parser overrides integer parsing. Completion parser falls back to normal integer parsing
|
|
AssertResult("testcustomvarrefparser 20", 1);
|
|
AssertResult("testcustomparser 20", 1);
|
|
|
|
// Custom parser is used to generate completion options
|
|
AssertCompletionContains("testcustomvarrefparser", "testcustomparser");
|
|
AssertCompletion("testcustomvarrefparser ", new([new("A")], "B"));
|
|
AssertCompletion("testcustomvarrefparser 2", new([new("A")], "B"));
|
|
AssertCompletionEmpty("testcustomvarrefparser 2 ");
|
|
AssertCompletionContains("testcustomparser", "testcustomparser");
|
|
AssertCompletion("testcustomparser ", new([new("A")], "B"));
|
|
AssertCompletion("testcustomparser 2", new([new("A")], "B"));
|
|
AssertCompletionEmpty("testcustomparser 2 ");
|
|
|
|
// custom Parsers still support variables and blocks, unless explicitly forbidden
|
|
AssertResult("testcustomvarrefparser { i 20 }", 20);
|
|
AssertResult("testcustomvarrefparser $x", -1);
|
|
|
|
ParseError<ExpectedNumericError>("testcustomparser { i 20 }");
|
|
ParseError<ExpectedNumericError>("testcustomparser $x");
|
|
|
|
// Variable and block completions still work
|
|
AssertCompletionSingle("testcustomvarrefparser $", "$x");
|
|
AssertCompletionSingle("testcustomvarrefparser $x", "$x");
|
|
|
|
AssertCompletionSingle("testcustomvarrefparser { i 2 ", "}");
|
|
AssertCompletionSingle("testcustomvarrefparser { i 2 ", "}");
|
|
});
|
|
}
|
|
}
|