diff_tests.py 47.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Copyright (C) 2011  Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import isc.log
import unittest
Jelte Jansen's avatar
Jelte Jansen committed
18
from isc.datasrc import ZoneFinder
19
20
from isc.dns import Name, RRset, RRClass, RRType, RRTTL, Rdata, \
    RRsetCollectionBase
21
22
from isc.xfrin.diff import Diff, NoSuchZone

23
24
25
26
27
28
29
class TestError(Exception):
    """
    Just to have something to be raised during the tests.
    Not used outside.
    """
    pass

30
31
32
33
34
35
36
37
38
39
class DiffTest(unittest.TestCase):
    """
    Tests for the isc.xfrin.diff.Diff class.

    It also plays role of a data source and an updater, so it can manipulate
    some test variables while being called.
    """
    def setUp(self):
        """
        This sets internal variables so we can see nothing was called yet.
40
41

        It also creates some variables used in multiple tests.
42
        """
43
        # Track what was called already
44
        self.__updater_requested = False
45
46
        self.__compact_called = False
        self.__data_operations = []
47
48
        self.__apply_called = False
        self.__commit_called = False
49
        self.__broken_called = False
50
        self.__warn_called = False
51
        self.__should_replace = False
52
53
54
55
56
57
58
        self.__find_called = False
        self.__find_name = None
        self.__find_type = None
        self.__find_options = None
        self.__find_all_called = False
        self.__find_all_name = None
        self.__find_all_options = None
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
        # Some common values
        self.__rrclass = RRClass.IN()
        self.__type = RRType.A()
        self.__ttl = RRTTL(3600)
        # And RRsets
        # Create two valid rrsets
        self.__rrset1 = RRset(Name('a.example.org.'), self.__rrclass,
                              self.__type, self.__ttl)
        self.__rdata = Rdata(self.__type, self.__rrclass, '192.0.2.1')
        self.__rrset1.add_rdata(self.__rdata)
        self.__rrset2 = RRset(Name('b.example.org.'), self.__rrclass,
                              self.__type, self.__ttl)
        self.__rrset2.add_rdata(self.__rdata)
        # And two invalid
        self.__rrset_empty = RRset(Name('empty.example.org.'), self.__rrclass,
                                   self.__type, self.__ttl)
        self.__rrset_multi = RRset(Name('multi.example.org.'), self.__rrclass,
                                   self.__type, self.__ttl)
        self.__rrset_multi.add_rdata(self.__rdata)
        self.__rrset_multi.add_rdata(Rdata(self.__type, self.__rrclass,
                                           '192.0.2.2'))
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
        # Also create a few other (valid) rrsets
        # A SOA record
        self.__rrset_soa = RRset(Name('example.org.'), self.__rrclass,
                                 RRType.SOA(), RRTTL(3600))
        self.__rrset_soa.add_rdata(Rdata(RRType.SOA(), self.__rrclass,
                                         "ns1.example.org. " +
                                         "admin.example.org. " +
                                         "1233 3600 1800 2419200 7200"))
        # A few single-rr rrsets that together would for a multi-rr rrset
        self.__rrset3 = RRset(Name('c.example.org.'), self.__rrclass,
                              RRType.TXT(), self.__ttl)
        self.__rrset3.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "one"))
        self.__rrset4 = RRset(Name('c.example.org.'), self.__rrclass,
                              RRType.TXT(), self.__ttl)
        self.__rrset4.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "two"))
        self.__rrset5 = RRset(Name('c.example.org.'), self.__rrclass,
                              RRType.TXT(), self.__ttl)
        self.__rrset5.add_rdata(Rdata(RRType.TXT(), self.__rrclass, "three"))
        self.__rrset6 = RRset(Name('d.example.org.'), self.__rrclass,
                              RRType.A(), self.__ttl)
        self.__rrset6.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.1"))
        self.__rrset7 = RRset(Name('d.example.org.'), self.__rrclass,
                              RRType.A(), self.__ttl)
        self.__rrset7.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.2"))

106
107
    def __mock_compact(self):
        """
108
        This can be put into the diff to hook into its compact method and see
109
110
111
112
        if it gets called.
        """
        self.__compact_called = True

113
114
115
116
117
118
119
    def __mock_apply(self):
        """
        This can be put into the diff to hook into its apply method and see
        it gets called.
        """
        self.__apply_called = True

120
121
122
123
124
125
126
127
128
    def __broken_operation(self, *args):
        """
        This can be used whenever an operation should fail. It raises TestError.
        It should take whatever amount of parameters needed, so it can be put
        quite anywhere.
        """
        self.__broken_called = True
        raise TestError("Test error")

129
130
131
132
133
134
    def warn(self, *args):
        """
        This is for checking the warn function was called, we replace the logger
        in the tested module.
        """
        self.__warn_called = True
135
136
        # Also log the message so we can check the log format (manually)
        self.orig_logger.warn(*args)
137

138
139
140
141
142
143
144
    def commit(self):
        """
        This is part of pretending to be a zone updater. This notes the commit
        was called.
        """
        self.__commit_called = True

145
146
147
148
149
150
151
    def add_rrset(self, rrset):
        """
        This one is part of pretending to be a zone updater. It writes down
        addition of an rrset was requested.
        """
        self.__data_operations.append(('add', rrset))

152
    def delete_rrset(self, rrset):
153
154
155
156
        """
        This one is part of pretending to be a zone updater. It writes down
        removal of an rrset was requested.
        """
157
        self.__data_operations.append(('delete', rrset))
