Преглед изворни кода

#2562 Add tests for SceneGraphThreadWarden's nested node handling

Added tests to validate thread safety in scenarios involving nested nodes, such as attaching/detaching children and grandchildren. Ensures proper restriction and release of thread protections for hierarchical structures. This strengthens coverage for complex scene graph operations.
Richard Tingle пре 3 месеци
родитељ
комит
43d8d44e99

+ 100 - 27
jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenTest.java

@@ -27,7 +27,7 @@ import static org.junit.Assert.fail;
 public class SceneGraphThreadWardenTest {
 
     private static ExecutorService executorService;
-    
+
     @SuppressWarnings({"ReassignedVariable", "AssertWithSideEffects"})
     @BeforeClass
     public static void setupClass() {
@@ -38,12 +38,12 @@ public class SceneGraphThreadWardenTest {
             throw new RuntimeException("WARNING: Assertions are not enabled! Tests may not work correctly.");
         }
     }
-    
+
     @Before
     public void setup() {
         executorService = newSingleThreadDaemonExecutor();
     }
-    
+
     @After
     public void tearDown() {
         executorService.shutdown();
@@ -57,20 +57,20 @@ public class SceneGraphThreadWardenTest {
     public void testNormalNodeMutationOnMainThread() {
         Node rootNode = new Node("root");
         SceneGraphThreadWarden.setup(rootNode);
-        
+
         // This should work fine since we're on the main thread
         Node child = new Node("child");
         rootNode.attachChild(child);
-        
+
         // Add another level of children
         Node grandchild = new Node("grandchild");
         child.attachChild(grandchild);
-        
+
         // Detach should also work fine
         child.detachChild(grandchild);
         rootNode.detachChild(child);
     }
-    
+
     /**
      * Test that node mutation on nodes not connected to the root node is fine even on a non main thread.
      * <p>
@@ -81,27 +81,27 @@ public class SceneGraphThreadWardenTest {
     public void testNodeMutationOnNonConnectedNodesOnNonMainThread() throws ExecutionException, InterruptedException {
         Node rootNode = new Node("root");
         SceneGraphThreadWarden.setup(rootNode);
-        
+
         Future<Node> nonConnectedNodeFuture = executorService.submit(() -> {
             // This should work fine since these nodes are not connected to the root node
             Node parent = new Node("parent");
             Node child = new Node("child");
             parent.attachChild(child);
-            
+
             // Add another level of children
             Node grandchild = new Node("grandchild");
             child.attachChild(grandchild);
-            
+
             return parent;
         });
-        
+
         // Get the result to ensure the task completed without exceptions
         Node nonConnectedNode = nonConnectedNodeFuture.get();
-        
+
         // Now we can attach it to the root node on the main thread
         rootNode.attachChild(nonConnectedNode);
     }
-    
+
     /**
      * Test that adding a node to the scene graph connected to the root node in a non main thread leads to an
      * exception.
@@ -110,18 +110,18 @@ public class SceneGraphThreadWardenTest {
     public void testAddingNodeToSceneGraphOnNonMainThread() throws InterruptedException {
         Node rootNode = new Node("root");
         SceneGraphThreadWarden.setup(rootNode);
-        
+
         // Create a child node and attach it to the root node
         Node child = new Node("child");
         rootNode.attachChild(child);
-        
+
         Future<Void> illegalMutationFuture = executorService.submit(() -> {
             // This should fail because we're trying to add a node to a node that's connected to the scene graph
             Node grandchild = new Node("grandchild");
             child.attachChild(grandchild);
             return null;
         });
-        
+
         try {
             illegalMutationFuture.get();
             fail("Expected an IllegalThreadSceneGraphMutation exception");
@@ -131,7 +131,7 @@ public class SceneGraphThreadWardenTest {
                     e.getCause() instanceof IllegalThreadSceneGraphMutation);
         }
     }
-    
+
     /**
      * Test that adding a node currently attached to a root node to a different node leads to an exception.
      * <p>
@@ -144,19 +144,19 @@ public class SceneGraphThreadWardenTest {
     public void testMovingNodeAttachedToRootOnNonMainThread() throws InterruptedException {
         Node rootNode = new Node("root");
         SceneGraphThreadWarden.setup(rootNode);
-        
+
         // Create two child nodes and attach them to the root node
         Node child1 = new Node("child1");
         Node child2 = new Node("child2");
 
         rootNode.attachChild(child2);
-        
+
         Future<Void> illegalMutationFuture = executorService.submit(() -> {
             // This should fail because we're trying to move a node that's connected to the root node
             child1.attachChild(child2); // This implicitly detaches child2 from rootNode
             return null;
         });
-        
+
         try {
             illegalMutationFuture.get();
             fail("Expected an IllegalThreadSceneGraphMutation exception");
@@ -166,7 +166,7 @@ public class SceneGraphThreadWardenTest {
                     e.getCause() instanceof IllegalThreadSceneGraphMutation);
         }
     }
-    
+
     /**
      * Test that detaching a node releases it from thread protection.
      */
@@ -174,21 +174,94 @@ public class SceneGraphThreadWardenTest {
     public void testDetachmentReleasesProtection() throws ExecutionException, InterruptedException {
         Node rootNode = new Node("root");
         SceneGraphThreadWarden.setup(rootNode);
-        
+
         // Create a child node and attach it to the root node
         Node child = new Node("child");
         rootNode.attachChild(child);
-        
+
         // Now detach it from the root node
         child.removeFromParent();
-        
+
         // Now we should be able to modify it on another thread
         Future<Void> legalMutationFuture = executorService.submit(() -> {
             Node grandchild = new Node("grandchild");
             child.attachChild(grandchild);
             return null;
         });
-        
+
+        // This should complete without exceptions
+        legalMutationFuture.get();
+    }
+
+    /**
+     * Test that adding a child to the root node also restricts the grandchild.
+     * This test will add a grandchild to a child BEFORE adding the child to the root,
+     * then try (and fail) to make an illegal on-thread change to the grandchild.
+     */
+    @Test
+    public void testAddingAChildToTheRootNodeAlsoRestrictsTheGrandChild() throws InterruptedException {
+        Node rootNode = new Node("root");
+        SceneGraphThreadWarden.setup(rootNode);
+
+        // Create a child node and a grandchild node
+        Node child = new Node("child");
+        Node grandchild = new Node("grandchild");
+
+        // Attach the grandchild to the child BEFORE adding the child to the root
+        child.attachChild(grandchild);
+
+        // Now attach the child to the root node
+        rootNode.attachChild(child);
+
+        // Try to make an illegal on-thread change to the grandchild
+        Future<Void> illegalMutationFuture = executorService.submit(() -> {
+            // This should fail because the grandchild is now restricted
+            Node greatGrandchild = new Node("greatGrandchild");
+            grandchild.attachChild(greatGrandchild);
+            return null;
+        });
+
+        try {
+            illegalMutationFuture.get();
+            fail("Expected an IllegalThreadSceneGraphMutation exception");
+        } catch (ExecutionException e) {
+            // This is expected - verify it's the right exception type
+            assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(),
+                    e.getCause() instanceof IllegalThreadSceneGraphMutation);
+        }
+    }
+
+    /**
+     * Test that removing a child from the root node also unrestricts the grandchild.
+     * This test will add a child with a grandchild to the root node, then remove the child
+     * and verify that the grandchild can be modified on a non-main thread.
+     */
+    @Test
+    public void testRemovingAChildFromTheRootNodeAlsoUnrestrictsTheGrandChild() throws ExecutionException, InterruptedException {
+        Node rootNode = new Node("root");
+        SceneGraphThreadWarden.setup(rootNode);
+
+        // Create a child node and a grandchild node
+        Node child = new Node("child");
+        Node grandchild = new Node("grandchild");
+
+        // Attach the grandchild to the child
+        child.attachChild(grandchild);
+
+        // Attach the child to the root node
+        rootNode.attachChild(child);
+
+        // Now remove the child from the root node
+        child.removeFromParent();
+
+        // Try to make a change to the grandchild on a non-main thread
+        Future<Void> legalMutationFuture = executorService.submit(() -> {
+            // This should succeed because the grandchild is no longer restricted
+            Node greatGrandchild = new Node("greatGrandchild");
+            grandchild.attachChild(greatGrandchild);
+            return null;
+        });
+
         // This should complete without exceptions
         legalMutationFuture.get();
     }
@@ -201,7 +274,7 @@ public class SceneGraphThreadWardenTest {
     private static ExecutorService newSingleThreadDaemonExecutor() {
         return Executors.newSingleThreadExecutor(daemonThreadFactory());
     }
-    
+
     /**
      * Creates a thread factory that produces daemon threads.
      */
@@ -212,4 +285,4 @@ public class SceneGraphThreadWardenTest {
             return t;
         };
     }
-}
+}