// Headless tests of the Kazah Tender (Tests/Tests.cpp convention). No renderer, no world. #include "Tests/DescentTestCommon.h" #include "Vehicles/KazahTender.h" #include "Components/StaticMeshComponent.h" #if WITH_DEV_AUTOMATION_TESTS // --------------------------------------------------------------------------------------------------------- // Kazah Tender // --------------------------------------------------------------------------------------------------------- IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentKazahLayoutTest, "Descent.Kazah.Layout", DESCENT_TEST_FLAGS) bool FDescentKazahLayoutTest::RunTest(const FString& Parameters) { const FVector Min = AKazahTender::GetModelBoundsMinCm(); const FVector Max = AKazahTender::GetModelBoundsMaxCm(); const FVector Size = Max - Min; // The user's quoted measurements: 6.99 m long, 3.27 m wide, 2.51 m tall. X is the long axis in Unreal. TestTrue(TEXT("length is 6.99 m"), FMath::IsNearlyEqual(Size.X, 699.0, 1.0)); TestTrue(TEXT("width is 3.27 m"), FMath::IsNearlyEqual(Size.Y, 327.0, 1.0)); TestTrue(TEXT("height is 2.51 m"), FMath::IsNearlyEqual(Size.Z, 251.0, 1.0)); TestTrue(TEXT("the nose (+X) is the longer side of the origin"), Max.X > -Min.X); TMap ByName; for (const FKazahPartDef& Def : AKazahTender::GetPartDefs()) { ByName.Add(Def.Name, &Def); } for (const TCHAR* Required : { TEXT("Body"), TEXT("Rotor_Port"), TEXT("Rotor_Stbd"), TEXT("Canopy_Glass"), TEXT("Canopy_Lid"), TEXT("Hatch_Lid"), TEXT("Tool_Housing"), TEXT("Nose_Lens") }) { TestTrue(*FString::Printf(TEXT("part %s exists"), Required), ByName.Contains(Required)); } // Every part, placed at its pivot, must lie inside the model's bounds (a flipped axis would break this). for (const FKazahPartDef& Def : AKazahTender::GetPartDefs()) { const FVector Lo = Def.PivotCm + Def.LocalBoundsMinCm; const FVector Hi = Def.PivotCm + Def.LocalBoundsMaxCm; const bool bInside = Lo.X >= Min.X - 0.5 && Lo.Y >= Min.Y - 0.5 && Lo.Z >= Min.Z - 0.5 && Hi.X <= Max.X + 0.5 && Hi.Y <= Max.Y + 0.5 && Hi.Z <= Max.Z + 0.5; TestTrue(*FString::Printf(TEXT("part %s lies inside the model bounds"), Def.Name), bInside); } if (ByName.Contains(TEXT("Rotor_Port")) && ByName.Contains(TEXT("Rotor_Stbd"))) { const FVector P = ByName[TEXT("Rotor_Port")]->PivotCm; const FVector S = ByName[TEXT("Rotor_Stbd")]->PivotCm; TestTrue(TEXT("port rotor is on the left (-Y), starboard on the right (+Y)"), P.Y < 0.0 && S.Y > 0.0); TestTrue(TEXT("rotors mirror each other (|Y| equal)"), FMath::IsNearlyEqual(P.Y, -S.Y, 1.0)); TestTrue(TEXT("rotors are aft of the origin"), P.X < 0.0 && S.X < 0.0); TestTrue(TEXT("rotors are at the same height"), FMath::IsNearlyEqual(P.Z, S.Z, 0.5)); } if (ByName.Contains(TEXT("Nose_Lens"))) { TestTrue(TEXT("nose lens is at the front"), ByName[TEXT("Nose_Lens")]->PivotCm.X > 300.0); } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentKazahRotorTest, "Descent.Kazah.RotorSpin", DESCENT_TEST_FLAGS) bool FDescentKazahRotorTest::RunTest(const FString& Parameters) { // An actor that was never spawned into a world still has its default components, which is all the rotor // maths needs; this keeps the test free of any world or renderer. AKazahTender* T = NewObject(GetTransientPackage()); if (!TestNotNull(TEXT("tender constructed"), T)) { return false; } UStaticMeshComponent* Port = T->GetPart(TEXT("Rotor_Port")); UStaticMeshComponent* Stbd = T->GetPart(TEXT("Rotor_Stbd")); if (!TestNotNull(TEXT("port rotor component"), Port) || !TestNotNull(TEXT("starboard rotor component"), Stbd)) { return false; } T->RotorRPM = 60.f; // one revolution per second T->AdvanceRotors(0.25f); TestTrue(TEXT("quarter second at 60 rpm is 90 degrees"), FMath::IsNearlyEqual(T->GetRotorAngleDeg(), 90.f, 0.01f)); TestTrue(TEXT("rotors spin about the forward axis (roll)"), FMath::IsNearlyEqual(FMath::Abs(Port->GetRelativeRotation().Roll), 90.f, 0.1f) && FMath::IsNearlyZero(Port->GetRelativeRotation().Pitch, 0.1f)); TestTrue(TEXT("rotors counter-rotate"), FMath::IsNearlyEqual(Port->GetRelativeRotation().Roll, -Stbd->GetRelativeRotation().Roll, 0.1f)); for (const FKazahPartDef& Def : AKazahTender::GetPartDefs()) { if (FName(Def.Name) == TEXT("Rotor_Port")) { TestTrue(TEXT("spinning does not move the hub"), Port->GetRelativeLocation().Equals(Def.PivotCm, 0.01)); } } T->RotorRPM = 0.f; const float Before = T->GetRotorAngleDeg(); T->AdvanceRotors(1.f); TestEqual(TEXT("0 rpm holds the angle"), T->GetRotorAngleDeg(), Before); return true; } #endif // WITH_DEV_AUTOMATION_TESTS