/*
* Copyright 2017-present Facebook, Inc.
*
* 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.facebook.buck.rules.keys;
import static com.facebook.buck.rules.BuildableProperties.Kind.LIBRARY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.Either;
import com.facebook.buck.rules.ArchiveMemberSourcePath;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.BuildTargetSourcePath;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.BuildableProperties;
import com.facebook.buck.rules.DefaultBuildTargetSourcePath;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.NonHashableSourcePathContainer;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.RuleKeyAppendable;
import com.facebook.buck.rules.RuleKeyObjectSink;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SourceRoot;
import com.facebook.buck.rules.SourceWithFlags;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.step.Step;
import com.facebook.buck.testutil.FakeFileHashCache;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.util.sha1.Sha1HashCode;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.hash.HashCode;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.junit.Test;
public class RuleKeyBuilderTest {
private static final BuildTarget TARGET_1 =
BuildTargetFactory.newInstance(Paths.get("/root"), "//example/base:one");
private static final BuildTarget TARGET_2 =
BuildTargetFactory.newInstance(Paths.get("/root"), "//example/base:one#flavor");
private static final BuildRule IGNORED_RULE = new EmptyRule(TARGET_1);
private static final BuildRule RULE_1 = new EmptyRule(TARGET_1);
private static final BuildRule RULE_2 = new EmptyRule(TARGET_2);
private static final RuleKey RULE_KEY_1 = new RuleKey("a002b39af204cdfaa5fdb67816b13867c32ac52c");
private static final RuleKey RULE_KEY_2 = new RuleKey("b67816b13867c32ac52ca002b39af204cdfaa5fd");
private static final RuleKeyAppendable IGNORED_APPENDABLE = new FakeRuleKeyAppendable("");
private static final RuleKeyAppendable APPENDABLE_1 = new FakeRuleKeyAppendable("");
private static final RuleKeyAppendable APPENDABLE_2 = new FakeRuleKeyAppendable("42");
private static final Path PATH_1 = Paths.get("path1");
private static final Path PATH_2 = Paths.get("path2");
private static final ProjectFilesystem FILESYSTEM = new FakeProjectFilesystem();
private static final SourcePath SOURCE_PATH_1 = new PathSourcePath(FILESYSTEM, PATH_1);
private static final SourcePath SOURCE_PATH_2 = new PathSourcePath(FILESYSTEM, PATH_2);
private static final ArchiveMemberSourcePath ARCHIVE_PATH_1 =
ArchiveMemberSourcePath.of(SOURCE_PATH_1, Paths.get("member"));
private static final ArchiveMemberSourcePath ARCHIVE_PATH_2 =
ArchiveMemberSourcePath.of(SOURCE_PATH_2, Paths.get("member"));
private static final DefaultBuildTargetSourcePath TARGET_PATH_1 =
new DefaultBuildTargetSourcePath(TARGET_1);
private static final DefaultBuildTargetSourcePath TARGET_PATH_2 =
new DefaultBuildTargetSourcePath(TARGET_2);
@Test
public void testUniqueness() {
String[] fieldKeys = new String[] {"key1", "key2"};
Object[] fieldValues =
new Object[] {
// Java types
null,
true,
false,
0,
42,
(long) 0,
(long) 42,
(short) 0,
(short) 42,
(byte) 0,
(byte) 42,
(float) 0,
(float) 42,
(double) 0,
(double) 42,
"",
"42",
new byte[0],
new byte[] {42},
new byte[] {42, 42},
DummyEnum.BLACK,
DummyEnum.WHITE,
Pattern.compile(""),
Pattern.compile("42"),
// Buck simple types
Sha1HashCode.of("a002b39af204cdfaa5fdb67816b13867c32ac52c"),
Sha1HashCode.of("b67816b13867c32ac52ca002b39af204cdfaa5fd"),
new SourceRoot(""),
new SourceRoot("42"),
RULE_KEY_1,
RULE_KEY_2,
BuildRuleType.of(""),
BuildRuleType.of("42"),
TARGET_1,
TARGET_2,
// Buck paths
new NonHashableSourcePathContainer(SOURCE_PATH_1),
new NonHashableSourcePathContainer(SOURCE_PATH_2),
SOURCE_PATH_1,
SOURCE_PATH_2,
ARCHIVE_PATH_1,
ARCHIVE_PATH_2,
TARGET_PATH_1,
TARGET_PATH_2,
SourceWithFlags.of(SOURCE_PATH_1, ImmutableList.of("42")),
SourceWithFlags.of(SOURCE_PATH_2, ImmutableList.of("42")),
// Buck rules & appendables
RULE_1,
RULE_2,
APPENDABLE_1,
APPENDABLE_2,
// Wrappers
Suppliers.ofInstance(42),
Optional.of(42),
Either.ofLeft(42),
Either.ofRight(42),
// Containers & nesting
ImmutableList.of(42),
ImmutableList.of(42, 42),
ImmutableMap.of(42, 42),
ImmutableList.of(ImmutableList.of(1, 2, 3, 4)),
ImmutableList.of(ImmutableList.of(1, 2), ImmutableList.of(3, 4)),
};
List<RuleKey> ruleKeys = new ArrayList<>();
List<String> desc = new ArrayList<>();
ruleKeys.add(newBuilder().build(RuleKey::new));
desc.add("<empty>");
for (String key : fieldKeys) {
for (Object val : fieldValues) {
ruleKeys.add(calcRuleKey(key, val));
String clazz = (val != null) ? val.getClass().getSimpleName() : "";
desc.add(String.format("{key=%s, val=%s{%s}}", key, clazz, val));
}
}
// all of the rule keys should be different
for (int i = 0; i < ruleKeys.size(); i++) {
for (int j = 0; j < i; j++) {
assertNotEquals(
String.format("Collision: %s == %s", desc.get(i), desc.get(j)),
ruleKeys.get(i),
ruleKeys.get(j));
}
}
}
@Test
public void testNoOp() {
RuleKey noop = newBuilder().build(RuleKey::new);
assertEquals(noop, calcRuleKey("key", ImmutableList.of()));
assertEquals(noop, calcRuleKey("key", ImmutableList.of().iterator()));
assertEquals(noop, calcRuleKey("key", ImmutableMap.of()));
assertEquals(noop, calcRuleKey("key", ImmutableList.of(IGNORED_RULE)));
assertEquals(noop, calcRuleKey("key", Suppliers.ofInstance(IGNORED_RULE)));
assertEquals(noop, calcRuleKey("key", Optional.of(IGNORED_RULE)));
assertEquals(noop, calcRuleKey("key", Either.ofLeft(IGNORED_RULE)));
assertEquals(noop, calcRuleKey("key", Either.ofRight(IGNORED_RULE)));
assertEquals(noop, calcRuleKey("key", IGNORED_RULE));
assertEquals(noop, calcRuleKey("key", IGNORED_APPENDABLE));
}
private RuleKey calcRuleKey(String key, @Nullable Object val) {
return newBuilder().setReflectively(key, val).build(RuleKey::new);
}
private RuleKeyBuilder<HashCode> newBuilder() {
Map<BuildTarget, BuildRule> ruleMap = ImmutableMap.of(TARGET_1, RULE_1, TARGET_2, RULE_2);
Map<BuildRule, RuleKey> ruleKeyMap = ImmutableMap.of(RULE_1, RULE_KEY_1, RULE_2, RULE_KEY_2);
Map<RuleKeyAppendable, RuleKey> appendableKeys =
ImmutableMap.of(APPENDABLE_1, RULE_KEY_1, APPENDABLE_2, RULE_KEY_2);
BuildRuleResolver ruleResolver = new FakeBuildRuleResolver(ruleMap);
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
FakeFileHashCache hashCache =
new FakeFileHashCache(
ImmutableMap.of(
FILESYSTEM.resolve(PATH_1), HashCode.fromInt(0),
FILESYSTEM.resolve(PATH_2), HashCode.fromInt(42)),
ImmutableMap.of(
pathResolver.getAbsoluteArchiveMemberPath(ARCHIVE_PATH_1), HashCode.fromInt(0),
pathResolver.getAbsoluteArchiveMemberPath(ARCHIVE_PATH_2), HashCode.fromInt(42)),
ImmutableMap.of());
return new RuleKeyBuilder<HashCode>(
ruleFinder, pathResolver, hashCache, RuleKeyBuilder.createDefaultHasher()) {
@Override
protected RuleKeyBuilder<HashCode> setBuildRule(BuildRule rule) {
if (rule == IGNORED_RULE) {
return this;
}
return setBuildRuleKey(ruleKeyMap.get(rule));
}
@Override
public RuleKeyBuilder<HashCode> setAppendableRuleKey(RuleKeyAppendable appendable) {
if (appendable == IGNORED_APPENDABLE) {
return this;
}
return setAppendableRuleKey(appendableKeys.get(appendable));
}
@Override
protected RuleKeyBuilder<HashCode> setSourcePath(SourcePath sourcePath) throws IOException {
if (sourcePath instanceof BuildTargetSourcePath) {
return setSourcePathAsRule((BuildTargetSourcePath) sourcePath);
} else {
return setSourcePathDirectly(sourcePath);
}
}
@Override
protected RuleKeyBuilder<HashCode> setNonHashingSourcePath(SourcePath sourcePath) {
return setNonHashingSourcePathDirectly(sourcePath);
}
};
}
// This ugliness is necessary as we don't have mocks in Buck unit tests.
private static class FakeBuildRuleResolver extends BuildRuleResolver {
private final Map<BuildTarget, BuildRule> ruleMap;
public FakeBuildRuleResolver(Map<BuildTarget, BuildRule> ruleMap) {
super(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
this.ruleMap = ruleMap;
}
@Override
public BuildRule getRule(BuildTarget target) {
return Preconditions.checkNotNull(ruleMap.get(target), "No rule for target: " + target);
}
}
private static class FakeRuleKeyAppendable implements RuleKeyAppendable {
private final String field;
public FakeRuleKeyAppendable(String field) {
this.field = field;
}
@Override
public void appendToRuleKey(RuleKeyObjectSink sink) {
sink.setReflectively("field", field);
}
}
private static class EmptyRule implements BuildRule {
private final BuildTarget target;
public EmptyRule(BuildTarget target) {
this.target = target;
}
@Override
public BuildTarget getBuildTarget() {
return target;
}
@Override
public String getType() {
return "empty";
}
@Override
public BuildableProperties getProperties() {
return new BuildableProperties(LIBRARY);
}
@Override
public ImmutableSortedSet<BuildRule> getBuildDeps() {
return ImmutableSortedSet.of();
}
@Override
public ProjectFilesystem getProjectFilesystem() {
return new FakeProjectFilesystem();
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context, BuildableContext buildableContext) {
throw new UnsupportedOperationException("getBuildSteps");
}
@Nullable
@Override
public SourcePath getSourcePathToOutput() {
return null;
}
@Override
public boolean isCacheable() {
return true;
}
}
private enum DummyEnum {
BLACK,
WHITE,
}
}