xref: /aosp_15_r20/external/bazelbuild-rules_python/tests/runfiles/runfiles_test.py (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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