1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this file, 3# You can obtain one at http://mozilla.org/MPL/2.0/. 4from __future__ import absolute_import 5from __future__ import unicode_literals 6 7import sys 8import unittest 9 10from mozfile.mozfile import NamedTemporaryFile 11 12from mach.config import ( 13 BooleanType, 14 ConfigException, 15 ConfigSettings, 16 IntegerType, 17 PathType, 18 PositiveIntegerType, 19 StringType, 20) 21from mach.decorators import SettingsProvider 22from mozunit import main 23from six import string_types 24 25 26CONFIG1 = r""" 27[foo] 28 29bar = bar_value 30baz = /baz/foo.c 31""" 32 33CONFIG2 = r""" 34[foo] 35 36bar = value2 37""" 38 39 40@SettingsProvider 41class Provider1(object): 42 config_settings = [ 43 ("foo.bar", StringType, "desc"), 44 ("foo.baz", PathType, "desc"), 45 ] 46 47 48@SettingsProvider 49class ProviderDuplicate(object): 50 config_settings = [ 51 ("dupesect.foo", StringType, "desc"), 52 ("dupesect.foo", StringType, "desc"), 53 ] 54 55 56@SettingsProvider 57class Provider2(object): 58 config_settings = [ 59 ("a.string", StringType, "desc"), 60 ("a.boolean", BooleanType, "desc"), 61 ("a.pos_int", PositiveIntegerType, "desc"), 62 ("a.int", IntegerType, "desc"), 63 ("a.path", PathType, "desc"), 64 ] 65 66 67@SettingsProvider 68class Provider3(object): 69 @classmethod 70 def config_settings(cls): 71 return [ 72 ("a.string", "string", "desc"), 73 ("a.boolean", "boolean", "desc"), 74 ("a.pos_int", "pos_int", "desc"), 75 ("a.int", "int", "desc"), 76 ("a.path", "path", "desc"), 77 ] 78 79 80@SettingsProvider 81class Provider4(object): 82 config_settings = [ 83 ("foo.abc", StringType, "desc", "a", {"choices": set("abc")}), 84 ("foo.xyz", StringType, "desc", "w", {"choices": set("xyz")}), 85 ] 86 87 88@SettingsProvider 89class Provider5(object): 90 config_settings = [ 91 ("foo.*", "string", "desc"), 92 ("foo.bar", "string", "desc"), 93 ] 94 95 96class TestConfigSettings(unittest.TestCase): 97 def test_empty(self): 98 s = ConfigSettings() 99 100 self.assertEqual(len(s), 0) 101 self.assertNotIn("foo", s) 102 103 def test_duplicate_option(self): 104 s = ConfigSettings() 105 106 with self.assertRaises(ConfigException): 107 s.register_provider(ProviderDuplicate) 108 109 def test_simple(self): 110 s = ConfigSettings() 111 s.register_provider(Provider1) 112 113 self.assertEqual(len(s), 1) 114 self.assertIn("foo", s) 115 116 foo = s["foo"] 117 foo = s.foo 118 119 self.assertEqual(len(foo), 0) 120 self.assertEqual(len(foo._settings), 2) 121 122 self.assertIn("bar", foo._settings) 123 self.assertIn("baz", foo._settings) 124 125 self.assertNotIn("bar", foo) 126 foo["bar"] = "value1" 127 self.assertIn("bar", foo) 128 129 self.assertEqual(foo["bar"], "value1") 130 self.assertEqual(foo.bar, "value1") 131 132 def test_assignment_validation(self): 133 s = ConfigSettings() 134 s.register_provider(Provider2) 135 136 a = s.a 137 138 # Assigning an undeclared setting raises. 139 exc_type = AttributeError if sys.version_info < (3, 0) else KeyError 140 with self.assertRaises(exc_type): 141 a.undefined = True 142 143 with self.assertRaises(KeyError): 144 a["undefined"] = True 145 146 # Basic type validation. 147 a.string = "foo" 148 a.string = "foo" 149 150 with self.assertRaises(TypeError): 151 a.string = False 152 153 a.boolean = True 154 a.boolean = False 155 156 with self.assertRaises(TypeError): 157 a.boolean = "foo" 158 159 a.pos_int = 5 160 a.pos_int = 0 161 162 with self.assertRaises(ValueError): 163 a.pos_int = -1 164 165 with self.assertRaises(TypeError): 166 a.pos_int = "foo" 167 168 a.int = 5 169 a.int = 0 170 a.int = -5 171 172 with self.assertRaises(TypeError): 173 a.int = 1.24 174 175 with self.assertRaises(TypeError): 176 a.int = "foo" 177 178 a.path = "/home/gps" 179 a.path = "foo.c" 180 a.path = "foo/bar" 181 a.path = "./foo" 182 183 def retrieval_type_helper(self, provider): 184 s = ConfigSettings() 185 s.register_provider(provider) 186 187 a = s.a 188 189 a.string = "foo" 190 a.boolean = True 191 a.pos_int = 12 192 a.int = -4 193 a.path = "./foo/bar" 194 195 self.assertIsInstance(a.string, string_types) 196 self.assertIsInstance(a.boolean, bool) 197 self.assertIsInstance(a.pos_int, int) 198 self.assertIsInstance(a.int, int) 199 self.assertIsInstance(a.path, string_types) 200 201 def test_retrieval_type(self): 202 self.retrieval_type_helper(Provider2) 203 self.retrieval_type_helper(Provider3) 204 205 def test_choices_validation(self): 206 s = ConfigSettings() 207 s.register_provider(Provider4) 208 209 foo = s.foo 210 foo.abc 211 with self.assertRaises(ValueError): 212 foo.xyz 213 214 with self.assertRaises(ValueError): 215 foo.abc = "e" 216 217 foo.abc = "b" 218 foo.xyz = "y" 219 220 def test_wildcard_options(self): 221 s = ConfigSettings() 222 s.register_provider(Provider5) 223 224 foo = s.foo 225 226 self.assertIn("*", foo._settings) 227 self.assertNotIn("*", foo) 228 229 foo.baz = "value1" 230 foo.bar = "value2" 231 232 self.assertIn("baz", foo) 233 self.assertEqual(foo.baz, "value1") 234 235 self.assertIn("bar", foo) 236 self.assertEqual(foo.bar, "value2") 237 238 def test_file_reading_single(self): 239 temp = NamedTemporaryFile(mode="wt") 240 temp.write(CONFIG1) 241 temp.flush() 242 243 s = ConfigSettings() 244 s.register_provider(Provider1) 245 246 s.load_file(temp.name) 247 248 self.assertEqual(s.foo.bar, "bar_value") 249 250 def test_file_reading_multiple(self): 251 """Loading multiple files has proper overwrite behavior.""" 252 temp1 = NamedTemporaryFile(mode="wt") 253 temp1.write(CONFIG1) 254 temp1.flush() 255 256 temp2 = NamedTemporaryFile(mode="wt") 257 temp2.write(CONFIG2) 258 temp2.flush() 259 260 s = ConfigSettings() 261 s.register_provider(Provider1) 262 263 s.load_files([temp1.name, temp2.name]) 264 265 self.assertEqual(s.foo.bar, "value2") 266 267 def test_file_reading_missing(self): 268 """Missing files should silently be ignored.""" 269 270 s = ConfigSettings() 271 272 s.load_file("/tmp/foo.ini") 273 274 def test_file_writing(self): 275 s = ConfigSettings() 276 s.register_provider(Provider2) 277 278 s.a.string = "foo" 279 s.a.boolean = False 280 281 temp = NamedTemporaryFile("wt") 282 s.write(temp) 283 temp.flush() 284 285 s2 = ConfigSettings() 286 s2.register_provider(Provider2) 287 288 s2.load_file(temp.name) 289 290 self.assertEqual(s.a.string, s2.a.string) 291 self.assertEqual(s.a.boolean, s2.a.boolean) 292 293 294if __name__ == "__main__": 295 main() 296