1# Copyright 2020 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Tests for pw_cli.envparse."""
15
16import math
17import unittest
18
19import pw_cli.envparse as envparse
20
21# pylint: disable=no-member
22
23
24class ErrorError(Exception):
25    pass
26
27
28def error(value: str):
29    raise ErrorError('error!')
30
31
32class TestEnvironmentParser(unittest.TestCase):
33    """Tests for envparse.EnvironmentParser."""
34    def setUp(self):
35        self.raw_env = {
36            'PATH': '/bin:/usr/bin:/usr/local/bin',
37            'FOO': '2020',
38            'ReVeRsE': 'pigweed',
39        }
40
41        self.parser = envparse.EnvironmentParser()
42        self.parser.add_var('PATH')
43        self.parser.add_var('FOO', type=int)
44        self.parser.add_var('BAR', type=bool)
45        self.parser.add_var('BAZ', type=float, default=math.pi)
46        self.parser.add_var('ReVeRsE', type=lambda s: s[::-1])
47        self.parser.add_var('INT', type=int)
48        self.parser.add_var('ERROR', type=error)
49
50    def test_string_value(self):
51        env = self.parser.parse_env(env=self.raw_env)
52        self.assertEqual(env.PATH, self.raw_env['PATH'])
53
54    def test_int_value(self):
55        env = self.parser.parse_env(env=self.raw_env)
56        self.assertEqual(env.FOO, 2020)
57
58    def test_custom_value(self):
59        env = self.parser.parse_env(env=self.raw_env)
60        self.assertEqual(env.ReVeRsE, 'deewgip')
61
62    def test_empty_value(self):
63        env = self.parser.parse_env(env=self.raw_env)
64        self.assertEqual(env.BAR, None)
65
66    def test_default_value(self):
67        env = self.parser.parse_env(env=self.raw_env)
68        self.assertEqual(env.BAZ, math.pi)
69
70    def test_unknown_key(self):
71        env = self.parser.parse_env(env=self.raw_env)
72        with self.assertRaises(AttributeError):
73            env.BBBBB  # pylint: disable=pointless-statement
74
75    def test_bad_value(self):
76        raw_env = {**self.raw_env, 'INT': 'not an int'}
77        with self.assertRaises(envparse.EnvironmentValueError) as ctx:
78            self.parser.parse_env(env=raw_env)
79
80        self.assertEqual(ctx.exception.variable, 'INT')
81        self.assertIsInstance(ctx.exception.__cause__, ValueError)
82
83    def test_custom_exception(self):
84        raw_env = {**self.raw_env, 'ERROR': 'error'}
85        with self.assertRaises(envparse.EnvironmentValueError) as ctx:
86            self.parser.parse_env(env=raw_env)
87
88        self.assertEqual(ctx.exception.variable, 'ERROR')
89        self.assertIsInstance(ctx.exception.__cause__, ErrorError)
90
91
92class TestEnvironmentParserWithPrefix(unittest.TestCase):
93    """Tests for envparse.EnvironmentParser using a prefix."""
94    def setUp(self):
95        self.raw_env = {
96            'PW_FOO': '001',
97            'PW_BAR': '010',
98            'PW_BAZ': '100',
99            'IGNORED': '011',
100        }
101
102    def test_parse_unrecognized_variable(self):
103        parser = envparse.EnvironmentParser(prefix='PW_')
104        parser.add_var('PW_FOO')
105        parser.add_var('PW_BAR')
106
107        with self.assertRaises(ValueError):
108            parser.parse_env(env=self.raw_env)
109
110    def test_parse_unrecognized_but_allowed_suffix(self):
111        parser = envparse.EnvironmentParser(prefix='PW_')
112        parser.add_allowed_suffix('_ALLOWED_SUFFIX')
113
114        env = parser.parse_env(env={'PW_FOO_ALLOWED_SUFFIX': '001'})
115        self.assertEqual(env.PW_FOO_ALLOWED_SUFFIX, '001')
116
117    def test_parse_allowed_suffix_but_not_suffix(self):
118        parser = envparse.EnvironmentParser(prefix='PW_')
119        parser.add_allowed_suffix('_ALLOWED_SUFFIX')
120
121        with self.assertRaises(ValueError):
122            parser.parse_env(env={'PW_FOO_ALLOWED_SUFFIX_FOO': '001'})
123
124    def test_parse_ignore_unrecognized(self):
125        parser = envparse.EnvironmentParser(prefix='PW_',
126                                            error_on_unrecognized=False)
127        parser.add_var('PW_FOO')
128        parser.add_var('PW_BAR')
129
130        env = parser.parse_env(env=self.raw_env)
131        self.assertEqual(env.PW_FOO, self.raw_env['PW_FOO'])
132        self.assertEqual(env.PW_BAR, self.raw_env['PW_BAR'])
133
134    def test_add_var_without_prefix(self):
135        parser = envparse.EnvironmentParser(prefix='PW_')
136        with self.assertRaises(ValueError):
137            parser.add_var('FOO')
138
139
140class TestStrictBool(unittest.TestCase):
141    """Tests for envparse.strict_bool."""
142    def setUp(self):
143        self.good_bools = ['true', '1', 'TRUE', 'tRuE']
144        self.bad_bools = [
145            '', 'false', '0', 'foo', '2', '999', 'ok', 'yes', 'no'
146        ]
147
148    def test_good_bools(self):
149        self.assertTrue(
150            all(envparse.strict_bool(val) for val in self.good_bools))
151
152    def test_bad_bools(self):
153        self.assertFalse(
154            any(envparse.strict_bool(val) for val in self.bad_bools))
155
156
157if __name__ == '__main__':
158    unittest.main()
159