Skip to content

Commit 63fc191

Browse files
committed
Give the tests for lost remote repo files their own suite
The tests for files that the remote repo contents cache has lost set up a very particular kind of failure and run several builds each, so they get their own target with its own setup instead of growing the general remote repo contents cache suite. The setup shared by both suites moves into a base class, and the repo with a root and a subpackage BUILD file that the existing test builds becomes a helper for the tests that follow.
1 parent 27f9635 commit 63fc191

4 files changed

Lines changed: 190 additions & 128 deletions

File tree

src/test/py/bazel/BUILD

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,16 +408,28 @@ py_test(
408408
],
409409
)
410410

411+
py_library(
412+
name = "remote_repo_contents_cache_test_base",
413+
testonly = 1,
414+
srcs = ["bzlmod/remote_repo_contents_cache_test_base.py"],
415+
deps = [":test_base"],
416+
)
417+
411418
py_test(
412419
name = "remote_repo_contents_cache_test",
413420
size = "large",
414421
srcs = ["bzlmod/remote_repo_contents_cache_test.py"],
415422
shard_count = 2,
416423
tags = ["requires-network"],
417-
deps = [
418-
":bzlmod_test_utils",
419-
":test_base",
420-
],
424+
deps = [":remote_repo_contents_cache_test_base"],
425+
)
426+
427+
py_test(
428+
name = "remote_repo_contents_cache_rewinding_test",
429+
size = "large",
430+
srcs = ["bzlmod/remote_repo_contents_cache_rewinding_test.py"],
431+
tags = ["requires-network"],
432+
deps = [":remote_repo_contents_cache_test_base"],
421433
)
422434

423435
py_test(
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Copyright 2026 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+
15+
import os
16+
import re
17+
from absl.testing import absltest
18+
from src.test.py.bazel.bzlmod import remote_repo_contents_cache_test_base
19+
20+
21+
class RemoteRepoContentsCacheRewindingTest(
22+
remote_repo_contents_cache_test_base.RemoteRepoContentsCacheTestBase
23+
):
24+
"""Tests recovery of repo files lost from the remote cache."""
25+
26+
def _setupRepoWithSubpackage(self):
27+
self.ScratchFile(
28+
'MODULE.bazel',
29+
[
30+
'repo = use_repo_rule("//:repo.bzl", "repo")',
31+
'repo(name = "my_repo")',
32+
],
33+
)
34+
35+
self.ScratchFile('BUILD.bazel')
36+
self.ScratchFile(
37+
'repo.bzl',
38+
[
39+
'def _repo_impl(rctx):',
40+
(
41+
' rctx.file("BUILD", "filegroup(name=\'root\','
42+
" srcs=['root.txt'])\")"
43+
),
44+
' rctx.file("root.txt", "root")',
45+
(
46+
' rctx.file("sub/BUILD", "filegroup(name=\'sub\','
47+
" srcs=['sub.txt'])\")"
48+
),
49+
' rctx.file("sub/sub.txt", "sub")',
50+
' print("JUST FETCHED")',
51+
' return rctx.repo_metadata(reproducible=True)',
52+
'repo = repository_rule(_repo_impl)',
53+
],
54+
)
55+
56+
return self.RepoDir('my_repo')
57+
58+
def testLostRemoteFile_build(self):
59+
# Create a repo with two BUILD files (one in a subpackage), build a target
60+
# from one to cause it to be cached, then build that target again after
61+
# expunging to verify it is cached.
62+
# Then, restart the worker and build a target in the other build file.
63+
repo_dir = self._setupRepoWithSubpackage()
64+
65+
# First fetch: not cached
66+
_, _, stderr = self.RunBazel(['build', '@my_repo//:root'])
67+
self.assertIn('JUST FETCHED', '\n'.join(stderr))
68+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'BUILD')))
69+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
70+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
71+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
72+
73+
# After expunging: cached
74+
self.RunBazel(['clean', '--expunge'])
75+
_, _, stderr = self.RunBazel(['build', '@my_repo//:root'])
76+
self.assertNotIn('JUST FETCHED', '\n'.join(stderr))
77+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'BUILD')))
78+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
79+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
80+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
81+
82+
# Lose all remote files.
83+
self.ClearRemoteCache()
84+
85+
# Build the other target: fails due to the lost input
86+
_, _, stderr = self.RunBazel(['build', '@my_repo//sub:sub'])
87+
# First restart recovers @my_repo, the next one recovers @platforms.
88+
self.assertEqual(
89+
2,
90+
stderr.count(
91+
'Found transient remote cache error, retrying the build...'
92+
),
93+
)
94+
canonical_repo_name = repo_dir[repo_dir.rfind('/') + 1 :]
95+
stderr = '\n'.join(stderr)
96+
self.assertRegex(
97+
stderr,
98+
'external/%s/sub/BUILD with digest .*/.* no longer available in the'
99+
' remote cache'
100+
% re.escape(canonical_repo_name),
101+
)
102+
self.assertIn('JUST FETCHED', stderr)
103+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'BUILD')))
104+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
105+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
106+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
107+
108+
# After expunging again: cached
109+
self.RunBazel(['clean', '--expunge'])
110+
_, _, stderr = self.RunBazel(['build', '@my_repo//sub:sub'])
111+
self.assertNotIn('JUST FETCHED', '\n'.join(stderr))
112+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'BUILD')))
113+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'root.txt')))
114+
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
115+
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
116+
if __name__ == '__main__':
117+
absltest.main()

