1# Copyright 2018 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://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, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import tempfile 17import unittest 18from typing import Any, List, Optional 19 20from python.runfiles import runfiles 21 22 23class RunfilesTest(unittest.TestCase): 24 """Unit tests for `rules_python.python.runfiles.Runfiles`.""" 25 26 def testRlocationArgumentValidation(self) -> None: 27 r = runfiles.Create({"RUNFILES_DIR": "whatever"}) 28 assert r is not None # mypy doesn't understand the unittest api. 29 self.assertRaises(ValueError, lambda: r.Rlocation(None)) # type: ignore 30 self.assertRaises(ValueError, lambda: r.Rlocation("")) 31 self.assertRaises(TypeError, lambda: r.Rlocation(1)) # type: ignore 32 self.assertRaisesRegex( 33 ValueError, "is not normalized", lambda: r.Rlocation("../foo") 34 ) 35 self.assertRaisesRegex( 36 ValueError, "is not normalized", lambda: r.Rlocation("foo/..") 37 ) 38 self.assertRaisesRegex( 39 ValueError, "is not normalized", lambda: r.Rlocation("foo/../bar") 40 ) 41 self.assertRaisesRegex( 42 ValueError, "is not normalized", lambda: r.Rlocation("./foo") 43 ) 44 self.assertRaisesRegex( 45 ValueError, "is not normalized", lambda: r.Rlocation("foo/.") 46 ) 47 self.assertRaisesRegex( 48 ValueError, "is not normalized", lambda: r.Rlocation("foo/./bar") 49 ) 50 self.assertRaisesRegex( 51 ValueError, "is not normalized", lambda: r.Rlocation("//foobar") 52 ) 53 self.assertRaisesRegex( 54 ValueError, "is not normalized", lambda: r.Rlocation("foo//") 55 ) 56 self.assertRaisesRegex( 57 ValueError, "is not normalized", lambda: r.Rlocation("foo//bar") 58 ) 59 self.assertRaisesRegex( 60 ValueError, 61 "is absolute without a drive letter", 62 lambda: r.Rlocation("\\foo"), 63 ) 64 65 def testCreatesManifestBasedRunfiles(self) -> None: 66 with _MockFile(contents=["a/b c/d"]) as mf: 67 r = runfiles.Create( 68 { 69 "RUNFILES_MANIFEST_FILE": mf.Path(), 70 "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value", 71 "TEST_SRCDIR": "always ignored", 72 } 73 ) 74 assert r is not None # mypy doesn't understand the unittest api. 75 self.assertEqual(r.Rlocation("a/b"), "c/d") 76 self.assertIsNone(r.Rlocation("foo")) 77 78 def testManifestBasedRunfilesEnvVars(self) -> None: 79 with _MockFile(name="MANIFEST") as mf: 80 r = runfiles.Create( 81 { 82 "RUNFILES_MANIFEST_FILE": mf.Path(), 83 "TEST_SRCDIR": "always ignored", 84 } 85 ) 86 assert r is not None # mypy doesn't understand the unittest api. 87 self.assertDictEqual( 88 r.EnvVars(), 89 { 90 "RUNFILES_MANIFEST_FILE": mf.Path(), 91 "RUNFILES_DIR": mf.Path()[: -len("/MANIFEST")], 92 "JAVA_RUNFILES": mf.Path()[: -len("/MANIFEST")], 93 }, 94 ) 95 96 with _MockFile(name="foo.runfiles_manifest") as mf: 97 r = runfiles.Create( 98 { 99 "RUNFILES_MANIFEST_FILE": mf.Path(), 100 "TEST_SRCDIR": "always ignored", 101 } 102 ) 103 assert r is not None # mypy doesn't understand the unittest api. 104 self.assertDictEqual( 105 r.EnvVars(), 106 { 107 "RUNFILES_MANIFEST_FILE": mf.Path(), 108 "RUNFILES_DIR": ( 109 mf.Path()[: -len("foo.runfiles_manifest")] + "foo.runfiles" 110 ), 111 "JAVA_RUNFILES": ( 112 mf.Path()[: -len("foo.runfiles_manifest")] + "foo.runfiles" 113 ), 114 }, 115 ) 116 117 with _MockFile(name="x_manifest") as mf: 118 r = runfiles.Create( 119 { 120 "RUNFILES_MANIFEST_FILE": mf.Path(), 121 "TEST_SRCDIR": "always ignored", 122 } 123 ) 124 assert r is not None # mypy doesn't understand the unittest api. 125 self.assertDictEqual( 126 r.EnvVars(), 127 { 128 "RUNFILES_MANIFEST_FILE": mf.Path(), 129 "RUNFILES_DIR": "", 130 "JAVA_RUNFILES": "", 131 }, 132 ) 133 134 def testCreatesDirectoryBasedRunfiles(self) -> None: 135 r = runfiles.Create( 136 { 137 "RUNFILES_DIR": "runfiles/dir", 138 "TEST_SRCDIR": "always ignored", 139 } 140 ) 141 assert r is not None # mypy doesn't understand the unittest api. 142 self.assertEqual(r.Rlocation("a/b"), "runfiles/dir/a/b") 143 self.assertEqual(r.Rlocation("foo"), "runfiles/dir/foo") 144 145 def testDirectoryBasedRunfilesEnvVars(self) -> None: 146 r = runfiles.Create( 147 { 148 "RUNFILES_DIR": "runfiles/dir", 149 "TEST_SRCDIR": "always ignored", 150 } 151 ) 152 assert r is not None # mypy doesn't understand the unittest api. 153 self.assertDictEqual( 154 r.EnvVars(), 155 { 156 "RUNFILES_DIR": "runfiles/dir", 157 "JAVA_RUNFILES": "runfiles/dir", 158 }, 159 ) 160 161 def testFailsToCreateManifestBasedBecauseManifestDoesNotExist(self) -> None: 162 def _Run(): 163 runfiles.Create({"RUNFILES_MANIFEST_FILE": "non-existing path"}) 164 165 self.assertRaisesRegex(IOError, "non-existing path", _Run) 166 167 def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self) -> None: 168 with _MockFile(contents=["a b"]) as mf: 169 runfiles.Create( 170 { 171 "RUNFILES_MANIFEST_FILE": mf.Path(), 172 "RUNFILES_DIR": "whatever", 173 "TEST_SRCDIR": "always ignored", 174 } 175 ) 176 runfiles.Create( 177 { 178 "RUNFILES_DIR": "whatever", 179 "TEST_SRCDIR": "always ignored", 180 } 181 ) 182 self.assertIsNone(runfiles.Create({"TEST_SRCDIR": "always ignored"})) 183 self.assertIsNone(runfiles.Create({"FOO": "bar"})) 184 185 def testManifestBasedRlocation(self) -> None: 186 with _MockFile( 187 contents=[ 188 "Foo/runfile1", 189 "Foo/runfile2 C:/Actual Path\\runfile2", 190 "Foo/Bar/runfile3 D:\\the path\\run file 3.txt", 191 "Foo/Bar/Dir E:\\Actual Path\\Directory", 192 ] 193 ) as mf: 194 r = runfiles.CreateManifestBased(mf.Path()) 195 self.assertEqual(r.Rlocation("Foo/runfile1"), "Foo/runfile1") 196 self.assertEqual(r.Rlocation("Foo/runfile2"), "C:/Actual Path\\runfile2") 197 self.assertEqual( 198 r.Rlocation("Foo/Bar/runfile3"), "D:\\the path\\run file 3.txt" 199 ) 200 self.assertEqual( 201 r.Rlocation("Foo/Bar/Dir/runfile4"), 202 "E:\\Actual Path\\Directory/runfile4", 203 ) 204 self.assertEqual( 205 r.Rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4"), 206 "E:\\Actual Path\\Directory/Deeply/Nested/runfile4", 207 ) 208 self.assertIsNone(r.Rlocation("unknown")) 209 if RunfilesTest.IsWindows(): 210 self.assertEqual(r.Rlocation("c:/foo"), "c:/foo") 211 self.assertEqual(r.Rlocation("c:\\foo"), "c:\\foo") 212 else: 213 self.assertEqual(r.Rlocation("/foo"), "/foo") 214 215 def testManifestBasedRlocationWithRepoMappingFromMain(self) -> None: 216 with _MockFile( 217 contents=[ 218 ",config.json,config.json~1.2.3", 219 ",my_module,_main", 220 ",my_protobuf,protobuf~3.19.2", 221 ",my_workspace,_main", 222 "protobuf~3.19.2,config.json,config.json~1.2.3", 223 "protobuf~3.19.2,protobuf,protobuf~3.19.2", 224 ] 225 ) as rm, _MockFile( 226 contents=[ 227 "_repo_mapping " + rm.Path(), 228 "config.json /etc/config.json", 229 "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", 230 "_main/bar/runfile /the/path/./to/other//other runfile.txt", 231 "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory", 232 ], 233 ) as mf: 234 r = runfiles.CreateManifestBased(mf.Path()) 235 236 self.assertEqual( 237 r.Rlocation("my_module/bar/runfile", ""), 238 "/the/path/./to/other//other runfile.txt", 239 ) 240 self.assertEqual( 241 r.Rlocation("my_workspace/bar/runfile", ""), 242 "/the/path/./to/other//other runfile.txt", 243 ) 244 self.assertEqual( 245 r.Rlocation("my_protobuf/foo/runfile", ""), 246 "C:/Actual Path\\protobuf\\runfile", 247 ) 248 self.assertEqual( 249 r.Rlocation("my_protobuf/bar/dir", ""), "E:\\Actual Path\\Directory" 250 ) 251 self.assertEqual( 252 r.Rlocation("my_protobuf/bar/dir/file", ""), 253 "E:\\Actual Path\\Directory/file", 254 ) 255 self.assertEqual( 256 r.Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", ""), 257 "E:\\Actual Path\\Directory/de eply/nes ted/fi~le", 258 ) 259 260 self.assertIsNone(r.Rlocation("protobuf/foo/runfile")) 261 self.assertIsNone(r.Rlocation("protobuf/bar/dir")) 262 self.assertIsNone(r.Rlocation("protobuf/bar/dir/file")) 263 self.assertIsNone(r.Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")) 264 265 self.assertEqual( 266 r.Rlocation("_main/bar/runfile", ""), 267 "/the/path/./to/other//other runfile.txt", 268 ) 269 self.assertEqual( 270 r.Rlocation("protobuf~3.19.2/foo/runfile", ""), 271 "C:/Actual Path\\protobuf\\runfile", 272 ) 273 self.assertEqual( 274 r.Rlocation("protobuf~3.19.2/bar/dir", ""), "E:\\Actual Path\\Directory" 275 ) 276 self.assertEqual( 277 r.Rlocation("protobuf~3.19.2/bar/dir/file", ""), 278 "E:\\Actual Path\\Directory/file", 279 ) 280 self.assertEqual( 281 r.Rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", ""), 282 "E:\\Actual Path\\Directory/de eply/nes ted/fi~le", 283 ) 284 285 self.assertEqual(r.Rlocation("config.json", ""), "/etc/config.json") 286 self.assertIsNone(r.Rlocation("_main", "")) 287 self.assertIsNone(r.Rlocation("my_module", "")) 288 self.assertIsNone(r.Rlocation("protobuf", "")) 289 290 def testManifestBasedRlocationWithRepoMappingFromOtherRepo(self) -> None: 291 with _MockFile( 292 contents=[ 293 ",config.json,config.json~1.2.3", 294 ",my_module,_main", 295 ",my_protobuf,protobuf~3.19.2", 296 ",my_workspace,_main", 297 "protobuf~3.19.2,config.json,config.json~1.2.3", 298 "protobuf~3.19.2,protobuf,protobuf~3.19.2", 299 ] 300 ) as rm, _MockFile( 301 contents=[ 302 "_repo_mapping " + rm.Path(), 303 "config.json /etc/config.json", 304 "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", 305 "_main/bar/runfile /the/path/./to/other//other runfile.txt", 306 "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory", 307 ], 308 ) as mf: 309 r = runfiles.CreateManifestBased(mf.Path()) 310 311 self.assertEqual( 312 r.Rlocation("protobuf/foo/runfile", "protobuf~3.19.2"), 313 "C:/Actual Path\\protobuf\\runfile", 314 ) 315 self.assertEqual( 316 r.Rlocation("protobuf/bar/dir", "protobuf~3.19.2"), 317 "E:\\Actual Path\\Directory", 318 ) 319 self.assertEqual( 320 r.Rlocation("protobuf/bar/dir/file", "protobuf~3.19.2"), 321 "E:\\Actual Path\\Directory/file", 322 ) 323 self.assertEqual( 324 r.Rlocation( 325 "protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 326 ), 327 "E:\\Actual Path\\Directory/de eply/nes ted/fi~le", 328 ) 329 330 self.assertIsNone(r.Rlocation("my_module/bar/runfile", "protobuf~3.19.2")) 331 self.assertIsNone(r.Rlocation("my_protobuf/foo/runfile", "protobuf~3.19.2")) 332 self.assertIsNone(r.Rlocation("my_protobuf/bar/dir", "protobuf~3.19.2")) 333 self.assertIsNone( 334 r.Rlocation("my_protobuf/bar/dir/file", "protobuf~3.19.2") 335 ) 336 self.assertIsNone( 337 r.Rlocation( 338 "my_protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 339 ) 340 ) 341 342 self.assertEqual( 343 r.Rlocation("_main/bar/runfile", "protobuf~3.19.2"), 344 "/the/path/./to/other//other runfile.txt", 345 ) 346 self.assertEqual( 347 r.Rlocation("protobuf~3.19.2/foo/runfile", "protobuf~3.19.2"), 348 "C:/Actual Path\\protobuf\\runfile", 349 ) 350 self.assertEqual( 351 r.Rlocation("protobuf~3.19.2/bar/dir", "protobuf~3.19.2"), 352 "E:\\Actual Path\\Directory", 353 ) 354 self.assertEqual( 355 r.Rlocation("protobuf~3.19.2/bar/dir/file", "protobuf~3.19.2"), 356 "E:\\Actual Path\\Directory/file", 357 ) 358 self.assertEqual( 359 r.Rlocation( 360 "protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 361 ), 362 "E:\\Actual Path\\Directory/de eply/nes ted/fi~le", 363 ) 364 365 self.assertEqual( 366 r.Rlocation("config.json", "protobuf~3.19.2"), "/etc/config.json" 367 ) 368 self.assertIsNone(r.Rlocation("_main", "protobuf~3.19.2")) 369 self.assertIsNone(r.Rlocation("my_module", "protobuf~3.19.2")) 370 self.assertIsNone(r.Rlocation("protobuf", "protobuf~3.19.2")) 371 372 def testDirectoryBasedRlocation(self) -> None: 373 # The _DirectoryBased strategy simply joins the runfiles directory and the 374 # runfile's path on a "/". This strategy does not perform any normalization, 375 # nor does it check that the path exists. 376 r = runfiles.CreateDirectoryBased("foo/bar baz//qux/") 377 self.assertEqual(r.Rlocation("arg"), "foo/bar baz//qux/arg") 378 if RunfilesTest.IsWindows(): 379 self.assertEqual(r.Rlocation("c:/foo"), "c:/foo") 380 self.assertEqual(r.Rlocation("c:\\foo"), "c:\\foo") 381 else: 382 self.assertEqual(r.Rlocation("/foo"), "/foo") 383 384 def testDirectoryBasedRlocationWithRepoMappingFromMain(self) -> None: 385 with _MockFile( 386 name="_repo_mapping", 387 contents=[ 388 "_,config.json,config.json~1.2.3", 389 ",my_module,_main", 390 ",my_protobuf,protobuf~3.19.2", 391 ",my_workspace,_main", 392 "protobuf~3.19.2,config.json,config.json~1.2.3", 393 "protobuf~3.19.2,protobuf,protobuf~3.19.2", 394 ], 395 ) as rm: 396 dir = os.path.dirname(rm.Path()) 397 r = runfiles.CreateDirectoryBased(dir) 398 399 self.assertEqual( 400 r.Rlocation("my_module/bar/runfile", ""), dir + "/_main/bar/runfile" 401 ) 402 self.assertEqual( 403 r.Rlocation("my_workspace/bar/runfile", ""), dir + "/_main/bar/runfile" 404 ) 405 self.assertEqual( 406 r.Rlocation("my_protobuf/foo/runfile", ""), 407 dir + "/protobuf~3.19.2/foo/runfile", 408 ) 409 self.assertEqual( 410 r.Rlocation("my_protobuf/bar/dir", ""), dir + "/protobuf~3.19.2/bar/dir" 411 ) 412 self.assertEqual( 413 r.Rlocation("my_protobuf/bar/dir/file", ""), 414 dir + "/protobuf~3.19.2/bar/dir/file", 415 ) 416 self.assertEqual( 417 r.Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", ""), 418 dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", 419 ) 420 421 self.assertEqual( 422 r.Rlocation("protobuf/foo/runfile", ""), dir + "/protobuf/foo/runfile" 423 ) 424 self.assertEqual( 425 r.Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le", ""), 426 dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi~le", 427 ) 428 429 self.assertEqual( 430 r.Rlocation("_main/bar/runfile", ""), dir + "/_main/bar/runfile" 431 ) 432 self.assertEqual( 433 r.Rlocation("protobuf~3.19.2/foo/runfile", ""), 434 dir + "/protobuf~3.19.2/foo/runfile", 435 ) 436 self.assertEqual( 437 r.Rlocation("protobuf~3.19.2/bar/dir", ""), 438 dir + "/protobuf~3.19.2/bar/dir", 439 ) 440 self.assertEqual( 441 r.Rlocation("protobuf~3.19.2/bar/dir/file", ""), 442 dir + "/protobuf~3.19.2/bar/dir/file", 443 ) 444 self.assertEqual( 445 r.Rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", ""), 446 dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", 447 ) 448 449 self.assertEqual(r.Rlocation("config.json", ""), dir + "/config.json") 450 451 def testDirectoryBasedRlocationWithRepoMappingFromOtherRepo(self) -> None: 452 with _MockFile( 453 name="_repo_mapping", 454 contents=[ 455 "_,config.json,config.json~1.2.3", 456 ",my_module,_main", 457 ",my_protobuf,protobuf~3.19.2", 458 ",my_workspace,_main", 459 "protobuf~3.19.2,config.json,config.json~1.2.3", 460 "protobuf~3.19.2,protobuf,protobuf~3.19.2", 461 ], 462 ) as rm: 463 dir = os.path.dirname(rm.Path()) 464 r = runfiles.CreateDirectoryBased(dir) 465 466 self.assertEqual( 467 r.Rlocation("protobuf/foo/runfile", "protobuf~3.19.2"), 468 dir + "/protobuf~3.19.2/foo/runfile", 469 ) 470 self.assertEqual( 471 r.Rlocation("protobuf/bar/dir", "protobuf~3.19.2"), 472 dir + "/protobuf~3.19.2/bar/dir", 473 ) 474 self.assertEqual( 475 r.Rlocation("protobuf/bar/dir/file", "protobuf~3.19.2"), 476 dir + "/protobuf~3.19.2/bar/dir/file", 477 ) 478 self.assertEqual( 479 r.Rlocation( 480 "protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 481 ), 482 dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", 483 ) 484 485 self.assertEqual( 486 r.Rlocation("my_module/bar/runfile", "protobuf~3.19.2"), 487 dir + "/my_module/bar/runfile", 488 ) 489 self.assertEqual( 490 r.Rlocation( 491 "my_protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 492 ), 493 dir + "/my_protobuf/bar/dir/de eply/nes ted/fi~le", 494 ) 495 496 self.assertEqual( 497 r.Rlocation("_main/bar/runfile", "protobuf~3.19.2"), 498 dir + "/_main/bar/runfile", 499 ) 500 self.assertEqual( 501 r.Rlocation("protobuf~3.19.2/foo/runfile", "protobuf~3.19.2"), 502 dir + "/protobuf~3.19.2/foo/runfile", 503 ) 504 self.assertEqual( 505 r.Rlocation("protobuf~3.19.2/bar/dir", "protobuf~3.19.2"), 506 dir + "/protobuf~3.19.2/bar/dir", 507 ) 508 self.assertEqual( 509 r.Rlocation("protobuf~3.19.2/bar/dir/file", "protobuf~3.19.2"), 510 dir + "/protobuf~3.19.2/bar/dir/file", 511 ) 512 self.assertEqual( 513 r.Rlocation( 514 "protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2" 515 ), 516 dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", 517 ) 518 519 self.assertEqual( 520 r.Rlocation("config.json", "protobuf~3.19.2"), dir + "/config.json" 521 ) 522 523 def testCurrentRepository(self) -> None: 524 # Under bzlmod, the current repository name is the empty string instead 525 # of the name in the workspace file. 526 if bool(int(os.environ["BZLMOD_ENABLED"])): 527 expected = "" 528 else: 529 expected = "rules_python" 530 r = runfiles.Create({"RUNFILES_DIR": "whatever"}) 531 assert r is not None # mypy doesn't understand the unittest api. 532 self.assertEqual(r.CurrentRepository(), expected) 533 534 @staticmethod 535 def IsWindows() -> bool: 536 return os.name == "nt" 537 538 539class _MockFile: 540 def __init__( 541 self, name: Optional[str] = None, contents: Optional[List[Any]] = None 542 ) -> None: 543 self._contents = contents or [] 544 self._name = name or "x" 545 self._path: Optional[str] = None 546 547 def __enter__(self) -> Any: 548 tmpdir = os.environ.get("TEST_TMPDIR") 549 self._path = os.path.join(tempfile.mkdtemp(dir=tmpdir), self._name) 550 with open(self._path, "wt") as f: 551 f.writelines(l + "\n" for l in self._contents) 552 return self 553 554 def __exit__( 555 self, 556 exc_type: Any, # pylint: disable=unused-argument 557 exc_value: Any, # pylint: disable=unused-argument 558 traceback: Any, # pylint: disable=unused-argument 559 ) -> None: 560 if self._path: 561 os.remove(self._path) 562 os.rmdir(os.path.dirname(self._path)) 563 564 def Path(self) -> str: 565 assert self._path is not None 566 return self._path 567 568 569if __name__ == "__main__": 570 unittest.main() 571