| // 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.common.truth.Truth.assertWithMessage; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Maps; |
| import com.google.devtools.build.lib.events.util.EventCollectionApparatus; |
| import com.google.devtools.build.lib.skyframe.DiffAwarenessManager.ProcessableModifiedFileSet; |
| import com.google.devtools.build.lib.vfs.FileSystem; |
| import com.google.devtools.build.lib.vfs.ModifiedFileSet; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; |
| import com.google.devtools.common.options.OptionsProvider; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Unit tests for {@link DiffAwarenessManager}, especially of the fact that it works in a sequential |
| * manner and of its correctness in the presence of unprocesed diffs. |
| */ |
| @RunWith(JUnit4.class) |
| public class DiffAwarenessManagerTest { |
| private FileSystem fs; |
| protected EventCollectionApparatus events; |
| |
| @Before |
| public final void createFileSystem() throws Exception { |
| fs = new InMemoryFileSystem(); |
| } |
| |
| @Before |
| public final void initializeEventCollectionApparatus() { |
| events = new EventCollectionApparatus(); |
| events.setFailFast(false); |
| } |
| |
| @Test |
| public void testEverythingModifiedIfNoDiffAwareness() throws Exception { |
| Root pathEntry = Root.fromPath(fs.getPath("/pathEntry")); |
| DiffAwarenessFactoryStub factory = new DiffAwarenessFactoryStub(); |
| DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory)); |
| assertWithMessage("Expected EVERYTHING_MODIFIED since there are no factories") |
| .that( |
| manager |
| .getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY) |
| .getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| events.assertNoWarningsOrErrors(); |
| } |
| |
| @Test |
| public void testResetAndSetPathEntriesCallClose() throws Exception { |
| Root pathEntry = Root.fromPath(fs.getPath("/pathEntry")); |
| ModifiedFileSet diff = ModifiedFileSet.NOTHING_MODIFIED; |
| DiffAwarenessStub diffAwareness1 = new DiffAwarenessStub(ImmutableList.of(diff)); |
| DiffAwarenessStub diffAwareness2 = new DiffAwarenessStub(ImmutableList.of(diff)); |
| DiffAwarenessFactoryStub factory = new DiffAwarenessFactoryStub(); |
| factory.inject(pathEntry, diffAwareness1); |
| DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory)); |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertWithMessage("diffAwareness1 shouldn't have been closed yet") |
| .that(diffAwareness1.closed()) |
| .isFalse(); |
| manager.reset(); |
| assertWithMessage("diffAwareness1 should have been closed by reset") |
| .that(diffAwareness1.closed()) |
| .isTrue(); |
| factory.inject(pathEntry, diffAwareness2); |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertWithMessage("diffAwareness2 shouldn't have been closed yet") |
| .that(diffAwareness2.closed()) |
| .isFalse(); |
| events.assertNoWarningsOrErrors(); |
| } |
| |
| @Test |
| public void testHandlesUnprocessedDiffs() throws Exception { |
| Root pathEntry = Root.fromPath(fs.getPath("/pathEntry")); |
| ModifiedFileSet diff1 = ModifiedFileSet.builder().modify(PathFragment.create("file1")).build(); |
| ModifiedFileSet diff2 = ModifiedFileSet.builder().modify(PathFragment.create("file2")).build(); |
| ModifiedFileSet diff3 = ModifiedFileSet.builder().modify(PathFragment.create("file3")).build(); |
| DiffAwarenessStub diffAwareness = |
| new DiffAwarenessStub(ImmutableList.of(diff1, diff2, diff3, DiffAwarenessStub.BROKEN_DIFF)); |
| DiffAwarenessFactoryStub factory = new DiffAwarenessFactoryStub(); |
| factory.inject(pathEntry, diffAwareness); |
| DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory)); |
| ProcessableModifiedFileSet firstProcessableDiff = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertWithMessage("Expected EVERYTHING_MODIFIED on first call to getDiff") |
| .that(firstProcessableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| firstProcessableDiff.markProcessed(); |
| ProcessableModifiedFileSet processableDiff1 = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff1.getModifiedFileSet()).isEqualTo(diff1); |
| ProcessableModifiedFileSet processableDiff2 = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff2.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.union(diff1, diff2)); |
| processableDiff2.markProcessed(); |
| ProcessableModifiedFileSet processableDiff3 = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff3.getModifiedFileSet()).isEqualTo(diff3); |
| events.assertNoWarningsOrErrors(); |
| ProcessableModifiedFileSet processableDiff4 = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff4.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| events.assertContainsWarning("error"); |
| } |
| |
| @Test |
| public void testHandlesBrokenDiffs() throws Exception { |
| Root pathEntry = Root.fromPath(fs.getPath("/pathEntry")); |
| DiffAwarenessFactoryStub factory1 = new DiffAwarenessFactoryStub(); |
| DiffAwarenessStub diffAwareness1 = |
| new DiffAwarenessStub(ImmutableList.<ModifiedFileSet>of(), 1); |
| factory1.inject(pathEntry, diffAwareness1); |
| DiffAwarenessFactoryStub factory2 = new DiffAwarenessFactoryStub(); |
| ModifiedFileSet diff2 = ModifiedFileSet.builder().modify(PathFragment.create("file2")).build(); |
| DiffAwarenessStub diffAwareness2 = |
| new DiffAwarenessStub(ImmutableList.of(diff2, DiffAwarenessStub.BROKEN_DIFF)); |
| factory2.inject(pathEntry, diffAwareness2); |
| DiffAwarenessFactoryStub factory3 = new DiffAwarenessFactoryStub(); |
| ModifiedFileSet diff3 = ModifiedFileSet.builder().modify(PathFragment.create("file3")).build(); |
| DiffAwarenessStub diffAwareness3 = new DiffAwarenessStub(ImmutableList.of(diff3)); |
| factory3.inject(pathEntry, diffAwareness3); |
| DiffAwarenessManager manager = |
| new DiffAwarenessManager(ImmutableList.of(factory1, factory2, factory3)); |
| |
| ProcessableModifiedFileSet processableDiff = |
| manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| events.assertNoWarningsOrErrors(); |
| assertWithMessage("Expected EVERYTHING_MODIFIED on first call to getDiff for diffAwareness1") |
| .that(processableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| processableDiff.markProcessed(); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| events.assertContainsEventWithFrequency("error in getCurrentView", 1); |
| assertWithMessage("Expected EVERYTHING_MODIFIED because of broken getCurrentView") |
| .that(processableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| processableDiff.markProcessed(); |
| factory1.remove(pathEntry); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertWithMessage("Expected EVERYTHING_MODIFIED on first call to getDiff for diffAwareness2") |
| .that(processableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| processableDiff.markProcessed(); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff.getModifiedFileSet()).isEqualTo(diff2); |
| processableDiff.markProcessed(); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| events.assertContainsEventWithFrequency("error in getDiff", 1); |
| assertWithMessage("Expected EVERYTHING_MODIFIED because of broken getDiff") |
| .that(processableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| processableDiff.markProcessed(); |
| factory2.remove(pathEntry); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertWithMessage("Expected EVERYTHING_MODIFIED on first call to getDiff for diffAwareness3") |
| .that(processableDiff.getModifiedFileSet()) |
| .isEqualTo(ModifiedFileSet.EVERYTHING_MODIFIED); |
| processableDiff.markProcessed(); |
| |
| processableDiff = manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY); |
| assertThat(processableDiff.getModifiedFileSet()).isEqualTo(diff3); |
| processableDiff.markProcessed(); |
| } |
| |
| private static class DiffAwarenessFactoryStub implements DiffAwareness.Factory { |
| |
| private final Map<Root, DiffAwareness> diffAwarenesses = Maps.newHashMap(); |
| |
| public void inject(Root pathEntry, DiffAwareness diffAwareness) { |
| diffAwarenesses.put(pathEntry, diffAwareness); |
| } |
| |
| public void remove(Root pathEntry) { |
| diffAwarenesses.remove(pathEntry); |
| } |
| |
| @Override |
| @Nullable |
| public DiffAwareness maybeCreate(Root pathEntry) { |
| return diffAwarenesses.get(pathEntry); |
| } |
| } |
| |
| private static class DiffAwarenessStub implements DiffAwareness { |
| |
| public static final ModifiedFileSet BROKEN_DIFF = |
| ModifiedFileSet.builder().modify(PathFragment.create("special broken marker")).build(); |
| |
| private boolean closed = false; |
| private int curSequenceNum = 0; |
| private final List<ModifiedFileSet> sequentialDiffs; |
| private final int brokenViewNum; |
| |
| public DiffAwarenessStub(List<ModifiedFileSet> sequentialDiffs) { |
| this(sequentialDiffs, -1); |
| } |
| |
| public DiffAwarenessStub(List<ModifiedFileSet> sequentialDiffs, int brokenViewNum) { |
| this.sequentialDiffs = sequentialDiffs; |
| this.brokenViewNum = brokenViewNum; |
| } |
| |
| private static class ViewStub implements DiffAwareness.View { |
| private final int sequenceNum; |
| |
| public ViewStub(int sequenceNum) { |
| this.sequenceNum = sequenceNum; |
| } |
| } |
| |
| @Override |
| public View getCurrentView(OptionsProvider options) throws BrokenDiffAwarenessException { |
| if (curSequenceNum == brokenViewNum) { |
| throw new BrokenDiffAwarenessException("error in getCurrentView"); |
| } |
| return new ViewStub(curSequenceNum++); |
| } |
| |
| @Override |
| public ModifiedFileSet getDiff(View oldView, View newView) throws BrokenDiffAwarenessException { |
| assertThat(oldView).isInstanceOf(ViewStub.class); |
| assertThat(newView).isInstanceOf(ViewStub.class); |
| ViewStub oldViewStub = (ViewStub) oldView; |
| ViewStub newViewStub = (ViewStub) newView; |
| Preconditions.checkState(newViewStub.sequenceNum >= oldViewStub.sequenceNum); |
| ModifiedFileSet diff = ModifiedFileSet.NOTHING_MODIFIED; |
| for (int num = oldViewStub.sequenceNum; num < newViewStub.sequenceNum; num++) { |
| ModifiedFileSet incrementalDiff = sequentialDiffs.get(num); |
| if (incrementalDiff == BROKEN_DIFF) { |
| throw new BrokenDiffAwarenessException("error in getDiff"); |
| } |
| diff = ModifiedFileSet.union(diff, incrementalDiff); |
| } |
| return diff; |
| } |
| |
| @Override |
| public String name() { |
| return "testingstub"; |
| } |
| |
| @Override |
| public void close() { |
| closed = true; |
| } |
| |
| public boolean closed() { |
| return closed; |
| } |
| } |
| } |