Created
June 1, 2026 16:43
-
-
Save tenpn/c36c20254646cb559dbee1f9476b5075 to your computer and use it in GitHub Desktop.
The skeleton of an Unreal 5.5 Spec test, but using CQTest's more convenient Command Builder for latent operations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "Misc/AutomationTest.h" | |
| #if WITH_AUTOMATION_TESTS | |
| #include "CQTest.h" | |
| #include "Commands/TestCommandBuilder.h" // FTestCommandBuilder - the fluent .Do()/.Until()/.Then() latent chain | |
| #include "Containers/Ticker.h" | |
| // --------------------------------------------------------------------------------------------------------------------- | |
| // MINIMAL SKELETON: an Automation Spec (Describe/It, BDD) that drives latent steps with CQTest's FTestCommandBuilder. | |
| // | |
| // Why the bridge? Spec and CQTest each have their own latent machinery, and they don't natively combine: | |
| // - A Spec enqueues ALL of its BeforeEach/It/AfterEach commands up front (when Define() builds the tree), so you | |
| // cannot enqueue a latent command from inside an It() body - it would run AFTER AfterEach. | |
| // - CQTest's FTestCommandBuilder.Build() instead hands you a single IAutomationLatentCommand you own and tick yourself. | |
| // | |
| // So we use a LatentIt (which gives us an FDoneDelegate to signal completion) and pump the built command on the core | |
| // ticker, calling Done.Execute() once the queue drains. That's the whole trick - see RunCommands() below. | |
| // | |
| // --------------------------------------------------------------------------------------------------------------------- | |
| BEGIN_DEFINE_SPEC(FCommandBuilderSkeletonSpec, | |
| "AutomatedAF.RuntimeTests.Examples.CommandBuilderSkeleton", | |
| EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) | |
| // Live tickers driving our built command queues, so AfterEach can tear any half-finished one down. | |
| TArray<FTSTicker::FDelegateHandle> Tickers; | |
| // Toy state the latent steps act on, to show .Do() (mutate) -> .Until() (wait) -> .Then() (assert) flowing across ticks. | |
| int32 Counter = 0; | |
| // The bridge: pump a built CQTest command on the core ticker; signal the Spec's Done when it finishes. | |
| // (We can't enqueue it on the framework - a Spec has already queued all its commands by the time an It() runs.) | |
| void RunCommands(const FDoneDelegate& Done, TSharedPtr<IAutomationLatentCommand> Commands) | |
| { | |
| if (!Commands.IsValid()) | |
| { | |
| Done.Execute(); | |
| return; | |
| } | |
| Tickers.Add(FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda( | |
| [Done, Commands](float) -> bool | |
| { | |
| if (Commands->Update()) // returns true once the whole .Do/.Until/.Then chain has drained | |
| { | |
| Done.Execute(); | |
| return false; // stop ticking this delegate | |
| } | |
| return true; // keep ticking next frame | |
| }))); | |
| } | |
| END_DEFINE_SPEC(FCommandBuilderSkeletonSpec) | |
| void FCommandBuilderSkeletonSpec::Define() | |
| { | |
| // A Spec is one long-lived object and does NOT auto-reset between It()s - reset shared state here. | |
| BeforeEach([this]() { Counter = 0; }); | |
| Describe("a latent command-builder chain", [this]() | |
| { | |
| LatentIt("runs Do -> Until -> Then across ticks", FTimespan::FromSeconds(30), [this](const FDoneDelegate& Done) | |
| { | |
| // Build the chain with the CQTest builder. Note: pass *this (the FAutomationTestBase) to the ctor so its | |
| // asserts report against this test. Nothing runs until RunCommands() starts pumping Build(). | |
| FTestCommandBuilder Step(*this); | |
| Step.Do(TEXT("kick something off"), [this]() { Counter = 1; }) | |
| .Until(TEXT("the work completes"), [this]() { return ++Counter >= 5; }, FTimespan::FromSeconds(10)) | |
| .Then(TEXT("assert the result"), [this]() { TestTrue(TEXT("counter reached target"), Counter >= 5); }); | |
| RunCommands(Done, Step.Build()); | |
| }); | |
| }); | |
| AfterEach([this]() | |
| { | |
| // Remove any tickers still alive (e.g. if a step failed mid-chain) so they can't bleed into the next It(). | |
| for (const FTSTicker::FDelegateHandle& H : Tickers) { FTSTicker::GetCoreTicker().RemoveTicker(H); } | |
| Tickers.Reset(); | |
| }); | |
| } | |
| #endif // WITH_AUTOMATION_TESTS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment