< Summary

Class:ThreatTests
Assembly:bamlab.test.editmode
File(s):/github/workspace/Assets/Tests/EditMode/ThreatTests.cs
Covered lines:122
Uncovered lines:9
Coverable lines:131
Total lines:310
Line coverage:93.1% (122 of 131)
Covered branches:0
Total branches:0
Covered methods:9
Total methods:11
Method coverage:81.8% (9 of 11)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
Setup()0%110100%
Teardown()0%440100%
TestDirectAttack_LoadedCorrectly()0%11092.11%
Threat_IsNotAssignable()0%110100%
FixedWingThreat_CalculateAccelerationInput_RespectsMaxForwardAcceleration()0%110100%
FixedWingThreat_CalculateAccelerationInput_RespectsMaxNormalAcceleration()0%110100%
RotaryWingThreat_CalculateAccelerationToWaypoint_RespectsMaxForwardAcceleration()0%110100%
RotaryWingThreat_CalculateAccelerationToWaypoint_RespectsMaxNormalAcceleration()0%110100%
MockAttackBehavior(...)0%2100%
GetNextWaypoint(...)0%2100%
RotaryWingThreat_CalculateAccelerationToWaypoint_ComputesCorrectly()0%110100%

File(s)

/github/workspace/Assets/Tests/EditMode/ThreatTests.cs

#LineLine coverage
 1using System;
 2using NUnit.Framework;
 3using UnityEngine;
 4using System.Collections.Generic;
 5using System.Linq;
 6using System.IO;
 7
 8public class ThreatTests : AgentTestBase {
 9  private FixedWingThreat fixedWingThreat;
 10  private RotaryWingThreat rotaryWingThreat;
 11
 12  private const string TestDirectAttackJson =
 13      @"
 14    {
 15        ""name"": ""TestDirectAttack"",
 16        ""attackBehaviorType"": ""DIRECT_ATTACK"",
 17        ""targetPosition"": {
 18            ""x"": 0.01,
 19            ""y"": 0.0,
 20            ""z"": 0.0
 21        },
 22        ""targetVelocity"": {
 23            ""x"": 0.0001,
 24            ""y"": 0.0,
 25            ""z"": 0.0
 26        },
 27        ""targetColliderSize"": {
 28            ""x"": 20.0,
 29            ""y"": 20.0,
 30            ""z"": 20.0
 31        },
 32        ""flightPlan"": {
 33            ""type"": ""DistanceToTarget"",
 34            ""waypoints"": [
 35                {
 36                    ""distance"": 10000.0,
 37                    ""altitude"": 500.0,
 38                    ""power"": ""MIL""
 39                },
 40                {
 41                    ""distance"": 5000.0,
 42                    ""altitude"": 100.0,
 43                    ""power"": ""MAX""
 44                }
 45            ]
 46        }
 47    }
 48    ";
 49
 750  public override void Setup() {
 751    base.Setup();
 52    // Write the hard-coded JSON to the file
 753    string attackConfigPath = Path.Combine(Application.streamingAssetsPath,
 54                                           "Configs/Behaviors/Attack/test_direct_attack.json");
 755    Directory.CreateDirectory(Path.GetDirectoryName(attackConfigPath));
 756    File.WriteAllText(attackConfigPath, TestDirectAttackJson);
 57    // Load configurations using ConfigLoader
 58    // Create dynamic configurations for threats
 759    var ucavConfig = new DynamicAgentConfig {
 60      agent_model = "ucav.json", attack_behavior = "test_direct_attack.json",
 61      initial_state = new InitialState { position = new Vector3(2000, 100, 4000),
 62                                         rotation = new Vector3(90, 0, 0),
 63                                         velocity = new Vector3(-50, 0, -100) },
 64      standard_deviation = new StandardDeviation { position = new Vector3(400, 30, 400),
 65                                                   velocity = new Vector3(0, 0, 15) },
 66      dynamic_config =
 67          new DynamicConfig { launch_config = new LaunchConfig { launch_time = 0 },
 68                              sensor_config =
 69                                  new SensorConfig { type = SensorType.IDEAL, frequency = 100 } }
 70    };
 71
 772    var quadcopterConfig = new DynamicAgentConfig {
 73      agent_model = "quadcopter.json", attack_behavior = "test_direct_attack.json",
 74      initial_state =
 75          new InitialState { position = new Vector3(0, 600, 6000), rotation = new Vector3(90, 0, 0),
 76                             velocity = new Vector3(0, 0, -50) },
 77      standard_deviation = new StandardDeviation { position = new Vector3(1000, 200, 100),
 78                                                   velocity = new Vector3(0, 0, 25) },
 79      dynamic_config =
 80          new DynamicConfig { launch_config = new LaunchConfig { launch_time = 0 },
 81                              sensor_config =
 82                                  new SensorConfig { type = SensorType.IDEAL, frequency = 100 } }
 83    };
 84
 785    Agent threatAgent = CreateTestThreat(ucavConfig);
 786    Assert.IsNotNull(threatAgent);
 787    Assert.IsTrue(threatAgent is FixedWingThreat);
 788    fixedWingThreat = (FixedWingThreat)threatAgent;
 789    Assert.IsNotNull(fixedWingThreat);
 90
 791    threatAgent = CreateTestThreat(quadcopterConfig);
 792    Assert.IsNotNull(threatAgent);
 793    Assert.IsTrue(threatAgent is RotaryWingThreat);
 794    rotaryWingThreat = (RotaryWingThreat)threatAgent;
 795    Assert.IsNotNull(rotaryWingThreat);
 796  }
 97
 798  public override void Teardown() {
 799    base.Teardown();
 100    // Delete the attack configuration file
 7101    string attackConfigPath = Path.Combine(Application.streamingAssetsPath,
 102                                           "Configs/Behaviors/Attack/test_direct_attack.json");
 14103    if (File.Exists(attackConfigPath)) {
 7104      File.Delete(attackConfigPath);
 7105    }
 106
 14107    if (fixedWingThreat != null) {
 7108      GameObject.DestroyImmediate(fixedWingThreat.gameObject);
 7109    }
 110
 14111    if (rotaryWingThreat != null) {
 7112      GameObject.DestroyImmediate(rotaryWingThreat.gameObject);
 7113    }
 7114  }
 115
 116  [Test]
 1117  public void TestDirectAttack_LoadedCorrectly() {
 118    // Arrange
 1119    try {
 120      // Act
 1121      DynamicAgentConfig config = new DynamicAgentConfig {
 122        agent_model = "ucav.json", attack_behavior = "test_direct_attack.json",
 123        initial_state = new InitialState(), standard_deviation = new StandardDeviation(),
 124        dynamic_config = new DynamicConfig()
 125      };
 126
 1127      Threat threat = CreateTestThreat(config);
 128
 129      // Assert
 1130      Assert.IsNotNull(threat, "Threat should not be null");
 131
 1132      AttackBehavior attackBehavior = GetPrivateField<AttackBehavior>(threat, "_attackBehavior");
 1133      Assert.IsNotNull(attackBehavior, "Attack behavior should not be null");
 1134      Assert.AreEqual("TestDirectAttack", attackBehavior.name);
 1135      Assert.AreEqual(AttackBehavior.AttackBehaviorType.DIRECT_ATTACK,
 136                      attackBehavior.attackBehaviorType);
 137
 1138      Assert.IsTrue(attackBehavior is DirectAttackBehavior,
 139                    "Attack behavior should be a DirectAttackBehavior");
 1140      DirectAttackBehavior directAttackBehavior = (DirectAttackBehavior)attackBehavior;
 141
 1142      Vector3 targetPosition = directAttackBehavior.targetPosition;
 1143      Assert.AreEqual(new Vector3(0.01f, 0, 0), targetPosition);
 144
 1145      DTTFlightPlan flightPlan = directAttackBehavior.flightPlan;
 1146      Assert.IsNotNull(flightPlan, "Flight plan should not be null");
 1147      Assert.AreEqual("DistanceToTarget", flightPlan.type);
 148
 1149      List<DTTWaypoint> dttWaypoints = flightPlan.waypoints;
 1150      Assert.IsNotNull(dttWaypoints, "Waypoints should not be null");
 1151      Assert.AreEqual(2, dttWaypoints.Count, "There should be 2 waypoints");
 152
 1153      Assert.AreEqual(5000f, dttWaypoints[0].distance);
 1154      Assert.AreEqual(100f, dttWaypoints[0].altitude);
 1155      Assert.AreEqual(PowerSetting.MAX, dttWaypoints[0].power);
 156
 1157      Assert.AreEqual(10000f, dttWaypoints[1].distance);
 1158      Assert.AreEqual(500f, dttWaypoints[1].altitude);
 1159      Assert.AreEqual(PowerSetting.MIL, dttWaypoints[1].power);
 160
 161      // Check targetVelocity
 1162      Vector3 targetVelocity = directAttackBehavior.targetVelocity;
 1163      Assert.AreEqual(new Vector3(0.0001f, 0f, 0f), targetVelocity,
 164                      "Target velocity should match the config");
 165
 166      // Check targetColliderSize
 1167      Vector3 targetColliderSize = directAttackBehavior.targetColliderSize;
 1168      Assert.AreEqual(new Vector3(20f, 20f, 20f), targetColliderSize,
 169                      "Target collider size should match the config");
 170
 171      // Check targetPosition (more precise check)
 1172      Assert.AreEqual(0.01f, targetPosition.x, 0.0001f, "Target position X should be 0.01");
 1173      Assert.AreEqual(0f, targetPosition.y, 0.0001f, "Target position Y should be 0");
 1174      Assert.AreEqual(0f, targetPosition.z, 0.0001f, "Target position Z should be 0");
 175
 176      // Clean up
 1177      GameObject.DestroyImmediate(simManager.gameObject);
 1178    } catch (AssertionException e) {
 0179      throw new AssertionException(
 180          e.Message + "\n" + "This test likely failed because you have edited " +
 181          "The test string at the top of the test. Please update the test with the new values.\n" +
 182          "If you need to change the test values, please update the test string at the top of the test.");
 183    }
 1184  }
 185
 186  [Test]
 1187  public void Threat_IsNotAssignable() {
 1188    Assert.IsFalse(fixedWingThreat.IsAssignable());
 1189    Assert.IsFalse(rotaryWingThreat.IsAssignable());
 1190  }
 191
 192  [Test]
 1193  public void FixedWingThreat_CalculateAccelerationInput_RespectsMaxForwardAcceleration() {
 1194    SetPrivateField(fixedWingThreat, "_currentWaypoint", Vector3.one * 1000f);
 1195    Vector3 acceleration =
 196        InvokePrivateMethod<Vector3>(fixedWingThreat, "CalculateAccelerationInput");
 1197    float maxForwardAcceleration =
 198        InvokePrivateMethod<float>(fixedWingThreat, "CalculateMaxForwardAcceleration");
 199    const float epsilon = 1e-5f;
 1200    Assert.LessOrEqual((Vector3.Project(acceleration, fixedWingThreat.transform.forward)).magnitude,
 201                       maxForwardAcceleration + epsilon);
 1202  }
 203
 204  [Test]
 1205  public void FixedWingThreat_CalculateAccelerationInput_RespectsMaxNormalAcceleration() {
 1206    SetPrivateField(fixedWingThreat, "_currentWaypoint", Vector3.one * 1000f);
 1207    Vector3 acceleration =
 208        InvokePrivateMethod<Vector3>(fixedWingThreat, "CalculateAccelerationInput");
 1209    float maxNormalAcceleration =
 210        InvokePrivateMethod<float>(fixedWingThreat, "CalculateMaxNormalAcceleration");
 211    const float epsilon = 1e-5f;
 1212    Assert.LessOrEqual(
 213        acceleration.magnitude, maxNormalAcceleration + epsilon,
 214        $"Acceleration magnitude {acceleration.magnitude} should be less than or equal to max normal acceleration {maxNo
 1215  }
 216
 217  [Test]
 1218  public void RotaryWingThreat_CalculateAccelerationToWaypoint_RespectsMaxForwardAcceleration() {
 1219    SetPrivateField(rotaryWingThreat, "_currentWaypoint", Vector3.one * 1000f);
 1220    Vector3 acceleration =
 221        InvokePrivateMethod<Vector3>(rotaryWingThreat, "CalculateAccelerationToWaypoint");
 1222    float maxForwardAcceleration =
 223        InvokePrivateMethod<float>(rotaryWingThreat, "CalculateMaxForwardAcceleration");
 224    const float epsilon = 1e-5f;
 1225    Assert.LessOrEqual((Vector3.Project(acceleration, fixedWingThreat.transform.forward)).magnitude,
 226                       maxForwardAcceleration + epsilon);
 1227  }
 228
 229  [Test]
 1230  public void RotaryWingThreat_CalculateAccelerationToWaypoint_RespectsMaxNormalAcceleration() {
 1231    SetPrivateField(rotaryWingThreat, "_currentWaypoint", Vector3.one * 1000f);
 1232    Vector3 acceleration =
 233        InvokePrivateMethod<Vector3>(rotaryWingThreat, "CalculateAccelerationToWaypoint");
 1234    float maxNormalAcceleration =
 235        InvokePrivateMethod<float>(rotaryWingThreat, "CalculateMaxNormalAcceleration");
 236    const float epsilon = 1e-5f;
 237    // Calculate the normal component of acceleration
 1238    Vector3 forwardComponent = Vector3.Project(acceleration, rotaryWingThreat.transform.forward);
 1239    Vector3 normalComponent = acceleration - forwardComponent;
 240
 1241    Assert.LessOrEqual(
 242        normalComponent.magnitude, maxNormalAcceleration + epsilon,
 243        $"Normal acceleration magnitude {normalComponent.magnitude} should be less than or equal to max normal accelerat
 1244  }
 245
 246  private class MockAttackBehavior : AttackBehavior {
 247    private Vector3 waypoint;
 248    private PowerSetting powerSetting;
 249
 0250    public MockAttackBehavior(Vector3 waypoint, PowerSetting powerSetting) {
 0251      this.waypoint = waypoint;
 0252      this.powerSetting = powerSetting;
 0253      this.name = "MockAttackBehavior";
 0254    }
 255
 256    public override (Vector3, PowerSetting)
 0257        GetNextWaypoint(Vector3 currentPosition, Vector3 targetPosition) {
 0258      return (waypoint, powerSetting);
 0259    }
 260  }
 261
 262  [Test]
 1263  public void RotaryWingThreat_CalculateAccelerationToWaypoint_ComputesCorrectly() {
 264    // Arrange
 1265    Vector3 initialPosition = new Vector3(0, 0, 0);
 1266    Vector3 waypoint = new Vector3(1000, 0, 0);
 1267    Vector3 initialVelocity = new Vector3(0, 0, 0);
 1268    float desiredSpeed = 50f;
 269
 1270    rotaryWingThreat.SetPosition(initialPosition);
 1271    SetPrivateField(rotaryWingThreat, "_currentWaypoint", waypoint);
 1272    rotaryWingThreat.SetVelocity(initialVelocity);
 1273    SetPrivateField(rotaryWingThreat, "_currentPowerSetting", PowerSetting.MIL);
 274
 275    // Assume PowerTableLookup returns 50 for PowerSetting.MIL
 276    // Assert that its true using PowerTableLookup
 1277    float powerSetting =
 278        InvokePrivateMethod<float>(rotaryWingThreat, "PowerTableLookup", PowerSetting.MIL);
 1279    Assert.AreEqual(desiredSpeed, powerSetting);
 280
 281    // Act
 1282    Vector3 accelerationInput =
 283        InvokePrivateMethod<Vector3>(rotaryWingThreat, "CalculateAccelerationToWaypoint");
 284
 285    // Assert
 1286    Vector3 toWaypoint = waypoint - initialPosition;
 1287    Vector3 expectedAccelerationDir = toWaypoint.normalized;
 1288    float expectedAccelerationMag = desiredSpeed / (float)Time.fixedDeltaTime;
 1289    Vector3 expectedAcceleration = expectedAccelerationDir * expectedAccelerationMag;
 290
 291    // Decompose acceleration into forward and normal acceleration components
 1292    Vector3 forwardAcceleration =
 293        Vector3.Project(expectedAcceleration, rotaryWingThreat.transform.forward);
 1294    Vector3 normalAcceleration = expectedAcceleration - forwardAcceleration;
 295
 296    // Limit acceleration magnitude
 1297    float maxForwardAcceleration =
 298        InvokePrivateMethod<float>(rotaryWingThreat, "CalculateMaxForwardAcceleration");
 1299    forwardAcceleration = Vector3.ClampMagnitude(forwardAcceleration, maxForwardAcceleration);
 1300    float maxNormalAcceleration =
 301        InvokePrivateMethod<float>(rotaryWingThreat, "CalculateMaxNormalAcceleration");
 1302    normalAcceleration = Vector3.ClampMagnitude(normalAcceleration, maxNormalAcceleration);
 1303    expectedAcceleration = forwardAcceleration + normalAcceleration;
 304
 1305    Assert.AreEqual(expectedAcceleration.magnitude, accelerationInput.magnitude, 0.1f,
 306                    "Acceleration magnitude should match expected");
 1307    Assert.AreEqual(expectedAcceleration.normalized, accelerationInput.normalized,
 308                    "Acceleration direction should be towards waypoint");
 1309  }
 310}