Skip to content

Commit

Permalink
add Stateful tests (#926)
Browse files Browse the repository at this point in the history
Easy .txt tests for testing mutation/side-effects, like Set(), Collect,
etc.
  • Loading branch information
MikeStall authored Dec 22, 2022
1 parent dd20b35 commit a45e82a
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the MIT license.

using System.IO;
using System.Threading.Tasks;
using Microsoft.PowerFx.Core.Tests;
using Microsoft.PowerFx.Interpreter.Tests.XUnitExtensions;
using Microsoft.PowerFx.Types;
using Xunit;
using static Microsoft.PowerFx.Interpreter.Tests.ExpressionEvaluationTests;

Expand Down Expand Up @@ -61,6 +63,34 @@ public void RunOne()
}
#endif

// Run cases in MutationScripts
// Normal tests have each line as an independent test case.
// Whereas these are fed into a repl and each file maintains state.
[Theory]
[InlineData("Simple1.txt")]
public void RunMutationTests(string file)
{
// var path = @"D:\dev\pa2\Power-Fx\src\tests\Microsoft.PowerFx.Interpreter.Tests\MutationScripts\Simple1.txt";

var path = Path.Combine(System.Environment.CurrentDirectory, "MutationScripts", file);

var config = new PowerFxConfig();
config.SymbolTable.EnableMutationFunctions();
var engine = new RecalcEngine(config);
var runner = new ReplRunner(engine);

var testRunner = new TestRunner(runner);

testRunner.AddFile(path);

var result = testRunner.RunTests();

if (result.Fail > 0)
{
Assert.Equal(string.Empty, result.Output);
}
}

// Since test discovery runs in a separate process, run a dedicated
// parse pass as a single unit test to verify all the .txt will parse.
// This doesn't actually run any tests.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
</ItemGroup>

<ItemGroup>
<Content Include="MutationScripts\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Update="InterpreterExpressionTestCases\*.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
>> Set(x, [1,2,3])
Table({Value:1},{Value:2},{Value:3})

// collect returns the record that was added
>> Collect(x, {Value : 4 })
{Value:4}

// Original was mutated
>> x
Table({Value:1},{Value:2},{Value:3},{Value:4})

Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ private static (RecalcEngine engine, RecordValue parameters) MutationFunctionsTe
return (new RecalcEngine(config), null);
}

// Interpret each test case independently
// Supports #setup directives.
internal class InterpreterRunner : BaseRunner
{
// For async tests, run in special mode.
Expand Down Expand Up @@ -313,5 +315,78 @@ protected override async Task<RunResult> RunAsyncInternal(string expr, string se
return new RunResult(newValueDeserialized);
}
}

// Runts through a .txt in sequence, allowing Set() functions that can create state.
// Useful for testing mutation functions.
internal class ReplRunner : BaseRunner
{
private readonly RecalcEngine _engine;
public ParserOptions _opts = new ParserOptions { AllowsSideEffects = true };

public ReplRunner(RecalcEngine engine)
{
_engine = engine;
}

protected override async Task<RunResult> RunAsyncInternal(string expr, string setupHandlerName = null)
{
if (TryMatchSet(expr, out var runResult))
{
return runResult;
}

var check = _engine.Check(expr, _opts);
if (!check.IsSuccess)
{
return new RunResult(check);
}
var result = check.GetEvaluator().Eval();
return new RunResult(result);
}

// Pattern match for Set(x,y) so that we can define the variable
public bool TryMatchSet(string expr, out RunResult runResult)
{
var parserOptions = new ParserOptions { AllowsSideEffects = true };

var parse = _engine.Parse(expr);
if (parse.IsSuccess)
{
if (parse.Root.Kind == Microsoft.PowerFx.Syntax.NodeKind.Call)
{
if (parse.Root is Microsoft.PowerFx.Syntax.CallNode call)
{
if (call.Head.Name.Value == "Set")
{
// Infer type based on arg1.
var arg0 = call.Args.ChildNodes[0];
if (arg0 is Microsoft.PowerFx.Syntax.FirstNameNode arg0node)
{
var arg0name = arg0node.Ident.Name.Value;

var arg1 = call.Args.ChildNodes[1];
var arg1expr = arg1.GetCompleteSpan().GetFragment(expr);

var check = _engine.Check(arg1expr);
if (check.IsSuccess)
{
var arg1Type = check.ReturnType;

var varValue = check.GetEvaluator().Eval();
_engine.UpdateVariable(arg0name, varValue);

runResult = new RunResult(varValue);
return true;
}
}
}
}
}
}

runResult = null;
return false;
}
}
}
}

0 comments on commit a45e82a

Please sign in to comment.