158

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
159
160
161
162
163
164
165
    def get_class(self):
        """
        This one is part of pretending to be a zone updater. It returns
        the IN class.
        """
        return self.__rrclass

166
    def get_updater(self, zone_name, replace, journaling=False):
167
168
169
170
171
172
173
174
        """
        This one pretends this is the data source client and serves
        getting an updater.

        If zone_name is 'none.example.org.', it returns None, otherwise
        it returns self.
        """
        # The diff should not delete the old data.
175
        self.assertEqual(self.__should_replace, replace)
176
177
        self.__updater_requested = True
        if zone_name == Name('none.example.org.'):
178
            # Pretend this zone doesn't exist
179
            return None
180
181
182
183
184
185
186

        # If journaling is enabled, record the fact; for a special zone
        # pretend that we don't support journaling.
        if journaling:
            if zone_name == Name('nodiff.example.org'):
                raise isc.datasrc.NotImplemented('journaling not supported')
            self.__journaling_enabled = True
187
        else:
188
189
190
            self.__journaling_enabled = False

        return self
191

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
    def find(self, name, rrtype, options=None):
        self.__find_called = True
        self.__find_name = name
        self.__find_type = rrtype
        self.__find_options = options
        # Doesn't really matter what is returned, as long
        # as the test can check that it's passed along
        return "find_return"

    def find_all(self, name, options=None):
        self.__find_all_called = True
        self.__find_all_name = name
        self.__find_all_options = options
        # Doesn't really matter what is returned, as long
        # as the test can check that it's passed along
        return "find_all_return"

209
210
211
212
213
214
215
    def test_create(self):
        """
        This test the case when the diff is successfuly created. It just
        tries it does not throw and gets the updater.
        """
        diff = Diff(self, Name('example.org.'))
        self.assertTrue(self.__updater_requested)
216
        self.assertEqual([], diff.get_buffer())
217
218
        # By default journaling is disabled
        self.assertFalse(self.__journaling_enabled)
219
220
221
222
223
224
225
226
227

    def test_create_nonexist(self):
        """
        Try to create a diff on a zone that doesn't exist. This should
        raise a correct exception.
        """
        self.assertRaises(NoSuchZone, Diff, self, Name('none.example.org.'))
        self.assertTrue(self.__updater_requested)

228
229
230
231
232
233
234
235
    def test_create_withjournal(self):
        Diff(self, Name('example.org'), False, True)
        self.assertTrue(self.__journaling_enabled)

    def test_create_nojournal(self):
        Diff(self, Name('nodiff.example.org'), False, True)
        self.assertFalse(self.__journaling_enabled)

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
236
    def __data_common(self, diff, method, operation):
237
        """
238
        Common part of test for test_add and test_delte.
239
240
        """
        # Try putting there the bad data first
241
242
        self.assertRaises(ValueError, method, self.__rrset_empty)
        self.assertRaises(ValueError, method, self.__rrset_multi)
243
244
        # They were not added
        self.assertEqual([], diff.get_buffer())
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
245
        # Put some proper data into the diff
246
247
        method(self.__rrset1)
        method(self.__rrset2)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
248
        dlist = [(operation, self.__rrset1), (operation, self.__rrset2)]
249
250
251
        self.assertEqual(dlist, diff.get_buffer())
        # Check the data are not destroyed by raising an exception because of
        # bad data
252
        self.assertRaises(ValueError, method, self.__rrset_empty)
253
254
        self.assertEqual(dlist, diff.get_buffer())

255
256
257
258
259
260
261
262
263
    def test_add(self):
        """
        Try to add few items into the diff and see they are stored in there.

        Also try passing an rrset that has differnt amount of RRs than 1.
        """
        diff = Diff(self, Name('example.org.'))
        self.__data_common(diff, diff.add_data, 'add')

264
    def test_delete(self):
265
266
267
268
269
270
271
        """
        Try scheduling removal of few items into the diff and see they are
        stored in there.

        Also try passing an rrset that has different amount of RRs than 1.
        """
        diff = Diff(self, Name('example.org.'))
272
        self.__data_common(diff, diff.delete_data, 'delete')
273

274
275
276
277
278
279
280
281
    def test_apply(self):
        """
        Schedule few additions and check the apply works by passing the
        data into the updater.
        """
        # Prepare the diff
        diff = Diff(self, Name('example.org.'))
        diff.add_data(self.__rrset1)
282
        diff.delete_data(self.__rrset2)
283
        dlist = [('add', self.__rrset1), ('delete', self.__rrset2)]
284
285
286
287
288
289
290
291
292
293
294
295
296
        self.assertEqual(dlist, diff.get_buffer())
        # Do the apply, hook the compact method
        diff.compact = self.__mock_compact
        diff.apply()
        # It should call the compact
        self.assertTrue(self.__compact_called)
        # And pass the data. Our local history of what happened is the same
        # format, so we can check the same way
        self.assertEqual(dlist, self.__data_operations)
        # And the buffer in diff should become empty, as everything
        # got inside.
        self.assertEqual([], diff.get_buffer())

297
298
299
300
301
302
303
304
305
306
    def test_commit(self):
        """
        If we call a commit, it should first apply whatever changes are
        left (we hook into that instead of checking the effect) and then
        the commit on the updater should have been called.

        Then we check it raises value error for whatever operation we try.
        """
        diff = Diff(self, Name('example.org.'))
        diff.add_data(self.__rrset1)
307
        orig_apply = diff.apply
