blob: a08bfc92fd17749dfc41507fb695838422acada8 [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.idea.blaze.cpp;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.idea.blaze.base.BlazeTestCase;
import com.google.idea.blaze.base.async.executor.BlazeExecutor;
import com.google.idea.blaze.base.async.executor.MockBlazeExecutor;
import com.google.idea.blaze.base.bazel.BazelBuildSystemProvider;
import com.google.idea.blaze.base.bazel.BuildSystemProvider;
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import com.google.idea.blaze.base.ideinfo.CIdeInfo;
import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
import com.google.idea.blaze.base.ideinfo.TargetMap;
import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
import com.google.idea.blaze.base.io.VirtualFileSystemProvider;
import com.google.idea.blaze.base.model.MockBlazeProjectDataBuilder;
import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.projectview.ProjectView;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.projectview.section.ListSection;
import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry;
import com.google.idea.blaze.base.projectview.section.sections.DirectorySection;
import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.ErrorCollector;
import com.google.idea.blaze.base.scope.output.IssueOutput;
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
import com.intellij.mock.MockPsiManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.impl.ProgressManagerImpl;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.impl.StubVirtualFile;
import com.intellij.psi.PsiManager;
import com.jetbrains.cidr.lang.OCLanguageKind;
import com.jetbrains.cidr.lang.workspace.OCResolveRootAndConfiguration;
import com.jetbrains.cidr.lang.workspace.headerRoots.HeadersSearchRoot;
import com.jetbrains.cidr.lang.workspace.headerRoots.IncludedHeadersRoot;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests that we group equivalent {@link BlazeResolveConfiguration}s. */
@RunWith(JUnit4.class)
public class BlazeResolveConfigurationEquivalenceTest extends BlazeTestCase {
private final BlazeContext context = new BlazeContext();
private final ErrorCollector errorCollector = new ErrorCollector();
private final WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File("/root"));
private BlazeConfigurationResolver resolver;
private BlazeConfigurationResolverResult resolverResult;
private LocalFileSystem mockFileSystem;
@Override
protected void initTest(Container applicationServices, Container projectServices) {
super.initTest(applicationServices, projectServices);
applicationServices.register(BlazeExecutor.class, new MockBlazeExecutor());
applicationServices.register(
CompilerVersionChecker.class, new MockCompilerVersionChecker("1234"));
applicationServices.register(ProgressManager.class, new ProgressManagerImpl());
applicationServices.register(VirtualFileManager.class, mock(VirtualFileManager.class));
mockFileSystem = mock(LocalFileSystem.class);
applicationServices.register(
VirtualFileSystemProvider.class, mock(VirtualFileSystemProvider.class));
when(VirtualFileSystemProvider.getInstance().getSystem()).thenReturn(mockFileSystem);
projectServices.register(PsiManager.class, new MockPsiManager(project));
projectServices.register(BlazeImportSettingsManager.class, new BlazeImportSettingsManager());
BuildSystemProvider buildSystemProvider = new BazelBuildSystemProvider();
registerExtensionPoint(BuildSystemProvider.EP_NAME, BuildSystemProvider.class)
.registerExtension(buildSystemProvider);
BlazeImportSettingsManager.getInstance(getProject())
.setImportSettings(
new BlazeImportSettings("", "", "", "", buildSystemProvider.buildSystem()));
context.addOutputSink(IssueOutput.class, errorCollector);
resolver = new BlazeConfigurationResolver(project);
resolverResult = BlazeConfigurationResolverResult.empty(project);
}
@Test
public void testEmptyConfigurations() {
ProjectView projectView =
projectView(
directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:one",
Kind.CC_BINARY,
sources("foo/bar/one.cc"),
defines(),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:two",
Kind.CC_BINARY,
sources("foo/bar/two.cc"),
defines(),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:three",
Kind.CC_BINARY,
sources("foo/bar/three.cc"),
defines(),
includes()))
.build();
List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
assertThat(configurations).hasSize(1);
assertThat(get(configurations, "//foo/bar:one and 2 other target(s)")).isNotNull();
for (BlazeResolveConfiguration configuration : configurations) {
assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
assertThat(getHeaders(configuration, OCLanguageKind.CPP)).isEmpty();
assertThat(configuration.getCompilerMacros()).isEqualTo(macros());
}
}
@Test
public void testDefines() {
ProjectView projectView =
projectView(
directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:one",
Kind.CC_BINARY,
sources("foo/bar/one.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:two",
Kind.CC_BINARY,
sources("foo/bar/two.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:three",
Kind.CC_BINARY,
sources("foo/bar/three.cc"),
defines("DIFFERENT=1"),
includes()))
.build();
List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
assertThat(configurations).hasSize(2);
assertThat(get(configurations, "//foo/bar:one and 1 other target(s)").getCompilerMacros())
.isEqualTo(macros("SAME=1"));
assertThat(get(configurations, "//foo/bar:three").getCompilerMacros())
.isEqualTo(macros("DIFFERENT=1"));
for (BlazeResolveConfiguration configuration : configurations) {
assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
assertThat(getHeaders(configuration, OCLanguageKind.CPP)).isEmpty();
}
}
@Test
public void testIncludes() {
ProjectView projectView =
projectView(
directories("foo/bar"), targets("//foo/bar:one", "//foo/bar:two", "//foo/bar:three"));
TargetMap targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:one",
Kind.CC_BINARY,
sources("foo/bar/one.cc"),
defines(),
includes("foo/same")))
.addTarget(
createCcTarget(
"//foo/bar:two",
Kind.CC_BINARY,
sources("foo/bar/two.cc"),
defines(),
includes("foo/same")))
.addTarget(
createCcTarget(
"//foo/bar:three",
Kind.CC_BINARY,
sources("foo/bar/three.cc"),
defines(),
includes("foo/different")))
.build();
VirtualFile includeSame = createVirtualFile("/root/foo/same");
VirtualFile includeDifferent = createVirtualFile("/root/foo/different");
List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
assertThat(configurations).hasSize(2);
assertThat(
getHeaders(
get(configurations, "//foo/bar:one and 1 other target(s)"), OCLanguageKind.CPP))
.containsExactly(header(includeSame));
assertThat(getHeaders(get(configurations, "//foo/bar:three"), OCLanguageKind.CPP))
.containsExactly(header(includeDifferent));
for (BlazeResolveConfiguration configuration : configurations) {
assertThat(configuration.getProjectHeadersRoots().getRoots()).isEmpty();
assertThat(configuration.getCompilerMacros()).isEqualTo(macros());
}
}
// Test a series of permutations of labels a, b, c, d.
// Initial state is {a=1, b=1, c=1, d=0}, and we flip some of the 1 to 0.
private TargetMap incrementalUpdateTestCaseInitialTargetMap() {
return TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:a",
Kind.CC_BINARY,
sources("foo/bar/a.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:b",
Kind.CC_BINARY,
sources("foo/bar/b.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:c",
Kind.CC_BINARY,
sources("foo/bar/c.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:d",
Kind.CC_BINARY,
sources("foo/bar/d.cc"),
defines("DIFFERENT=1"),
includes()))
.build();
}
// TODO(jvoung): This could be a separate Parameterized test.
private static final Map<List<String>, ReusedConfigurationExpectations>
permutationsAndExpectations =
ImmutableMap.<List<String>, ReusedConfigurationExpectations>builder()
.put(
ImmutableList.of("a"),
// Since we already had a config at 1 and one at 0, flipping any 1 to 0 will
// always
// result in reuse. The old configurations will get renamed.
new ReusedConfigurationExpectations(
ImmutableList.of(
"//foo/bar:a and 1 other target(s)", "//foo/bar:b and 1 other target(s)"),
ImmutableList.of()))
.put(
ImmutableList.of("b"),
new ReusedConfigurationExpectations(
ImmutableList.of(
"//foo/bar:a and 1 other target(s)", "//foo/bar:b and 1 other target(s)"),
ImmutableList.of()))
.put(
ImmutableList.of("c"),
new ReusedConfigurationExpectations(
ImmutableList.of(
"//foo/bar:a and 1 other target(s)", "//foo/bar:c and 1 other target(s)"),
ImmutableList.of()))
.put(
ImmutableList.of("a", "b"),
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:a and 2 other target(s)", "//foo/bar:c"),
ImmutableList.of()))
.put(
ImmutableList.of("b", "c"),
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:a", "//foo/bar:b and 2 other target(s)"),
ImmutableList.of()))
.put(
ImmutableList.of("a", "c"),
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:a and 2 other target(s)", "//foo/bar:b"),
ImmutableList.of()))
.put(
ImmutableList.of("a", "b", "c"),
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:a and 3 other target(s)"), ImmutableList.of()))
.build();
@Test
public void changeDefines_testIncrementalUpdate_0() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 0);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_1() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 1);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_2() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 2);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_3() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 3);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_4() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 4);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_5() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 5);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
}
@Test
public void changeDefines_testIncrementalUpdate_6() {
Map.Entry<List<String>, ReusedConfigurationExpectations> testCase =
Iterables.get(permutationsAndExpectations.entrySet(), 6);
do_changeDefines_testIncrementalUpdate(testCase.getKey(), testCase.getValue());
assertThat(permutationsAndExpectations.size()).isEqualTo(7);
}
private void do_changeDefines_testIncrementalUpdate(
List<String> labelsToFlip, ReusedConfigurationExpectations expectation) {
ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
List<BlazeResolveConfiguration> configurations =
resolve(projectView, incrementalUpdateTestCaseInitialTargetMap());
assertThat(configurations).hasSize(2);
assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
assertThat(get(configurations, "//foo/bar:d")).isNotNull();
TargetMapBuilder targetMapBuilder = TargetMapBuilder.builder().addTarget(createCcToolchain());
for (String target : ImmutableList.of("a", "b", "c")) {
if (labelsToFlip.contains(target)) {
targetMapBuilder.addTarget(
createCcTarget(
String.format("//foo/bar:%s", target),
Kind.CC_BINARY,
sources(String.format("foo/bar/%s.cc", target)),
defines("DIFFERENT=1"),
includes()));
} else {
targetMapBuilder.addTarget(
createCcTarget(
String.format("//foo/bar:%s", target),
Kind.CC_BINARY,
sources(String.format("foo/bar/%s.cc", target)),
defines("SAME=1"),
includes()));
}
}
targetMapBuilder.addTarget(
createCcTarget(
"//foo/bar:d",
Kind.CC_BINARY,
sources("foo/bar/d.cc"),
defines("DIFFERENT=1"),
includes()));
List<BlazeResolveConfiguration> newConfigurations =
resolve(projectView, targetMapBuilder.build());
assertReusedConfigs(configurations, newConfigurations, expectation);
}
@Test
public void changeDefinesWithSameStructure_testIncrementalUpdate() {
ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
TargetMap targetMap = incrementalUpdateTestCaseInitialTargetMap();
List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
assertThat(configurations).hasSize(2);
assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
assertThat(get(configurations, "//foo/bar:d")).isNotNull();
targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:a",
Kind.CC_BINARY,
sources("foo/bar/a.cc"),
defines("CHANGED=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:b",
Kind.CC_BINARY,
sources("foo/bar/b.cc"),
defines("CHANGED=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:c",
Kind.CC_BINARY,
sources("foo/bar/c.cc"),
defines("CHANGED=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:d",
Kind.CC_BINARY,
sources("foo/bar/d.cc"),
defines("DIFFERENT=1"),
includes()))
.build();
List<BlazeResolveConfiguration> newConfigurations = resolve(projectView, targetMap);
assertThat(newConfigurations).hasSize(2);
assertReusedConfigs(
configurations,
newConfigurations,
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:d"),
ImmutableList.of("//foo/bar:a and 2 other target(s)")));
}
@Test
public void changeDefinesMakeAllSame_testIncrementalUpdate() {
ProjectView projectView = projectView(directories("foo/bar"), targets("//foo/bar:...:all"));
TargetMap targetMap = incrementalUpdateTestCaseInitialTargetMap();
List<BlazeResolveConfiguration> configurations = resolve(projectView, targetMap);
assertThat(configurations).hasSize(2);
assertThat(get(configurations, "//foo/bar:a and 2 other target(s)")).isNotNull();
assertThat(get(configurations, "//foo/bar:d")).isNotNull();
targetMap =
TargetMapBuilder.builder()
.addTarget(createCcToolchain())
.addTarget(
createCcTarget(
"//foo/bar:a",
Kind.CC_BINARY,
sources("foo/bar/a.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:b",
Kind.CC_BINARY,
sources("foo/bar/b.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:c",
Kind.CC_BINARY,
sources("foo/bar/c.cc"),
defines("SAME=1"),
includes()))
.addTarget(
createCcTarget(
"//foo/bar:d",
Kind.CC_BINARY,
sources("foo/bar/d.cc"),
defines("SAME=1"),
includes()))
.build();
List<BlazeResolveConfiguration> newConfigurations = resolve(projectView, targetMap);
assertThat(newConfigurations).hasSize(1);
// What used to be "//foo/bar:a and 2 other target(s)" will be renamed to
// "//foo/bar:a and 3 other target(s)" and reused.
assertReusedConfigs(
configurations,
newConfigurations,
new ReusedConfigurationExpectations(
ImmutableList.of("//foo/bar:a and 3 other target(s)"), ImmutableList.of()));
}
private static List<ArtifactLocation> sources(String... paths) {
return Arrays.stream(paths)
.map(path -> ArtifactLocation.builder().setRelativePath(path).setIsSource(true).build())
.collect(Collectors.toList());
}
private static List<String> defines(String... defines) {
return Arrays.asList(defines);
}
private static List<ExecutionRootPath> includes(String... paths) {
return Arrays.stream(paths).map(ExecutionRootPath::new).collect(Collectors.toList());
}
private static TargetIdeInfo.Builder createCcTarget(
String label,
Kind kind,
List<ArtifactLocation> sources,
List<String> defines,
List<ExecutionRootPath> includes) {
TargetIdeInfo.Builder targetInfo =
TargetIdeInfo.builder().setLabel(label).setKind(kind).addDependency("//:toolchain");
sources.forEach(targetInfo::addSource);
return targetInfo.setCInfo(
CIdeInfo.builder()
.addSources(sources)
.addLocalDefines(defines)
.addLocalIncludeDirectories(includes));
}
private static TargetIdeInfo.Builder createCcToolchain() {
return TargetIdeInfo.builder()
.setLabel("//:toolchain")
.setKind(Kind.CC_TOOLCHAIN)
.setCToolchainInfo(
CToolchainIdeInfo.builder().setCppExecutable(new ExecutionRootPath("cc")));
}
private static ListSection<DirectoryEntry> directories(String... directories) {
return ListSection.builder(DirectorySection.KEY)
.addAll(
Arrays.stream(directories)
.map(directory -> DirectoryEntry.include(WorkspacePath.createIfValid(directory)))
.collect(Collectors.toList()))
.build();
}
private static ListSection<TargetExpression> targets(String... targets) {
return ListSection.builder(TargetSection.KEY)
.addAll(
Arrays.stream(targets).map(TargetExpression::fromString).collect(Collectors.toList()))
.build();
}
private static ProjectView projectView(
ListSection<DirectoryEntry> directories, ListSection<TargetExpression> targets) {
return ProjectView.builder().add(directories).add(targets).build();
}
private List<BlazeResolveConfiguration> resolve(ProjectView projectView, TargetMap targetMap) {
resolverResult =
resolver.update(
context,
workspaceRoot,
ProjectViewSet.builder().add(projectView).build(),
MockBlazeProjectDataBuilder.builder(workspaceRoot).setTargetMap(targetMap).build(),
resolverResult);
errorCollector.assertNoIssues();
return resolverResult.getAllConfigurations();
}
private static BlazeResolveConfiguration get(
List<BlazeResolveConfiguration> configurations, String name) {
List<BlazeResolveConfiguration> filteredConfigurations =
configurations
.stream()
.filter(c -> c.getDisplayName(false).equals(name))
.collect(Collectors.toList());
assertWithMessage(
String.format(
"%s contains %s",
configurations
.stream()
.map(c -> c.getDisplayName(false))
.collect(Collectors.toList()),
name))
.that(filteredConfigurations)
.hasSize(1);
return filteredConfigurations.get(0);
}
private BlazeCompilerMacros macros(String... defines) {
return new BlazeCompilerMacros(
project, null, null, ImmutableList.copyOf(defines), ImmutableMap.of());
}
private HeadersSearchRoot header(VirtualFile include) {
return new IncludedHeadersRoot(project, include, false, true);
}
private static List<HeadersSearchRoot> getHeaders(
BlazeResolveConfiguration configuration, OCLanguageKind languageKind) {
return configuration
.getLibraryHeadersRoots(new OCResolveRootAndConfiguration(configuration, languageKind))
.getRoots();
}
private VirtualFile createVirtualFile(String path) {
VirtualFile stub = new StubVirtualFile();
when(mockFileSystem.findFileByIoFile(new File(path))).thenReturn(stub);
return stub;
}
private static void assertReusedConfigs(
List<BlazeResolveConfiguration> oldConfigurations,
List<BlazeResolveConfiguration> newConfigurations,
ReusedConfigurationExpectations expected) {
for (String label : expected.reusedLabels) {
assertWithMessage(String.format("Checking that %s is reused", label))
.that(get(newConfigurations, label))
.isSameAs(get(oldConfigurations, label));
}
for (String label : expected.notReusedLabels) {
assertWithMessage(String.format("Checking that %s is NOT reused", label))
.that(get(newConfigurations, label))
.isNotSameAs(get(oldConfigurations, label));
}
}
private static class ReusedConfigurationExpectations {
final ImmutableCollection<String> reusedLabels;
final ImmutableCollection<String> notReusedLabels;
ReusedConfigurationExpectations(
ImmutableCollection<String> reusedLabels, ImmutableCollection<String> notReusedLabels) {
this.reusedLabels = reusedLabels;
this.notReusedLabels = notReusedLabels;
}
}
}