1import mailcap 2import os 3import copy 4import test.support 5from test.support import os_helper 6import unittest 7import sys 8 9# Location of mailcap file 10MAILCAPFILE = test.support.findfile("mailcap.txt") 11 12# Dict to act as mock mailcap entry for this test 13# The keys and values should match the contents of MAILCAPFILE 14MAILCAPDICT = { 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 61# For backwards compatibility, readmailcapfile() and lookup() still support 62# the old version of mailcapdict without line numbers. 63MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT) 64for entry_list in MAILCAPDICT_DEPRECATED.values(): 65 for entry in entry_list: 66 entry.pop('lineno') 67 68 69class HelperFunctionTest(unittest.TestCase): 70 71 def test_listmailcapfiles(self): 72 # The return value for listmailcapfiles() will vary by system. 73 # So verify that listmailcapfiles() returns a list of strings that is of 74 # non-zero length. 75 mcfiles = mailcap.listmailcapfiles() 76 self.assertIsInstance(mcfiles, list) 77 for m in mcfiles: 78 self.assertIsInstance(m, str) 79 with os_helper.EnvironmentVarGuard() as env: 80 # According to RFC 1524, if MAILCAPS env variable exists, use that 81 # and only that. 82 if "MAILCAPS" in env: 83 env_mailcaps = env["MAILCAPS"].split(os.pathsep) 84 else: 85 env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"] 86 env["MAILCAPS"] = os.pathsep.join(env_mailcaps) 87 mcfiles = mailcap.listmailcapfiles() 88 self.assertEqual(env_mailcaps, mcfiles) 89 90 def test_readmailcapfile(self): 91 # Test readmailcapfile() using test file. It should match MAILCAPDICT. 92 with open(MAILCAPFILE, 'r') as mcf: 93 with self.assertWarns(DeprecationWarning): 94 d = mailcap.readmailcapfile(mcf) 95 self.assertDictEqual(d, MAILCAPDICT_DEPRECATED) 96 97 def test_lookup(self): 98 # Test without key 99 expected = [{'view': 'animate %s', 'lineno': 12}, 100 {'view': 'mpeg_play %s', 'lineno': 13}] 101 actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg') 102 self.assertListEqual(expected, actual) 103 104 # Test with key 105 key = 'compose' 106 expected = [{'edit': 'audiocompose %s', 107 'compose': 'audiocompose %s', 108 'description': '"An audio fragment"', 109 'view': 'showaudio %s', 110 'lineno': 6}] 111 actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key) 112 self.assertListEqual(expected, actual) 113 114 # Test on user-defined dicts without line numbers 115 expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}] 116 actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg') 117 self.assertListEqual(expected, actual) 118 119 def test_subst(self): 120 plist = ['id=1', 'number=2', 'total=3'] 121 # test case: ([field, MIMEtype, filename, plist=[]], <expected string>) 122 test_cases = [ 123 (["", "audio/*", "foo.txt"], ""), 124 (["echo foo", "audio/*", "foo.txt"], "echo foo"), 125 (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), 126 (["echo %t", "audio/*", "foo.txt"], None), 127 (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"), 128 (["echo \\%t", "audio/*", "foo.txt"], "echo %t"), 129 (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), 130 (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3") 131 ] 132 for tc in test_cases: 133 self.assertEqual(mailcap.subst(*tc[0]), tc[1]) 134 135 136class GetcapsTest(unittest.TestCase): 137 138 def test_mock_getcaps(self): 139 # Test mailcap.getcaps() using mock mailcap file in this dir. 140 # Temporarily override any existing system mailcap file by pointing the 141 # MAILCAPS environment variable to our mock file. 142 with os_helper.EnvironmentVarGuard() as env: 143 env["MAILCAPS"] = MAILCAPFILE 144 caps = mailcap.getcaps() 145 self.assertDictEqual(caps, MAILCAPDICT) 146 147 def test_system_mailcap(self): 148 # Test mailcap.getcaps() with mailcap file(s) on system, if any. 149 caps = mailcap.getcaps() 150 self.assertIsInstance(caps, dict) 151 mailcapfiles = mailcap.listmailcapfiles() 152 existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)] 153 if existingmcfiles: 154 # At least 1 mailcap file exists, so test that. 155 for (k, v) in caps.items(): 156 self.assertIsInstance(k, str) 157 self.assertIsInstance(v, list) 158 for e in v: 159 self.assertIsInstance(e, dict) 160 else: 161 # No mailcap files on system. getcaps() should return empty dict. 162 self.assertEqual({}, caps) 163 164 165class FindmatchTest(unittest.TestCase): 166 167 def test_findmatch(self): 168 169 # default findmatch arguments 170 c = MAILCAPDICT 171 fname = "foo.txt" 172 plist = ["access-type=default", "name=john", "site=python.org", 173 "directory=/tmp", "mode=foo", "server=bar"] 174 audio_basic_entry = { 175 'edit': 'audiocompose %s', 176 'compose': 'audiocompose %s', 177 'description': '"An audio fragment"', 178 'view': 'showaudio %s', 179 'lineno': 6 180 } 181 audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7} 182 video_entry = {'view': 'animate %s', 'lineno': 12} 183 message_entry = { 184 'composetyped': 'extcompose %s', 185 'description': '"A reference to data stored in an external location"', 'needsterminal': '', 186 'view': 'showexternal %s %{access-type} %{name} %{site} %{directory} %{mode} %{server}', 187 'lineno': 10, 188 } 189 190 # test case: (findmatch args, findmatch keyword args, expected output) 191 # positional args: caps, MIMEtype 192 # keyword args: key="view", filename="/dev/null", plist=[] 193 # output: (command line, mailcap entry) 194 cases = [ 195 ([{}, "video/mpeg"], {}, (None, None)), 196 ([c, "foo/bar"], {}, (None, None)), 197 ([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)), 198 ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)), 199 ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)), 200 ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)), 201 ([c, "audio/basic", "foobar"], {}, (None, None)), 202 ([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)), 203 ([c, "audio/basic", "compose"], 204 {"filename": fname}, 205 ("audiocompose %s" % fname, audio_basic_entry)), 206 ([c, "audio/basic"], 207 {"key": "description", "filename": fname}, 208 ('"An audio fragment"', audio_basic_entry)), 209 ([c, "audio/*"], 210 {"filename": fname}, 211 (None, None)), 212 ([c, "audio/wav"], 213 {"filename": fname}, 214 ("/usr/local/bin/showaudio audio/wav", audio_entry)), 215 ([c, "message/external-body"], 216 {"plist": plist}, 217 ("showexternal /dev/null default john python.org /tmp foo bar", message_entry)) 218 ] 219 self._run_cases(cases) 220 221 @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system") 222 @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks") 223 def test_test(self): 224 # findmatch() will automatically check any "test" conditions and skip 225 # the entry if the check fails. 226 caps = {"test/pass": [{"test": "test 1 -eq 1"}], 227 "test/fail": [{"test": "test 1 -eq 0"}]} 228 # test case: (findmatch args, findmatch keyword args, expected output) 229 # positional args: caps, MIMEtype, key ("test") 230 # keyword args: N/A 231 # output: (command line, mailcap entry) 232 cases = [ 233 # findmatch will return the mailcap entry for test/pass because it evaluates to true 234 ([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})), 235 # findmatch will return None because test/fail evaluates to false 236 ([caps, "test/fail", "test"], {}, (None, None)) 237 ] 238 self._run_cases(cases) 239 240 def _run_cases(self, cases): 241 for c in cases: 242 self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2]) 243 244 245if __name__ == '__main__': 246 unittest.main() 247