308
309
310
311
312
313
        diff.apply = self.__mock_apply
        diff.commit()
        self.assertTrue(self.__apply_called)
        self.assertTrue(self.__commit_called)
        # The data should be handled by apply which we replaced.
        self.assertEqual([], self.__data_operations)
314
315
316
        # Now check all range of other methods raise ValueError
        self.assertRaises(ValueError, diff.commit)
        self.assertRaises(ValueError, diff.add_data, self.__rrset2)
317
        self.assertRaises(ValueError, diff.delete_data, self.__rrset1)
Jelte Jansen's avatar
Jelte Jansen committed
318
319
320
        self.assertRaises(ValueError, diff.find, Name('foo.example.org.'),
                          RRType.A())
        self.assertRaises(ValueError, diff.find_all, Name('foo.example.org.'))
321
322
323
324
325
        diff.apply = orig_apply
        self.assertRaises(ValueError, diff.apply)
        # This one does not state it should raise, so check it doesn't
        # But it is NOP in this situation anyway
        diff.compact()
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
    def test_autoapply(self):
        """
        Test the apply is called all by itself after 100 tasks are added.
        """
        diff = Diff(self, Name('example.org.'))
        # A method to check the apply is called _after_ the 100th element
        # is added. We don't use it anywhere else, so we define it locally
        # as lambda function
        def check():
            self.assertEqual(100, len(diff.get_buffer()))
            self.__mock_apply()
        orig_apply = diff.apply
        diff.apply = check
        # If we put 99, nothing happens yet
        for i in range(0, 99):
            diff.add_data(self.__rrset1)
        expected = [('add', self.__rrset1)] * 99
        self.assertEqual(expected, diff.get_buffer())
        self.assertFalse(self.__apply_called)
        # Now we push the 100th and it should call the apply method
        # This will _not_ flush the data yet, as we replaced the method.
        # It, however, would in the real life.
        diff.add_data(self.__rrset1)
        # Now the apply method (which is replaced by our check) should
        # have been called. If it wasn't, this is false. If it was, but
        # still with 99 elements, the check would complain
        self.assertTrue(self.__apply_called)
        # Reset the buffer by calling the original apply.
        orig_apply()
        self.assertEqual([], diff.get_buffer())
357
        # Similar with delete
358
359
        self.__apply_called = False
        for i in range(0, 99):
360
            diff.delete_data(self.__rrset2)
361
        expected = [('delete', self.__rrset2)] * 99
362
363
        self.assertEqual(expected, diff.get_buffer())
        self.assertFalse(self.__apply_called)
364
        diff.delete_data(self.__rrset2)
365
366
        self.assertTrue(self.__apply_called)

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
    def test_compact(self):
        """
        Test the compaction works as expected, eg. it compacts only consecutive
        changes of the same operation and on the same domain/type.

        The test case checks that it does merge them, but also puts some
        different operations "in the middle", changes the type and name and
        places the same kind of change further away of each other to see they
        are not merged in that case.
        """
        diff = Diff(self, Name('example.org.'))
        # Check we can do a compact on empty data, it shouldn't break
        diff.compact()
        self.assertEqual([], diff.get_buffer())
        # This data is the way it should look like after the compact
        # ('operation', 'domain.prefix', 'type', ['rdata', 'rdata'])
        # The notes say why the each of consecutive can't be merged
        data = [
            ('add', 'a', 'A', ['192.0.2.1', '192.0.2.2']),
            # Different type.
            ('add', 'a', 'AAAA', ['2001:db8::1', '2001:db8::2']),
            # Different operation
389
            ('delete', 'a', 'AAAA', ['2001:db8::3']),
390
            # Different domain
391
            ('delete', 'b', 'AAAA', ['2001:db8::4']),
392
393
394
395
396
397
398
399
400
401
402
403
404
405
            # This does not get merged with the first, even if logically
            # possible. We just don't do this.
            ('add', 'a', 'A', ['192.0.2.3'])
            ]
        # Now, fill the data into the diff, in a "flat" way, one by one
        for (op, nprefix, rrtype, rdata) in data:
            name = Name(nprefix + '.example.org.')
            rrtype_obj = RRType(rrtype)
            for rdatum in rdata:
                rrset = RRset(name, self.__rrclass, rrtype_obj, self.__ttl)
                rrset.add_rdata(Rdata(rrtype_obj, self.__rrclass, rdatum))
                if op == 'add':
                    diff.add_data(rrset)
                else:
406
                    diff.delete_data(rrset)
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
        # Compact it
        diff.compact()
        # Now check they got compacted. They should be in the same order as
        # pushed inside. So it should be the same as data modulo being in
        # the rrsets and isc.dns objects.
        def check():
            buf = diff.get_buffer()
            self.assertEqual(len(data), len(buf))
            for (expected, received) in zip(data, buf):
                (eop, ename, etype, edata) = expected
                (rop, rrrset) = received
                self.assertEqual(eop, rop)
                ename_obj = Name(ename + '.example.org.')
                self.assertEqual(ename_obj, rrrset.get_name())
                # We check on names to make sure they are printed nicely
                self.assertEqual(etype, str(rrrset.get_type()))
                rdata = rrrset.get_rdata()
                self.assertEqual(len(edata), len(rdata))
                # It should also preserve the order
                for (edatum, rdatum) in zip(edata, rdata):
                    self.assertEqual(edatum, str(rdatum))
        check()
        # Try another compact does nothing, but survives
        diff.compact()
        check()

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
433
434
435
436
437
438
439
440
441
    def test_wrong_class(self):
        """
        Test a wrong class of rrset is rejected.
        """
        diff = Diff(self, Name('example.org.'))
        rrset = RRset(Name('a.example.org.'), RRClass.CH(), RRType.NS(),
                      self.__ttl)
        rrset.add_rdata(Rdata(RRType.NS(), RRClass.CH(), 'ns.example.org.'))
        self.assertRaises(ValueError, diff.add_data, rrset)
