]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
This patch adds the ability to parse '$GENERATE' statements. See tests for examples. 16/head
authoruberj <uberj@onid.orst.edu>
Wed, 23 May 2012 17:44:04 +0000 (10:44 -0700)
committeruberj <uberj@onid.orst.edu>
Fri, 21 Sep 2012 16:12:03 +0000 (09:12 -0700)
.gitignore
dns/grange.py [new file with mode: 0644]
dns/zone.py
tests/generate.py [new file with mode: 0644]
tests/grange.py [new file with mode: 0644]

index 5592c971b018ecdfd08b3908e8facafe07946aa5..ae6319f8d37bc3cd1343f5b6a5d653936fb4f28c 100644 (file)
@@ -5,3 +5,4 @@ html
 html.zip
 html.tar.gz
 tests/*.out
+*.pyc
diff --git a/dns/grange.py b/dns/grange.py
new file mode 100644 (file)
index 0000000..2d4799d
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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.
+
+"""DNS GENERATE range conversion."""
+
+import dns
+
+def from_text(text):
+    """Convert the text form of a range in a GENERATE statement to an
+    integer.
+
+    @param text: the textual range
+    @type text: string
+    @return range: The start, stop and step values.
+    @type range: tuple
+    """
+    # TODO, figure out the bounds on start, stop and step.
+
+    import pdb
+    step = 1
+    cur = ''
+    state = 0
+    # state   0 1 2 3 4
+    #         x - y / z
+    for c in text:
+        if c == '-' and state == 0:
+            start = int(cur)
+            cur = ''
+            state = 2
+        elif c == '/':
+            stop = int(cur)
+            cur = ''
+            state = 4
+        elif c.isdigit():
+            cur += c
+        else:
+            raise dns.exception.SyntaxError("Could not parse %s" % (c))
+
+    if state in (1, 3):
+        raise dns.exception.SyntaxError
+
+    if state == 2:
+        stop = int(cur)
+
+    if state == 4:
+        step = int(cur)
+
+    assert step >= 1
+    assert start >= 0
+    assert start <= stop
+    # TODO, can start == stop?
+
+    return (start, stop, step)
index 404f81819fdd28ae9f59777465c37a163c9436b1..9efcf437d748fa9d3f5d460925b46d384178726d 100644 (file)
@@ -18,6 +18,7 @@
 from __future__ import generators
 
 import sys
+import re
 
 import dns.exception
 import dns.name
@@ -28,6 +29,8 @@ import dns.rdata
 import dns.rrset
 import dns.tokenizer
 import dns.ttl
+import dns.grange
+
 
 class BadZone(dns.exception.DNSException):
     """The zone is malformed."""
@@ -641,6 +644,166 @@ class _MasterReader(object):
         rds = n.find_rdataset(rdclass, rdtype, covers, True)
         rds.add(rd, ttl)
 
+    def _parse_modify(self, side):
+        # Here we catch everything in '{' '}' in a group so we can replace it
+        # with ''.
+        is_generate1 = re.compile("^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
+        is_generate2 = re.compile("^.*\$({(\+|-?)(\d+)}).*$")
+        is_generate3 = re.compile("^.*\$({(\+|-?)(\d+),(\d+)}).*$")
+        # Sometimes there are modifiers in the hostname. These come after
+        # the dollar sign. They are in the form: ${offset[,width[,base]]}.
+        # Make names
+        g1 = is_generate1.match(side)
+        if g1:
+            mod, sign, offset, width, base = g1.groups()
+            if sign == '':
+                sign = '+'
+        g2 = is_generate2.match(side)
+        if g2:
+            mod, sign, offset = g2.groups()
+            if sign == '':
+                sign = '+'
+            width = 0
+            base = 'd'
+        g3 = is_generate3.match(side)
+        if g3:
+            mod, sign, offset, width = g1.groups()
+            if sign == '':
+                sign = '+'
+            width = g1.groups()[2]
+            base = 'd'
+
+        if not (g1 or g2 or g3):
+            mod = ''
+            sign = '+'
+            offset = 0
+            width = 0
+            base = 'd'
+
+        if base != 'd':
+            raise NotImplemented
+
+        return mod, sign, offset, width, base
+
+    def _generate_line(self):
+        # range lhs [ttl] [class] type rhs [ comment ]
+        """Process one line containing the GENERATE statement from a DNS
+        master file."""
+        if self.current_origin is None:
+            raise UnknownOrigin
+
+        token = self.tok.get()
+        # Range (required)
+        try:
+            start, stop, step = dns.grange.from_text(token.value)
+            token = self.tok.get()
+            if not token.is_identifier():
+                raise dns.exception.SyntaxError
+        except:
+            raise dns.exception.SyntaxError
+
+        # lhs (required)
+        try:
+            lhs = token.value
+            token = self.tok.get()
+            if not token.is_identifier():
+                raise dns.exception.SyntaxError
+        except:
+            raise dns.exception.SyntaxError
+
+        # TTL
+        try:
+            ttl = dns.ttl.from_text(token.value)
+            token = self.tok.get()
+            if not token.is_identifier():
+                raise dns.exception.SyntaxError
+        except dns.ttl.BadTTL:
+            ttl = self.ttl
+        # Class
+        try:
+            rdclass = dns.rdataclass.from_text(token.value)
+            token = self.tok.get()
+            if not token.is_identifier():
+                raise dns.exception.SyntaxError
+        except dns.exception.SyntaxError:
+            raise dns.exception.SyntaxError
+        except:
+            rdclass = self.zone.rdclass
+        if rdclass != self.zone.rdclass:
+            raise dns.exception.SyntaxError("RR class is not zone's class")
+        # Type
+        try:
+            rdtype = dns.rdatatype.from_text(token.value)
+            token = self.tok.get()
+            if not token.is_identifier():
+                raise dns.exception.SyntaxError
+        except:
+            raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
+                    token.value)
+
+        # lhs (required)
+        try:
+            rhs = token.value
+        except:
+            raise dns.exception.SyntaxError
+
+
+        lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs)
+        rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs)
+        for i in range(start, stop + 1, step):
+            # +1 because bind is inclusive and python is exclusive
+
+            if lsign == '+':
+                lindex = i + int(loffset)
+            elif lsign == '-':
+                lindex = i - int(loffset)
+
+            if rsign == '-':
+                rindex = i - int(roffset)
+            elif rsign == '+':
+                rindex = i + int(roffset)
+
+            lzfindex = str(lindex).zfill(int(lwidth))
+            rzfindex = str(rindex).zfill(int(rwidth))
+
+
+            name = lhs.replace('$%s' % (lmod), lzfindex)
+            rdata = rhs.replace('$%s' % (rmod), rzfindex)
+
+            self.last_name = dns.name.from_text(name, self.current_origin)
+            name = self.last_name
+            if not name.is_subdomain(self.zone.origin):
+                self._eat_line()
+                return
+            if self.relativize:
+                name = name.relativize(self.zone.origin)
+
+            n = self.zone.nodes.get(name)
+            if n is None:
+                n = self.zone.node_factory()
+                self.zone.nodes[name] = n
+            try:
+                rd = dns.rdata.from_text(rdclass, rdtype, rdata,
+                                         self.current_origin, False)
+            except dns.exception.SyntaxError:
+                # Catch and reraise.
+                (ty, va) = sys.exc_info()[:2]
+                raise va
+            except:
+                # All exceptions that occur in the processing of rdata
+                # are treated as syntax errors.  This is not strictly
+                # correct, but it is correct almost all of the time.
+                # We convert them to syntax errors so that we can emit
+                # helpful filename:line info.
+                (ty, va) = sys.exc_info()[:2]
+                raise dns.exception.SyntaxError("caught exception %s: %s" %
+                        (str(ty), str(va)))
+
+            rd.choose_relativity(self.zone.origin, self.relativize)
+            covers = rd.covers()
+            rds = n.find_rdataset(rdclass, rdtype, covers, True)
+            rds.add(rd, ttl)
+
     def read(self):
         """Read a DNS master file and build a zone object.
 
@@ -701,6 +864,8 @@ class _MasterReader(object):
                         self.tok = dns.tokenizer.Tokenizer(self.current_file,
                                                            filename)
                         self.current_origin = new_origin
+                    elif u == '$GENERATE':
+                        self._generate_line()
                     else:
                         raise dns.exception.SyntaxError("Unknown master file directive '" + u + "'")
                     continue
diff --git a/tests/generate.py b/tests/generate.py
new file mode 100644 (file)
index 0000000..43c5f9a
--- /dev/null
@@ -0,0 +1,499 @@
+# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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 sys
+sys.path.insert(0, '../')  # Force the local project to be *the* dns
+
+import cStringIO
+import filecmp
+import os
+import unittest
+
+import dns.exception
+import dns.rdata
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+import dns.zone
+
+import pprint
+
+pp = pprint.PrettyPrinter(indent=2)
+
+import pdb
+example_text = """$TTL 1h
+$ORIGIN 0.0.192.IN-ADDR.ARPA.
+$GENERATE 1-2 0 CNAME SERVER$.EXAMPLE.
+"""
+
+example_text1 = """$TTL 1h
+$ORIGIN 0.0.192.IN-ADDR.ARPA.
+$GENERATE 1-10 fooo$ CNAME $.0
+"""
+
+example_text2 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 3-5 foo$ A 10.0.0.$
+"""
+
+example_text3 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 4-8/2 foo$ A 10.0.0.$
+"""
+
+example_text4 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-13 wp-db${-10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR.
+"""
+
+example_text5 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-13 wp-db${10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR.
+"""
+
+example_text6 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-13 wp-db${+10,2,d}.services.mozilla.com 0 CNAME SERVER.FOOBAR.
+"""
+
+example_text7 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-13     sync${-10}.db   IN  A   10.10.16.0
+"""
+
+example_text8 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-12 wp-db${-10,2,d} IN A 10.10.16.0
+"""
+
+example_text9 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 11-12 wp-db${-10,2,d} IN A 10.10.16.0
+$GENERATE 11-13     sync${-10}.db   IN  A   10.10.16.0
+"""
+example_text10 = """$TTL 1h
+@ 3600 IN SOA foo bar 1 2 3 4 5
+@ 3600 IN NS ns1
+@ 3600 IN NS ns2
+bar.foo 300 IN MX 0 blaz.foo
+ns1 3600 IN A 10.0.0.1
+ns2 3600 IN A 10.0.0.2
+$GENERATE 27-28 $.2 PTR zlb${-26}.oob
+"""
+
+
+class GenerateTestCase(unittest.TestCase):
+
+    def testFromText(self):
+        def bad():
+            z = dns.zone.from_text(example_text, 'example.', relativize=True)
+        self.failUnlessRaises(dns.zone.NoSOA, bad)
+
+    def testFromText1(self):
+        def bad():
+            z = dns.zone.from_text(example_text1, 'example.', relativize=True)
+        self.failUnlessRaises(dns.zone.NoSOA, bad)
+
+    def testIterateAllRdatas2(self):
+        z = dns.zone.from_text(example_text2, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+                (dns.name.from_text('ns1', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+                (dns.name.from_text('ns2', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+                (dns.name.from_text('foo3', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.3')),
+                (dns.name.from_text('foo4', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.4')),
+                (dns.name.from_text('foo5', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.5'))]
+
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testIterateAllRdatas3(self):
+        z = dns.zone.from_text(example_text3, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+                (dns.name.from_text('foo4', None), 3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.4')),
+                (dns.name.from_text('foo6', None), 3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.6')),
+                (dns.name.from_text('foo8', None), 3600,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.8'))]
+        exl.sort()
+        self.failUnless(l == exl)
+    def testGenerate1(self):
+        z = dns.zone.from_text(example_text4, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+
+                (dns.name.from_text('wp-db01.services.mozilla.com', None),
+                    0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db02.services.mozilla.com', None),
+                    0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db03.services.mozilla.com', None),
+                    0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.'))]
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testGenerate2(self):
+        z = dns.zone.from_text(example_text5, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+
+                (dns.name.from_text('wp-db21.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db22.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db23.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.'))]
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testGenerate3(self):
+        z = dns.zone.from_text(example_text6, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+                (dns.name.from_text('wp-db21.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db22.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.')),
+
+                (dns.name.from_text('wp-db23.services.mozilla.com', None), 0L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
+                                    'SERVER.FOOBAR.'))]
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testGenerate4(self):
+        z = dns.zone.from_text(example_text7, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+
+                (dns.name.from_text('sync1.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+
+                (dns.name.from_text('sync2.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+
+                (dns.name.from_text('sync3.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0'))]
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testGenerate6(self):
+        z = dns.zone.from_text(example_text9, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+
+                (dns.name.from_text('wp-db01', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+                (dns.name.from_text('wp-db02', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+
+                (dns.name.from_text('sync1.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+
+                (dns.name.from_text('sync2.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0')),
+
+                (dns.name.from_text('sync3.db', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.10.16.0'))]
+        exl.sort()
+        self.failUnless(l == exl)
+
+    def testGenerate7(self):
+        z = dns.zone.from_text(example_text10, 'example.', relativize=True)
+        l = list(z.iterate_rdatas())
+        l.sort()
+        exl = [(dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns1')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.NS,
+                                    'ns2')),
+               (dns.name.from_text('@', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SOA,
+                                    'foo bar 1 2 3 4 5')),
+               (dns.name.from_text('bar.foo', None),
+                300L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
+                                    '0 blaz.foo')),
+               (dns.name.from_text('ns1', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.1')),
+               (dns.name.from_text('ns2', None),
+                3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
+                                    '10.0.0.2')),
+
+                (dns.name.from_text('27.2', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR,
+                                    'zlb1.oob')),
+
+                (dns.name.from_text('28.2', None), 3600L,
+                dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.PTR,
+                                    'zlb2.oob'))]
+
+        exl.sort()
+        self.failUnless(l == exl)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/grange.py b/tests/grange.py
new file mode 100644 (file)
index 0000000..cbfc896
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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 sys
+sys.path.insert(0, '../')
+
+import cStringIO
+import filecmp
+import os
+import unittest
+
+import dns
+import dns.exception
+import dns.grange
+
+import pdb
+
+
+
+class GRangeTestCase(unittest.TestCase):
+
+    def testFromText1(self):
+        start, stop, step = dns.grange.from_text('1-1')
+        self.assertEqual(start, 1)
+        self.assertEqual(stop, 1)
+        self.assertEqual(step, 1)
+
+    def testFromText2(self):
+        start, stop, step = dns.grange.from_text('1-4')
+        self.assertEqual(start, 1)
+        self.assertEqual(stop, 4)
+        self.assertEqual(step, 1)
+
+    def testFromText3(self):
+        start, stop, step = dns.grange.from_text('4-255')
+        self.assertEqual(start, 4)
+        self.assertEqual(stop, 255)
+        self.assertEqual(step, 1)
+
+    def testFromText4(self):
+        start, stop, step = dns.grange.from_text('1-1/1')
+        self.assertEqual(start, 1)
+        self.assertEqual(stop, 1)
+        self.assertEqual(step, 1)
+
+    def testFromText5(self):
+        start, stop, step = dns.grange.from_text('1-4/2')
+        self.assertEqual(start, 1)
+        self.assertEqual(stop, 4)
+        self.assertEqual(step, 2)
+
+    def testFromText6(self):
+        start, stop, step = dns.grange.from_text('4-255/77')
+        self.assertEqual(start, 4)
+        self.assertEqual(stop, 255)
+        self.assertEqual(step, 77)
+
+    def testFailFromText1(self):
+        def bad():
+            start = 2
+            stop = 1
+            step = 1
+            dns.grange.from_text('{0}-{1}/{2}'.format(start, stop, step))
+        self.assertRaises(AssertionError, bad)
+
+    def testFailFromText2(self):
+        def bad():
+            start = '-1'
+            stop = 3
+            step = 1
+            dns.grange.from_text('{0}-{1}/{2}'.format(start, stop, step))
+        self.assertRaises(dns.exception.SyntaxError, bad)
+
+    def testFailFromText2(self):
+        def bad():
+            start = 1
+            stop = 4
+            step = '-2'
+            dns.grange.from_text('{0}-{1}/{2}'.format(start, stop, step))
+        self.assertRaises(dns.exception.SyntaxError, bad)
+
+if __name__ == '__main__':
+    unittest.main()