Ticket #5989: names-examples-5596-1.patch

File names-examples-5596-1.patch, 11.5 KB (added by Richard Wall, 7 years ago)

Use task.react in all names examples, standardise the script output and add some tests

  • doc/names/examples/dns-service.py

    === modified file 'doc/names/examples/dns-service.py'
     
    44# See LICENSE for details.
    55
    66"""
    7 Sample app to lookup SRV records in DNS.
    8 To run this script:
    9 $ python dns-service.py <service> <proto> <domain>
    10 where,
    11 service = the symbolic name of the desired service.
    12 proto = the transport protocol of the desired service; this is usually either TCP or UDP.
    13 domain =  the domain name for which this record is valid.
    14 e.g.:
    15 $ python dns-service.py sip udp yahoo.com
    16 $ python dns-service.py xmpp-client tcp gmail.com
     7USAGE: python dns-service.py SERVICE PROTO DOMAINNAME
     8
     9Print the SRV records for a given DOMAINNAME eg
     10
     11 python dns-service.py xmpp-client tcp gmail.com
     12
     13SERVICE: the symbolic name of the desired service.
     14
     15PROTO: the transport protocol of the desired service; this is usually
     16       either TCP or UDP.
     17
     18DOMAINNAME: the domain name for which this record is valid.
    1719"""
    18 
    19 from twisted.names import client
    20 from twisted.internet import reactor
    2120import sys
    2221
    23 def printAnswer((answers, auth, add)):
    24     if not len(answers):
    25         print 'No answers'
     22from twisted.names import client, error
     23from twisted.internet.task import react
     24
     25
     26def printResult(records, domainname):
     27    """
     28    Print the SRV records for the domainname or an error message if no
     29    SRV records were found.
     30    """
     31    answers, authority, additional = records
     32    if answers:
     33        sys.stdout.write(
     34            domainname + ' IN \n ' +
     35            '\n '.join(str(x.payload) for x in answers) +
     36            '\n')
    2637    else:
    27         print '\n'.join([str(x.payload) for x in answers])
    28     reactor.stop()
    29 
    30 def printFailure(arg):
    31     print "error: could not resolve:", arg
    32     reactor.stop()
    33 
    34 try:
    35     service, proto, domain = sys.argv[1:]
    36 except ValueError:
    37     sys.stderr.write('%s: usage:\n' % sys.argv[0] +
    38                      '  %s SERVICE PROTO DOMAIN\n' % sys.argv[0])
    39     sys.exit(1)
    40 
    41 resolver = client.Resolver('/etc/resolv.conf')
    42 d = resolver.lookupService('_%s._%s.%s' % (service, proto, domain), [1])
    43 d.addCallbacks(printAnswer, printFailure)
    44 
    45 reactor.run()
     38        sys.stderr.write(
     39            'ERROR: No SRV records found for name %r\n' % (domainname,))
     40
     41
     42def printError(failure, domainname):
     43    """
     44    Print a friendly error message if the domainname could not be
     45    resolved.
     46    """
     47    failure.trap(error.DNSNameError)
     48    sys.stderr.write('ERROR: domain name not found %r\n' % (domainname,))
     49
     50
     51def main(reactor, *argv):
     52    try:
     53        service, proto, domainname = sys.argv[1:]
     54    except ValueError:
     55        sys.stderr.write(
     56            __doc__.lstrip() + '\n'
     57            'ERROR: incorrect command line arguments\n')
     58        raise SystemExit(1)
     59
     60    resolver = client.Resolver('/etc/resolv.conf')
     61    domainname = '_%s._%s.%s' % (service, proto, domainname)
     62    d = resolver.lookupService(domainname)
     63    d.addCallback(printResult, domainname)
     64    d.addErrback(printError, domainname)
     65    return d
     66
     67if __name__ == '__main__':
     68    react(main, sys.argv[1:])
  • doc/names/examples/index.xhtml

    === modified file 'doc/names/examples/index.xhtml'
     
    1212
    1313    <h2>DNS (Twisted Names)</h2>
    1414    <ul>
    15         <li><a href="testdns.py">testdns.py</a> - Prints the results of an Address record lookup, Mail-Exchanger record lookup, and Nameserver record lookup for the given hostname for a given hostname.</li>
     15        <li><a href="testdns.py">testdns.py</a> - Prints the results of an Address record lookup, Mail-Exchanger record lookup, and Nameserver record lookup for the given domain name.</li>
    1616        <li><a href="dns-service.py">dns-service.py</a> - Searches for SRV records in DNS.</li>
    1717        <li><a href="gethostbyname.py">gethostbyname.py</a> - Returns the IP address for a given hostname.</li>
    1818    </ul>
    1919</body>
    2020</html>
    21 
  • doc/names/examples/testdns.py

    === modified file 'doc/names/examples/testdns.py' (properties changed: -x to +x)
     
    44# See LICENSE for details.
    55
    66"""
    7 Prints the results of an Address record lookup, Mail-Exchanger record
    8 lookup, and Nameserver record lookup for the given hostname for a
    9 given hostname.
    10 
    11 To run this script:
    12 $ python testdns.py <hostname>
    13 e.g.:
    14 $ python testdns.py www.google.com
     7USAGE: python testdns.py DOMAINNAME
     8
     9Print the Address records, Mail-Exchanger records and the Nameserver
     10records for the given domain name. eg
     11
     12 python testdns.py google.com
    1513"""
    1614import sys
    1715
    18 from twisted.names import client
    19 from twisted.internet import defer, reactor
    20 from twisted.names import dns, error
    21 
    22 
    23 r = client.Resolver('/etc/resolv.conf')
    24 
    25 
    26 def formatResult(a, heading):
    27     answer, authority, additional = a
     16from twisted.internet import defer
     17from twisted.internet.task import react
     18from twisted.names import client, dns, error
     19
     20
     21def formatRecords(records, heading):
     22    """
     23    Extract only the answer records and return them as a neatly
     24    formatted string beneath the given heading.
     25    """
     26    answers, authority, additional = records
    2827    lines = ['# ' + heading]
    29     for a in answer:
     28    for a in answers:
    3029        line = [
    3130            a.name,
    3231            dns.QUERY_CLASSES.get(a.cls, 'UNKNOWN (%d)' % (a.cls,)),
     
    3635    return '\n'.join(line for line in lines)
    3736
    3837
    39 def printError(f):
    40     f.trap(defer.FirstError)
    41     f = f.value.subFailure
    42     f.trap(error.DomainError)
    43     print f.value.__class__.__name__, f.value.message.queries
    44 
    45 
    46 def printResults(res):
    47     for r in res:
    48         print r
    49         print
    50 
    51 
    52 if __name__ == '__main__':
    53     domainname = sys.argv[1]
    54 
     38def printResults(results, domainname):
     39    """
     40    Print the formatted results for each DNS record type.
     41    """
     42    sys.stdout.write('# Domain Summary for %r\n' % (domainname,))
     43    sys.stdout.write('\n\n'.join(results) + '\n')
     44
     45
     46def printError(failure, domainname):
     47    """
     48    Print a friendly error message if the hostname could not be
     49    resolved.
     50    """
     51    failure.trap(defer.FirstError)
     52    failure = failure.value.subFailure
     53    failure.trap(error.DNSNameError)
     54    sys.stderr.write('ERROR: domain name not found %r\n' % (domainname,))
     55
     56
     57def main(reactor, *argv):
     58    try:
     59        domainname = argv[0]
     60    except IndexError:
     61        sys.stderr.write(
     62            __doc__.lstrip() + '\n'
     63            'ERROR: missing DOMAINNAME argument\n')
     64        raise SystemExit(1)
     65
     66    r = client.Resolver('/etc/resolv.conf')
    5567    d = defer.gatherResults([
    5668            r.lookupAddress(domainname).addCallback(
    57                 formatResult, 'Addresses'),
     69                formatRecords, 'Addresses'),
    5870            r.lookupMailExchange(domainname).addCallback(
    59                 formatResult, 'Mail Exchangers'),
     71                formatRecords, 'Mail Exchangers'),
    6072            r.lookupNameservers(domainname).addCallback(
    61                 formatResult, 'Nameservers'),
     73                formatRecords, 'Nameservers'),
    6274            ], consumeErrors=True)
    6375
    64     d.addCallbacks(printResults, printError)
    65 
    66     d.addBoth(lambda ign: reactor.stop())
    67 
    68     reactor.run()
     76    d.addCallback(printResults, domainname)
     77    d.addErrback(printError, domainname)
     78    return d
     79
     80
     81if __name__ == '__main__':
     82    react(main, sys.argv[1:])
  • twisted/names/test/test_examples.py

    === added file 'twisted/names/test/test_examples.py'
     
     1# Copyright (c) Twisted Matrix Laboratories.
     2# See LICENSE for details.
     3
     4"""
     5Tests for L{twisted.names.tap}.
     6"""
     7import os
     8import sys
     9from StringIO import StringIO
     10
     11from twisted.python.filepath import FilePath
     12from twisted.trial.unittest import SkipTest, TestCase
     13
     14
     15class ExampleTestBase(object):
     16    """
     17    This is a mixin which adds an example to the path, tests it, and then
     18    removes it from the path and unimports the modules which the test loaded.
     19    Test cases which test example code and documentation listings should use
     20    this.
     21
     22    This is done this way so that examples can live in isolated path entries,
     23    next to the documentation, replete with their own plugin packages and
     24    whatever other metadata they need.  Also, example code is a rare instance
     25    of it being valid to have multiple versions of the same code in the
     26    repository at once, rather than relying on version control, because
     27    documentation will often show the progression of a single piece of code as
     28    features are added to it, and we want to test each one.
     29    """
     30
     31    examplePath = None
     32
     33    def setUp(self):
     34        """
     35        Add our example directory to the path and record which modules are
     36        currently loaded.
     37        """
     38        self.fakeErr = StringIO()
     39        self.originalErr, sys.stderr = sys.stderr, self.fakeErr
     40        self.originalPath = sys.path[:]
     41        self.originalModules = sys.modules.copy()
     42        here = FilePath(__file__).parent().parent().parent().parent()
     43        for childName in self.examplePath:
     44            here = here.child(childName)
     45        if not here.exists():
     46            raise SkipTest("Examples (%s) not found - cannot test" % (here.path,))
     47        sys.path.append(here.parent().path)
     48        # Import the example as a module
     49        moduleName = here.basename().split('.')[0]
     50        self.example = __import__(moduleName)
     51        self.examplePath = here
     52
     53
     54    def tearDown(self):
     55        """
     56        Remove the example directory from the path and remove all modules loaded by
     57        the test from sys.modules.
     58        """
     59        sys.modules.clear()
     60        sys.modules.update(self.originalModules)
     61        sys.path[:] = self.originalPath
     62        sys.stderr = self.originalErr
     63
     64
     65    def test_executable(self):
     66        """
     67        The example scripts should start with the standard shebang
     68        line and should be executable.
     69        """
     70        self.assertEquals(
     71            self.examplePath.open().readline().rstrip(),
     72            '#!/usr/bin/env python')
     73
     74        mode = oct(os.stat(self.examplePath.path).st_mode)[-3:]
     75        self.assertEquals(
     76            mode, '775',
     77            'Wrong permissions. %r on %r' % (mode, self.examplePath.path))
     78
     79
     80    def test_usage(self):
     81        """
     82        The example script prints a usage message to stderr and raises
     83        SystemExit if it is passed incorrect command line
     84        arguments. The first line should contain a USAGE summary and
     85        the last line should contain an ERROR, explaining that
     86        incorrect arguments were supplied.
     87        """
     88        self.assertRaises(SystemExit, self.example.main, None)
     89        err = self.fakeErr.getvalue().splitlines()
     90        self.assertEquals(err[0][:len('USAGE:')], 'USAGE:')
     91        self.assertEquals(err[-1][:len('ERROR:')], 'ERROR:')
     92
     93
     94class TestDnsTests(ExampleTestBase, TestCase):
     95    """
     96    Tests for the testdns.py example script.
     97    """
     98
     99    examplePath = 'doc/names/examples/testdns.py'.split('/')
     100
     101
     102class GetHostByNameTests(ExampleTestBase, TestCase):
     103    """
     104    Tests for the gethostbyname.py example script.
     105    """
     106
     107    examplePath = 'doc/names/examples/gethostbyname.py'.split('/')
     108
     109
     110class DnsServiceTests(ExampleTestBase, TestCase):
     111    """
     112    Tests for the dns-service.py example script.
     113    """
     114
     115    examplePath = 'doc/names/examples/dns-service.py'.split('/')