diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py index 301477560bb224..ddc3b59364b687 100644 --- a/src/test/py/bazel/bzlmod/bazel_module_test.py +++ b/src/test/py/bazel/bzlmod/bazel_module_test.py @@ -694,5 +694,68 @@ def testRunfilesRepoMappingManifest(self): self.Path('bazel-bin/external/bar~2.0/bar.runfiles_manifest')) as f: self.assertIn('_repo_mapping ', f.read()) + def testJavaRunfilesLibraryRepoMapping(self): + self.main_registry.setModuleBasePath('projects') + projects_dir = self.main_registry.projects + + self.main_registry.createLocalPathModule('data', '1.0', 'data') + projects_dir.joinpath('data').mkdir(exist_ok=True) + scratchFile(projects_dir.joinpath('data', 'WORKSPACE')) + scratchFile(projects_dir.joinpath('data', 'foo.txt'), ['hello']) + scratchFile(projects_dir.joinpath('data', 'BUILD'), + ['exports_files(["foo.txt"])']) + + self.main_registry.createLocalPathModule('test', '1.0', 'test', + {'data': '1.0'}) + projects_dir.joinpath('test').mkdir(exist_ok=True) + scratchFile(projects_dir.joinpath('test', 'WORKSPACE')) + scratchFile(projects_dir.joinpath('test', 'BUILD'), [ + 'java_test(', + ' name = "test",' + ' srcs = ["Test.java"],', + ' main_class = "com.example.Test",', + ' use_testrunner = False,', + ' data = ["@data//:foo.txt"],', + ' args = ["$(rlocationpath @data//:foo.txt)"],', + ' deps = ["@bazel_tools//tools/java/runfiles"],', + ')' + ]) + scratchFile(projects_dir.joinpath('test', 'Test.java'), [ + 'package com.example;', + '', + 'import com.google.devtools.build.runfiles.AutoBazelRepository;', + 'import com.google.devtools.build.runfiles.Runfiles;', + '', + 'import java.io.File;', + 'import java.io.IOException;', + '', + '@AutoBazelRepository', + 'public class Test {', + ' public static void main(String[] args) throws IOException {', + ' Runfiles.Preloaded rp = Runfiles.preload();', + ' if (!new File(rp.unmapped().rlocation(args[0])).exists()) {', + ' System.exit(1);', + ' }', + ' if (!new File(rp.withSourceRepository(AutoBazelRepository_Test.NAME).rlocation("data/foo.txt")).exists()) {', + ' System.exit(1);', + ' }', + ' }', + '}', + ]) + + self.ScratchFile('MODULE.bazel', ['bazel_dep(name="test",version="1.0")']) + self.ScratchFile('WORKSPACE') + + # Run sandboxed on Linux and macOS. + exit_code, stderr, stdout = self.RunBazel( + ['test', '@test//:test', '--test_output=errors', + '--test_env=RUNFILES_LIB_DEBUG=1'], allow_failure=True) + self.AssertExitCode(exit_code, 0, stderr, stdout) + # Run unsandboxed on all platforms. + exit_code, stderr, stdout = self.RunBazel( + ['run', '@test//:test'], allow_failure=True, + env_add={"RUNFILES_LIB_DEBUG": "1"}) + self.AssertExitCode(exit_code, 0, stderr, stdout) + if __name__ == '__main__': unittest.main() diff --git a/tools/java/runfiles/Runfiles.java b/tools/java/runfiles/Runfiles.java index 9a3aa26c4517a9..5d75e093a78ae2 100644 --- a/tools/java/runfiles/Runfiles.java +++ b/tools/java/runfiles/Runfiles.java @@ -17,12 +17,16 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.ref.SoftReference; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; /** * Runfiles lookup library for Bazel-built Java binaries and tests. @@ -45,15 +49,58 @@ * import com.google.devtools.build.runfiles.Runfiles; * * - *

3. Create a Runfiles object and use rlocation to look up runfile paths: + *

