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 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import unicode_literals 6 7import os 8import unittest 9 10from shutil import rmtree 11 12from tempfile import ( 13 gettempdir, 14 mkdtemp, 15) 16 17from mozfile.mozfile import NamedTemporaryFile 18 19from mozunit import main 20 21from mozbuild.mozconfig import ( 22 MozconfigFindException, 23 MozconfigLoadException, 24 MozconfigLoader, 25) 26 27 28class TestMozconfigLoader(unittest.TestCase): 29 def setUp(self): 30 self._old_env = dict(os.environ) 31 os.environ.pop('MOZCONFIG', None) 32 os.environ.pop('MOZ_OBJDIR', None) 33 os.environ.pop('CC', None) 34 os.environ.pop('CXX', None) 35 self._temp_dirs = set() 36 37 def tearDown(self): 38 os.environ.clear() 39 os.environ.update(self._old_env) 40 41 for d in self._temp_dirs: 42 rmtree(d) 43 44 def get_loader(self): 45 return MozconfigLoader(self.get_temp_dir()) 46 47 def get_temp_dir(self): 48 d = mkdtemp() 49 self._temp_dirs.add(d) 50 51 return d 52 53 def test_find_legacy_env(self): 54 """Ensure legacy mozconfig path definitions result in error.""" 55 56 os.environ[b'MOZ_MYCONFIG'] = '/foo' 57 58 with self.assertRaises(MozconfigFindException) as e: 59 self.get_loader().find_mozconfig() 60 61 self.assertTrue(e.exception.message.startswith('The MOZ_MYCONFIG')) 62 63 def test_find_multiple_configs(self): 64 """Ensure multiple relative-path MOZCONFIGs result in error.""" 65 relative_mozconfig = '.mconfig' 66 os.environ[b'MOZCONFIG'] = relative_mozconfig 67 68 srcdir = self.get_temp_dir() 69 curdir = self.get_temp_dir() 70 dirs = [srcdir, curdir] 71 loader = MozconfigLoader(srcdir) 72 for d in dirs: 73 path = os.path.join(d, relative_mozconfig) 74 with open(path, 'wb') as f: 75 f.write(path) 76 77 orig_dir = os.getcwd() 78 try: 79 os.chdir(curdir) 80 with self.assertRaises(MozconfigFindException) as e: 81 loader.find_mozconfig() 82 finally: 83 os.chdir(orig_dir) 84 85 self.assertIn('exists in more than one of', e.exception.message) 86 for d in dirs: 87 self.assertIn(d, e.exception.message) 88 89 def test_find_multiple_but_identical_configs(self): 90 """Ensure multiple relative-path MOZCONFIGs pointing at the same file are OK.""" 91 relative_mozconfig = '../src/.mconfig' 92 os.environ[b'MOZCONFIG'] = relative_mozconfig 93 94 topdir = self.get_temp_dir() 95 srcdir = os.path.join(topdir, 'src') 96 os.mkdir(srcdir) 97 curdir = os.path.join(topdir, 'obj') 98 os.mkdir(curdir) 99 100 loader = MozconfigLoader(srcdir) 101 path = os.path.join(srcdir, relative_mozconfig) 102 with open(path, 'w'): 103 pass 104 105 orig_dir = os.getcwd() 106 try: 107 os.chdir(curdir) 108 self.assertEqual(os.path.realpath(loader.find_mozconfig()), 109 os.path.realpath(path)) 110 finally: 111 os.chdir(orig_dir) 112 113 def test_find_no_relative_configs(self): 114 """Ensure a missing relative-path MOZCONFIG is detected.""" 115 relative_mozconfig = '.mconfig' 116 os.environ[b'MOZCONFIG'] = relative_mozconfig 117 118 srcdir = self.get_temp_dir() 119 curdir = self.get_temp_dir() 120 dirs = [srcdir, curdir] 121 loader = MozconfigLoader(srcdir) 122 123 orig_dir = os.getcwd() 124 try: 125 os.chdir(curdir) 126 with self.assertRaises(MozconfigFindException) as e: 127 loader.find_mozconfig() 128 finally: 129 os.chdir(orig_dir) 130 131 self.assertIn('does not exist in any of', e.exception.message) 132 for d in dirs: 133 self.assertIn(d, e.exception.message) 134 135 def test_find_relative_mozconfig(self): 136 """Ensure a relative MOZCONFIG can be found in the srcdir.""" 137 relative_mozconfig = '.mconfig' 138 os.environ[b'MOZCONFIG'] = relative_mozconfig 139 140 srcdir = self.get_temp_dir() 141 curdir = self.get_temp_dir() 142 dirs = [srcdir, curdir] 143 loader = MozconfigLoader(srcdir) 144 145 path = os.path.join(srcdir, relative_mozconfig) 146 with open(path, 'w'): 147 pass 148 149 orig_dir = os.getcwd() 150 try: 151 os.chdir(curdir) 152 self.assertEqual(os.path.normpath(loader.find_mozconfig()), 153 os.path.normpath(path)) 154 finally: 155 os.chdir(orig_dir) 156 157 def test_find_abs_path_not_exist(self): 158 """Ensure a missing absolute path is detected.""" 159 os.environ[b'MOZCONFIG'] = '/foo/bar/does/not/exist' 160 161 with self.assertRaises(MozconfigFindException) as e: 162 self.get_loader().find_mozconfig() 163 164 self.assertIn('path that does not exist', e.exception.message) 165 self.assertTrue(e.exception.message.endswith('/foo/bar/does/not/exist')) 166 167 def test_find_path_not_file(self): 168 """Ensure non-file paths are detected.""" 169 170 os.environ[b'MOZCONFIG'] = gettempdir() 171 172 with self.assertRaises(MozconfigFindException) as e: 173 self.get_loader().find_mozconfig() 174 175 self.assertIn('refers to a non-file', e.exception.message) 176 self.assertTrue(e.exception.message.endswith(gettempdir())) 177 178 def test_find_default_files(self): 179 """Ensure default paths are used when present.""" 180 for p in MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS: 181 d = self.get_temp_dir() 182 path = os.path.join(d, p) 183 184 with open(path, 'w'): 185 pass 186 187 self.assertEqual(MozconfigLoader(d).find_mozconfig(), path) 188 189 def test_find_multiple_defaults(self): 190 """Ensure we error when multiple default files are present.""" 191 self.assertGreater(len(MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS), 1) 192 193 d = self.get_temp_dir() 194 for p in MozconfigLoader.DEFAULT_TOPSRCDIR_PATHS: 195 with open(os.path.join(d, p), 'w'): 196 pass 197 198 with self.assertRaises(MozconfigFindException) as e: 199 MozconfigLoader(d).find_mozconfig() 200 201 self.assertIn('Multiple default mozconfig files present', 202 e.exception.message) 203 204 def test_find_deprecated_path_srcdir(self): 205 """Ensure we error when deprecated path locations are present.""" 206 for p in MozconfigLoader.DEPRECATED_TOPSRCDIR_PATHS: 207 d = self.get_temp_dir() 208 with open(os.path.join(d, p), 'w'): 209 pass 210 211 with self.assertRaises(MozconfigFindException) as e: 212 MozconfigLoader(d).find_mozconfig() 213 214 self.assertIn('This implicit location is no longer', 215 e.exception.message) 216 self.assertIn(d, e.exception.message) 217 218 def test_find_deprecated_home_paths(self): 219 """Ensure we error when deprecated home directory paths are present.""" 220 221 for p in MozconfigLoader.DEPRECATED_HOME_PATHS: 222 home = self.get_temp_dir() 223 os.environ[b'HOME'] = home 224 path = os.path.join(home, p) 225 226 with open(path, 'w'): 227 pass 228 229 with self.assertRaises(MozconfigFindException) as e: 230 self.get_loader().find_mozconfig() 231 232 self.assertIn('This implicit location is no longer', 233 e.exception.message) 234 self.assertIn(path, e.exception.message) 235 236 def test_read_no_mozconfig(self): 237 # This is basically to ensure changes to defaults incur a test failure. 238 result = self.get_loader().read_mozconfig() 239 240 self.assertEqual(result, { 241 'path': None, 242 'topobjdir': None, 243 'configure_args': None, 244 'make_flags': None, 245 'make_extra': None, 246 'env': None, 247 'vars': None, 248 }) 249 250 def test_read_empty_mozconfig(self): 251 with NamedTemporaryFile(mode='w') as mozconfig: 252 result = self.get_loader().read_mozconfig(mozconfig.name) 253 254 self.assertEqual(result['path'], mozconfig.name) 255 self.assertIsNone(result['topobjdir']) 256 self.assertEqual(result['configure_args'], []) 257 self.assertEqual(result['make_flags'], []) 258 self.assertEqual(result['make_extra'], []) 259 260 for f in ('added', 'removed', 'modified'): 261 self.assertEqual(len(result['vars'][f]), 0) 262 self.assertEqual(len(result['env'][f]), 0) 263 264 self.assertEqual(result['env']['unmodified'], {}) 265 266 def test_read_capture_ac_options(self): 267 """Ensures ac_add_options calls are captured.""" 268 with NamedTemporaryFile(mode='w') as mozconfig: 269 mozconfig.write('ac_add_options --enable-debug\n') 270 mozconfig.write('ac_add_options --disable-tests --enable-foo\n') 271 mozconfig.write('ac_add_options --foo="bar baz"\n') 272 mozconfig.flush() 273 274 result = self.get_loader().read_mozconfig(mozconfig.name) 275 self.assertEqual(result['configure_args'], [ 276 '--enable-debug', '--disable-tests', '--enable-foo', 277 '--foo=bar baz']) 278 279 def test_read_ac_options_substitution(self): 280 """Ensure ac_add_options values are substituted.""" 281 with NamedTemporaryFile(mode='w') as mozconfig: 282 mozconfig.write('ac_add_options --foo=@TOPSRCDIR@\n') 283 mozconfig.flush() 284 285 loader = self.get_loader() 286 result = loader.read_mozconfig(mozconfig.name) 287 self.assertEqual(result['configure_args'], [ 288 '--foo=%s' % loader.topsrcdir]) 289 290 def test_read_capture_mk_options(self): 291 """Ensures mk_add_options calls are captured.""" 292 with NamedTemporaryFile(mode='w') as mozconfig: 293 mozconfig.write('mk_add_options MOZ_OBJDIR=/foo/bar\n') 294 mozconfig.write('mk_add_options MOZ_MAKE_FLAGS="-j8 -s"\n') 295 mozconfig.write('mk_add_options FOO="BAR BAZ"\n') 296 mozconfig.write('mk_add_options BIZ=1\n') 297 mozconfig.flush() 298 299 result = self.get_loader().read_mozconfig(mozconfig.name) 300 self.assertEqual(result['topobjdir'], '/foo/bar') 301 self.assertEqual(result['make_flags'], ['-j8', '-s']) 302 self.assertEqual(result['make_extra'], ['FOO=BAR BAZ', 'BIZ=1']) 303 304 def test_read_empty_mozconfig_objdir_environ(self): 305 os.environ[b'MOZ_OBJDIR'] = b'obj-firefox' 306 with NamedTemporaryFile(mode='w') as mozconfig: 307 result = self.get_loader().read_mozconfig(mozconfig.name) 308 self.assertEqual(result['topobjdir'], 'obj-firefox') 309 310 def test_read_capture_mk_options_objdir_environ(self): 311 """Ensures mk_add_options calls are captured and override the environ.""" 312 os.environ[b'MOZ_OBJDIR'] = b'obj-firefox' 313 with NamedTemporaryFile(mode='w') as mozconfig: 314 mozconfig.write('mk_add_options MOZ_OBJDIR=/foo/bar\n') 315 mozconfig.flush() 316 317 result = self.get_loader().read_mozconfig(mozconfig.name) 318 self.assertEqual(result['topobjdir'], '/foo/bar') 319 320 def test_read_moz_objdir_substitution(self): 321 """Ensure @TOPSRCDIR@ substitution is recognized in MOZ_OBJDIR.""" 322 with NamedTemporaryFile(mode='w') as mozconfig: 323 mozconfig.write('mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/some-objdir') 324 mozconfig.flush() 325 326 loader = self.get_loader() 327 result = loader.read_mozconfig(mozconfig.name) 328 329 self.assertEqual(result['topobjdir'], '%s/some-objdir' % 330 loader.topsrcdir) 331 332 def test_read_new_variables(self): 333 """New variables declared in mozconfig file are detected.""" 334 with NamedTemporaryFile(mode='w') as mozconfig: 335 mozconfig.write('CC=/usr/local/bin/clang\n') 336 mozconfig.write('CXX=/usr/local/bin/clang++\n') 337 mozconfig.flush() 338 339 result = self.get_loader().read_mozconfig(mozconfig.name) 340 341 self.assertEqual(result['vars']['added'], { 342 'CC': '/usr/local/bin/clang', 343 'CXX': '/usr/local/bin/clang++'}) 344 self.assertEqual(result['env']['added'], {}) 345 346 def test_read_exported_variables(self): 347 """Exported variables are caught as new variables.""" 348 with NamedTemporaryFile(mode='w') as mozconfig: 349 mozconfig.write('export MY_EXPORTED=woot\n') 350 mozconfig.flush() 351 352 result = self.get_loader().read_mozconfig(mozconfig.name) 353 354 self.assertEqual(result['vars']['added'], {}) 355 self.assertEqual(result['env']['added'], { 356 'MY_EXPORTED': 'woot'}) 357 358 def test_read_modify_variables(self): 359 """Variables modified by mozconfig are detected.""" 360 old_path = os.path.realpath(b'/usr/bin/gcc') 361 new_path = os.path.realpath(b'/usr/local/bin/clang') 362 os.environ[b'CC'] = old_path 363 364 with NamedTemporaryFile(mode='w') as mozconfig: 365 mozconfig.write('CC="%s"\n' % new_path) 366 mozconfig.flush() 367 368 result = self.get_loader().read_mozconfig(mozconfig.name) 369 370 self.assertEqual(result['vars']['modified'], {}) 371 self.assertEqual(result['env']['modified'], { 372 'CC': (old_path, new_path) 373 }) 374 375 def test_read_unmodified_variables(self): 376 """Variables modified by mozconfig are detected.""" 377 cc_path = os.path.realpath(b'/usr/bin/gcc') 378 os.environ[b'CC'] = cc_path 379 380 with NamedTemporaryFile(mode='w') as mozconfig: 381 mozconfig.flush() 382 383 result = self.get_loader().read_mozconfig(mozconfig.name) 384 385 self.assertEqual(result['vars']['unmodified'], {}) 386 self.assertEqual(result['env']['unmodified'], { 387 'CC': cc_path 388 }) 389 390 def test_read_removed_variables(self): 391 """Variables unset by the mozconfig are detected.""" 392 cc_path = os.path.realpath(b'/usr/bin/clang') 393 os.environ[b'CC'] = cc_path 394 395 with NamedTemporaryFile(mode='w') as mozconfig: 396 mozconfig.write('unset CC\n') 397 mozconfig.flush() 398 399 result = self.get_loader().read_mozconfig(mozconfig.name) 400 401 self.assertEqual(result['vars']['removed'], {}) 402 self.assertEqual(result['env']['removed'], { 403 'CC': cc_path}) 404 405 def test_read_multiline_variables(self): 406 """Ensure multi-line variables are captured properly.""" 407 with NamedTemporaryFile(mode='w') as mozconfig: 408 mozconfig.write('multi="foo\nbar"\n') 409 mozconfig.write('single=1\n') 410 mozconfig.flush() 411 412 result = self.get_loader().read_mozconfig(mozconfig.name) 413 414 self.assertEqual(result['vars']['added'], { 415 'multi': 'foo\nbar', 416 'single': '1' 417 }) 418 self.assertEqual(result['env']['added'], {}) 419 420 def test_read_topsrcdir_defined(self): 421 """Ensure $topsrcdir references work as expected.""" 422 with NamedTemporaryFile(mode='w') as mozconfig: 423 mozconfig.write('TEST=$topsrcdir') 424 mozconfig.flush() 425 426 loader = self.get_loader() 427 result = loader.read_mozconfig(mozconfig.name) 428 429 self.assertEqual(result['vars']['added']['TEST'], 430 loader.topsrcdir.replace(os.sep, '/')) 431 self.assertEqual(result['env']['added'], {}) 432 433 def test_read_empty_variable_value(self): 434 """Ensure empty variable values are parsed properly.""" 435 with NamedTemporaryFile(mode='w') as mozconfig: 436 mozconfig.write('EMPTY=\n') 437 mozconfig.write('export EXPORT_EMPTY=\n') 438 mozconfig.flush() 439 440 result = self.get_loader().read_mozconfig(mozconfig.name) 441 442 self.assertEqual(result['vars']['added'], { 443 'EMPTY': '', 444 }) 445 self.assertEqual(result['env']['added'], { 446 'EXPORT_EMPTY': '' 447 }) 448 449 def test_read_load_exception(self): 450 """Ensure non-0 exit codes in mozconfigs are handled properly.""" 451 with NamedTemporaryFile(mode='w') as mozconfig: 452 mozconfig.write('echo "hello world"\n') 453 mozconfig.write('exit 1\n') 454 mozconfig.flush() 455 456 with self.assertRaises(MozconfigLoadException) as e: 457 self.get_loader().read_mozconfig(mozconfig.name) 458 459 self.assertTrue(e.exception.message.startswith( 460 'Evaluation of your mozconfig exited with an error')) 461 self.assertEquals(e.exception.path, 462 mozconfig.name.replace(os.sep, '/')) 463 self.assertEquals(e.exception.output, ['hello world']) 464 465 466if __name__ == '__main__': 467 main() 468