blob: c829ae75e7e2fce132bda98fa4c1263467a403fe [file] [log] [blame]
// Copyright 2015 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.lib.packages.util;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.GlobCache;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.PackageValidator;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.syntax.Starlark;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
/** Base class for PackageFactory tests. */
// TODO(adonovan): merge this down into PackageFactory---one small step towards ending
// a history abuse of the "extends" keyword.
public abstract class PackageFactoryTestBase {
protected Scratch scratch;
protected EventCollectionApparatus events = new EventCollectionApparatus();
protected DummyPackageValidator dummyPackageValidator = new DummyPackageValidator();
protected PackageFactoryApparatus packages =
new PackageFactoryApparatus(events.reporter(), dummyPackageValidator);
protected Root root;
protected Package expectEvalSuccess(String... content)
throws InterruptedException, IOException, NoSuchPackageException {
Path file = scratch.file("pkg/BUILD", content);
Package pkg = packages.eval("pkg", RootedPath.toRootedPath(root, file));
assertThat(pkg.containsErrors()).isFalse();
return pkg;
}
protected void expectEvalError(String expectedError, String... content) throws Exception {
events.setFailFast(false);
Path file = scratch.file("pkg/BUILD", content);
Package pkg = packages.eval("pkg", RootedPath.toRootedPath(root, file));
assertWithMessage("Expected evaluation error, but none was not reported")
.that(pkg.containsErrors())
.isTrue();
events.assertContainsError(expectedError);
}
protected Path throwOnReaddir = null;
protected static AttributeMap attributes(Rule rule) {
return RawAttributeMapper.of(rule);
}
protected static void assertEvaluates(Package pkg, List<String> expected, String... include)
throws Exception {
assertEvaluates(pkg, expected, ImmutableList.copyOf(include), Collections.<String>emptyList());
}
protected static void assertEvaluates(
Package pkg, List<String> expected, List<String> include, List<String> exclude)
throws Exception {
GlobCache globCache =
new GlobCache(
pkg.getFilename().asPath().getParentDirectory(),
pkg.getPackageIdentifier(),
ImmutableSet.of(),
PackageFactoryApparatus.createEmptyLocator(),
null,
TestUtils.getPool(),
-1);
assertThat(globCache.globUnsorted(include, exclude, false, true))
.containsExactlyElementsIn(expected);
}
@Before
public final void initializeFileSystem() throws Exception {
FileSystem fs =
new InMemoryFileSystem() {
@Override
public Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
if (path.equals(throwOnReaddir)) {
throw new FileNotFoundException(path.getPathString());
}
return super.readdir(path, followSymlinks);
}
};
Path tmpPath = fs.getPath("/");
scratch = new Scratch(tmpPath);
root = Root.fromPath(scratch.dir("/"));
}
protected Path emptyBuildFile(String packageName) {
return emptyFile("/" + packageName + "/BUILD");
}
protected Path emptyFile(String path) {
try {
return scratch.file(path);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected boolean isValidPackageName(String packageName) throws Exception {
// Write a license decl just in case it's a third_party package:
Path buildFile = scratch.file("/" + packageName + "/BUILD", "licenses(['notice'])");
Package pkg = packages.createPackage(packageName, RootedPath.toRootedPath(root, buildFile));
return !pkg.containsErrors();
}
/********************************************************************
* *
* Test "glob" function in build language *
* *
********************************************************************/
protected void assertGlobFails(String globCallExpression, String expectedError) throws Exception {
Package pkg = buildPackageWithGlob(globCallExpression);
events.assertContainsError(expectedError);
assertThat(pkg.containsErrors()).isTrue();
}
private Package buildPackageWithGlob(String globCallExpression) throws Exception {
scratch.deleteFile("/dummypackage/BUILD");
Path file = scratch.file("/dummypackage/BUILD", "x = " + globCallExpression);
return packages.eval("dummypackage", RootedPath.toRootedPath(root, file));
}
private List<Pair<String, Boolean>> createGlobCacheKeys(
List<String> expressions, boolean excludeDirs) {
List<Pair<String, Boolean>> keys = Lists.newArrayListWithCapacity(expressions.size());
for (String expression : expressions) {
keys.add(Pair.of(expression, excludeDirs));
}
return keys;
}
/**
* Test globbing in the context of a package, using the build language.
* We use the specially setup "globs" test package and the files beneath it.
* @param result the expected list of filenames that match the glob
* @param includes an include pattern for the glob
* @param excludes an exclude pattern for the glob
* @param excludeDirs an exclude_directories flag for the glob
* @throws Exception if the glob doesn't match the expected result.
*/
protected void assertGlobMatches(
List<String> result, List<String> includes, List<String> excludes, boolean excludeDirs)
throws Exception {
Pair<Package, GlobCache> evaluated =
evaluateGlob(
includes,
excludes,
excludeDirs,
Starlark.format("(result == sorted(%r)) or fail('incorrect glob result')", result));
Package pkg = evaluated.first;
GlobCache globCache = evaluated.second;
// Ensure all of the patterns are recorded against this package:
assertThat(globCache.getKeySet().containsAll(createGlobCacheKeys(includes, excludeDirs)))
.isTrue();
assertThat(pkg.containsErrors()).isFalse();
}
/**
* Evaluate a glob() call against a test directory and BUILD code to process the results.
* @param includes a list of glob patterns; glob will include these files.
* @param excludes a list of glob patterns to exclude even if previously included.
* @param excludeDirs true if directories should be excluded from the match.
* @param resultAssertion code in the BUILD language that can access the variable result,
* to which the result of the glob will be bound, and that may contain an assertion on it.
* @return a Package and a GlobCache.
* @throws Exception if the processResult code causes a failure.
*/
private Pair<Package, GlobCache> evaluateGlob(
List<String> includes, List<String> excludes, boolean excludeDirs, String resultAssertion)
throws Exception {
Path globsDir = scratch.dir("/globs");
globsDir.getChild("subdir").createDirectory();
for (String file : ImmutableList.of("Wombat1.java", "Wombat2.java", "subdir/Wombat3.java")) {
FileSystemUtils.createEmptyFile(globsDir.getRelative(file));
}
Path file =
scratch.file(
"/globs/BUILD",
Starlark.format(
"result = glob(%r, exclude=%r, exclude_directories=%r)",
includes, excludes, excludeDirs ? 1 : 0),
resultAssertion);
return packages.evalAndReturnGlobCache(
"globs", RootedPath.toRootedPath(root, file), packages.parse(file));
}
protected void assertGlobProducesError(String pattern, boolean errorExpected) throws Exception {
events.setFailFast(false);
Package pkg =
evaluateGlob(ImmutableList.of(pattern), Collections.<String>emptyList(), false, "").first;
assertThat(pkg.containsErrors()).isEqualTo(errorExpected);
boolean foundError = false;
for (Event event : events.collector()) {
if (event.getMessage().contains("glob")) {
if (!errorExpected) {
fail("error not expected for glob pattern " + pattern + ", but got: " + event);
return;
}
foundError = errorExpected;
break;
}
}
assertThat(foundError).isEqualTo(errorExpected);
}
/**
* {@link PackageValidator} whose functionality can be swapped out on demand via {@link #setImpl}.
*/
protected static class DummyPackageValidator implements PackageValidator {
private PackageValidator underlying = PackageValidator.NOOP_VALIDATOR;
/** Sets {@link PackageValidator} implementation to use. */
public void setImpl(PackageValidator impl) {
this.underlying = impl;
}
@Override
public void validate(Package pkg, ExtendedEventHandler eventHandler)
throws InvalidPackageException {
underlying.validate(pkg, eventHandler);
}
}
}