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 absolute_import, print_function, unicode_literals 6 7import contextlib 8import os 9import sys 10import textwrap 11import traceback 12import unittest 13 14from mozunit import ( 15 main, 16 MockedOpen, 17) 18 19from mozbuild.configure import ConfigureError 20from mozbuild.configure.lint import LintSandbox 21 22import mozpack.path as mozpath 23 24test_data_path = mozpath.abspath(mozpath.dirname(__file__)) 25test_data_path = mozpath.join(test_data_path, "data") 26 27 28class TestLint(unittest.TestCase): 29 def lint_test(self, options=[], env={}): 30 sandbox = LintSandbox(env, ["configure"] + options) 31 32 sandbox.run(mozpath.join(test_data_path, "moz.configure")) 33 34 def moz_configure(self, source): 35 return MockedOpen( 36 {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)} 37 ) 38 39 @contextlib.contextmanager 40 def assertRaisesFromLine(self, exc_type, line): 41 with self.assertRaises(exc_type) as e: 42 yield e 43 44 _, _, tb = sys.exc_info() 45 self.assertEquals( 46 traceback.extract_tb(tb)[-1][:2], 47 (mozpath.join(test_data_path, "moz.configure"), line), 48 ) 49 50 def test_configure_testcase(self): 51 # Lint python/mozbuild/mozbuild/test/configure/data/moz.configure 52 self.lint_test() 53 54 def test_depends_failures(self): 55 with self.moz_configure( 56 """ 57 option('--foo', help='foo') 58 @depends('--foo') 59 def foo(value): 60 return value 61 62 @depends('--help', foo) 63 @imports('os') 64 def bar(help, foo): 65 return foo 66 """ 67 ): 68 self.lint_test() 69 70 with self.assertRaisesFromLine(ConfigureError, 7) as e: 71 with self.moz_configure( 72 """ 73 option('--foo', help='foo') 74 @depends('--foo') 75 def foo(value): 76 return value 77 78 @depends('--help', foo) 79 def bar(help, foo): 80 return foo 81 """ 82 ): 83 self.lint_test() 84 85 self.assertEquals(str(e.exception), "The dependency on `--help` is unused") 86 87 with self.assertRaisesFromLine(ConfigureError, 3) as e: 88 with self.moz_configure( 89 """ 90 option('--foo', help='foo') 91 @depends('--foo') 92 @imports('os') 93 def foo(value): 94 return value 95 96 @depends('--help', foo) 97 @imports('os') 98 def bar(help, foo): 99 return foo 100 """ 101 ): 102 self.lint_test() 103 104 self.assertEquals( 105 str(e.exception), 106 "Missing '--help' dependency because `bar` depends on '--help' and `foo`", 107 ) 108 109 with self.assertRaisesFromLine(ConfigureError, 7) as e: 110 with self.moz_configure( 111 """ 112 @template 113 def tmpl(): 114 qux = 42 115 116 option('--foo', help='foo') 117 @depends('--foo') 118 def foo(value): 119 qux 120 return value 121 122 @depends('--help', foo) 123 @imports('os') 124 def bar(help, foo): 125 return foo 126 tmpl() 127 """ 128 ): 129 self.lint_test() 130 131 self.assertEquals( 132 str(e.exception), 133 "Missing '--help' dependency because `bar` depends on '--help' and `foo`", 134 ) 135 136 with self.moz_configure( 137 """ 138 option('--foo', help='foo') 139 @depends('--foo') 140 def foo(value): 141 return value 142 143 include(foo) 144 """ 145 ): 146 self.lint_test() 147 148 with self.assertRaisesFromLine(ConfigureError, 3) as e: 149 with self.moz_configure( 150 """ 151 option('--foo', help='foo') 152 @depends('--foo') 153 @imports('os') 154 def foo(value): 155 return value 156 157 include(foo) 158 """ 159 ): 160 self.lint_test() 161 162 self.assertEquals(str(e.exception), "Missing '--help' dependency") 163 164 with self.assertRaisesFromLine(ConfigureError, 3) as e: 165 with self.moz_configure( 166 """ 167 option('--foo', help='foo') 168 @depends('--foo') 169 @imports('os') 170 def foo(value): 171 return value 172 173 @depends(foo) 174 def bar(value): 175 return value 176 177 include(bar) 178 """ 179 ): 180 self.lint_test() 181 182 self.assertEquals(str(e.exception), "Missing '--help' dependency") 183 184 with self.assertRaisesFromLine(ConfigureError, 3) as e: 185 with self.moz_configure( 186 """ 187 option('--foo', help='foo') 188 @depends('--foo') 189 @imports('os') 190 def foo(value): 191 return value 192 193 option('--bar', help='bar', when=foo) 194 """ 195 ): 196 self.lint_test() 197 198 self.assertEquals(str(e.exception), "Missing '--help' dependency") 199 200 # This would have failed with "Missing '--help' dependency" 201 # in the past, because of the reference to the builtin False. 202 with self.moz_configure( 203 """ 204 option('--foo', help='foo') 205 @depends('--foo') 206 def foo(value): 207 return False or value 208 209 option('--bar', help='bar', when=foo) 210 """ 211 ): 212 self.lint_test() 213 214 # However, when something that is normally a builtin is overridden, 215 # we should still want the dependency on --help. 216 with self.assertRaisesFromLine(ConfigureError, 7) as e: 217 with self.moz_configure( 218 """ 219 @template 220 def tmpl(): 221 sorted = 42 222 223 option('--foo', help='foo') 224 @depends('--foo') 225 def foo(value): 226 return sorted 227 228 option('--bar', help='bar', when=foo) 229 tmpl() 230 """ 231 ): 232 self.lint_test() 233 234 self.assertEquals(str(e.exception), "Missing '--help' dependency") 235 236 # There is a default restricted `os` module when there is no explicit 237 # @imports, and it's fine to use it without a dependency on --help. 238 with self.moz_configure( 239 """ 240 option('--foo', help='foo') 241 @depends('--foo') 242 def foo(value): 243 os 244 return value 245 246 include(foo) 247 """ 248 ): 249 self.lint_test() 250 251 with self.assertRaisesFromLine(ConfigureError, 3) as e: 252 with self.moz_configure( 253 """ 254 option('--foo', help='foo') 255 @depends('--foo') 256 def foo(value): 257 return 258 259 include(foo) 260 """ 261 ): 262 self.lint_test() 263 264 self.assertEquals(str(e.exception), "The dependency on `--foo` is unused") 265 266 with self.assertRaisesFromLine(ConfigureError, 5) as e: 267 with self.moz_configure( 268 """ 269 @depends(when=True) 270 def bar(): 271 return 272 @depends(bar) 273 def foo(value): 274 return 275 276 include(foo) 277 """ 278 ): 279 self.lint_test() 280 281 self.assertEquals(str(e.exception), "The dependency on `bar` is unused") 282 283 with self.assertRaisesFromLine(ConfigureError, 2) as e: 284 with self.moz_configure( 285 """ 286 @depends(depends(when=True)(lambda: None)) 287 def foo(value): 288 return 289 290 include(foo) 291 """ 292 ): 293 self.lint_test() 294 295 self.assertEquals(str(e.exception), "The dependency on `<lambda>` is unused") 296 297 with self.assertRaisesFromLine(ConfigureError, 9) as e: 298 with self.moz_configure( 299 """ 300 @template 301 def tmpl(): 302 @depends(when=True) 303 def bar(): 304 return 305 return bar 306 qux = tmpl() 307 @depends(qux) 308 def foo(value): 309 return 310 311 include(foo) 312 """ 313 ): 314 self.lint_test() 315 316 self.assertEquals(str(e.exception), "The dependency on `qux` is unused") 317 318 def test_default_enable(self): 319 # --enable-* with default=True is not allowed. 320 with self.moz_configure( 321 """ 322 option('--enable-foo', default=False, help='foo') 323 """ 324 ): 325 self.lint_test() 326 with self.assertRaisesFromLine(ConfigureError, 2) as e: 327 with self.moz_configure( 328 """ 329 option('--enable-foo', default=True, help='foo') 330 """ 331 ): 332 self.lint_test() 333 self.assertEquals( 334 str(e.exception), 335 "--disable-foo should be used instead of " "--enable-foo with default=True", 336 ) 337 338 def test_default_disable(self): 339 # --disable-* with default=False is not allowed. 340 with self.moz_configure( 341 """ 342 option('--disable-foo', default=True, help='foo') 343 """ 344 ): 345 self.lint_test() 346 with self.assertRaisesFromLine(ConfigureError, 2) as e: 347 with self.moz_configure( 348 """ 349 option('--disable-foo', default=False, help='foo') 350 """ 351 ): 352 self.lint_test() 353 self.assertEquals( 354 str(e.exception), 355 "--enable-foo should be used instead of " 356 "--disable-foo with default=False", 357 ) 358 359 def test_default_with(self): 360 # --with-* with default=True is not allowed. 361 with self.moz_configure( 362 """ 363 option('--with-foo', default=False, help='foo') 364 """ 365 ): 366 self.lint_test() 367 with self.assertRaisesFromLine(ConfigureError, 2) as e: 368 with self.moz_configure( 369 """ 370 option('--with-foo', default=True, help='foo') 371 """ 372 ): 373 self.lint_test() 374 self.assertEquals( 375 str(e.exception), 376 "--without-foo should be used instead of " "--with-foo with default=True", 377 ) 378 379 def test_default_without(self): 380 # --without-* with default=False is not allowed. 381 with self.moz_configure( 382 """ 383 option('--without-foo', default=True, help='foo') 384 """ 385 ): 386 self.lint_test() 387 with self.assertRaisesFromLine(ConfigureError, 2) as e: 388 with self.moz_configure( 389 """ 390 option('--without-foo', default=False, help='foo') 391 """ 392 ): 393 self.lint_test() 394 self.assertEquals( 395 str(e.exception), 396 "--with-foo should be used instead of " "--without-foo with default=False", 397 ) 398 399 def test_default_func(self): 400 # Help text for an option with variable default should contain 401 # {enable|disable} rule. 402 with self.moz_configure( 403 """ 404 option(env='FOO', help='foo') 405 option('--enable-bar', default=depends('FOO')(lambda x: bool(x)), 406 help='{Enable|Disable} bar') 407 """ 408 ): 409 self.lint_test() 410 with self.assertRaisesFromLine(ConfigureError, 3) as e: 411 with self.moz_configure( 412 """ 413 option(env='FOO', help='foo') 414 option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\ 415 help='Enable bar') 416 """ 417 ): 418 self.lint_test() 419 self.assertEquals( 420 str(e.exception), 421 '`help` should contain "{Enable|Disable}" because of ' 422 "non-constant default", 423 ) 424 425 def test_large_offset(self): 426 with self.assertRaisesFromLine(ConfigureError, 375): 427 with self.moz_configure( 428 """ 429 option(env='FOO', help='foo') 430 """ 431 + "\n" * 371 432 + """ 433 option('--enable-bar', default=depends('FOO')(lambda x: bool(x)),\ 434 help='Enable bar') 435 """ 436 ): 437 self.lint_test() 438 439 def test_undefined_global(self): 440 with self.assertRaisesFromLine(NameError, 6) as e: 441 with self.moz_configure( 442 """ 443 option(env='FOO', help='foo') 444 @depends('FOO') 445 def foo(value): 446 if value: 447 return unknown 448 return value 449 """ 450 ): 451 self.lint_test() 452 453 self.assertEquals(str(e.exception), "global name 'unknown' is not defined") 454 455 # Ideally, this would raise on line 4, where `unknown` is used, but 456 # python disassembly doesn't give use the information. 457 with self.assertRaisesFromLine(NameError, 2) as e: 458 with self.moz_configure( 459 """ 460 @template 461 def tmpl(): 462 @depends(unknown) 463 def foo(value): 464 if value: 465 return True 466 return foo 467 tmpl() 468 """ 469 ): 470 self.lint_test() 471 472 self.assertEquals(str(e.exception), "global name 'unknown' is not defined") 473 474 def test_unnecessary_imports(self): 475 with self.assertRaisesFromLine(NameError, 3) as e: 476 with self.moz_configure( 477 """ 478 option(env='FOO', help='foo') 479 @depends('FOO') 480 @imports(_from='__builtin__', _import='list') 481 def foo(value): 482 if value: 483 return list() 484 return value 485 """ 486 ): 487 self.lint_test() 488 489 self.assertEquals( 490 str(e.exception), "builtin 'list' doesn't need to be imported" 491 ) 492 493 494if __name__ == "__main__": 495 main() 496