Skip to content

Commit

Permalink
Fix ElseIfStatement double-evaluation in awaited context (#485)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Apr 1, 2022
1 parent 14b3bd7 commit f2f99f9
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 47 deletions.
137 changes: 97 additions & 40 deletions Fluid.Tests/IfStatementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@
using System.Threading.Tasks;
using Fluid.Ast;
using Fluid.Values;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Fluid.Tests
{
public class IfStatementTests
{
private Expression TRUE = new LiteralExpression(BooleanValue.True);
private Expression FALSE = new LiteralExpression(BooleanValue.False);

private List<Statement> TEXT(string text)
private static List<Statement> TEXT(string text)
{
return new List<Statement> { new TextSpanStatement(text) };
}

[Fact]
public async Task IfCanProcessWhenTrue()
private static Expression BooleanExpression(bool value, bool async)
{
var boolean = BooleanValue.Create(value);
return async ? new AwaitedExpression(boolean) : new LiteralExpression(boolean);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfCanProcessWhenTrue(bool async)
{
var e = new IfStatement(
TRUE,
BooleanExpression(true, async),
new List<Statement> { new TextSpanStatement("x") }
);

Expand All @@ -33,11 +37,13 @@ public async Task IfCanProcessWhenTrue()
Assert.Equal("x", sw.ToString());
}

[Fact]
public async Task IfDoesntProcessWhenFalse()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfDoesntProcessWhenFalse(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
new List<Statement> { new TextSpanStatement("x") }
);

Expand All @@ -47,11 +53,13 @@ public async Task IfDoesntProcessWhenFalse()
Assert.Equal("", sw.ToString());
}

[Fact]
public async Task IfDoesntProcessElseWhenTrue()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfDoesntProcessElseWhenTrue(bool async)
{
var e = new IfStatement(
TRUE,
BooleanExpression(true, async),
new List<Statement> {
new TextSpanStatement("x")
},
Expand All @@ -65,11 +73,13 @@ public async Task IfDoesntProcessElseWhenTrue()
Assert.Equal("x", sw.ToString());
}

[Fact]
public async Task IfProcessElseWhenFalse()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessElseWhenFalse(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
new List<Statement> {
new TextSpanStatement("x")
},
Expand All @@ -84,14 +94,16 @@ public async Task IfProcessElseWhenFalse()
Assert.Equal("y", sw.ToString());
}

[Fact]
public async Task IfProcessElseWhenNoOther()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessElseWhenNoOther(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
TEXT("a"),
new ElseStatement(TEXT("b")),
new List<ElseIfStatement> { new ElseIfStatement(FALSE, TEXT("c")) }
new List<ElseIfStatement> { new ElseIfStatement(BooleanExpression(false, async), TEXT("c")) }
);

var sw = new StringWriter();
Expand All @@ -100,14 +112,16 @@ public async Task IfProcessElseWhenNoOther()
Assert.Equal("b", sw.ToString());
}

[Fact]
public async Task IfProcessElseIf()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessElseIf(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
TEXT("a"),
new ElseStatement(TEXT("b")),
new List<ElseIfStatement> { new ElseIfStatement(TRUE, TEXT("c")) }
new List<ElseIfStatement> { new ElseIfStatement(BooleanExpression(true, async), TEXT("c")) }
);

var sw = new StringWriter();
Expand All @@ -116,16 +130,18 @@ public async Task IfProcessElseIf()
Assert.Equal("c", sw.ToString());
}

[Fact]
public async Task IfProcessMultipleElseIf()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessMultipleElseIf(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
TEXT("a"),
new ElseStatement(TEXT("b")),
new List<ElseIfStatement> {
new ElseIfStatement(FALSE, TEXT("c")),
new ElseIfStatement(TRUE, TEXT("d"))}
new ElseIfStatement(BooleanExpression(false, async), TEXT("c")),
new ElseIfStatement(BooleanExpression(true, async), TEXT("d"))}
);

var sw = new StringWriter();
Expand All @@ -134,16 +150,18 @@ public async Task IfProcessMultipleElseIf()
Assert.Equal("d", sw.ToString());
}

[Fact]
public async Task IfProcessFirstElseIf()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessFirstElseIf(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async),
TEXT("a"),
new ElseStatement(TEXT("b")),
new List<ElseIfStatement> {
new ElseIfStatement(TRUE, TEXT("c")),
new ElseIfStatement(TRUE, TEXT("d"))}
new ElseIfStatement(BooleanExpression(true, async), TEXT("c")),
new ElseIfStatement(BooleanExpression(true, async), TEXT("d"))}
);

