test_examples.py 5.2 KB

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