test_mailcap.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import copy
  2. import os
  3. import sys
  4. import test.support
  5. import unittest
  6. import warnings
  7. from test.support import os_helper
  8. from test.support import warnings_helper
  9. mailcap = warnings_helper.import_deprecated('mailcap')
  10. # Location of mailcap file
  11. MAILCAPFILE = test.support.findfile("mailcap.txt")
  12. # Dict to act as mock mailcap entry for this test
  13. # The keys and values should match the contents of MAILCAPFILE
  14. MAILCAPDICT = {
  15. 'application/x-movie':
  16. [{'compose': 'moviemaker %s',
  17. 'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
  18. 'description': '"Movie"',
  19. 'view': 'movieplayer %s',
  20. 'lineno': 4}],
  21. 'application/*':
  22. [{'copiousoutput': '',
  23. 'view': 'echo "This is \\"%t\\" but is 50 \\% Greek to me" \\; cat %s',
  24. 'lineno': 5}],
  25. 'audio/basic':
  26. [{'edit': 'audiocompose %s',
  27. 'compose': 'audiocompose %s',
  28. 'description': '"An audio fragment"',
  29. 'view': 'showaudio %s',
  30. 'lineno': 6}],
  31. 'video/mpeg':
  32. [{'view': 'mpeg_play %s', 'lineno': 13}],
  33. 'application/postscript':
  34. [{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
  35. {'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
  36. 'application/x-dvi':
  37. [{'view': 'xdvi %s', 'lineno': 3}],
  38. 'message/external-body':
  39. [{'composetyped': 'extcompose %s',
  40. 'description': '"A reference to data stored in an external location"',
  41. 'needsterminal': '',
  42. 'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
  43. 'lineno': 10}],
  44. 'text/richtext':
  45. [{'test': 'test "`echo %{charset} | tr \'[A-Z]\' \'[a-z]\'`" = iso-8859-8',
  46. 'copiousoutput': '',
  47. 'view': 'shownonascii iso-8859-8 -e richtext -p %s',
  48. 'lineno': 11}],
  49. 'image/x-xwindowdump':
  50. [{'view': 'display %s', 'lineno': 9}],
  51. 'audio/*':
  52. [{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
  53. 'video/*':
  54. [{'view': 'animate %s', 'lineno': 12}],
  55. 'application/frame':
  56. [{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
  57. 'image/rgb':
  58. [{'view': 'display %s', 'lineno': 8}]
  59. }
  60. # For backwards compatibility, readmailcapfile() and lookup() still support
  61. # the old version of mailcapdict without line numbers.
  62. MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT)
  63. for entry_list in MAILCAPDICT_DEPRECATED.values():
  64. for entry in entry_list:
  65. entry.pop('lineno')
  66. class HelperFunctionTest(unittest.TestCase):
  67. def test_listmailcapfiles(self):
  68. # The return value for listmailcapfiles() will vary by system.
  69. # So verify that listmailcapfiles() returns a list of strings that is of
  70. # non-zero length.
  71. mcfiles = mailcap.listmailcapfiles()
  72. self.assertIsInstance(mcfiles, list)
  73. for m in mcfiles:
  74. self.assertIsInstance(m, str)
  75. with os_helper.EnvironmentVarGuard() as env:
  76. # According to RFC 1524, if MAILCAPS env variable exists, use that
  77. # and only that.
  78. if "MAILCAPS" in env:
  79. env_mailcaps = env["MAILCAPS"].split(os.pathsep)
  80. else:
  81. env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"]
  82. env["MAILCAPS"] = os.pathsep.join(env_mailcaps)
  83. mcfiles = mailcap.listmailcapfiles()
  84. self.assertEqual(env_mailcaps, mcfiles)
  85. def test_readmailcapfile(self):
  86. # Test readmailcapfile() using test file. It should match MAILCAPDICT.
  87. with open(MAILCAPFILE, 'r') as mcf:
  88. with self.assertWarns(DeprecationWarning):
  89. d = mailcap.readmailcapfile(mcf)
  90. self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
  91. def test_lookup(self):
  92. # Test without key
  93. expected = [{'view': 'animate %s', 'lineno': 12},
  94. {'view': 'mpeg_play %s', 'lineno': 13}]
  95. actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
  96. self.assertListEqual(expected, actual)
  97. # Test with key
  98. key = 'compose'
  99. expected = [{'edit': 'audiocompose %s',
  100. 'compose': 'audiocompose %s',
  101. 'description': '"An audio fragment"',
  102. 'view': 'showaudio %s',
  103. 'lineno': 6}]
  104. actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
  105. self.assertListEqual(expected, actual)
  106. # Test on user-defined dicts without line numbers
  107. expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
  108. actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
  109. self.assertListEqual(expected, actual)
  110. def test_subst(self):
  111. plist = ['id=1', 'number=2', 'total=3']
  112. # test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
  113. test_cases = [
  114. (["", "audio/*", "foo.txt"], ""),
  115. (["echo foo", "audio/*", "foo.txt"], "echo foo"),
  116. (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
  117. (["echo %t", "audio/*", "foo.txt"], None),
  118. (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
  119. (["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
  120. (["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
  121. (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
  122. ]
  123. for tc in test_cases:
  124. self.assertEqual(mailcap.subst(*tc[0]), tc[1])
  125. class GetcapsTest(unittest.TestCase):
  126. def test_mock_getcaps(self):
  127. # Test mailcap.getcaps() using mock mailcap file in this dir.
  128. # Temporarily override any existing system mailcap file by pointing the
  129. # MAILCAPS environment variable to our mock file.
  130. with os_helper.EnvironmentVarGuard() as env:
  131. env["MAILCAPS"] = MAILCAPFILE
  132. caps = mailcap.getcaps()
  133. self.assertDictEqual(caps, MAILCAPDICT)
  134. def test_system_mailcap(self):
  135. # Test mailcap.getcaps() with mailcap file(s) on system, if any.
  136. caps = mailcap.getcaps()
  137. self.assertIsInstance(caps, dict)
  138. mailcapfiles = mailcap.listmailcapfiles()
  139. existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)]
  140. if existingmcfiles:
  141. # At least 1 mailcap file exists, so test that.
  142. for (k, v) in caps.items():
  143. self.assertIsInstance(k, str)
  144. self.assertIsInstance(v, list)
  145. for e in v:
  146. self.assertIsInstance(e, dict)
  147. else:
  148. # No mailcap files on system. getcaps() should return empty dict.
  149. self.assertEqual({}, caps)
  150. class FindmatchTest(unittest.TestCase):
  151. def test_findmatch(self):
  152. # default findmatch arguments
  153. c = MAILCAPDICT
  154. fname = "foo.txt"
  155. plist = ["access-type=default", "name=john", "site=python.org",
  156. "directory=/tmp", "mode=foo", "server=bar"]
  157. audio_basic_entry = {
  158. 'edit': 'audiocompose %s',
  159. 'compose': 'audiocompose %s',
  160. 'description': '"An audio fragment"',
  161. 'view': 'showaudio %s',
  162. 'lineno': 6
  163. }
  164. audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7}
  165. video_entry = {'view': 'animate %s', 'lineno': 12}
  166. message_entry = {
  167. 'composetyped': 'extcompose %s',
  168. 'description': '"A reference to data stored in an external location"', 'needsterminal': '',
  169. 'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}',
  170. 'lineno': 10,
  171. }
  172. # test case: (findmatch args, findmatch keyword args, expected output)
  173. # positional args: caps, MIMEtype
  174. # keyword args: key="view", filename="/dev/null", plist=[]
  175. # output: (command line, mailcap entry)
  176. cases = [
  177. ([{}, "video/mpeg"], {}, (None, None)),
  178. ([c, "foo/bar"], {}, (None, None)),
  179. ([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
  180. ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
  181. ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
  182. ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
  183. ([c, "audio/basic", "foobar"], {}, (None, None)),
  184. ([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)),
  185. ([c, "audio/basic", "compose"],
  186. {"filename": fname},
  187. ("audiocompose %s" % fname, audio_basic_entry)),
  188. ([c, "audio/basic"],
  189. {"key": "description", "filename": fname},
  190. ('"An audio fragment"', audio_basic_entry)),
  191. ([c, "audio/*"],
  192. {"filename": fname},
  193. (None, None)),
  194. ([c, "audio/wav"],
  195. {"filename": fname},
  196. ("/usr/local/bin/showaudio audio/wav", audio_entry)),
  197. ([c, "message/external-body"],
  198. {"plist": plist},
  199. ("showexternal /dev/null default john python.org /tmp foo bar", message_entry))
  200. ]
  201. self._run_cases(cases)
  202. @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system")
  203. @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks")
  204. @unittest.skipUnless(
  205. test.support.has_subprocess_support,
  206. "'test' command needs process support."
  207. )
  208. def test_test(self):
  209. # findmatch() will automatically check any "test" conditions and skip
  210. # the entry if the check fails.
  211. caps = {"test/pass": [{"test": "test 1 -eq 1"}],
  212. "test/fail": [{"test": "test 1 -eq 0"}]}
  213. # test case: (findmatch args, findmatch keyword args, expected output)
  214. # positional args: caps, MIMEtype, key ("test")
  215. # keyword args: N/A
  216. # output: (command line, mailcap entry)
  217. cases = [
  218. # findmatch will return the mailcap entry for test/pass because it evaluates to true
  219. ([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})),
  220. # findmatch will return None because test/fail evaluates to false
  221. ([caps, "test/fail", "test"], {}, (None, None))
  222. ]
  223. self._run_cases(cases)
  224. def _run_cases(self, cases):
  225. for c in cases:
  226. self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2])
  227. if __name__ == '__main__':
  228. unittest.main()