1# Ridiculously simple test of the winsound module for Windows.
2
3import unittest
4from test import test_support
5test_support.requires('audio')
6import time
7import os
8import subprocess
9
10winsound = test_support.import_module('winsound')
11ctypes = test_support.import_module('ctypes')
12import _winreg
13
14def has_sound(sound):
15    """Find out if a particular event is configured with a default sound"""
16    try:
17        # Ask the mixer API for the number of devices it knows about.
18        # When there are no devices, PlaySound will fail.
19        if ctypes.windll.winmm.mixerGetNumDevs() is 0:
20            return False
21
22        key = _winreg.OpenKeyEx(_winreg.HKEY_CURRENT_USER,
23                "AppEvents\Schemes\Apps\.Default\{0}\.Default".format(sound))
24        value = _winreg.EnumValue(key, 0)[1]
25        if value is not u"":
26            return True
27        else:
28            return False
29    except WindowsError:
30        return False
31
32class BeepTest(unittest.TestCase):
33    # As with PlaySoundTest, incorporate the _have_soundcard() check
34    # into our test methods.  If there's no audio device present,
35    # winsound.Beep returns 0 and GetLastError() returns 127, which
36    # is: ERROR_PROC_NOT_FOUND ("The specified procedure could not
37    # be found").  (FWIW, virtual/Hyper-V systems fall under this
38    # scenario as they have no sound devices whatsoever  (not even
39    # a legacy Beep device).)
40
41    def test_errors(self):
42        self.assertRaises(TypeError, winsound.Beep)
43        self.assertRaises(ValueError, winsound.Beep, 36, 75)
44        self.assertRaises(ValueError, winsound.Beep, 32768, 75)
45
46    def test_extremes(self):
47        self._beep(37, 75)
48        self._beep(32767, 75)
49
50    def test_increasingfrequency(self):
51        for i in xrange(100, 2000, 100):
52            self._beep(i, 75)
53
54    def _beep(self, *args):
55        # these tests used to use _have_soundcard(), but it's quite
56        # possible to have a soundcard, and yet have the beep driver
57        # disabled. So basically, we have no way of knowing whether
58        # a beep should be produced or not, so currently if these
59        # tests fail we're ignoring them
60        #
61        # XXX the right fix for this is to define something like
62        # _have_enabled_beep_driver() and use that instead of the
63        # try/except below
64        try:
65            winsound.Beep(*args)
66        except RuntimeError:
67            pass
68
69class MessageBeepTest(unittest.TestCase):
70
71    def tearDown(self):
72        time.sleep(0.5)
73
74    def test_default(self):
75        self.assertRaises(TypeError, winsound.MessageBeep, "bad")
76        self.assertRaises(TypeError, winsound.MessageBeep, 42, 42)
77        winsound.MessageBeep()
78
79    def test_ok(self):
80        winsound.MessageBeep(winsound.MB_OK)
81
82    def test_asterisk(self):
83        winsound.MessageBeep(winsound.MB_ICONASTERISK)
84
85    def test_exclamation(self):
86        winsound.MessageBeep(winsound.MB_ICONEXCLAMATION)
87
88    def test_hand(self):
89        winsound.MessageBeep(winsound.MB_ICONHAND)
90
91    def test_question(self):
92        winsound.MessageBeep(winsound.MB_ICONQUESTION)
93
94
95class PlaySoundTest(unittest.TestCase):
96
97    def test_errors(self):
98        self.assertRaises(TypeError, winsound.PlaySound)
99        self.assertRaises(TypeError, winsound.PlaySound, "bad", "bad")
100        self.assertRaises(
101            RuntimeError,
102            winsound.PlaySound,
103            "none", winsound.SND_ASYNC | winsound.SND_MEMORY
104        )
105
106    @unittest.skipUnless(has_sound("SystemAsterisk"), "No default SystemAsterisk")
107    def test_alias_asterisk(self):
108        if _have_soundcard():
109            winsound.PlaySound('SystemAsterisk', winsound.SND_ALIAS)
110        else:
111            self.assertRaises(
112                RuntimeError,
113                winsound.PlaySound,
114                'SystemAsterisk', winsound.SND_ALIAS
115            )
116
117    @unittest.skipUnless(has_sound("SystemExclamation"), "No default SystemExclamation")
118    def test_alias_exclamation(self):
119        if _have_soundcard():
120            winsound.PlaySound('SystemExclamation', winsound.SND_ALIAS)
121        else:
122            self.assertRaises(
123                RuntimeError,
124                winsound.PlaySound,
125                'SystemExclamation', winsound.SND_ALIAS
126            )
127
128    @unittest.skipUnless(has_sound("SystemExit"), "No default SystemExit")
129    def test_alias_exit(self):
130        if _have_soundcard():
131            winsound.PlaySound('SystemExit', winsound.SND_ALIAS)
132        else:
133            self.assertRaises(
134                RuntimeError,
135                winsound.PlaySound,
136                'SystemExit', winsound.SND_ALIAS
137            )
138
139    @unittest.skipUnless(has_sound("SystemHand"), "No default SystemHand")
140    def test_alias_hand(self):
141        if _have_soundcard():
142            winsound.PlaySound('SystemHand', winsound.SND_ALIAS)
143        else:
144            self.assertRaises(
145                RuntimeError,
146                winsound.PlaySound,
147                'SystemHand', winsound.SND_ALIAS
148            )
149
150    @unittest.skipUnless(has_sound("SystemQuestion"), "No default SystemQuestion")
151    def test_alias_question(self):
152        if _have_soundcard():
153            winsound.PlaySound('SystemQuestion', winsound.SND_ALIAS)
154        else:
155            self.assertRaises(
156                RuntimeError,
157                winsound.PlaySound,
158                'SystemQuestion', winsound.SND_ALIAS
159            )
160
161    def test_alias_fallback(self):
162        # This test can't be expected to work on all systems.  The MS
163        # PlaySound() docs say:
164        #
165        #     If it cannot find the specified sound, PlaySound uses the
166        #     default system event sound entry instead.  If the function
167        #     can find neither the system default entry nor the default
168        #     sound, it makes no sound and returns FALSE.
169        #
170        # It's known to return FALSE on some real systems.
171
172        # winsound.PlaySound('!"$%&/(#+*', winsound.SND_ALIAS)
173        return
174
175    def test_alias_nofallback(self):
176        if _have_soundcard():
177            # Note that this is not the same as asserting RuntimeError
178            # will get raised:  you cannot convert this to
179            # self.assertRaises(...) form.  The attempt may or may not
180            # raise RuntimeError, but it shouldn't raise anything other
181            # than RuntimeError, and that's all we're trying to test
182            # here.  The MS docs aren't clear about whether the SDK
183            # PlaySound() with SND_ALIAS and SND_NODEFAULT will return
184            # True or False when the alias is unknown.  On Tim's WinXP
185            # box today, it returns True (no exception is raised).  What
186            # we'd really like to test is that no sound is played, but
187            # that requires first wiring an eardrum class into unittest
188            # <wink>.
189            try:
190                winsound.PlaySound(
191                    '!"$%&/(#+*',
192                    winsound.SND_ALIAS | winsound.SND_NODEFAULT
193                )
194            except RuntimeError:
195                pass
196        else:
197            self.assertRaises(
198                RuntimeError,
199                winsound.PlaySound,
200                '!"$%&/(#+*', winsound.SND_ALIAS | winsound.SND_NODEFAULT
201            )
202
203    def test_stopasync(self):
204        if _have_soundcard():
205            winsound.PlaySound(
206                'SystemQuestion',
207                winsound.SND_ALIAS | winsound.SND_ASYNC | winsound.SND_LOOP
208            )
209            time.sleep(0.5)
210            try:
211                winsound.PlaySound(
212                    'SystemQuestion',
213                    winsound.SND_ALIAS | winsound.SND_NOSTOP
214                )
215            except RuntimeError:
216                pass
217            else: # the first sound might already be finished
218                pass
219            winsound.PlaySound(None, winsound.SND_PURGE)
220        else:
221            # Issue 8367: PlaySound(None, winsound.SND_PURGE)
222            # does not raise on systems without a sound card.
223            pass
224
225
226def _get_cscript_path():
227    """Return the full path to cscript.exe or None."""
228    for dir in os.environ.get("PATH", "").split(os.pathsep):
229        cscript_path = os.path.join(dir, "cscript.exe")
230        if os.path.exists(cscript_path):
231            return cscript_path
232
233__have_soundcard_cache = None
234def _have_soundcard():
235    """Return True iff this computer has a soundcard."""
236    global __have_soundcard_cache
237    if __have_soundcard_cache is None:
238        cscript_path = _get_cscript_path()
239        if cscript_path is None:
240            # Could not find cscript.exe to run our VBScript helper. Default
241            # to True: most computers these days *do* have a soundcard.
242            return True
243
244        check_script = os.path.join(os.path.dirname(__file__),
245                                    "check_soundcard.vbs")
246        p = subprocess.Popen([cscript_path, check_script],
247                             stdout=subprocess.PIPE)
248        __have_soundcard_cache = not p.wait()
249    return __have_soundcard_cache
250
251
252def test_main():
253    test_support.run_unittest(BeepTest, MessageBeepTest, PlaySoundTest)
254
255if __name__=="__main__":
256    test_main()
257