1import getpass
2import os
3import unittest
4from io import BytesIO, StringIO, TextIOWrapper
5from unittest import mock
6from test import support
7
8try:
9    import termios
10except ImportError:
11    termios = None
12try:
13    import pwd
14except ImportError:
15    pwd = None
16
17@mock.patch('os.environ')
18class GetpassGetuserTest(unittest.TestCase):
19
20    def test_username_takes_username_from_env(self, environ):
21        expected_name = 'some_name'
22        environ.get.return_value = expected_name
23        self.assertEqual(expected_name, getpass.getuser())
24
25    def test_username_priorities_of_env_values(self, environ):
26        environ.get.return_value = None
27        try:
28            getpass.getuser()
29        except ImportError: # in case there's no pwd module
30            pass
31        except KeyError:
32            # current user has no pwd entry
33            pass
34        self.assertEqual(
35            environ.get.call_args_list,
36            [mock.call(x) for x in ('LOGNAME', 'USER', 'LNAME', 'USERNAME')])
37
38    def test_username_falls_back_to_pwd(self, environ):
39        expected_name = 'some_name'
40        environ.get.return_value = None
41        if pwd:
42            with mock.patch('os.getuid') as uid, \
43                    mock.patch('pwd.getpwuid') as getpw:
44                uid.return_value = 42
45                getpw.return_value = [expected_name]
46                self.assertEqual(expected_name,
47                                 getpass.getuser())
48                getpw.assert_called_once_with(42)
49        else:
50            self.assertRaises(ImportError, getpass.getuser)
51
52
53class GetpassRawinputTest(unittest.TestCase):
54
55    def test_flushes_stream_after_prompt(self):
56        # see issue 1703
57        stream = mock.Mock(spec=StringIO)
58        input = StringIO('input_string')
59        getpass._raw_input('some_prompt', stream, input=input)
60        stream.flush.assert_called_once_with()
61
62    def test_uses_stderr_as_default(self):
63        input = StringIO('input_string')
64        prompt = 'some_prompt'
65        with mock.patch('sys.stderr') as stderr:
66            getpass._raw_input(prompt, input=input)
67            stderr.write.assert_called_once_with(prompt)
68
69    @mock.patch('sys.stdin')
70    def test_uses_stdin_as_default_input(self, mock_input):
71        mock_input.readline.return_value = 'input_string'
72        getpass._raw_input(stream=StringIO())
73        mock_input.readline.assert_called_once_with()
74
75    @mock.patch('sys.stdin')
76    def test_uses_stdin_as_different_locale(self, mock_input):
77        stream = TextIOWrapper(BytesIO(), encoding="ascii")
78        mock_input.readline.return_value = "Hasło: "
79        getpass._raw_input(prompt="Hasło: ",stream=stream)
80        mock_input.readline.assert_called_once_with()
81
82
83    def test_raises_on_empty_input(self):
84        input = StringIO('')
85        self.assertRaises(EOFError, getpass._raw_input, input=input)
86
87    def test_trims_trailing_newline(self):
88        input = StringIO('test\n')
89        self.assertEqual('test', getpass._raw_input(input=input))
90
91
92# Some of these tests are a bit white-box.  The functional requirement is that
93# the password input be taken directly from the tty, and that it not be echoed
94# on the screen, unless we are falling back to stderr/stdin.
95
96# Some of these might run on platforms without termios, but play it safe.
97@unittest.skipUnless(termios, 'tests require system with termios')
98class UnixGetpassTest(unittest.TestCase):
99
100    def test_uses_tty_directly(self):
101        with mock.patch('os.open') as open, \
102                mock.patch('io.FileIO') as fileio, \
103                mock.patch('io.TextIOWrapper') as textio:
104            # By setting open's return value to None the implementation will
105            # skip code we don't care about in this test.  We can mock this out
106            # fully if an alternate implementation works differently.
107            open.return_value = None
108            getpass.unix_getpass()
109            open.assert_called_once_with('/dev/tty',
110                                         os.O_RDWR | os.O_NOCTTY)
111            fileio.assert_called_once_with(open.return_value, 'w+')
112            textio.assert_called_once_with(fileio.return_value)
113
114    def test_resets_termios(self):
115        with mock.patch('os.open') as open, \
116                mock.patch('io.FileIO'), \
117                mock.patch('io.TextIOWrapper'), \
118                mock.patch('termios.tcgetattr') as tcgetattr, \
119                mock.patch('termios.tcsetattr') as tcsetattr:
120            open.return_value = 3
121            fake_attrs = [255, 255, 255, 255, 255]
122            tcgetattr.return_value = list(fake_attrs)
123            getpass.unix_getpass()
124            tcsetattr.assert_called_with(3, mock.ANY, fake_attrs)
125
126    def test_falls_back_to_fallback_if_termios_raises(self):
127        with mock.patch('os.open') as open, \
128                mock.patch('io.FileIO') as fileio, \
129                mock.patch('io.TextIOWrapper') as textio, \
130                mock.patch('termios.tcgetattr'), \
131                mock.patch('termios.tcsetattr') as tcsetattr, \
132                mock.patch('getpass.fallback_getpass') as fallback:
133            open.return_value = 3
134            fileio.return_value = BytesIO()
135            tcsetattr.side_effect = termios.error
136            getpass.unix_getpass()
137            fallback.assert_called_once_with('Password: ',
138                                             textio.return_value)
139
140    def test_flushes_stream_after_input(self):
141        # issue 7208
142        with mock.patch('os.open') as open, \
143                mock.patch('io.FileIO'), \
144                mock.patch('io.TextIOWrapper'), \
145                mock.patch('termios.tcgetattr'), \
146                mock.patch('termios.tcsetattr'):
147            open.return_value = 3
148            mock_stream = mock.Mock(spec=StringIO)
149            getpass.unix_getpass(stream=mock_stream)
150            mock_stream.flush.assert_called_with()
151
152    def test_falls_back_to_stdin(self):
153        with mock.patch('os.open') as os_open, \
154                mock.patch('sys.stdin', spec=StringIO) as stdin:
155            os_open.side_effect = IOError
156            stdin.fileno.side_effect = AttributeError
157            with support.captured_stderr() as stderr:
158                with self.assertWarns(getpass.GetPassWarning):
159                    getpass.unix_getpass()
160            stdin.readline.assert_called_once_with()
161            self.assertIn('Warning', stderr.getvalue())
162            self.assertIn('Password:', stderr.getvalue())
163
164
165if __name__ == "__main__":
166    unittest.main()
167