blob: 9e23729866410d881755be6d1f6bc8baedc6b744 [file] [log] [blame]
// Copyright 2017 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.pkgcache;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.analysis.util.DefaultBuildOptionsForTesting;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.SkylarkSemanticsOptions;
import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
import com.google.devtools.build.lib.skyframe.DiffAwareness;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
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 com.google.devtools.common.options.OptionsParser;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for package loading.
*/
@RunWith(JUnit4.class)
public class BuildFileModificationTest extends FoundationTestCase {
private ManualClock clock = new ManualClock();
private AnalysisMock analysisMock;
private ConfiguredRuleClassProvider ruleClassProvider;
private SkyframeExecutor skyframeExecutor;
private final ActionKeyContext actionKeyContext = new ActionKeyContext();
@Before
public final void disableLogging() throws Exception {
Logger.getLogger("com.google.devtools").setLevel(Level.SEVERE);
}
@Before
public final void initializeSkyframeExecutor() throws Exception {
analysisMock = AnalysisMock.get();
ruleClassProvider = analysisMock.createRuleClassProvider();
BlazeDirectories directories =
new BlazeDirectories(
new ServerDirectories(outputBase, outputBase, outputBase),
rootDirectory,
/* defaultSystemJavabase= */ null,
analysisMock.getProductName());
skyframeExecutor =
SequencedSkyframeExecutor.create(
analysisMock
.getPackageFactoryBuilderForTesting(directories)
.build(ruleClassProvider, fileSystem),
fileSystem,
directories,
actionKeyContext,
/* workspaceStatusActionFactory= */ null,
ruleClassProvider.getBuildInfoFactories(),
ImmutableList.<DiffAwareness.Factory>of(),
analysisMock.getSkyFunctions(directories),
ImmutableList.<SkyValueDirtinessChecker>of(),
BazelSkyframeExecutorConstants.HARDCODED_BLACKLISTED_PACKAGE_PREFIXES,
BazelSkyframeExecutorConstants.ADDITIONAL_BLACKLISTED_PACKAGE_PREFIXES_FILE,
BazelSkyframeExecutorConstants.CROSS_REPOSITORY_LABEL_VIOLATION_STRATEGY,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY,
BazelSkyframeExecutorConstants.ACTION_ON_IO_EXCEPTION_READING_BUILD_FILE,
DefaultBuildOptionsForTesting.getDefaultBuildOptionsForTest(ruleClassProvider));
skyframeExecutor.injectExtraPrecomputedValues(
ImmutableList.of(
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE,
Optional.<RootedPath>absent())));
TestConstants.processSkyframeExecutorForTesting(skyframeExecutor);
OptionsParser parser = OptionsParser.newOptionsParser(
PackageCacheOptions.class, SkylarkSemanticsOptions.class);
analysisMock.getInvocationPolicyEnforcer().enforce(parser);
setUpSkyframe(
parser.getOptions(PackageCacheOptions.class),
parser.getOptions(SkylarkSemanticsOptions.class));
}
private void setUpSkyframe(
PackageCacheOptions packageCacheOptions,
SkylarkSemanticsOptions skylarkSemanticsOptions) {
PathPackageLocator pkgLocator =
PathPackageLocator.create(
null,
packageCacheOptions.packagePath,
reporter,
rootDirectory,
rootDirectory,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY);
packageCacheOptions.showLoadingProgress = true;
packageCacheOptions.globbingThreads = 7;
skyframeExecutor.preparePackageLoading(
pkgLocator,
packageCacheOptions,
skylarkSemanticsOptions,
UUID.randomUUID(),
ImmutableMap.<String, String>of(),
new TimestampGranularityMonitor(clock));
skyframeExecutor.setActionEnv(ImmutableMap.<String, String>of());
skyframeExecutor.setDeletedPackages(
ImmutableSet.copyOf(packageCacheOptions.getDeletedPackages()));
}
@Override
protected FileSystem createFileSystem() {
return new InMemoryFileSystem(clock);
}
private void invalidatePackages() throws InterruptedException {
skyframeExecutor.invalidateFilesUnderPathForTesting(
reporter, ModifiedFileSet.EVERYTHING_MODIFIED, Root.fromPath(rootDirectory));
}
private Package getPackage(String packageName)
throws NoSuchPackageException, InterruptedException {
return skyframeExecutor.getPackageManager().getPackage(reporter,
PackageIdentifier.createInMainRepo(packageName));
}
@Test
public void testCTimeChangeDetectedWithError() throws Exception {
reporter.removeHandler(failFastHandler);
Path build = scratch.file(
"a/BUILD", "cc_library(name='a', feet='stinky')".getBytes(StandardCharsets.ISO_8859_1));
Package a1 = getPackage("a");
assertThat(a1.containsErrors()).isTrue();
assertContainsEvent("//a:a: no such attribute 'feet'");
eventCollector.clear();
// writeContent updates mtime and ctime. Note that we keep the content length exactly the same.
clock.advanceMillis(1);
FileSystemUtils.writeContent(
build, "cc_library(name='a', srcs=['a.cc'])".getBytes(StandardCharsets.ISO_8859_1));
invalidatePackages();
Package a2 = getPackage("a");
assertThat(a2).isNotSameAs(a1);
assertThat(a2.containsErrors()).isFalse();
assertNoEvents();
}
@Test
public void testCTimeChangeDetected() throws Exception {
Path path = scratch.file(
"pkg/BUILD", "cc_library(name = 'foo')\n".getBytes(StandardCharsets.ISO_8859_1));
Package oldPkg = getPackage("pkg");
// Note that the content has exactly the same length as before.
clock.advanceMillis(1);
FileSystemUtils.writeContent(
path, "cc_library(name = 'bar')\n".getBytes(StandardCharsets.ISO_8859_1));
assertThat(getPackage("pkg"))
.isSameAs(oldPkg); // Change only becomes visible after invalidatePackages.
invalidatePackages();
Package newPkg = getPackage("pkg");
assertThat(newPkg).isNotSameAs(oldPkg);
assertThat(newPkg.getTarget("bar")).isNotNull();
}
@Test
public void testLengthChangeDetected() throws Exception {
reporter.removeHandler(failFastHandler);
Path build = scratch.file(
"a/BUILD", "cc_library(name='a', srcs=['a.cc'])".getBytes(StandardCharsets.ISO_8859_1));
Package a1 = getPackage("a");
eventCollector.clear();
// Note that we didn't advance the clock, so ctime/mtime is the same as before.
// However, the file contents are one byte longer.
FileSystemUtils.writeContent(
build, "cc_library(name='ab', srcs=['a.cc'])".getBytes(StandardCharsets.ISO_8859_1));
invalidatePackages();
Package a2 = getPackage("a");
assertThat(a2).isNotSameAs(a1);
assertNoEvents();
}
@Test
public void testTouchedBuildFileCausesReloadAfterSync() throws Exception {
Path path = scratch.file("pkg/BUILD",
"cc_library(name = 'foo')");
Package oldPkg = getPackage("pkg");
// Change ctime to 1.
clock.advanceMillis(1);
path.setLastModifiedTime(1001);
assertThat(getPackage("pkg")).isSameAs(oldPkg); // change not yet visible
invalidatePackages();
Package newPkg = getPackage("pkg");
assertThat(newPkg).isNotSameAs(oldPkg);
}
}