src/test/py/bazel/bzlmod/remote_repo_contents_cache_test.py

Lines changed: 4 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@
1515
# pylint: disable=g-long-ternary
1616
# pylint: disable=g-bad-todo
1717

18-
import json
1918
import os
20-
import re
2119
import tempfile
2220
from absl.testing import absltest
23-
from src.test.py.bazel import test_base
21+
from src.test.py.bazel.bzlmod import remote_repo_contents_cache_test_base
2422

2523
# Whether repos containing symlinks that point out of the repo can be added to
2624
# the remote repo contents cache. If False, such repos are refetched instead of
@@ -30,39 +28,9 @@
3028
CROSS_REPO_SYMLINKS_CACHEABLE = False
3129

3230

33-
class RemoteRepoContentsCacheTest(test_base.TestBase):
34-
35-
def setUp(self):
36-
test_base.TestBase.setUp(self)
37-
self._worker_port = self.StartRemoteWorker()
38-
self.ScratchFile(
39-
'.bazelrc',
40-
[
41-
'startup --experimental_remote_repo_contents_cache',
42-
# Only use the remote repo contents cache.
43-
'common --repo_contents_cache=',
44-
'common --remote_cache=grpc://localhost:' + str(self._worker_port),
45-
'common --auth_enabled=false',
46-
'common --remote_timeout=3600s',
47-
'common --verbose_failures',
48-
],
49-
)
50-
51-
def tearDown(self):
52-
test_base.TestBase.tearDown(self)
53-
self.StopRemoteWorker()
54-
55-
def RepoDir(self, repo_name, cwd=None):
56-
_, stdout, _ = self.RunBazel(['info', 'output_base'], cwd=cwd)
57-
self.assertLen(stdout, 1)
58-
output_base = stdout[0].strip()
59-
60-
_, stdout, _ = self.RunBazel(['mod', 'dump_repo_mapping', ''], cwd=cwd)
61-
self.assertLen(stdout, 1)
62-
mapping = json.loads(stdout[0])
63-
canonical_repo_name = mapping[repo_name]
64-
65-
return output_base + '/external/' + canonical_repo_name
31+
class RemoteRepoContentsCacheTest(
32+
remote_repo_contents_cache_test_base.RemoteRepoContentsCacheTestBase
33+
):
6634