442
        self.assertRaises(ValueError, diff.delete_data, rrset)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
443

444
445
446
447
448
449
450
    def __do_raise_test(self):
        """
        Do a raise test. Expects that one of the operations is exchanged for
        broken version.
        """
        diff = Diff(self, Name('example.org.'))
        diff.add_data(self.__rrset1)
451
        diff.delete_data(self.__rrset2)
452
453
454
        self.assertRaises(TestError, diff.commit)
        self.assertTrue(self.__broken_called)
        self.assertRaises(ValueError, diff.add_data, self.__rrset1)
455
        self.assertRaises(ValueError, diff.delete_data, self.__rrset2)
456
457
458
459
460
461
462
463
464
465
466
        self.assertRaises(ValueError, diff.commit)
        self.assertRaises(ValueError, diff.apply)

    def test_raise_add(self):
        """
        Test the exception from add_rrset is propagated and the diff can't be
        used afterwards.
        """
        self.add_rrset = self.__broken_operation
        self.__do_raise_test()

467
    def test_raise_delete(self):
468
        """
469
        Test the exception from delete_rrset is propagated and the diff can't be
470
471
        used afterwards.
        """
472
        self.delete_rrset = self.__broken_operation
473
474
475
476
477
478
479
480
481
482
        self.__do_raise_test()

    def test_raise_commit(self):
        """
        Test the exception from updater's commit gets propagated and it can't be
        used afterwards.
        """
        self.commit = self.__broken_operation
        self.__do_raise_test()

483
484
485
486
487
    def test_ttl(self):
        """
        Test the TTL handling. A warn function should have been called if they
        differ, but that's all, it should not crash or raise.
        """
488
        self.orig_logger = isc.xfrin.diff.logger
489
490
491
492
493
494
495
496
        try:
            isc.xfrin.diff.logger = self
            diff = Diff(self, Name('example.org.'))
            diff.add_data(self.__rrset1)
            rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
                                  self.__type, RRTTL(120))
            rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.2'))
            diff.add_data(rrset2)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
497
498
499
500
            rrset2 = RRset(Name('a.example.org.'), self.__rrclass,
                                  self.__type, RRTTL(6000))
            rrset2.add_rdata(Rdata(self.__type, self.__rrclass, '192.10.2.3'))
            diff.add_data(rrset2)
501
502
503
            # They should get compacted together and complain.
            diff.compact()
            self.assertEqual(1, len(diff.get_buffer()))
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
504
505
506
            # The TTL stays on the first value, no matter if smaller or bigger
            # ones come later.
            self.assertEqual(self.__ttl, diff.get_buffer()[0][1].get_ttl())
507
508
            self.assertTrue(self.__warn_called)
        finally:
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
            isc.xfrin.diff.logger = self.orig_logger

    def test_rrsig_ttl(self):
        '''Similar to the previous test, but for RRSIGs of different covered
        types.

        They shouldn't be compacted.

        '''
        diff = Diff(self, Name('example.org.'))
        rrsig1 = RRset(Name('example.org'), self.__rrclass,
                       RRType.RRSIG(), RRTTL(3600))
        rrsig1.add_rdata(Rdata(RRType.RRSIG(), self.__rrclass,
                               'A 5 3 3600 20000101000000 20000201000000 ' +
                               '0 example.org. FAKEFAKEFAKE'))
        diff.add_data(rrsig1)
        rrsig2 = RRset(Name('example.org'), self.__rrclass,
                       RRType.RRSIG(), RRTTL(1800))
        rrsig2.add_rdata(Rdata(RRType.RRSIG(), self.__rrclass,
                               'AAAA 5 3 3600 20000101000000 20000201000000 ' +
                               '1 example.org. FAKEFAKEFAKE'))
        diff.add_data(rrsig2)
        diff.compact()
        self.assertEqual(2, len(diff.get_buffer()))
533

534
535
    def test_replace(self):
        '''
536
        Test that when we want to replace the whole zone, it is propagated.
537
        '''
538
539
540
541
        self.__should_replace = True
        diff = Diff(self, "example.org.", True)
        self.assertTrue(self.__updater_requested)

542
543
544
545
546
547
548
549
550
551
552
553
    def test_get_buffer(self):
        '''
        Test that the getters raise when used in the wrong mode
        '''
        diff_multi = Diff(self, Name('example.org.'), single_update_mode=False)
        self.assertRaises(ValueError, diff_multi.get_single_update_buffers)
        self.assertEqual([], diff_multi.get_buffer())

        diff_single = Diff(self, Name('example.org.'), single_update_mode=True)
        self.assertRaises(ValueError, diff_single.get_buffer)
        self.assertEqual(([], []), diff_single.get_single_update_buffers())

554
555
556
557
558
559
560
561
562
563
564
    def test_finds_single(self):
        '''
        Test that find_updated() and find_all_updated() can only be used
        in single-update-mode.
        '''
        diff_multi = Diff(self, Name('example.org.'), single_update_mode=False)
        self.assertRaises(ValueError, diff_multi.find_updated,
                          Name('example.org.'), RRType.A())
        self.assertRaises(ValueError, diff_multi.find_all_updated,
                          Name('example.org.'))

