Skip to content

Instantly share code, notes, and snippets.

@emanuelefirmani
Created May 22, 2023 21:29
Show Gist options
  • Save emanuelefirmani/bf9635f50f5c99b2bb20f2ce4adfe31c to your computer and use it in GitHub Desktop.
Save emanuelefirmani/bf9635f50f5c99b2bb20f2ce4adfe31c to your computer and use it in GitHub Desktop.
Simple linear skippable state maching
using Context = System.Collections.Generic.Dictionary<string, object>;
using ExecFunc = System.Func<System.Collections.Generic.Dictionary<string, object>, System.Collections.Generic.Dictionary<string, object>>;
namespace CeacEvaluationSelector.Web.Test;
internal enum WfSteps
{
Initial,
Step1,
Step2,
Step3,
Final
}
internal record Step(WfSteps WfStep, Context Context, List<WfSteps> SkippedTests, ExecFunc Func);
internal static class Operations
{
internal static Context ExecuteNullStep(Context context) => context;
internal static Context ExecuteStep1(Context context)
{
context.Add("step1", true);
return context;
}
internal static Context ExecuteStep2(Context context)
{
context.Add("step2", "ok");
return context;
}
internal static Context ExecuteStep3(Context context)
{
context.Add("step3", new List<string>());
return context;
}
}
internal static class Factory
{
internal static Step GetInitial(List<WfSteps> skippedTests) =>
new(WfSteps.Initial, new Context(), skippedTests, Operations.ExecuteNullStep);
static Func<Context, Context> GetSkippableFunc(WfSteps wfStep, List<WfSteps> skippedTests) =>
skippedTests.Contains(wfStep)
? Operations.ExecuteNullStep
: GetFunc(wfStep);
static Func<Context, Context> GetFunc(WfSteps wfStep) =>
wfStep switch
{
WfSteps.Step1 => Operations.ExecuteStep1,
WfSteps.Step2 => Operations.ExecuteStep2,
WfSteps.Step3 => Operations.ExecuteStep3,
WfSteps.Final => Operations.ExecuteNullStep,
_ => throw new ArgumentOutOfRangeException(nameof(wfStep), wfStep, null)
};
internal static Step GetNextStep(this Step step)
{
var newContext = step.Func(step.Context);
var nextStep = (WfSteps) (int)step.WfStep + 1;
return new Step(nextStep, newContext, step.SkippedTests, GetSkippableFunc(nextStep, step.SkippedTests));
}
}
public class Tests
{
[Fact]
void skips_first()
{
var initial = Factory.GetInitial(new List<WfSteps> { WfSteps.Step1 });
var first = initial.GetNextStep();
var second = first.GetNextStep();
var third = second.GetNextStep();
var final = third.GetNextStep();
Assert.Equal(WfSteps.Step1, first.WfStep);
Assert.Equal(WfSteps.Step2, second.WfStep);
Assert.Equal(WfSteps.Step3, third.WfStep);
Assert.Equal(WfSteps.Final, final.WfStep);
Assert.False(final.Context.ContainsKey("step1"));
Assert.Equal("ok", final.Context["step2"]);
Assert.True(final.Context["step3"] is List<string>);
}
[Fact]
void skips_second()
{
var initial = Factory.GetInitial(new List<WfSteps> { WfSteps.Step2 });
var first = initial.GetNextStep();
var second = first.GetNextStep();
var third = second.GetNextStep();
var final = third.GetNextStep();
Assert.Equal(WfSteps.Step1, first.WfStep);
Assert.Equal(WfSteps.Step2, second.WfStep);
Assert.Equal(WfSteps.Step3, third.WfStep);
Assert.Equal(WfSteps.Final, final.WfStep);
Assert.True((bool)final.Context["step1"]);
Assert.False(final.Context.ContainsKey("step2"));
Assert.True(final.Context["step3"] is List<string>);
}
[Fact]
void skips_first_and_third()
{
var initial = Factory.GetInitial(new List<WfSteps> { WfSteps.Step1, WfSteps.Step3 });
var first = initial.GetNextStep();
var second = first.GetNextStep();
var third = second.GetNextStep();
var final = third.GetNextStep();
Assert.Equal(WfSteps.Step1, first.WfStep);
Assert.Equal(WfSteps.Step2, second.WfStep);
Assert.Equal(WfSteps.Step3, third.WfStep);
Assert.Equal(WfSteps.Final, final.WfStep);
Assert.False(final.Context.ContainsKey("step1"));
Assert.Equal("ok", final.Context["step2"]);
Assert.False(final.Context.ContainsKey("step3"));
}
[Fact]
void skips_all()
{
var initial = Factory.GetInitial(new List<WfSteps> { WfSteps.Step1, WfSteps.Step2, WfSteps.Step3 });
var first = initial.GetNextStep();
var second = first.GetNextStep();
var third = second.GetNextStep();
var final = third.GetNextStep();
Assert.Equal(WfSteps.Step1, first.WfStep);
Assert.Equal(WfSteps.Step2, second.WfStep);
Assert.Equal(WfSteps.Step3, third.WfStep);
Assert.Equal(WfSteps.Final, final.WfStep);
Assert.False(final.Context.ContainsKey("step1"));
Assert.False(final.Context.ContainsKey("step2"));
Assert.False(final.Context.ContainsKey("step3"));
}
}
@arialdomartini
Copy link

using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using Context = System.Collections.Generic.Dictionary<string, object>;

namespace CeacEvaluationSelector.Web.Test;

delegate Context Step(Context context);

static class MyApp
{
    internal static readonly Step[] Steps = { Step1, Step2, Step3 };

    internal static Context Step1(Context context)
    {
        context.Add("step1", true);
        return context;
    }

    internal static Context Step2(Context context)
    {
        context.Add("step2", "ok");
        return context;
    }

    static Context Step3(Context context)
    {
        context.Add("step3", new List<string>());
        return context;
    }

    public static Context Run(IEnumerable<Step> steps, Context input, Step[] skipping) =>
        steps
            .Where(s => !skipping.Contains(s))
            .Aggregate(input, (Func<Context, Step, Context>)((con, s) => s(con)));
}




public class Tests
{
    [Fact]
    void skips_first()
    {
        var result =
            MyApp.Run(
                steps: MyApp.Steps,
                input: new Context(),
                skipping: new Step[] { MyApp.Step1 });

        Assert.False(result.ContainsKey("step1"));
        Assert.Equal("ok", result["step2"]);
        Assert.True(result["step3"] is List<string>);
    }

    [Fact]
    void skips_second()
    {
        var result =
            MyApp.Run(
                steps: MyApp.Steps,
                input: new Context(),
                skipping: new Step[] { MyApp.Step2 });
        ;

        Assert.True((bool)result["step1"]);
        Assert.False(result.ContainsKey("step2"));
        Assert.True(result["step3"] is List<string>);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment