diff options
Diffstat (limited to 'qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java')
-rw-r--r-- | qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java new file mode 100644 index 0000000000..c4d1a1e614 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java @@ -0,0 +1,429 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.server.subscription.SubscriptionList.SubscriptionNode; +import org.apache.qpid.server.subscription.SubscriptionList.SubscriptionNodeIterator; +import org.apache.qpid.test.utils.QpidTestCase; + +public class SubscriptionListTest extends QpidTestCase +{ + private SubscriptionList _subList; + private MockSubscription _sub1; + private MockSubscription _sub2; + private MockSubscription _sub3; + private SubscriptionNode _node; + + protected void setUp() + { + _subList = new SubscriptionList(); + + _sub1 = new MockSubscription(); + _sub2 = new MockSubscription(); + _sub3 = new MockSubscription(); + + _subList.add(_sub1); + _subList.add(_sub2); + _subList.add(_sub3); + + _node = _subList.getHead(); + } + + /** + * Test that if the first (non-head) node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, and the + * subsequent viable node is returned instead. + */ + public void testFindNextSkipsFirstDeletedNode() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 2nd subscription", _sub2, _node.getSubscription()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if a central node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, + * and the subsequent viable node is returned instead. + */ + public void testFindNextSkipsCentralDeletedNode() + { + assertNotNull("Returned node should not be null", _node = _node.findNext()); + + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if the last node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, + * and null is returned instead. + */ + public void testFindNextSkipsLastDeletedNode() + { + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 1st subscription", _sub1, _node.getSubscription()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 2nd subscription", _sub2, _node.getSubscription()); + + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub3).delete()); + + assertNull("Returned node should be null", _node = _node.findNext()); + } + + /** + * Test that if multiple nodes in the list are deleted (but still present), they + * are not returned when searching through the list for the next viable node, + * and the subsequent viable node is returned instead. + */ + public void testFindNextSkipsMultipleDeletedNode() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if a node in the list is marked 'deleted' it is still present in the list + * until actually removed. counter-test to verify above testing of getNext() method. + */ + public void testDeletedNodeStillPresent() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + + assertNotNull("Node marked deleted should still be present", getNodeForSubscription(_subList, _sub1)); + assertEquals("All 3 nodes are still expected to be present", 3, countNodes(_subList)); + } + + /** + * Traverses the list nodes in a non-mutating fashion, returning the first node which matches the given + * Subscription, or null if none is found. + */ + private SubscriptionNode getNodeForSubscription(final SubscriptionList list, final Subscription sub) + { + SubscriptionNode node = list.getHead(); + while (node != null && node.getSubscription() != sub) + { + node = node.nextNode(); + } + + return node; + } + + /** + * Counts the number of (non-head) nodes in the list. + */ + private int countNodes(final SubscriptionList list) + { + SubscriptionNode node = list.getHead(); + int count; + for(count = -1; node != null; count++) + { + node = node.nextNode(); + } + + return count; + } + + /** + * Tests that the head is returned as expected, and isn't the node for the first subscription. + */ + public void testGetHead() + { + assertNotNull("List head should be non null", _node); + assertNotSame("Head should not be node for first subscription", + _node, getNodeForSubscription(_subList, _sub1)); + } + + /** + * Tests that the size is returned correctly in the face of additions and removals. + */ + public void testGetSize() + { + SubscriptionList subList = new SubscriptionList(); + + assertEquals("Unexpected size result", 0, subList.size()); + + Subscription sub1 = new MockSubscription(); + Subscription sub2 = new MockSubscription(); + Subscription sub3 = new MockSubscription(); + + subList.add(sub1); + assertEquals("Unexpected size result", 1, subList.size()); + + subList.add(sub2); + assertEquals("Unexpected size result", 2, subList.size()); + + subList.add(sub3); + assertEquals("Unexpected size result", 3, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub1)); + assertEquals("Unexpected size result", 2, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub2)); + assertEquals("Unexpected size result", 1, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub3)); + assertEquals("Unexpected size result", 0, subList.size()); + } + + /** + * Test that if the first (non-head) node in the list is removed it is no longer + * present in the node structure of the list at all. + */ + public void testRemoveFirstNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub1)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + } + + /** + * Test that if a central node in the list is removed it is no longer + * present in the node structure of the list at all. + */ + public void testRemoveCentralNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub2)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub2)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + } + + /** + * Test that if the subscription contained in the last node of the list is removed + * it is no longer present in the node structure of the list at all. However, + * as the last node in the structure can't actually be removed a dummy will instead + * be present. + */ + public void testRemoveLastNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub3)); + + //We actually expect 3 nodes to remain this time, because the last node cant be removed for thread safety reasons, + //however a dummy final node can be used as substitute to allow removal of the subscription node. + assertEquals("Unexpected number of nodes", 2 + 1, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + } + + /** + * Test that if the subscription not contained in the list is requested to be removed + * that the removal fails + */ + public void testRemoveNonExistantNode() + { + Subscription sub4 = new MockSubscription(); + assertNull("Should not have been a node present for the subscription", getNodeForSubscription(_subList, sub4)); + assertFalse("Removing subscription node should not have succeeded", _subList.remove(sub4)); + assertEquals("Unexpected number of nodes", 3, countNodes(_subList)); + } + + /** + * Test that if a subscription node which occurs later in the main list than the marked node is + * removed from the list after the marked node is also removed, then the marker node doesn't + * serve to retain the subsequent nodes in the list structure (and thus memory) despite their + * removal. + */ + public void testDeletedMarkedNodeDoesntLeakSubsequentlyDeletedNodes() + { + //get the nodes out the list for the 1st and 3rd subscriptions + SubscriptionNode sub1Node = getNodeForSubscription(_subList, _sub1); + assertNotNull("Should have been a node present for the subscription", sub1Node); + SubscriptionNode sub3Node = getNodeForSubscription(_subList, _sub3); + assertNotNull("Should have been a node present for the subscription", sub3Node); + + //mark the first subscription node + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), sub1Node)); + + //remove the 1st subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + //verify the 1st subscription is no longer the marker node (replaced by a dummy), or in the main list structure + assertNotSame("Unexpected marker node", sub1Node, _subList.getMarkedNode()); + assertNull("Should not have been a node present in the list structure for the marked-but-removed sub1 node", + getNodeForSubscription(_subList, _sub1)); + + //remove the 2nd subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub2)); + + //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node + //in its list structure is now the 3rd subscription (since the 2nd was removed too) + assertEquals("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); + + //remove the 3rd and final/tail subscription + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + + //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node + //in its list structure is now the dummy tail (since the 3rd subscription was removed, and a dummy + //tail was inserted) and NOT the 3rd sub node. + assertNotSame("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); + assertTrue("Unexpected next node", _subList.getMarkedNode().nextNode().isDeleted()); + assertNull("Next non-deleted node from the marker should now be the list end, i.e. null", _subList.getMarkedNode().findNext()); + } + + /** + * Test that the marked node 'findNext' behaviour is as expected after a subscription is added + * to the list following the tail subscription node being removed while it is the marked node. + * That is, that the new subscriptions node is returned by getMarkedNode().findNext(). + */ + public void testMarkedNodeFindsNewSubscriptionAfterRemovingTailWhilstMarked() + { + //get the node out the list for the 3rd subscription + SubscriptionNode sub3Node = getNodeForSubscription(_subList, _sub3); + assertNotNull("Should have been a node present for the subscription", sub3Node); + + //mark the 3rd subscription node + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), sub3Node)); + + //verify calling findNext on the marked node returns null, i.e. the end of the list has been reached + assertEquals("Unexpected node after marked node", null, _subList.getMarkedNode().findNext()); + + //remove the 3rd(marked) subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + + //add a new 4th subscription to the list + Subscription sub4 = new MockSubscription(); + _subList.add(sub4); + + //get the node out the list for the 4th subscription + SubscriptionNode sub4Node = getNodeForSubscription(_subList, sub4); + assertNotNull("Should have been a node present for the subscription", sub4Node); + + //verify the marked node (which is now a dummy substitute for the 3rd subscription) returns + //the 4th subscriptions node as the next non-deleted node. + assertEquals("Unexpected next node", sub4Node, _subList.getMarkedNode().findNext()); + } + + /** + * Test that setting the marked node to null doesn't cause problems during remove operations + */ + public void testRemoveWithNullMarkedNode() + { + //set the marker to null + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), null)); + + //remove the 1st subscription from the main list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + + //verify the 1st subscription is no longer in the main list structure + assertNull("Should not have been a node present in the main list structure for sub1", + getNodeForSubscription(_subList, _sub1)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + } + + /** + * Tests that after the first (non-head) node of the list is marked deleted but has not + * yet been removed, the iterator still skips it. + */ + public void testIteratorSkipsFirstDeletedNode() + { + //'delete' but dont remove the node for the 1st subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + assertNotNull("Should still have been a node present for the deleted subscription", + getNodeForSubscription(_subList, _sub1)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 2nd subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub2, iter.getNode().getSubscription()); + + //verify the iterator returns the 3rd subscriptions node and not the 2nd. + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub3, iter.getNode().getSubscription()); + } + + /** + * Tests that after a central node of the list is marked deleted but has not yet been removed, + * the iterator still skips it. + */ + public void testIteratorSkipsCentralDeletedNode() + { + //'delete' but dont remove the node for the 2nd subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + assertNotNull("Should still have been a node present for the deleted subscription", + getNodeForSubscription(_subList, _sub2)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 1st subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub1, iter.getNode().getSubscription()); + + //verify the iterator returns the 3rd subscriptions node and not the 2nd. + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub3, iter.getNode().getSubscription()); + } + + /** + * Tests that after the last node of the list is marked deleted but has not yet been removed, + * the iterator still skips it. + */ + public void testIteratorSkipsDeletedFinalNode() + { + //'delete' but dont remove the node for the 3rd subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub3).delete()); + assertNotNull("Should still have been a node present for the deleted 3rd subscription", + getNodeForSubscription(_subList, _sub3)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 1st subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub1, iter.getNode().getSubscription()); + + //verify the iterator returns the 2nd subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub2, iter.getNode().getSubscription()); + + //verify the iterator can no longer advance and does not return a subscription node + assertFalse("Iterator should not have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", null, iter.getNode()); + } +} |