blob: 4334b3e4071a81e10d3cf966b880092b9ef43463 [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.skyframe;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.cache.CompactPersistentActionCache;
import com.google.devtools.build.lib.testutil.BlazeTestUtils;
import com.google.devtools.build.lib.testutil.Suite;
import com.google.devtools.build.lib.testutil.TestSpec;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.UnixGlob;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* These tests belong to {@link TimestampBuilderTest}, but they're in a separate class for now
* because they are a little slower.
*/
@TestSpec(size = Suite.MEDIUM_TESTS)
@RunWith(JUnit4.class)
public class TimestampBuilderMediumTest extends TimestampBuilderTestCase {
private Path cacheRoot;
private CompactPersistentActionCache cache;
@Before
public final void setCache() throws Exception {
// BlazeRuntime.setupLogging(Level.FINEST); // Uncomment this for debugging.
cacheRoot = scratch.dir("cacheRoot");
cache = createCache();
}
private CompactPersistentActionCache createCache() throws IOException {
return new CompactPersistentActionCache(cacheRoot, clock);
}
/**
* Creates and returns a new caching builder based on a given {@code cache}.
*/
private Builder persistentBuilder(CompactPersistentActionCache cache) throws Exception {
return createBuilder(cache);
}
// TODO(blaze-team): (2009) :
// - test timestamp monotonicity is not required (i.e. set mtime backwards)
// - test change of key causes rebuild
@Test
public void testUnneededInputs() throws Exception {
Artifact hello = createSourceArtifact("hello");
BlazeTestUtils.makeEmptyFile(hello.getPath());
Artifact optional = createSourceArtifact("hello.optional");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button = createActionButton(Sets.newHashSet(hello, optional), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
cache = createCache();
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
BlazeTestUtils.makeEmptyFile(optional.getPath());
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
optional.getPath().delete();
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
// Creating a new persistent cache does not cause a rebuild
cache.save();
cache = createCache();
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
}
@Test
public void testPersistentCache_ModifyingInputCausesActionReexecution() throws Exception {
// /hello -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
BlazeTestUtils.makeEmptyFile(hello.getPath());
Artifact goodbye = createDerivedArtifact("goodbye");
Button button = createActionButton(Sets.newHashSet(hello), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
hello.getPath().setWritable(true);
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "new content");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
buildArtifacts(persistentBuilder(createCache()), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
}
@Test
public void testModifyingInputCausesActionReexecution() throws Exception {
// /hello -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
FileSystemUtils.createDirectoryAndParents(hello.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content1");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button = createActionButton(Sets.newHashSet(hello), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // still not rebuilt
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content2");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
buildArtifacts(persistentBuilder(createCache()), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
}
@Test
public void testArtifactOrderingDoesNotMatter() throws Exception {
// (/hello,/there) -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
Artifact there = createSourceArtifact("there");
FileSystemUtils.createDirectoryAndParents(hello.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "hello");
FileSystemUtils.writeContentAsLatin1(there.getPath(), "there");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button =
createActionButton(
Sets.newLinkedHashSet(ImmutableList.of(hello, there)), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Now create duplicate graph, with swapped order.
clearActions();
Artifact goodbye2 = createDerivedArtifact("goodbye");
Button button2 =
createActionButton(
Sets.newLinkedHashSet(ImmutableList.of(there, hello)), Sets.newHashSet(goodbye2));
button2.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button2.pressed).isFalse(); // still not rebuilt
}
@Test
public void testOldCacheKeysAreCleanedUp() throws Exception {
// [action1] -> (/goodbye), cache key will be /goodbye
Artifact goodbye = createDerivedArtifact("goodbye");
FileSystemUtils.createDirectoryAndParents(goodbye.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(goodbye.getPath(), "test");
Button button = createActionButton(emptySet, Sets.newLinkedHashSet(ImmutableList.of(goodbye)));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
// action1 is cached using the cache key /goodbye.
assertThat(cache.get(goodbye.getExecPathString())).isNotNull();
// [action2] -> (/hello,/goodbye), cache key will be /hello
clearActions();
Artifact hello = createDerivedArtifact("hello");
Artifact goodbye2 = createDerivedArtifact("goodbye");
Button button2 =
createActionButton(emptySet, Sets.newLinkedHashSet(ImmutableList.of(hello, goodbye2)));
button2.pressed = false;
buildArtifacts(persistentBuilder(cache), hello, goodbye2);
assertThat(button2.pressed).isTrue(); // rebuilt
// action2 is cached using the cache key /hello.
assertThat(cache.get(hello.getExecPathString())).isNotNull();
// Now, action1 should no longer be in the cache.
assertThat(cache.get(goodbye.getExecPathString())).isNull();
}
@Test
public void testArtifactNamesMatter() throws Exception {
// /hello -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
FileSystemUtils.createDirectoryAndParents(hello.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "hello");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button = createActionButton(Sets.newHashSet(hello), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Now create duplicate graph, replacing "hello" with "hi".
clearActions();
Artifact hi = createSourceArtifact("hi");
FileSystemUtils.createDirectoryAndParents(hi.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hi.getPath(), "hello");
Artifact goodbye2 = createDerivedArtifact("goodbye");
Button button2 = createActionButton(Sets.newHashSet(hi), Sets.newHashSet(goodbye2));
button2.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye2);
assertThat(button2.pressed).isTrue(); // name changed. must rebuild.
}
@Test
public void testDuplicateInputs() throws Exception {
// (/hello,/hello) -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
FileSystemUtils.createDirectoryAndParents(hello.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "hello");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button =
createActionButton(Lists.<Artifact>newArrayList(hello, hello), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "hello2");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
buildArtifacts(persistentBuilder(createCache()), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
}
/**
* Tests that changing timestamp of the input file without changing it content
* does not cause action reexecution when metadata cache uses file digests in
* addition to the timestamp.
*/
@Test
public void testModifyingTimestampOnlyDoesNotCauseActionReexecution() throws Exception {
// /hello -> [action] -> /goodbye
Artifact hello = createSourceArtifact("hello");
FileSystemUtils.createDirectoryAndParents(hello.getPath().getParentDirectory());
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "content1");
Artifact goodbye = createDerivedArtifact("goodbye");
Button button = createActionButton(Sets.newHashSet(hello), Sets.newHashSet(goodbye));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent caches, including metadata cache does not cause
// a rebuild
cache.save();
Builder builder = persistentBuilder(createCache());
buildArtifacts(builder, goodbye);
assertThat(button.pressed).isFalse(); // not rebuilt
}
@Test
public void testPersistentCache_ModifyingOutputCausesActionReexecution() throws Exception {
// [action] -> /hello
Artifact hello = createDerivedArtifact("hello");
Button button = createActionButton(emptySet, Sets.newHashSet(hello));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
hello.getPath().setWritable(true);
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "new content");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
buildArtifacts(persistentBuilder(createCache()), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
}
@Test
public void testPersistentCache_missingFilenameIndexCausesActionReexecution() throws Exception {
// [action] -> /hello
Artifact hello = createDerivedArtifact("hello");
Button button = createActionButton(emptySet, Sets.newHashSet(hello));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
hello.getPath().setWritable(true);
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "new content");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
// Creating a new persistent cache does not cause a rebuild
cache.save();
// Remove filename index file.
assertThat(
Iterables.getOnlyElement(
UnixGlob.forPath(cacheRoot).addPattern("filename_index*").globInterruptible())
.delete())
.isTrue();
// Now first cache creation attempt should cause IOException while renaming corrupted files.
// Second attempt will initialize empty cache, causing rebuild.
try {
createCache();
fail("Expected IOException");
} catch (IOException e) {
assertThat(e).hasMessage("Failed action cache referential integrity check: empty index");
}
buildArtifacts(persistentBuilder(createCache()), hello);
assertThat(button.pressed).isTrue(); // rebuilt due to the missing filename index
}
@Test
public void testPersistentCache_failedIntegrityCheckCausesActionReexecution() throws Exception {
// [action] -> /hello
Artifact hello = createDerivedArtifact("hello");
Button button = createActionButton(emptySet, Sets.newHashSet(hello));
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // built
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
hello.getPath().setWritable(true);
FileSystemUtils.writeContentAsLatin1(hello.getPath(), "new content");
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isTrue(); // rebuilt
button.pressed = false;
buildArtifacts(persistentBuilder(cache), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
cache.save();
// Get filename index path and store a copy of it.
Path indexPath =
Iterables.getOnlyElement(
UnixGlob.forPath(cacheRoot).addPattern("filename_index*").globInterruptible());
Path indexCopy = scratch.resolve("index_copy");
FileSystemUtils.copyFile(indexPath, indexCopy);
// Add extra records to the action cache and indexer.
Artifact helloExtra = createDerivedArtifact("hello_extra");
Button buttonExtra = createActionButton(emptySet, Sets.newHashSet(helloExtra));
buildArtifacts(persistentBuilder(cache), helloExtra);
assertThat(buttonExtra.pressed).isTrue(); // built
cache.save();
assertThat(indexPath.getFileSize()).isGreaterThan(indexCopy.getFileSize());
// Validate current cache.
buildArtifacts(persistentBuilder(createCache()), hello);
assertThat(button.pressed).isFalse(); // not rebuilt
// Restore outdated file index.
FileSystemUtils.copyFile(indexCopy, indexPath);
// Now first cache creation attempt should cause IOException while renaming corrupted files.
// Second attempt will initialize empty cache, causing rebuild.
try {
createCache();
fail("Expected IOException");
} catch (IOException e) {
assertThat(e).hasMessageThat().contains("Failed action cache referential integrity check");
}
// Validate cache with incorrect (out-of-date) filename index.
buildArtifacts(persistentBuilder(createCache()), hello);
assertThat(button.pressed).isTrue(); // rebuilt due to the out-of-date index
}
}