/[thuban]/branches/WIP-pyshapelib-bramz/test/test_connector.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/test/test_connector.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1144 - (hide annotations)
Tue Jun 10 11:56:55 2003 UTC (21 years, 8 months ago) by bh
Original Path: trunk/thuban/test/test_connector.py
File MIME type: text/x-python
File size: 10946 byte(s)
* Thuban/Lib/connector.py (Connector.Issue): Iterate over a copy
of the receivers list so that unsubscribing in a receiver doesn't
modify it while iterating over it.

* test/test_connector.py
(ConnectorTest.test_disconnect_in_receiver): New. Test whether
unsubscribing in a receiver works correctly. See docstring for
details

1 bh 1144 # Copyright (c) 2002, 2003 by Intevation GmbH
2 bh 329 # Authors:
3     # Bernhard Herzog <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     """
9     Test the Connector class
10     """
11    
12     __version__ = "$Revision$"
13     # $Source$
14     # $Id$
15    
16     import unittest
17    
18     import support
19     support.initthuban()
20    
21     from Thuban.Lib.connector import Connector, Publisher
22    
23     # some messages used in the tests
24     SIMPLE = "SIMPLE"
25     PARAM = "PARAM"
26    
27     class SimplePublisher:
28    
29     """A version of Publisher that uses a specific connector.
30    
31     The Publisher class in Thuban.Lib.connector uses the global
32     connector in the same module.
33     """
34    
35     def __init__(self, connector):
36     self.connector = connector
37    
38     def __del__(self):
39     self.connector.RemovePublisher(self)
40    
41     def issue(self):
42     """Issue a SIMPLE message without parameters"""
43     self.connector.Issue(self, SIMPLE)
44    
45     def issue_arg(self):
46     """Issue a PARAM message with 42 as parameter"""
47     self.connector.Issue(self, PARAM, 42)
48    
49    
50     class RealPublisher(Publisher):
51    
52     """Extended version of Publisher for testing purposes.
53    
54     Publisher is not intended to be used directly. It is used as a base
55     class for objects that send messages when they change. So we do just
56     that here and derive from Publisher to provide some simple methods
57     that issue messages.
58     """
59    
60     def simple_action(self):
61     """Issue a SIMPLE message without parameters"""
62     self.issue(SIMPLE)
63    
64     def param_action(self):
65     """Issue a PARAM message with 42 as parameter"""
66     self.issue(PARAM, 42)
67    
68    
69     class Receiver:
70    
71     """Class to be used as a generic receiver of messages.
72    
73     An instance of this class has some methods that can be used as
74     subscribers for messages. These messages put information about the
75     messages they received into the public instance variable messages.
76     See the method's doc-strings for more information.
77    
78     Furthermore, the class is instantiated with a test case object as
79     parameter and the instance notifies the test case when it's being
80     instantiated and deleted so that the test case can determine which
81     objects weren't deleted.
82     """
83    
84     def __init__(self, testcase):
85     """Initialize the object for the given testcase.
86    
87     Call the testcase's expect_delete method with self as parameter.
88     """
89     self.testcase = testcase
90     self.testcase.expect_delete(self)
91     self.reset()
92    
93     def __del__(self):
94     """Tell the test case that the object has been deleted"""
95     self.testcase.deleted(self)
96    
97     def reset(self):
98     """Clear the list of received messages"""
99     self.messages = []
100    
101     def no_params(self):
102     """Method for subscriptions without parameters
103    
104     Add the tuple ("no_params",) to self.messages
105     """
106     self.messages.append(("no_params",))
107    
108     def with_params(self, *args):
109     """Method for subscriptions with parameters
110    
111     Add a tuple with the string 'params' followed by the arguments
112     of this function (except for the self parameter) to
113     self.messages.
114     """
115     self.messages.append(("params",) + args)
116    
117    
118    
119     class DeletionTestMixin:
120    
121     """Mixin class to check for memory leaks.
122    
123     Mixin class for test that want to determine whether certain objects
124     have been destroyed.
125    
126     This class maintains two lists, deleted_objects and
127     expected_deletions to determine whether all objects which are
128     expected to be deleted by a test are actually deleted.
129     """
130    
131     def setUp(self):
132     """Initialize self.deleted_objects and self.expected_deletions"""
133     self.deleted_objects = []
134     self.expected_deletions = []
135    
136     def expect_delete(self, obj):
137     """Append the id of obj to the self.expected_deletions"""
138     self.expected_deletions.append(id(obj))
139    
140     def deleted(self, obj):
141     """Append the id of obj to the self.deleted_objects"""
142     self.deleted_objects.append(id(obj))
143    
144     def check_deletetions(self):
145     """Assert equality of self.expected_deletions and self.deleted_objects
146    
147     This check simply compares the lists for equality and thus
148     effectively assumes that the objects are deleted in the same
149     order in which they're added to the list which if used only for
150     Receiver instances is the order in which they're instantiated.
151     """
152     self.assertEquals(self.expected_deletions, self.deleted_objects)
153    
154    
155     class ConnectorTest(unittest.TestCase, DeletionTestMixin):
156    
157     """Test cases for the Connector class.
158    
159     These tests use the SimplePublisher class instead of the Publisher
160     class in Thuban.Lib.connector because we only want to test the
161     connector here.
162     """
163    
164     def setUp(self):
165     """Extend the inherited method to create a Connector instance.
166    
167     Bind the Connector to self.connector.
168     """
169     self.connector = Connector()
170     DeletionTestMixin.setUp(self)
171    
172     def test_issue_simple(self):
173     """Test connector issue without parameters"""
174     # Make a publisher and a subscriber and connect the two
175     pub = SimplePublisher(self.connector)
176     rec = Receiver(self)
177     self.connector.Connect(pub, SIMPLE, rec.no_params, ())
178    
179     # now the publisher should have subscribers
180     self.assert_(self.connector.HasSubscribers(pub))
181    
182     # Issue a message and check whether the receiver got it
183     pub.issue()
184     self.assertEquals(rec.messages, [("no_params",)])
185     rec.reset()
186    
187     # disconnect and check that the message doesn't get send anymore
188     self.connector.Disconnect(pub, SIMPLE, rec.no_params, ())
189     pub.issue()
190     self.assertEquals(rec.messages, [])
191    
192     # now the publisher should have no subscribers
193     self.failIf(self.connector.HasSubscribers(pub))
194    
195     # make sure that all references have been deleted
196     del rec
197     self.check_deletetions()
198    
199     def test_issue_param(self):
200     """Test connector issue with parameters"""
201     pub = SimplePublisher(self.connector)
202     rec = Receiver(self)
203     # Three cases: 1. The parameter supplied by pub.issue_arg, 2.
204     # only the parameter given when connecting, 3. both
205     self.connector.Connect(pub, PARAM, rec.with_params, ())
206     self.connector.Connect(pub, SIMPLE, rec.with_params, ("deliverator",))
207     self.connector.Connect(pub, PARAM, rec.with_params, ("loglo",))
208    
209     pub.issue_arg()
210     pub.issue()
211     self.assertEquals(rec.messages, [("params", 42),
212     ("params", 42, "loglo"),
213     ("params", "deliverator")])
214    
215     # make sure that all references have been deleted
216     self.connector.RemovePublisher(pub)
217     del rec
218     self.check_deletetions()
219    
220     def test_cyclic_references(self):
221     """Test whether connector avoids cyclic references"""
222     pub = SimplePublisher(self.connector)
223     rec = Receiver(self)
224     self.connector.Connect(pub, SIMPLE, rec.no_params, ())
225    
226     # deleting pub and rec should be enough that the last reference
227     # to rec has been dropped because the connector doesn't keep
228     # references to the publishers and SimplePublisher's __del__
229     # method removes all subscriptions
230     del pub
231     del rec
232     self.check_deletetions()
233    
234 bh 1144 def test_disconnect_in_receiver(self):
235     """Test unsubscribing from a channel while receiving a message
236 bh 329
237 bh 1144 There was a bug in the connector implementation in the following
238     situation:
239    
240     - 2 receivers for the same channel
241    
242     - the reiver called first unsubscribes itself from that channel
243     in response to a message on that channel
244    
245     Now the second receiver is never called because the list of
246     receivers was modified by Disconnect while the connecter was
247     iterating over it.
248     """
249     messages = []
250     def rec1(*args):
251     try:
252     messages.append("rec1")
253     self.connector.Disconnect(None, SIMPLE, rec1, ())
254     except:
255     self.fail("Exception in rec1")
256     def rec2(*args):
257     try:
258     messages.append("rec2")
259     self.connector.Disconnect(None, SIMPLE, rec2, ())
260     except:
261     self.fail("Exception in rec1")
262    
263     self.connector.Connect(None, SIMPLE, rec1, ())
264     self.connector.Connect(None, SIMPLE, rec2, ())
265    
266     self.connector.Issue(None, SIMPLE)
267    
268     self.assertEquals(messages, [("rec1"), ("rec2")])
269    
270    
271 bh 329 class TestPublisher(unittest.TestCase, DeletionTestMixin):
272    
273     """Tests for the Publisher class"""
274    
275     def setUp(self):
276     DeletionTestMixin.setUp(self)
277    
278     def test_issue_simple(self):
279     """Test Publisher message without parameters"""
280     # Make a publisher and a subscriber and connect the two
281     pub = RealPublisher()
282     rec = Receiver(self)
283     pub.Subscribe(SIMPLE, rec.no_params)
284    
285     # Issue a message and check whether the receiver got it
286     pub.simple_action()
287     self.assertEquals(rec.messages, [("no_params",)])
288     rec.reset()
289    
290     # disconnect and check that the message doesn't get send anymore
291     pub.Unsubscribe(SIMPLE, rec.no_params)
292     pub.simple_action()
293     self.assertEquals(rec.messages, [])
294    
295     # make sure that all references have been deleted
296     del rec
297     self.check_deletetions()
298    
299     def test_issue_param(self):
300     """Test Publisher message with parameters"""
301     pub = RealPublisher()
302     rec = Receiver(self)
303     # Three cases: 1. The parameter supplied by pub.issue_arg, 2.
304     # only the parameter given when connecting, 3. both
305     pub.Subscribe(PARAM, rec.with_params)
306     pub.Subscribe(SIMPLE, rec.with_params, "deliverator")
307     pub.Subscribe(PARAM, rec.with_params, "loglo")
308    
309     pub.param_action()
310     pub.simple_action()
311     self.assertEquals(rec.messages, [("params", 42),
312     ("params", 42, "loglo"),
313     ("params", "deliverator")])
314    
315     # make sure that all references have been deleted
316     pub.Destroy()
317     del rec
318     self.check_deletetions()
319    
320     def test_cyclic_references(self):
321     """Test whether Publisher avoids cyclic references"""
322     pub = RealPublisher()
323     rec = Receiver(self)
324     pub.Subscribe(SIMPLE, rec.no_params, ())
325    
326     # deleting pub and rec should be enough that the last reference
327     # to rec has been dropped because the connector doesn't keep
328     # references to the publishers and SimplePublisher's __del__
329     # method removes all subscriptions
330     del pub
331     del rec
332     self.check_deletetions()
333    
334    
335    
336     if __name__ == "__main__":
337     unittest.main()

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26