565
566
567
568
569
570
571
572
573
    def test_single_update_mode(self):
        '''
        Test single-update mode. In this mode, updates and deletes can
        be done in any order, but there may only be one changeset.
        For both updates and deletes, exactly one SOA rr must be given,
        and it must be the first change.
        '''

        # full rrset for A (to check compact())
574
575
576
577
578
579
580
581
582
        txt = RRset(Name('c.example.org.'), self.__rrclass, RRType.TXT(),
                    RRTTL(3600))
        txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "one"))
        txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "two"))
        txt.add_rdata(Rdata(txt.get_type(), txt.get_class(), "three"))
        a = RRset(Name('d.example.org.'), self.__rrclass, RRType.A(),
                  RRTTL(3600))
        a.add_rdata(Rdata(a.get_type(), a.get_class(), "192.0.2.1"))
        a.add_rdata(Rdata(a.get_type(), a.get_class(), "192.0.2.2"))
583
584
585
586
587
588

        diff = Diff(self, Name('example.org.'), single_update_mode=True)

        # adding a first should fail
        self.assertRaises(ValueError, diff.add_data, a)
        # But soa should work
589
        diff.add_data(self.__rrset_soa)
590
        # And then A should as well
591
592
593
        diff.add_data(self.__rrset3)
        diff.add_data(self.__rrset4)
        diff.add_data(self.__rrset5)
594
        # But another SOA should fail again
595
        self.assertRaises(ValueError, diff.add_data, self.__rrset_soa)
596
597

        # Same for delete
598
599
600
601
602
        self.assertRaises(ValueError, diff.delete_data, self.__rrset6)
        diff.delete_data(self.__rrset_soa)
        diff.delete_data(self.__rrset6)
        diff.delete_data(self.__rrset7)
        self.assertRaises(ValueError, diff.delete_data, self.__rrset_soa)
603
604
605
606

        # Not compacted yet, so the buffers should be as we
        # filled them
        (delbuf, addbuf) = diff.get_single_update_buffers()
607
608
609
610
611
612
613
        self.assertEqual([('delete', self.__rrset_soa),
                          ('delete', self.__rrset6),
                          ('delete', self.__rrset7)], delbuf)
        self.assertEqual([('add', self.__rrset_soa),
                          ('add', self.__rrset3),
                          ('add', self.__rrset4),
                          ('add', self.__rrset5)], addbuf)
614
615
616
617
618
619
620
621

        # Compact should compact the A records in both buffers
        diff.compact()
        (delbuf, addbuf) = diff.get_single_update_buffers()
        # need rrset equality again :/
        self.assertEqual(2, len(delbuf))
        self.assertEqual(2, len(delbuf[0]))
        self.assertEqual('delete', delbuf[0][0])
622
        self.assertEqual(self.__rrset_soa.to_text(), delbuf[0][1].to_text())
623
624
        self.assertEqual(2, len(delbuf[1]))
        self.assertEqual('delete', delbuf[1][0])
625
        self.assertEqual(a.to_text(), delbuf[1][1].to_text())
626
627
628
629

        self.assertEqual(2, len(addbuf))
        self.assertEqual(2, len(addbuf[0]))
        self.assertEqual('add', addbuf[0][0])
630
        self.assertEqual(self.__rrset_soa.to_text(), addbuf[0][1].to_text())
631
632
        self.assertEqual(2, len(addbuf[1]))
        self.assertEqual('add', addbuf[1][0])
633
        self.assertEqual(txt.to_text(), addbuf[1][1].to_text())
634
635
636
637
638
639
640
641
642
643
644
645

        # Apply should reset the buffers
        diff.apply()
        (delbuf, addbuf) = diff.get_single_update_buffers()
        self.assertEqual([], delbuf)
        self.assertEqual([], addbuf)

        # Now the change has been applied, and the buffers are cleared,
        # Adding non-SOA records should fail again.
        self.assertRaises(ValueError, diff.add_data, a)
        self.assertRaises(ValueError, diff.delete_data, a)

646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
    def test_add_delete_same(self):
        '''
        Test that if a record is added, then deleted, it is not added to
        both buffers, but remove from the addition, and vice versa
        '''
        diff = Diff(self, Name('example.org.'), single_update_mode=True)
        # Need SOA records first
        diff.delete_data(self.__rrset_soa)
        diff.add_data(self.__rrset_soa)

        deletions, additions = diff.get_single_update_buffers()
        self.assertEqual(1, len(deletions))
        self.assertEqual(1, len(additions))

        diff.add_data(self.__rrset1)
        deletions, additions = diff.get_single_update_buffers()
        self.assertEqual(1, len(deletions))
        self.assertEqual(2, len(additions))

        diff.delete_data(self.__rrset1)
        deletions, additions = diff.get_single_update_buffers()
        self.assertEqual(1, len(deletions))
        self.assertEqual(1, len(additions))

        diff.delete_data(self.__rrset2)
        deletions, additions = diff.get_single_update_buffers()
        self.assertEqual(2, len(deletions))
        self.assertEqual(1, len(additions))

        diff.add_data(self.__rrset2)
        deletions, additions = diff.get_single_update_buffers()
        self.assertEqual(1, len(deletions))
        self.assertEqual(1, len(additions))

680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
    def test_find(self):
        diff = Diff(self, Name('example.org.'))
        name = Name('www.example.org.')
        rrtype = RRType.A()

        self.assertFalse(self.__find_called)
        self.assertEqual(None, self.__find_name)
        self.assertEqual(None, self.__find_type)
        self.assertEqual(None, self.__find_options)

        self.assertEqual("find_return", diff.find(name, rrtype))

        self.assertTrue(self.__find_called)
        self.assertEqual(name, self.__find_name)
        self.assertEqual(rrtype, self.__find_type)