6735
def testCachedAfterCleanExpunge(self):
6836
self.ScratchFile(
@@ -1889,94 +1857,6 @@ def testRepoExternalSymlinkWithNativeTargetRepoLocalAction(self):
18891857
if CROSS_REPO_SYMLINKS_CACHEABLE:
18901858
self.assertFalse(os.path.exists(os.path.join(my_repo_dir, 'BUILD')))
18911859

1892-
def testLostRemoteFile_build(self):
1893-
# Create a repo with two BUILD files (one in a subpackage), build a target
1894-
# from one to cause it to be cached, then build that target again after
1895-
# expunging to verify it is cached.
1896-
# Then, restart the worker and build a target in the other build file.
1897-
self.ScratchFile(
1898-
'MODULE.bazel',
1899-
[
1900-
'repo = use_repo_rule("//:repo.bzl", "repo")',
1901-
'repo(name = "my_repo")',
1902-
],
1903-
)
1904-
1905-
self.ScratchFile('BUILD.bazel')
1906-
self.ScratchFile(
1907-
'repo.bzl',
1908-
[
1909-
'def _repo_impl(rctx):',
1910-
(
1911-
' rctx.file("BUILD", "filegroup(name=\'root\','
1912-
" srcs=['root.txt'])\")"
1913-
),
1914-
' rctx.file("root.txt", "root")',
1915-
(
1916-
' rctx.file("sub/BUILD", "filegroup(name=\'sub\','
1917-
" srcs=['sub.txt'])\")"
1918-
),
1919-
' rctx.file("sub/sub.txt", "sub")',
1920-
' print("JUST FETCHED")',
1921-
' return rctx.repo_metadata(reproducible=True)',
1922-
'repo = repository_rule(_repo_impl)',
1923-
],
1924-
)
1925-
1926-
repo_dir = self.RepoDir('my_repo')
1927-
1928-
# First fetch: not cached
1929-
_, _, stderr = self.RunBazel(['build', '@my_repo//:root'])
1930-
self.assertIn('JUST FETCHED', '\n'.join(stderr))
1931-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'BUILD')))
1932-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
1933-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
1934-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
1935-
1936-
# After expunging: cached
1937-
self.RunBazel(['clean', '--expunge'])
1938-
_, _, stderr = self.RunBazel(['build', '@my_repo//:root'])
1939-
self.assertNotIn('JUST FETCHED', '\n'.join(stderr))
1940-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'BUILD')))
1941-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
1942-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
1943-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
1944-
1945-
# Lose all remote files.
1946-
self.ClearRemoteCache()
1947-
1948-
# Build the other target: fails due to the lost input
1949-
_, _, stderr = self.RunBazel(['build', '@my_repo//sub:sub'])
1950-
# First restart recovers @my_repo, the next one recovers @platforms.
1951-
self.assertEqual(
1952-
2,
1953-
stderr.count(
1954-
'Found transient remote cache error, retrying the build...'
1955-
),
1956-
)
1957-
canonical_repo_name = repo_dir[repo_dir.rfind('/') + 1 :]
1958-
stderr = '\n'.join(stderr)
1959-
self.assertRegex(
1960-
stderr,
1961-
'external/%s/sub/BUILD with digest .*/.* no longer available in the'
1962-
' remote cache'
1963-
% re.escape(canonical_repo_name),
1964-
)
1965-
self.assertIn('JUST FETCHED', stderr)
1966-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'BUILD')))
1967-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'root.txt')))
1968-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
1969-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
1970-
1971-
# After expunging again: cached
1972-
self.RunBazel(['clean', '--expunge'])
1973-
_, _, stderr = self.RunBazel(['build', '@my_repo//sub:sub'])
1974-
self.assertNotIn('JUST FETCHED', '\n'.join(stderr))
1975-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'BUILD')))
1976-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'root.txt')))
1977-
self.assertFalse(os.path.exists(os.path.join(repo_dir, 'sub/BUILD')))
1978-
self.assertTrue(os.path.exists(os.path.join(repo_dir, 'sub/sub.txt')))
1979-
19801860
def testMemoryPressureRestartDuringCachedFetch(self):
19811861
# Regression test for a cached repo fetch that is interrupted by memory
19821862
# pressure (Skyframe drops the fetch's WorkerSkyKeyComputeState, which
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2026 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+
15+
import json
16+
from src.test.py.bazel import test_base
17+
18+
19+
class RemoteRepoContentsCacheTestBase(test_base.TestBase):
20+
"""Common setup for tests of the remote repo contents cache."""
21+
22+
def setUp(self):
23+
test_base.TestBase.setUp(self)
24+
self._worker_port = self.StartRemoteWorker()
25+
self.ScratchFile('.bazelrc', self.BazelrcLines())
26+
27+
def tearDown(self):
28+
test_base.TestBase.tearDown(self)
29+
self.StopRemoteWorker()
30+
31+
def BazelrcLines(self):
32+
"""Returns the lines of the .bazelrc shared by all tests."""
33+
return [
34+
'startup --experimental_remote_repo_contents_cache',
35+
# Only use the remote repo contents cache.
36+
'common --repo_contents_cache=',
37+
'common --remote_cache=grpc://localhost:' + str(self._worker_port),
38+
'common --auth_enabled=false',
39+
'common --remote_timeout=3600s',
40+
'common --verbose_failures',
41+
]
42+
43+
def RepoDir(self, repo_name, cwd=None):
44+
_, stdout, _ = self.RunBazel(['info', 'output_base'], cwd=cwd)
45+
self.assertLen(stdout, 1)
46+
output_base = stdout[0].strip()
47+
48+
_, stdout, _ = self.RunBazel(['mod', 'dump_repo_mapping', ''], cwd=cwd)
49+
self.assertLen(stdout, 1)
50+
mapping = json.loads(stdout[0])
51+
canonical_repo_name = mapping[repo_name]
52+
53+
return output_base + '/external/' + canonical_repo_name

0 commit comments

Comments
 (0)