3. Create a {@link Preloaded} object: * *

  *   public void myFunction() {
- *     Runfiles runfiles = Runfiles.create();
- *     String path = runfiles.rlocation("my_workspace/path/to/my/data.txt");
+ *     Runfiles.Preloaded runfiles = Runfiles.preload();
  *     ...
  * 
* + *

4. To look up a runfile, use either of the following approaches: + * + *

4a. Annotate the class from which runfiles should be looked up with + * {@link AutoBazelRepository} and obtain the name of the Bazel repository containing the class from + * a constant generated by this annotation: + * + *

+ *   import com.google.devtools.build.runfiles.AutoBazelRepository;
+ *   @AutoBazelRepository
+ *   public class MyClass {
+ *     public void myFunction() {
+ *       Runfiles.Preloaded runfiles = Runfiles.preload();
+ *       String path = runfiles.withSourceRepository(AutoBazelRepository_MyClass.NAME)
+ *                             .rlocation("my_workspace/path/to/my/data.txt");
+ *       ...
+ *
+ * 
+ * + *

4b. Let Bazel compute the path passed to rlocation and pass it into a java_binary + * via an argument or an environment variable: + * + *

+ *   java_binary(
+ *       name = "my_binary",
+ *       srcs = ["MyClass.java"],
+ *       data = ["@my_workspace//path/to/my:data.txt"],
+ *       env = {"MY_RUNFILE": "$(rlocationpath @my_workspace//path/to/my:data.txt)"},
+ *   )
+ * 
+ * + *
+ *   public class MyClass {
+ *     public void myFunction() {
+ *       Runfiles.Preloaded runfiles = Runfiles.preload();
+ *       String path = runfiles.unmapped().rlocation(System.getenv("MY_RUNFILE"));
+ *       ...
+ *
+ * 
+ * + * For more details on why it is required to pass in the current repository name, see + * {@see https://bazel.build/build/bzlmod#repository-names}. + * + *

Subprocesses

+ * *

If you want to start subprocesses that also need runfiles, you need to set the right * environment variables for them: * @@ -64,23 +111,138 @@ * ... * Process p = pb.start(); * + * + *

{@link Preloaded} vs. {@link Runfiles}

+ * + *

Instances of {@link Preloaded} are meant to be stored and passed around to other components + * that need to access runfiles. They are created by calling {@link Runfiles#preload()} + * {@link Runfiles#preload(Map)} and immutably encapsulate all data required to look up runfiles + * with the repository mapping of any Bazel repository specified at a later time. + * + *

Creating {@link Runfiles} instances can be costly, so applications should try to create as few + * instances as possible. {@link Runfiles#preload()}, but not {@link Runfiles#preload(Map)}, returns + * a single global, softly cached instance of {@link Preloaded} that is constructed based on the + * JVM's environment variables. + * + *

Instance of {@link Runfiles} are only meant to be used by code located in a single Bazel + * repository and should not be passed around. They are created by calling + * {@link Preloaded#withSourceRepository(String)} or {@link Preloaded#unmapped()} and in addition to + * the data in {@link Preloaded} also fix a source repository relative to which apparent repository + * names are resolved. + * + *

Creating {@link Preloaded} instances is cheap. */ -public abstract class Runfiles { +public final class Runfiles { + + /** + * A class that encapsulates all data required to look up runfiles relative to any Bazel + * repository fixed at a later time. + * + *

This class is immutable. + */ + public abstract static class Preloaded { + + /** + * See {@link com.google.devtools.build.lib.analysis.RepoMappingManifestAction.Entry}. + */ + static class RepoMappingKey { + + public final String sourceRepo; + public final String targetRepoApparentName; + + public RepoMappingKey(String sourceRepo, String targetRepoApparentName) { + this.sourceRepo = sourceRepo; + this.targetRepoApparentName = targetRepoApparentName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RepoMappingKey that = (RepoMappingKey) o; + return sourceRepo.equals(that.sourceRepo) && targetRepoApparentName.equals( + that.targetRepoApparentName); + } - // Package-private constructor, so only package-private classes may extend it. - private Runfiles() {} + @Override + public int hashCode() { + return Objects.hash(sourceRepo, targetRepoApparentName); + } + } + + /** + * Returns a {@link Runfiles} instance that uses the provided source repository's repository + * mapping to translate apparent into canonical repository names. + * + *

{@see https://bazel.build/build/bzlmod#repository-names} + * + * @param sourceRepository the canonical name of the Bazel repository relative to which apparent + * repository names should be resolved. Should generally coincide with + * the Bazel repository that contains the caller of this method, which + * can be obtained via {@link AutoBazelRepository}. + * @return a {@link Runfiles} instance that looks up runfiles relative to the provided source + * repository and shares all other data with this {@link Preloaded} instance. + */ + public final Runfiles withSourceRepository(String sourceRepository) { + Util.checkArgument(sourceRepository != null); + return new Runfiles(this, sourceRepository); + } + + /** + * Returns a {@link Runfiles} instance backed by the preloaded runfiles data that can be used to + * look up runfiles paths with canonical repository names only. + * + * @return a {@link Runfiles} instance that can only look up paths with canonical repository + * names and shared all data with this {@link Preloaded} instance. + */ + public final Runfiles unmapped() { + return new Runfiles(this, null); + } + + protected abstract Map getEnvVars(); + + protected abstract String rlocationChecked(String path); + + protected abstract Map getRepoMapping(); + + // Private constructor, so only nested classes may extend it. + private Preloaded() { + } + } + + private static final String MAIN_REPOSITORY = ""; + + private static SoftReference DEFAULT_INSTANCE = new SoftReference<>(null); + + private final Preloaded preloadedRunfiles; + private final String sourceRepository; + + private Runfiles(Preloaded preloadedRunfiles, String sourceRepository) { + this.preloadedRunfiles = preloadedRunfiles; + this.sourceRepository = sourceRepository; + } /** - * Returns a new {@link Runfiles} instance. + * Returns the softly cached global {@link Runfiles.Preloaded} instance, creating it if needed. * *

This method passes the JVM's environment variable map to {@link #create(Map)}. */ - public static Runfiles create() throws IOException { - return create(System.getenv()); + public static synchronized Preloaded preload() throws IOException { + Preloaded instance = DEFAULT_INSTANCE.get(); + if (instance != null) { + return instance; + } + instance = preload(System.getenv()); + DEFAULT_INSTANCE = new SoftReference<>(instance); + return instance; } /** - * Returns a new {@link Runfiles} instance. + * Returns a new {@link Runfiles.Preloaded} instance. * *

The returned object is either: * @@ -101,10 +263,10 @@ public static Runfiles create() throws IOException { * manifest file upon instantiation. * * @throws IOException if RUNFILES_MANIFEST_ONLY=1 is in {@code env} but there's no - * "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", or "JAVA_RUNFILES" key in {@code env} or their - * values are empty, or some IO error occurs + * "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", or "JAVA_RUNFILES" key in + * {@code env} or their values are empty, or some IO error occurs */ - public static Runfiles create(Map env) throws IOException { + public static Preloaded preload(Map env) throws IOException { if (isManifestOnly(env)) { // On Windows, Bazel sets RUNFILES_MANIFEST_ONLY=1. // On every platform, Bazel also sets RUNFILES_MANIFEST_FILE, but on Linux and macOS it's @@ -115,20 +277,66 @@ public static Runfiles create(Map env) throws IOException { } } + /** + * Returns a new {@link Runfiles} instance. + * + *

Deprecated: Use {@link #preload()} instead. With {@code --enable_bzlmod}, this + * function does not work correctly. + * + *

This method passes the JVM's environment variable map to {@link #create(Map)}. + */ + @Deprecated + public static Runfiles create() throws IOException { + return preload().withSourceRepository(MAIN_REPOSITORY); + } + + /** + * Returns a new {@link Runfiles} instance. + * + *

Deprecated: Use {@link #preload(Map)} )} instead. With {@code --enable_bzlmod}, this + * function does not work correctly. + * + *

The returned object is either: + * + *

+ * + *

If {@code env} contains "RUNFILES_MANIFEST_ONLY" with value "1", this method returns a + * manifest-based implementation. The manifest's path is defined by the "RUNFILES_MANIFEST_FILE" + * key's value in {@code env}. + * + *

Otherwise this method returns a directory-based implementation. The directory's path is + * defined by the value in {@code env} under the "RUNFILES_DIR" key, or if absent, then under the + * "JAVA_RUNFILES" key. + * + *

Note about performance: the manifest-based implementation eagerly reads and caches the whole + * manifest file upon instantiation. + * + * @throws IOException if RUNFILES_MANIFEST_ONLY=1 is in {@code env} but there's no + * "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", or "JAVA_RUNFILES" key in + * {@code env} or their values are empty, or some IO error occurs + */ + @Deprecated + public static Runfiles create(Map env) throws IOException { + return preload(env).withSourceRepository(MAIN_REPOSITORY); + } + /** * Returns the runtime path of a runfile (a Bazel-built binary's/test's data-dependency). * - *

The returned path may not be valid. The caller should check the path's validity and that the - * path exists. + *

The returned path may not be valid. The caller should check the path's validity and that + * the path exists. * *

The function may return null. In that case the caller can be sure that the rule does not * know about this data-dependency. * * @param path runfiles-root-relative path of the runfile * @throws IllegalArgumentException if {@code path} fails validation, for example if it's null or - * empty, or not normalized (contains "./", "../", or "//") + * empty, or not normalized (contains "./", "../", or "//") */ - public final String rlocation(String path) { + public String rlocation(String path) { Util.checkArgument(path != null); Util.checkArgument(!path.isEmpty()); Util.checkArgument( @@ -145,7 +353,19 @@ public final String rlocation(String path) { if (new File(path).isAbsolute()) { return path; } - return rlocationChecked(path); + + if (sourceRepository == null) { + return preloadedRunfiles.rlocationChecked(path); + } + String[] apparentTargetAndRemainder = path.split("/", 2); + if (apparentTargetAndRemainder.length < 2) { + return preloadedRunfiles.rlocationChecked(path); + } + String targetCanonical = preloadedRunfiles.getRepoMapping().getOrDefault( + new Preloaded.RepoMappingKey(sourceRepository, apparentTargetAndRemainder[0]), + apparentTargetAndRemainder[0]); + return preloadedRunfiles.rlocationChecked( + targetCanonical + "/" + apparentTargetAndRemainder[1]); } /** @@ -154,9 +374,13 @@ public final String rlocation(String path) { *

The caller should add the returned key-value pairs to the environment of subprocesses in * case those subprocesses are also Bazel-built binaries that need to use runfiles. */ - public abstract Map getEnvVars(); + public Map getEnvVars() { + return preloadedRunfiles.getEnvVars(); + } - /** Returns true if the platform supports runfiles only via manifests. */ + /** + * Returns true if the platform supports runfiles only via manifests. + */ private static boolean isManifestOnly(Map env) { return "1".equals(env.get("RUNFILES_MANIFEST_ONLY")); } @@ -183,50 +407,48 @@ private static String getRunfilesDir(Map env) throws IOException return value; } - abstract String rlocationChecked(String path); + private static Map loadRepositoryMapping(String path) + throws IOException { + if (path == null || !new File(path).exists()) { + return Collections.emptyMap(); + } + + try (BufferedReader r = new BufferedReader(new FileReader(path, StandardCharsets.UTF_8))) { + return Collections.unmodifiableMap(r.lines() + .filter(line -> !line.isEmpty()) + .map(line -> { + String[] split = line.split(","); + if (split.length != 3) { + throw new IllegalArgumentException( + "Invalid line in repository mapping: '" + line + "'"); + } + return split; + }) + .collect(Collectors.toMap( + split -> new Preloaded.RepoMappingKey(split[0], split[1]), + split -> split[2]))); + } + } + + /** + * {@link Runfiles} implementation that parses a runfiles-manifest file to look up runfiles. + */ + private static final class ManifestBased extends Runfiles.Preloaded { - /** {@link Runfiles} implementation that parses a runfiles-manifest file to look up runfiles. */ - private static final class ManifestBased extends Runfiles { private final Map runfiles; private final String manifestPath; + private final Map repoMapping; ManifestBased(String manifestPath) throws IOException { Util.checkArgument(manifestPath != null); Util.checkArgument(!manifestPath.isEmpty()); this.manifestPath = manifestPath; this.runfiles = loadRunfiles(manifestPath); - } - - private static Map loadRunfiles(String path) throws IOException { - HashMap result = new HashMap<>(); - try (BufferedReader r = - new BufferedReader( - new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { - String line = null; - while ((line = r.readLine()) != null) { - int index = line.indexOf(' '); - String runfile = (index == -1) ? line : line.substring(0, index); - String realPath = (index == -1) ? line : line.substring(index + 1); - result.put(runfile, realPath); - } - } - return Collections.unmodifiableMap(result); - } - - private static String findRunfilesDir(String manifest) { - if (manifest.endsWith("/MANIFEST") - || manifest.endsWith("\\MANIFEST") - || manifest.endsWith(".runfiles_manifest")) { - String path = manifest.substring(0, manifest.length() - 9); - if (new File(path).isDirectory()) { - return path; - } - } - return ""; + this.repoMapping = loadRepositoryMapping(rlocationChecked("_repo_mapping")); } @Override - public String rlocationChecked(String path) { + protected String rlocationChecked(String path) { String exactMatch = runfiles.get(path); if (exactMatch != null) { return exactMatch; @@ -245,7 +467,7 @@ public String rlocationChecked(String path) { } @Override - public Map getEnvVars() { + protected Map getEnvVars() { HashMap result = new HashMap<>(4); result.put("RUNFILES_MANIFEST_ONLY", "1"); result.put("RUNFILES_MANIFEST_FILE", manifestPath); @@ -255,25 +477,68 @@ public Map getEnvVars() { result.put("JAVA_RUNFILES", runfilesDir); return result; } + + @Override + protected Map getRepoMapping() { + return repoMapping; + } + + private static Map loadRunfiles(String path) throws IOException { + HashMap result = new HashMap<>(); + try (BufferedReader r = + new BufferedReader( + new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { + String line = null; + while ((line = r.readLine()) != null) { + int index = line.indexOf(' '); + String runfile = (index == -1) ? line : line.substring(0, index); + String realPath = (index == -1) ? line : line.substring(index + 1); + result.put(runfile, realPath); + } + } + return Collections.unmodifiableMap(result); + } + + private static String findRunfilesDir(String manifest) { + if (manifest.endsWith("/MANIFEST") + || manifest.endsWith("\\MANIFEST") + || manifest.endsWith(".runfiles_manifest")) { + String path = manifest.substring(0, manifest.length() - 9); + if (new File(path).isDirectory()) { + return path; + } + } + return ""; + } } - /** {@link Runfiles} implementation that appends runfiles paths to the runfiles root. */ - private static final class DirectoryBased extends Runfiles { + /** + * {@link Runfiles} implementation that appends runfiles paths to the runfiles root. + */ + private static final class DirectoryBased extends Preloaded { + private final String runfilesRoot; + private final Map repoMapping; - DirectoryBased(String runfilesDir) { + DirectoryBased(String runfilesDir) throws IOException { Util.checkArgument(!Util.isNullOrEmpty(runfilesDir)); Util.checkArgument(new File(runfilesDir).isDirectory()); this.runfilesRoot = runfilesDir; + this.repoMapping = loadRepositoryMapping(rlocationChecked("_repo_mapping")); } @Override - String rlocationChecked(String path) { + protected String rlocationChecked(String path) { return runfilesRoot + "/" + path; } @Override - public Map getEnvVars() { + protected Map getRepoMapping() { + return repoMapping; + } + + @Override + protected Map getEnvVars() { HashMap result = new HashMap<>(2); result.put("RUNFILES_DIR", runfilesRoot); // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can pick up RUNFILES_DIR. @@ -282,11 +547,11 @@ public Map getEnvVars() { } } - static Runfiles createManifestBasedForTesting(String manifestPath) throws IOException { + static Preloaded createManifestBasedForTesting(String manifestPath) throws IOException { return new ManifestBased(manifestPath); } - static Runfiles createDirectoryBasedForTesting(String runfilesDir) { + static Preloaded createDirectoryBasedForTesting(String runfilesDir) throws IOException { return new DirectoryBased(runfilesDir); } } diff --git a/tools/java/runfiles/testing/BUILD b/tools/java/runfiles/testing/BUILD index 82d7836e16cde3..841724b36dc453 100644 --- a/tools/java/runfiles/testing/BUILD +++ b/tools/java/runfiles/testing/BUILD @@ -36,7 +36,6 @@ java_library( name = "test_deps", testonly = 1, exports = [ - ":mock-file", "//third_party:guava", "//third_party:guava-testlib", "//third_party:junit4", @@ -44,11 +43,3 @@ java_library( "//tools/java/runfiles", ], ) - -java_library( - name = "mock-file", - testonly = 1, - srcs = ["MockFile.java"], - exports = ["//third_party:guava"], - deps = ["//third_party:guava"], -) diff --git a/tools/java/runfiles/testing/MockFile.java b/tools/java/runfiles/testing/MockFile.java deleted file mode 100644 index 886bfcbcc82a38..00000000000000 --- a/tools/java/runfiles/testing/MockFile.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.devtools.build.runfiles; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -final class MockFile implements Closeable { - - public final Path path; - - public MockFile(ImmutableList lines) throws IOException { - String testTmpdir = System.getenv("TEST_TMPDIR"); - if (Strings.isNullOrEmpty(testTmpdir)) { - throw new IOException("$TEST_TMPDIR is empty or undefined"); - } - path = Files.createTempFile(new File(testTmpdir).toPath(), null, null); - Files.write(path, lines, StandardCharsets.UTF_8); - } - - @Override - public void close() throws IOException { - if (path != null) { - Files.delete(path); - } - } -} diff --git a/tools/java/runfiles/testing/RunfilesTest.java b/tools/java/runfiles/testing/RunfilesTest.java index e98ca554a8a27d..22605c3bf0952c 100644 --- a/tools/java/runfiles/testing/RunfilesTest.java +++ b/tools/java/runfiles/testing/RunfilesTest.java @@ -25,17 +25,23 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Unit tests for {@link Runfiles}. */ +/** + * Unit tests for {@link Runfiles}. + */ @RunWith(JUnit4.class) public final class RunfilesTest { + @Rule + public TemporaryFolder tempDir = new TemporaryFolder(new File(System.getenv("TEST_TMPDIR"))); + private static boolean isWindows() { return File.separatorChar == '\\'; } @@ -44,7 +50,7 @@ private void assertRlocationArg(Runfiles runfiles, String path, @Nullable String IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> runfiles.rlocation(path)); if (error != null) { - assertThat(e).hasMessageThat().contains(error); + assertThat(e).hasMessageThat().contains(error); } } @@ -71,24 +77,23 @@ public void testRlocationArgumentValidation() throws Exception { @Test public void testCreatesManifestBasedRunfiles() throws Exception { - try (MockFile mf = new MockFile(ImmutableList.of("a/b c/d"))) { - Runfiles r = - Runfiles.create( - ImmutableMap.of( - "RUNFILES_MANIFEST_ONLY", "1", - "RUNFILES_MANIFEST_FILE", mf.path.toString(), - "RUNFILES_DIR", "ignored when RUNFILES_MANIFEST_ONLY=1", - "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", - "TEST_SRCDIR", "should always be ignored")); - assertThat(r.rlocation("a/b")).isEqualTo("c/d"); - assertThat(r.rlocation("foo")).isNull(); - - if (isWindows()) { - assertThat(r.rlocation("c:/foo")).isEqualTo("c:/foo"); - assertThat(r.rlocation("c:\\foo")).isEqualTo("c:\\foo"); - } else { - assertThat(r.rlocation("/foo")).isEqualTo("/foo"); - } + Path mf = tempFile("foo.runfiles_manifest", ImmutableList.of("a/b c/d")); + Runfiles r = + Runfiles.create( + ImmutableMap.of( + "RUNFILES_MANIFEST_ONLY", "1", + "RUNFILES_MANIFEST_FILE", mf.toString(), + "RUNFILES_DIR", "ignored when RUNFILES_MANIFEST_ONLY=1", + "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", + "TEST_SRCDIR", "should always be ignored")); + assertThat(r.rlocation("a/b")).isEqualTo("c/d"); + assertThat(r.rlocation("foo")).isNull(); + + if (isWindows()) { + assertThat(r.rlocation("c:/foo")).isEqualTo("c:/foo"); + assertThat(r.rlocation("c:\\foo")).isEqualTo("c:\\foo"); + } else { + assertThat(r.rlocation("/foo")).isEqualTo("/foo"); } } @@ -146,7 +151,7 @@ public void testIgnoresTestSrcdirWhenJavaRunfilesIsUndefinedAndJustFails() throw "RUNFILES_DIR", "", "JAVA_RUNFILES", "", "RUNFILES_MANIFEST_FILE", - "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", + "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", "TEST_SRCDIR", "should always be ignored"))); assertThat(e).hasMessageThat().contains("$RUNFILES_DIR and $JAVA_RUNFILES"); } @@ -166,12 +171,7 @@ public void testFailsToCreateManifestBasedBecauseManifestDoesNotExist() { @Test public void testManifestBasedEnvVars() throws Exception { - Path dir = - Files.createTempDirectory( - FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")), null); - - Path mf = dir.resolve("MANIFEST"); - Files.write(mf, Collections.emptyList(), StandardCharsets.UTF_8); + Path mf = tempFile("MANIFEST", ImmutableList.of()); Map envvars = Runfiles.create( ImmutableMap.of( @@ -186,13 +186,12 @@ public void testManifestBasedEnvVars() throws Exception { "RUNFILES_MANIFEST_ONLY", "RUNFILES_MANIFEST_FILE", "RUNFILES_DIR", "JAVA_RUNFILES"); assertThat(envvars.get("RUNFILES_MANIFEST_ONLY")).isEqualTo("1"); assertThat(envvars.get("RUNFILES_MANIFEST_FILE")).isEqualTo(mf.toString()); - assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(dir.toString()); - assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(dir.toString()); + assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(tempDir.getRoot().toString()); + assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(tempDir.getRoot().toString()); - Path rfDir = dir.resolve("foo.runfiles"); + Path rfDir = tempDir.getRoot().toPath().resolve("foo.runfiles"); Files.createDirectories(rfDir); - mf = dir.resolve("foo.runfiles_manifest"); - Files.write(mf, Collections.emptyList(), StandardCharsets.UTF_8); + mf = tempFile("foo.runfiles_manifest", ImmutableList.of()); envvars = Runfiles.create( ImmutableMap.of( @@ -210,81 +209,339 @@ public void testManifestBasedEnvVars() throws Exception { @Test public void testDirectoryBasedEnvVars() throws Exception { - Path dir = - Files.createTempDirectory( - FileSystems.getDefault().getPath(System.getenv("TEST_TMPDIR")), null); - Map envvars = Runfiles.create( ImmutableMap.of( "RUNFILES_MANIFEST_FILE", "ignored when RUNFILES_MANIFEST_ONLY is not set to 1", "RUNFILES_DIR", - dir.toString(), + tempDir.getRoot().toString(), "JAVA_RUNFILES", "ignored when RUNFILES_DIR has a value", "TEST_SRCDIR", "should always be ignored")) .getEnvVars(); assertThat(envvars.keySet()).containsExactly("RUNFILES_DIR", "JAVA_RUNFILES"); - assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(dir.toString()); - assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(dir.toString()); + assertThat(envvars.get("RUNFILES_DIR")).isEqualTo(tempDir.getRoot().toString()); + assertThat(envvars.get("JAVA_RUNFILES")).isEqualTo(tempDir.getRoot().toString()); } @Test - public void testDirectoryBasedRlocation() { + public void testDirectoryBasedRlocation() throws IOException { // The DirectoryBased implementation simply joins the runfiles directory and the runfile's path // on a "/". DirectoryBased does not perform any normalization, nor does it check that the path // exists. File dir = new File(System.getenv("TEST_TMPDIR"), "mock/runfiles"); assertThat(dir.mkdirs()).isTrue(); - Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).withSourceRepository(""); // Escaping for "\": once for string and once for regex. assertThat(r.rlocation("arg")).matches(".*[/\\\\]mock[/\\\\]runfiles[/\\\\]arg"); } @Test public void testManifestBasedRlocation() throws Exception { - try (MockFile mf = - new MockFile( - ImmutableList.of( - "Foo/runfile1 C:/Actual Path\\runfile1", - "Foo/Bar/runfile2 D:\\the path\\run file 2.txt", - "Foo/Bar/Dir E:\\Actual Path\\Directory"))) { - Runfiles r = Runfiles.createManifestBasedForTesting(mf.path.toString()); - assertThat(r.rlocation("Foo/runfile1")).isEqualTo("C:/Actual Path\\runfile1"); - assertThat(r.rlocation("Foo/Bar/runfile2")).isEqualTo("D:\\the path\\run file 2.txt"); - assertThat(r.rlocation("Foo/Bar/Dir")).isEqualTo("E:\\Actual Path\\Directory"); - assertThat(r.rlocation("Foo/Bar/Dir/File")).isEqualTo("E:\\Actual Path\\Directory/File"); - assertThat(r.rlocation("Foo/Bar/Dir/Deeply/Nested/File")) - .isEqualTo("E:\\Actual Path\\Directory/Deeply/Nested/File"); - assertThat(r.rlocation("unknown")).isNull(); - } + Path mf = tempFile("MANIFEST", ImmutableList.of( + "Foo/runfile1 C:/Actual Path\\runfile1", + "Foo/Bar/runfile2 D:\\the path\\run file 2.txt", + "Foo/Bar/Dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + assertThat(r.rlocation("Foo/runfile1")).isEqualTo("C:/Actual Path\\runfile1"); + assertThat(r.rlocation("Foo/Bar/runfile2")).isEqualTo("D:\\the path\\run file 2.txt"); + assertThat(r.rlocation("Foo/Bar/Dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("Foo/Bar/Dir/File")).isEqualTo("E:\\Actual Path\\Directory/File"); + assertThat(r.rlocation("Foo/Bar/Dir/Deeply/Nested/File")) + .isEqualTo("E:\\Actual Path\\Directory/Deeply/Nested/File"); + assertThat(r.rlocation("unknown")).isNull(); } @Test - public void testDirectoryBasedCtorArgumentValidation() { - assertThrows( - IllegalArgumentException.class, () -> Runfiles.createDirectoryBasedForTesting(null)); + public void testManifestBasedRlocationWithRepoMapping_fromMain() throws Exception { + Path rm = tempFile("foo.repo_mapping", ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Path mf = tempFile("foo.runfiles_manifest", ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo( + "/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("my_workspace/bar/runfile")).isEqualTo( + "/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isEqualTo( + "C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isEqualTo( + "E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + "E:\\Actual Path\\Directory/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + "/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + "C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + "E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + "E:\\Actual Path\\Directory/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } - assertThrows(IllegalArgumentException.class, () -> Runfiles.createDirectoryBasedForTesting("")); + @Test + public void testManifestBasedRlocationUnmapped() throws Exception { + Path rm = tempFile("foo.repo_mapping", ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Path mf = tempFile("foo.runfiles_manifest", ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()).unmapped(); + + assertThat(r.rlocation("my_module/bar/runfile")).isNull(); + assertThat(r.rlocation("my_workspace/bar/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isNull(); + + assertThat(r.rlocation("protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + "/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + "C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + "E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + "E:\\Actual Path\\Directory/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } + + @Test + public void testManifestBasedRlocationWithRepoMapping_fromOtherRepo() throws Exception { + Path rm = tempFile("foo.repo_mapping", ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Path mf = tempFile("foo.runfiles/MANIFEST", ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory")); + Runfiles r = Runfiles.createManifestBasedForTesting(mf.toString()) + .withSourceRepository("protobuf~3.19.2"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo("C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf/bar/dir/file")).isEqualTo("E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + "E:\\Actual Path\\Directory/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("my_module/bar/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isNull(); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isNull(); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + "/the/path/./to/other//other runfile.txt"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + "C:/Actual Path\\protobuf\\runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo("E:\\Actual Path\\Directory"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + "E:\\Actual Path\\Directory/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + "E:\\Actual Path\\Directory/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo("/etc/config.json"); + assertThat(r.rlocation("_main")).isNull(); + assertThat(r.rlocation("my_module")).isNull(); + assertThat(r.rlocation("protobuf")).isNull(); + } + + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromMain() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + tempFile(dir.resolve("_repo_mapping").toString(), ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).withSourceRepository(""); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo( + dir + "/_main/bar/runfile"); + assertThat(r.rlocation("my_workspace/bar/runfile")).isEqualTo( + dir + "/_main/bar/runfile"); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isEqualTo( + dir + "/protobuf~3.19.2/foo/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo(dir + "/protobuf~3.19.2/bar/dir"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + dir + "/protobuf~3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo(dir + "/protobuf~3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedRlocationUnmapped() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path rm = tempFile(dir.resolve("_repo_mapping").toString(), ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()).unmapped(); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo( + dir + "/my_module/bar/runfile"); + assertThat(r.rlocation("my_workspace/bar/runfile")).isEqualTo( + dir + "/my_workspace/bar/runfile"); + assertThat(r.rlocation("my_protobuf/foo/runfile")).isEqualTo( + dir + "/my_protobuf/foo/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir")).isEqualTo(dir + "/my_protobuf/bar/dir"); + assertThat(r.rlocation("my_protobuf/bar/dir/file")).isEqualTo( + dir + "/my_protobuf/bar/dir/file"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/my_protobuf/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + dir + "/protobuf~3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo(dir + "/protobuf~3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromOtherRepo() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path rm = tempFile(dir.resolve("_repo_mapping").toString(), ImmutableList.of( + ",my_module,_main", + ",my_protobuf,protobuf~3.19.2", + ",my_workspace,_main", + "protobuf~3.19.2,protobuf,protobuf~3.19.2")); + Runfiles r = Runfiles.createDirectoryBasedForTesting(dir.toString()) + .withSourceRepository("protobuf~3.19.2"); + + assertThat(r.rlocation("protobuf/foo/runfile")).isEqualTo(dir + "/protobuf~3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf/bar/dir")).isEqualTo(dir + "/protobuf~3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf/bar/dir/file")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("my_module/bar/runfile")).isEqualTo(dir + "/my_module/bar/runfile"); + assertThat(r.rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/my_protobuf/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("_main/bar/runfile")).isEqualTo( + dir + "/_main/bar/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/foo/runfile")).isEqualTo( + dir + "/protobuf~3.19.2/foo/runfile"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir")).isEqualTo(dir + "/protobuf~3.19.2/bar/dir"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/file")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/file"); + assertThat(r.rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le")).isEqualTo( + dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le"); + + assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); + } + + @Test + public void testDirectoryBasedCtorArgumentValidation() throws IOException { + assertThrows(IllegalArgumentException.class, + () -> Runfiles.createDirectoryBasedForTesting(null).withSourceRepository("")); + + assertThrows(IllegalArgumentException.class, + () -> Runfiles.createDirectoryBasedForTesting("").withSourceRepository("")); assertThrows( IllegalArgumentException.class, - () -> Runfiles.createDirectoryBasedForTesting("non-existent directory is bad")); + () -> Runfiles.createDirectoryBasedForTesting("non-existent directory is bad") + .withSourceRepository("")); - Runfiles.createDirectoryBasedForTesting(System.getenv("TEST_TMPDIR")); + Runfiles.createDirectoryBasedForTesting(System.getenv("TEST_TMPDIR")).withSourceRepository(""); } @Test public void testManifestBasedCtorArgumentValidation() throws Exception { assertThrows( - IllegalArgumentException.class, () -> Runfiles.createManifestBasedForTesting(null)); + IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting(null).withSourceRepository("")); - assertThrows(IllegalArgumentException.class, () -> Runfiles.createManifestBasedForTesting("")); + assertThrows(IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting("").withSourceRepository("")); - try (MockFile mf = new MockFile(ImmutableList.of("a b"))) { - Runfiles.createManifestBasedForTesting(mf.path.toString()); - } + Path mf = tempFile("foobar", ImmutableList.of("a b")); + Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + } + + @Test + public void testInvalidRepoMapping() throws Exception { + Path rm = tempFile("foo.repo_mapping", ImmutableList.of("a,b,c,d")); + Path mf = tempFile("foo.runfiles/MANIFEST", ImmutableList.of("_repo_mapping " + rm)); + assertThrows(IllegalArgumentException.class, + () -> Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository("")); + } + + private Path tempFile(String path, ImmutableList lines) throws IOException { + Path file = tempDir.getRoot().toPath().resolve(path.replace('/', File.separatorChar)); + Files.createDirectories(file.getParent()); + return Files.write(file, lines, StandardCharsets.UTF_8); } }