Jelte Jansen's avatar
Jelte Jansen committed
695
        self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
696
697
698
699
700
701
                         self.__find_options)

    def test_find_options(self):
        diff = Diff(self, Name('example.org.'))
        name = Name('foo.example.org.')
        rrtype = RRType.TXT()
Jelte Jansen's avatar
Jelte Jansen committed
702
        options = ZoneFinder.NO_WILDCARD
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722

        self.assertEqual("find_return", diff.find(name, rrtype, options))

        self.assertTrue(self.__find_called)
        self.assertEqual(name, self.__find_name)
        self.assertEqual(rrtype, self.__find_type)
        self.assertEqual(options, self.__find_options)

    def test_find_all(self):
        diff = Diff(self, Name('example.org.'))
        name = Name('www.example.org.')

        self.assertFalse(self.__find_all_called)
        self.assertEqual(None, self.__find_all_name)
        self.assertEqual(None, self.__find_all_options)

        self.assertEqual("find_all_return", diff.find_all(name))

        self.assertTrue(self.__find_all_called)
        self.assertEqual(name, self.__find_all_name)
Jelte Jansen's avatar
Jelte Jansen committed
723
        self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
724
725
726
727
728
                         self.__find_all_options)

    def test_find_all_options(self):
        diff = Diff(self, Name('example.org.'))
        name = Name('www.example.org.')
Jelte Jansen's avatar
Jelte Jansen committed
729
        options = isc.datasrc.ZoneFinder.NO_WILDCARD
730
731
732
733
734
735
736
737
738
739

        self.assertFalse(self.__find_all_called)
        self.assertEqual(None, self.__find_all_name)
        self.assertEqual(None, self.__find_all_options)

        self.assertEqual("find_all_return", diff.find_all(name, options))

        self.assertTrue(self.__find_all_called)
        self.assertEqual(name, self.__find_all_name)
        self.assertEqual(options, self.__find_all_options)
740

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
    def __common_remove_rr_from_buffer(self, diff, add_method, remove_method,
                                       op_str, buf_nr):
        add_method(self.__rrset_soa)
        add_method(self.__rrset2)
        add_method(self.__rrset3)
        add_method(self.__rrset4)

        # sanity check
        buf = diff.get_single_update_buffers()[buf_nr]
        expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
                                                   self.__rrset2,
                                                   self.__rrset3,
                                                   self.__rrset4 ] ]
        result = [ (op, str(rr)) for (op, rr) in buf ]
        self.assertEqual(expected, result)

        # remove one
        self.assertTrue(remove_method(self.__rrset2))
        buf = diff.get_single_update_buffers()[buf_nr]
        expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
                                                   self.__rrset3,
                                                   self.__rrset4 ] ]
        result = [ (op, str(rr)) for (op, rr) in buf ]
        self.assertEqual(expected, result)

        # SOA should not be removed
        self.assertFalse(remove_method(self.__rrset_soa))
        buf = diff.get_single_update_buffers()[buf_nr]
        expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
                                                   self.__rrset3,
                                                   self.__rrset4 ] ]
        result = [ (op, str(rr)) for (op, rr) in buf ]
        self.assertEqual(expected, result)

        # remove another
        self.assertTrue(remove_method(self.__rrset4))
        buf = diff.get_single_update_buffers()[buf_nr]
        expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
                                                   self.__rrset3 ] ]
        result = [ (op, str(rr)) for (op, rr) in buf ]
        self.assertEqual(expected, result)

        # remove nonexistent should return False
        self.assertFalse(remove_method(self.__rrset4))
        buf = diff.get_single_update_buffers()[buf_nr]
        expected = [ (op_str, str(rr)) for rr in [ self.__rrset_soa,
                                                   self.__rrset3 ] ]
        result = [ (op, str(rr)) for (op, rr) in buf ]
        self.assertEqual(expected, result)

    def test_remove_rr_from_additions(self):
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        self.__common_remove_rr_from_buffer(diff, diff.add_data,
                                               diff._remove_rr_from_additions,
                                               'add', 1)

    def test_remove_rr_from_deletions(self):
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        self.__common_remove_rr_from_buffer(diff, diff.delete_data,
                                            diff._remove_rr_from_deletions,
                                            'delete', 0)

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
    def __create_find(self, result, rrset, flags):
        '''
        Overwrites the local find() method with a method that returns
        the tuple (result, rrset, flags)
        '''
        def new_find(name, rrtype, fflags):
            return (result, rrset, flags)
        self.find = new_find

    def __create_find_all(self, result, rrsets, flags):
        '''
        Overwrites the local find() method with a method that returns
        the tuple (result, rrsets, flags)
        '''
        def new_find_all(name, fflags):
            return (result, rrsets, flags)
        self.find_all = new_find_all