var sw = new StringWriter();
Expand All @@ -152,22 +170,61 @@ public async Task IfProcessFirstElseIf()
Assert.Equal("c", sw.ToString());
}

[Fact]
public async Task IfProcessNoMatchElseIf()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessNoMatchElseIf(bool async)
{
var e = new IfStatement(
FALSE,
BooleanExpression(false, async: false),
TEXT("a"),
null,
new List<ElseIfStatement> {
new ElseIfStatement(FALSE, TEXT("c")),
new ElseIfStatement(FALSE, TEXT("d"))}
new ElseIfStatement(BooleanExpression(false, async), TEXT("c")),
new ElseIfStatement(BooleanExpression(false, async), TEXT("d"))}
);

var sw = new StringWriter();
await e.WriteToAsync(sw, HtmlEncoder.Default, new TemplateContext());

Assert.Equal("", sw.ToString());
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task IfProcessAwaitedAndElse(bool async)
{
var e = new IfStatement(
BooleanExpression(false, async: false),
TEXT("a"),
new ElseStatement(TEXT("c")),
new List<ElseIfStatement>
{
new ElseIfStatement(BooleanExpression(false, async), TEXT("b"))
}
);

var sw = new StringWriter();
await e.WriteToAsync(sw, HtmlEncoder.Default, new TemplateContext());

Assert.Equal("c", sw.ToString());
}
}

sealed class AwaitedExpression : Expression
{
private readonly FluidValue _result;

public AwaitedExpression(FluidValue result)
{
_result = result;
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
await Task.Delay(10);
return _result;
}
}
}
14 changes: 7 additions & 7 deletions Fluid/Ast/IfStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,15 @@ public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncode
var elseIfConditionTask = elseIf.Condition.EvaluateAsync(context);
if (!elseIfConditionTask.IsCompletedSuccessfully)
{
var writeTask = elseIf.WriteToAsync(writer, encoder, context);
return AwaitedElseBranch(elseIfConditionTask, writeTask, writer, encoder, context, i + 1);
return AwaitedElseBranch(elseIf, elseIfConditionTask, elseIfTask: null, writer, encoder, context, i + 1);
}

if (elseIfConditionTask.Result.ToBooleanValue())
{
var writeTask = elseIf.WriteToAsync(writer, encoder, context);
if (!writeTask.IsCompletedSuccessfully)
{
return AwaitedElseBranch(elseIfConditionTask, writeTask, writer, encoder, context, i + 1);
return AwaitedElseBranch(elseIf, elseIfConditionTask, writeTask, writer, encoder, context, i + 1);
}

return new ValueTask<Completion>(writeTask.Result);
Expand Down Expand Up @@ -139,15 +138,16 @@ private async ValueTask<Completion> Awaited(
}
else
{
await AwaitedElseBranch(new ValueTask<FluidValue>(BooleanValue.False), new ValueTask<Completion>(), writer, encoder, context, startIndex: 0);
await AwaitedElseBranch(null, new ValueTask<FluidValue>(BooleanValue.False), new ValueTask<Completion>(), writer, encoder, context, startIndex: 0);
}

return Completion.Normal;
}

private async ValueTask<Completion> AwaitedElseBranch(
ElseIfStatement elseIf,
ValueTask<FluidValue> conditionTask,
ValueTask<Completion> elseIfTask,
ValueTask<Completion>? elseIfTask,
TextWriter writer,
TextEncoder encoder,
TemplateContext context,
Expand All @@ -156,12 +156,12 @@ private async ValueTask<Completion> AwaitedElseBranch(
bool condition = (await conditionTask).ToBooleanValue();
if (condition)
{
await elseIfTask;
return await (elseIfTask ?? elseIf.WriteToAsync(writer, encoder, context));
}

for (var i = startIndex; i < _elseIfStatements.Count; i++)
{
var elseIf = _elseIfStatements[i];
elseIf = _elseIfStatements[i];
if ((await elseIf.Condition.EvaluateAsync(context)).ToBooleanValue())
{
return await elseIf.WriteToAsync(writer, encoder, context);
Expand Down

0 comments on commit f2f99f9

Please sign in to comment.