blob: ac1de030e130d070655e4f8da2bb585ae1382843 [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 com.google.devtools.build.lib.actions.FileArtifactValue.createForTesting;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.io.BaseEncoding;
import com.google.common.testing.EqualsTester;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.testutil.ManualClock;
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.inmemoryfs.InMemoryFileSystem;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class FileArtifactValueTest {
private final ManualClock clock = new ManualClock();
private final FileSystem fs = new InMemoryFileSystem(clock);
private Path scratchFile(String name, long mtime, String content) throws IOException {
Path path = fs.getPath(name);
path.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(path, content);
path.setLastModifiedTime(mtime);
return path;
}
private Path scratchDir(String name, long mtime) throws IOException {
Path path = fs.getPath(name);
path.createDirectoryAndParents();
path.setLastModifiedTime(mtime);
return path;
}
private static byte[] toBytes(String hex) {
return BaseEncoding.base16().upperCase().decode(hex);
}
@Test
public void testEqualsAndHashCode() throws Exception {
// Each "equality group" is checked for equality within itself (including hashCode equality)
// and inequality with members of other equality groups.
new EqualsTester()
.addEqualityGroup(
FileArtifactValue.createForNormalFile(
toBytes("00112233445566778899AABBCCDDEEFF"),
/*proxy=*/ null,
1L,
/*isShareable=*/ true),
FileArtifactValue.createForNormalFile(
toBytes("00112233445566778899AABBCCDDEEFF"),
/*proxy=*/ null,
1L,
/*isShareable=*/ true))
.addEqualityGroup(
FileArtifactValue.createForNormalFile(
toBytes("00112233445566778899AABBCCDDEEFF"),
/*proxy=*/ null,
2L,
/*isShareable=*/ true))
.addEqualityGroup(FileArtifactValue.createForDirectoryWithMtime(1))
.addEqualityGroup(
FileArtifactValue.createForNormalFile(
toBytes("FFFFFF00000000000000000000000000"),
/*proxy=*/ null,
1L,
/*isShareable=*/ true))
.addEqualityGroup(
FileArtifactValue.createForNormalFile(
toBytes("FFFFFF00000000000000000000000000"),
/*proxy=*/ null,
1L,
/*isShareable=*/ false))
.addEqualityGroup(
FileArtifactValue.createForDirectoryWithMtime(2),
FileArtifactValue.createForDirectoryWithMtime(2))
.addEqualityGroup(FileArtifactValue.OMITTED_FILE_MARKER)
.addEqualityGroup(FileArtifactValue.MISSING_FILE_MARKER)
.addEqualityGroup(FileArtifactValue.DEFAULT_MIDDLEMAN)
.addEqualityGroup("a string")
.testEquals();
}
@Test
public void testEquality() throws Exception {
Path path1 = scratchFile("/dir/artifact1", 0L, "content");
Path path2 = scratchFile("/dir/artifact2", 0L, "content");
Path digestPath = scratchFile("/dir/diffDigest", 0L, "1234567");
Path mtimePath = scratchFile("/dir/diffMtime", 1L, "content");
Path empty1 = scratchFile("/dir/empty1", 0L, "");
Path empty2 = scratchFile("/dir/empty2", 1L, "");
Path empty3 = scratchFile("/dir/empty3", 1L, "");
Path dir1 = scratchDir("/dir1", 0L);
Path dir2 = scratchDir("/dir2", 1L);
Path dir3 = scratchDir("/dir3", 1L);
new EqualsTester()
// We check for ctime and inode equality for paths.
.addEqualityGroup(createForTesting(path1))
.addEqualityGroup(createForTesting(path2))
.addEqualityGroup(createForTesting(mtimePath))
.addEqualityGroup(createForTesting(digestPath))
.addEqualityGroup(createForTesting(empty1))
.addEqualityGroup(createForTesting(empty2))
.addEqualityGroup(createForTesting(empty3))
// We check for mtime equality for directories.
.addEqualityGroup(createForTesting(dir1))
.addEqualityGroup(createForTesting(dir2), createForTesting(dir3))
.testEquals();
}
@Test
public void testCtimeInEquality() throws Exception {
Path path = scratchFile("/dir/artifact1", 0L, "content");
FileArtifactValue before = createForTesting(path);
clock.advanceMillis(1);
path.chmod(0777);
FileArtifactValue after = createForTesting(path);
assertThat(before).isNotEqualTo(after);
}
@Test
public void testNoMtimeIfNonemptyFile() throws Exception {
Path path = scratchFile("/root/non-empty", 1L, "abc");
FileArtifactValue value = createForTesting(path);
assertThat(value.getDigest()).isEqualTo(path.getDigest());
assertThat(value.getSize()).isEqualTo(3L);
assertThrows(
"mtime for non-empty file should not be stored.",
UnsupportedOperationException.class,
() -> value.getModifiedTime());
}
@Test
public void testDirectory() throws Exception {
Path path = scratchDir("/dir", /*mtime=*/ 1L);
FileArtifactValue value = createForTesting(path);
assertThat(value.getDigest()).isNull();
assertThat(value.getModifiedTime()).isEqualTo(1L);
}
// Empty files are the same as normal files -- mtime is not stored.
@Test
public void testEmptyFile() throws Exception {
Path path = scratchFile("/root/empty", 1L, "");
path.setLastModifiedTime(1L);
FileArtifactValue value = createForTesting(path);
assertThat(value.getDigest()).isEqualTo(path.getDigest());
assertThat(value.getSize()).isEqualTo(0L);
assertThrows(
"mtime for non-empty file should not be stored.",
UnsupportedOperationException.class,
() -> value.getModifiedTime());
}
@Test
public void testIOException() throws Exception {
final IOException exception = new IOException("beep");
FileSystem fs =
new InMemoryFileSystem() {
@Override
public byte[] getDigest(Path path) throws IOException {
throw exception;
}
@Override
protected byte[] getFastDigest(Path path) throws IOException {
throw exception;
}
};
Path path = fs.getPath("/some/path");
path.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(path, "content");
IOException e = assertThrows(IOException.class, () -> createForTesting(path));
assertThat(e).isSameInstanceAs(exception);
}
@Test
public void testUptodateCheck() throws Exception {
Path path = scratchFile("/dir/artifact1", 0L, "content");
FileArtifactValue value = createForTesting(path);
clock.advanceMillis(1);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
clock.advanceMillis(1);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
clock.advanceMillis(1);
path.setLastModifiedTime(123); // Changing mtime implicitly updates ctime.
assertThat(value.wasModifiedSinceDigest(path)).isTrue();
clock.advanceMillis(1);
assertThat(value.wasModifiedSinceDigest(path)).isTrue();
}
@Test
public void testUptodateCheckDeleteFile() throws Exception {
Path path = scratchFile("/dir/artifact1", 0L, "content");
FileArtifactValue value = createForTesting(path);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
path.delete();
assertThat(value.wasModifiedSinceDigest(path)).isTrue();
}
@Test
public void testUptodateCheckDirectory() throws Exception {
// For now, we don't attempt to detect changes to directories.
Path path = scratchDir("/dir", 0L);
FileArtifactValue value = createForTesting(path);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
path.delete();
clock.advanceMillis(1);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
}
@Test
public void testUptodateChangeFileToDirectory() throws Exception {
// For now, we don't attempt to detect changes to directories.
Path path = scratchFile("/dir/file", 0L, "");
FileArtifactValue value = createForTesting(path);
assertThat(value.wasModifiedSinceDigest(path)).isFalse();
// If we only check ctime, then we need to change the clock here, or we get a ctime match on the
// stat.
path.delete();
path.createDirectoryAndParents();
clock.advanceMillis(1);
assertThat(value.wasModifiedSinceDigest(path)).isTrue();
}
@Test
public void testIsMarkerValue_marker() {
assertThat(FileArtifactValue.DEFAULT_MIDDLEMAN.isMarkerValue()).isTrue();
assertThat(FileArtifactValue.MISSING_FILE_MARKER.isMarkerValue()).isTrue();
assertThat(FileArtifactValue.OMITTED_FILE_MARKER.isMarkerValue()).isTrue();
}
@Test
public void testIsMarkerValue_notMarker() throws Exception {
FileArtifactValue value = createForTesting(scratchFile("/dir/artifact1", 0L, "content"));
assertThat(value.isMarkerValue()).isFalse();
}
}