821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
    def __check_find_call(self, method, query_rrset, expected_rcode,
                          expected_rdatas=None):
        '''
        Helper for find tests; calls the given method with the name and
        type of the given rrset. Checks for the given rcode.
        If expected_rdatas is not none, the result name, and type are
        checked to match the given rrset ones, and the rdatas are checked
        to be equal.
        The given method must have the same arguments and return type
        as find()
        '''
        result, rrset, _ = method(query_rrset.get_name(),
                                  query_rrset.get_type())
        self.assertEqual(expected_rcode, result)
        if expected_rdatas is not None:
            self.assertEqual(query_rrset.get_name(), rrset.get_name())
            self.assertEqual(query_rrset.get_type(), rrset.get_type())
            if expected_rdatas is not None:
                self.assertEqual(expected_rdatas, rrset.get_rdata())
        else:
            self.assertEqual(None, rrset)

    def __check_find_all_call(self, method, query_rrset, expected_rcode,
                              expected_rrs=[]):
        '''
        Helper for find tests; calls the given method with the name and
        type of the given rrset. Checks for the given rcode.
        If expected_rdatas is not none, the result name, and type are
        checked to match the given rrset ones, and the rdatas are checked
        to be equal.
        The given method must have the same arguments and return type
        as find()
        '''
        result, rrsets, _ = method(query_rrset.get_name())
        self.assertEqual(expected_rcode, result)
        # We have no real equality function for rrsets, but since
        # the rrsets in question are themselves returns, pointer equality
        # works as well
        self.assertEqual(expected_rrs, rrsets)

861
862
863
864
865
866
867
868
869
870
871
872
    def test_find_updated_existing_data(self):
        '''
        Tests whether existent data is updated with the additions and
        deletions from the Diff
        '''
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        diff.add_data(self.__rrset_soa)
        diff.delete_data(self.__rrset_soa)

        # override the actual find method
        self.__create_find(ZoneFinder.SUCCESS, self.__rrset3, 0)

873
874
875
876
877
878
879
        # sanity check
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())

        # check that normal find also returns the original data
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
880
881
882

        # Adding another should have it returned in the find_updated
        diff.add_data(self.__rrset4)
883
884
885
886
887
888
889
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata() +
                               self.__rrset4.get_rdata())

        # check that normal find still returns the original data
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
890
891
892

        # Adding a different type should have no effect
        diff.add_data(self.__rrset2)
893
894
895
896
897
898
899
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata() +
                               self.__rrset4.get_rdata())

        # check that normal find still returns the original data
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
900
901
902

        # Deleting 3 now should result in only 4 being updated
        diff.delete_data(self.__rrset3)
903
904
905
906
907
908
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset4.get_rdata())

        # check that normal find still returns the original data
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
909
910
911

        # Deleting 4 now should result in empty rrset
        diff.delete_data(self.__rrset4)
912
913
914
915
916
917
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.NXRRSET)

        # check that normal find still returns the original data
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
918
919
920
921
922
923
924
925
926
927
928
929
930
931

    def test_find_updated_nonexistent_data(self):
        '''
        Test whether added data for a query that would originally result
        in NXDOMAIN works
        '''
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        diff.add_data(self.__rrset_soa)
        diff.delete_data(self.__rrset_soa)

        # override the actual find method
        self.__create_find(ZoneFinder.NXDOMAIN, None, 0)

        # Sanity check
932
933
934
935
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
936
937
938

        # Add data and see it is returned
        diff.add_data(self.__rrset3)
939
940
941
942
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
943
944
945

        # Add unrelated data, result should be the same
        diff.add_data(self.__rrset2)
946
947
948
949
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.SUCCESS, self.__rrset3.get_rdata())
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
950
951
952
953
954

        # Remove, result should now be NXDOMAIN again
        diff.delete_data(self.__rrset3)
        result, rrset, _ = diff.find_updated(self.__rrset3.get_name(),
                                             self.__rrset3.get_type())
955
956
957
958
        self.__check_find_call(diff.find_updated, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
        self.__check_find_call(diff.find, self.__rrset3,
                               ZoneFinder.NXDOMAIN)
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977

    def test_find_updated_other(self):
        '''
        Test that any other ZoneFinder.result code is directly
        passed on.
        '''
        diff = Diff(self, Name('example.org'), single_update_mode=True)

        # Add and delete some data to make sure it's not used
        diff.add_data(self.__rrset_soa)
        diff.add_data(self.__rrset3)
        diff.delete_data(self.__rrset_soa)
        diff.delete_data(self.__rrset2)

        for rcode in [ ZoneFinder.DELEGATION,
                       ZoneFinder.CNAME,
                       ZoneFinder.DNAME ]:
            # override the actual find method
            self.__create_find(rcode, None, 0)
978
979
            self.__check_find_call(diff.find, self.__rrset3, rcode)
            self.__check_find_call(diff.find_updated, self.__rrset3, rcode)
980
981
982
983
984
985
986
987
988
989
990
991
992
993

    def test_find_all_existing_data(self):
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        diff.add_data(self.__rrset_soa)
        diff.delete_data(self.__rrset_soa)

        # override the actual find method
        self.__create_find_all(ZoneFinder.SUCCESS, [ self.__rrset3 ], 0)

        # Sanity check
        result, rrsets, _ = diff.find_all_updated(self.__rrset3.get_name())
        self.assertEqual(ZoneFinder.SUCCESS, result)
        self.assertEqual([self.__rrset3], rrsets)

994
995
996
997
998
        self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
                                   ZoneFinder.SUCCESS, [self.__rrset3])
        self.__check_find_all_call(diff.find_all, self.__rrset3,
                                   ZoneFinder.SUCCESS, [self.__rrset3])

999
1000
1001
1002
1003
1004
        # Add a second rr with different type at same name
        add_rrset = RRset(self.__rrset3.get_name(), self.__rrclass,
                          RRType.A(), self.__ttl)
        add_rrset.add_rdata(Rdata(RRType.A(), self.__rrclass, "192.0.2.2"))
        diff.add_data(add_rrset)

