summaryrefslogtreecommitdiff
path: root/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java
blob: c4d1a1e614b9df1a990078e8acd9bc15702c81b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
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());
    }
}