1005
1006
1007
1008
1009
        self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
                                   ZoneFinder.SUCCESS,
                                   [self.__rrset3, add_rrset])
        self.__check_find_all_call(diff.find_all, self.__rrset3,
                                   ZoneFinder.SUCCESS, [self.__rrset3])
1010
1011
1012

        # Remove original one
        diff.delete_data(self.__rrset3)
1013
1014
1015
1016
        self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
                                   ZoneFinder.SUCCESS, [add_rrset])
        self.__check_find_all_call(diff.find_all, self.__rrset3,
                                   ZoneFinder.SUCCESS, [self.__rrset3])
1017
1018
1019
1020
1021
1022
1023

        # And remove new one, result should then become NXDOMAIN
        diff.delete_data(add_rrset)
        result, rrsets, _ = diff.find_all_updated(self.__rrset3.get_name())

        self.assertEqual(ZoneFinder.NXDOMAIN, result)
        self.assertEqual([ ], rrsets)
1024
1025
1026
1027
        self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
                                   ZoneFinder.NXDOMAIN)
        self.__check_find_all_call(diff.find_all, self.__rrset3,
                                   ZoneFinder.SUCCESS, [self.__rrset3])
1028
1029
1030
1031
1032

    def test_find_all_nonexistent_data(self):
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        diff.add_data(self.__rrset_soa)
        diff.delete_data(self.__rrset_soa)
1033

1034
1035
1036
        self.__create_find_all(ZoneFinder.NXDOMAIN, [], 0)

        # Sanity check
1037
1038
1039
1040
        self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
        self.__check_find_all_call(diff.find_all, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
1041
1042
1043

        # Adding data should change the result
        diff.add_data(self.__rrset2)
1044
1045
1046
1047
        self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
                                   ZoneFinder.SUCCESS, [ self.__rrset2 ])
        self.__check_find_all_call(diff.find_all, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
1048
1049
1050

        # Adding data at other name should not
        diff.add_data(self.__rrset3)
1051
1052
1053
1054
        self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
                                   ZoneFinder.SUCCESS, [ self.__rrset2 ])
        self.__check_find_all_call(diff.find_all, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
1055
1056
1057

        # Deleting it should revert to original
        diff.delete_data(self.__rrset2)
1058
1059
1060
1061
        self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
        self.__check_find_all_call(diff.find_all, self.__rrset2,
                                   ZoneFinder.NXDOMAIN)
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080

    def test_find_all_other_results(self):
        '''
        Any result code other than SUCCESS and NXDOMAIN should cause
        the results to be passed on directly
        '''
        diff = Diff(self, Name('example.org'), single_update_mode=True)

        # Add and delete some data to make sure it's not used
        diff.add_data(self.__rrset_soa)
        diff.add_data(self.__rrset3)
        diff.delete_data(self.__rrset_soa)
        diff.delete_data(self.__rrset2)

        for rcode in [ ZoneFinder.NXRRSET,
                       ZoneFinder.DELEGATION,
                       ZoneFinder.CNAME,
                       ZoneFinder.DNAME ]:
            # override the actual find method
1081
1082
1083
1084
1085
1086
1087
1088
1089
            self.__create_find_all(rcode, [], 0)
            self.__check_find_all_call(diff.find_all_updated, self.__rrset2,
                                       rcode)
            self.__check_find_all_call(diff.find_all_updated, self.__rrset3,
                                       rcode)
            self.__check_find_all_call(diff.find_all, self.__rrset2,
                                       rcode)
            self.__check_find_all_call(diff.find_all, self.__rrset3,
                                       rcode)
1090

1091
    class Collection(isc.dns.RRsetCollectionBase):
1092
        '''
1093
1094
1095
        Our own mock RRsetCollection. We only pass it through, but we
        still define an (mostly empty) find method to satisfy the
        expectations.
1096
        '''
1097
1098
1099
1100
        def __init__(self):
            '''
            Empty init. The base class's __init__ can't be called,
            so we need to provide our own to shadow it -- and make sure
1101
            not to call the super().__init__().
1102
1103
1104
1105
1106
1107
1108
1109
1110
            '''
            pass

        def find(self, name, rrclass, rrtype):
            '''
            Empty find method. Returns None to each query (pretends
            the collection is empty. Present mostly for completeness.
            '''
            return None
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126

    def get_rrset_collection(self):
        '''
        Part of pretending to be the zone updater. This returns the rrset
        collection (a mock one, unuseable) for the updater.
        '''
        return self.Collection()

    def test_get_rrset_collection(self):
        '''
        Test the diff can return corresponding rrset collection. Test
        it applies the data first.
        '''
        diff = Diff(self, Name('example.org'), single_update_mode=True)
        diff.add_data(self.__rrset_soa)
        collection = diff.get_rrset_collection()
1127
        # Check it is applied
1128
1129
1130
1131
        self.assertEqual(1, len(self.__data_operations))
        self.assertEqual('add', self.__data_operations[0][0])
        # Check the returned one is actually RRsetCollection
        self.assertTrue(collection, self.Collection)
1132
1133
1134
1135
        # The collection is just the mock from above, so this doesn't do much
        # testing, but we check that the mock got through and didn't get hurt.
        self.assertIsNone(collection.find(Name('example.org'), RRClass.IN(),
                                          RRType.SOA()))
1136

1137
1138
if __name__ == "__main__":
    isc.log.init("bind10")
1139
    isc.log.resetUnitTestRootLogger()
1140
    unittest.main()