/* * Copyright 2013-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.coercer; import static com.facebook.buck.rules.TestCellBuilder.createCellRoots; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.Hint; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Maps; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import org.immutables.value.Value; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class ConstructorArgMarshallerImmutableTest { public static final BuildTarget TARGET = BuildTargetFactory.newInstance("//example/path:three"); private Path basePath; private ConstructorArgMarshaller marshaller; private ProjectFilesystem filesystem; @Rule public ExpectedException expected = ExpectedException.none(); @Before public void setUpInspector() { basePath = Paths.get("example", "path"); marshaller = new ConstructorArgMarshaller(new DefaultTypeCoercerFactory()); filesystem = new FakeProjectFilesystem(); } @Test public void shouldPopulateAStringValue() throws Exception { DtoWithString built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithString.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("string", "cheese")); assertEquals("cheese", built.getString()); } @Test public void shouldPopulateABooleanValue() throws Exception { DtoWithBoolean built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithBoolean.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "booleanOne", true, "booleanTwo", true)); assertTrue(built.getBooleanOne()); assertTrue(built.isBooleanTwo()); } @Test public void shouldPopulateBuildTargetValues() throws Exception { DtoWithBuildTargets built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithBuildTargets.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "target", "//cake:walk", "local", ":fish")); assertEquals( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//cake:walk"), built.getTarget()); assertEquals( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//example/path:fish"), built.getLocal()); } @Test public void shouldPopulateANumericValue() throws Exception { DtoWithLong built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithLong.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("number", 42L)); assertEquals(42, built.getNumber()); } @Test public void shouldPopulateAPathValue() throws Exception { DtoWithPath built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithPath.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("path", "Fish.java")); assertEquals(Paths.get("example/path", "Fish.java"), built.getPath()); } @Test public void shouldPopulateSourcePaths() throws Exception { ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//example/path:peas"); DtoWithSourcePaths built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithSourcePaths.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "filePath", "cheese.txt", "targetPath", ":peas")); assertEquals( new PathSourcePath(projectFilesystem, Paths.get("example/path/cheese.txt")), built.getFilePath()); assertEquals(new DefaultBuildTargetSourcePath(target), built.getTargetPath()); } @Test public void shouldPopulateAnImmutableSortedSet() throws Exception { BuildTarget t1 = BuildTargetFactory.newInstance("//please/go:here"); BuildTarget t2 = BuildTargetFactory.newInstance("//example/path:there"); // Note: the ordering is reversed from the natural ordering DtoWithImmutableSortedSet built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithImmutableSortedSet.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "stuff", ImmutableList.of("//please/go:here", ":there"))); assertEquals(ImmutableSortedSet.of(t2, t1), built.getStuff()); } @Test public void shouldPopulateSets() throws Exception { DtoWithSetOfPaths built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithSetOfPaths.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("paths", ImmutableList.of("one", "two"))); assertEquals( ImmutableSet.of(Paths.get("example/path/one"), Paths.get("example/path/two")), built.getPaths()); } @Test public void shouldPopulateLists() throws Exception { DtoWithListOfStrings built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithListOfStrings.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("list", ImmutableList.of("alpha", "beta"))); assertEquals(ImmutableList.of("alpha", "beta"), built.getList()); } @Test public void onlyFieldNamedDepsAreConsideredDeclaredDeps() throws Exception { final String dep = "//is/a/declared:dep"; final String notDep = "//is/not/a/declared:dep"; BuildTarget declaredDep = BuildTargetFactory.newInstance(dep); Map<String, Object> args = Maps.newHashMap(); args.put("deps", ImmutableList.of(dep)); args.put("notdeps", ImmutableList.of(notDep)); ImmutableSet.Builder<BuildTarget> declaredDeps = ImmutableSet.builder(); DtoWithDepsAndNotDeps built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDepsAndNotDeps.class, declaredDeps, args); assertEquals(ImmutableSet.of(declaredDep), declaredDeps.build()); assertEquals(ImmutableSet.of(declaredDep), built.getDeps()); } @Test public void fieldsWithIsDepEqualsFalseHintAreNotTreatedAsDeps() throws Exception { String dep = "//should/be:ignored"; Map<String, Object> args = ImmutableMap.of("deps", ImmutableList.of(dep)); ImmutableSet.Builder<BuildTarget> declaredDeps = ImmutableSet.builder(); DtoWithFakeDeps built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithFakeDeps.class, declaredDeps, args); assertEquals(ImmutableSet.of(), declaredDeps.build()); assertEquals(ImmutableSet.of(BuildTargetFactory.newInstance(dep)), built.getDeps()); } @Test public void collectionsAreOptional() throws Exception { DtoWithCollections built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithCollections.class, ImmutableSet.builder(), ImmutableMap.of()); assertEquals(ImmutableSet.of(), built.getSet()); assertEquals(ImmutableSet.of(), built.getImmutableSet()); assertEquals(ImmutableSortedSet.of(), built.getSortedSet()); assertEquals(ImmutableSortedSet.of(), built.getImmutableSortedSet()); assertEquals(ImmutableList.of(), built.getList()); assertEquals(ImmutableList.of(), built.getImmutableList()); assertEquals(ImmutableMap.of(), built.getMap()); assertEquals(ImmutableMap.of(), built.getImmutableMap()); } @Test public void optionalCollectionsWithoutAValueWillBeSetToAnEmptyOptionalCollection() throws Exception { // Deliberately not populating args Map<String, Object> args = ImmutableMap.of(); DtoWithOptionalSetOfStrings built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalSetOfStrings.class, ImmutableSet.builder(), args); assertEquals(Optional.empty(), built.getStrings()); } @Test public void errorsOnMissingValues() throws Exception { expected.expect(HumanReadableException.class); expected.expectMessage(containsString(TARGET.getFullyQualifiedName())); expected.expectMessage(containsString("missing required")); expected.expectMessage(containsString("booleanOne")); expected.expectMessage(containsString("booleanTwo")); marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithBoolean.class, ImmutableSet.builder(), ImmutableMap.of()); } @Test public void errorsOnBadChecks() throws Exception { expected.expect(RuntimeException.class); expected.expectMessage(containsString(TARGET.getFullyQualifiedName())); expected.expectMessage(containsString("NOT THE SECRETS")); marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithCheck.class, ImmutableSet.builder(), ImmutableMap.of("string", "secrets")); } @Test public void noErrorsOnGoodChecks() throws Exception { DtoWithCheck built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithCheck.class, ImmutableSet.builder(), ImmutableMap.of("string", "not secrets")); assertEquals("not secrets", built.getString()); } @Test(expected = ParamInfoException.class) public void shouldBeAnErrorToAttemptToSetASingleValueToACollection() throws Exception { marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithString.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("string", ImmutableList.of("a", "b"))); } @Test(expected = ParamInfoException.class) public void shouldBeAnErrorToAttemptToSetACollectionToASingleValue() throws Exception { marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithSetOfStrings.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("strings", "isn't going to happen")); } @Test(expected = ParamInfoException.class) public void shouldBeAnErrorToSetTheWrongTypeOfValueInACollection() throws Exception { marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithSetOfStrings.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("strings", ImmutableSet.of(true, false))); } @Test public void shouldNormalizePaths() throws Exception { DtoWithPath built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithPath.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("path", "./bar/././fish.txt")); assertEquals(basePath.resolve("bar/fish.txt").normalize(), built.getPath()); } @Test public void shouldSetBuildTargetParameters() throws Exception { DtoWithBuildTargetList built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithBuildTargetList.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "single", "//com/example:cheese", "sameBuildFileTarget", ":cake", "targets", ImmutableList.of(":cake", "//com/example:cheese"))); BuildTarget cheese = BuildTargetFactory.newInstance("//com/example:cheese"); BuildTarget cake = BuildTargetFactory.newInstance("//example/path:cake"); assertEquals(cheese, built.getSingle()); assertEquals(cake, built.getSameBuildFileTarget()); assertEquals(ImmutableList.of(cake, cheese), built.getTargets()); } @Test public void specifyingZeroIsNotConsideredOptional() throws Exception { DtoWithOptionalInteger built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalInteger.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("number", 0)); assertEquals(Optional.of(0), built.getNumber()); } @Test public void canPopulateSimpleConstructorArgFromBuildFactoryParams() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//example/path:path"); ImmutableMap<String, Object> args = ImmutableMap.<String, Object>builder() .put("required", "cheese") .put("notRequired", "cake") // Long because that's what comes from python. .put("num", 42L) .put("optionalLong", 88L) .put("needed", true) // Skipping optional boolean. .put("aSrcPath", ":path") .put("aPath", "./File.java") .put("notAPath", "./NotFile.java") .build(); DtoWithVariousTypes built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithVariousTypes.class, ImmutableSet.builder(), args); assertEquals("cheese", built.getRequired()); assertEquals("cake", built.getNotRequired().get()); assertEquals(42, built.getNum()); assertEquals(Optional.of(88L), built.getOptionalLong()); assertTrue(built.isNeeded()); assertEquals(Optional.empty(), built.isNotNeeded()); DefaultBuildTargetSourcePath expected = new DefaultBuildTargetSourcePath(target); assertEquals(expected, built.getASrcPath()); assertEquals(Paths.get("example/path/NotFile.java"), built.getNotAPath().get()); } @Test public void shouldNotPopulateDefaultValues() throws Exception { // This is not an ImmutableMap so we can test null values. Map<String, Object> args = Maps.newHashMap(); args.put("defaultString", null); args.put("defaultSourcePath", null); DtoWithOptionalValues built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalValues.class, ImmutableSet.builder(), args); assertEquals(Optional.empty(), built.getNoString()); assertEquals(Optional.empty(), built.getDefaultString()); assertEquals(Optional.empty(), built.getNoSourcePath()); assertEquals(Optional.empty(), built.getDefaultSourcePath()); } @Test public void shouldRespectSpecifiedDefaultValues() throws Exception { // This is not an ImmutableMap so we can test null values. Map<String, Object> args = Maps.newHashMap(); args.put("something", null); args.put("things", null); DtoWithDefaultValues built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDefaultValues.class, ImmutableSet.builder(), args); assertEquals("foo", built.getSomething()); assertEquals(ImmutableList.of("bar"), built.getThings()); assertEquals(365, built.getMore()); assertTrue(built.getBeGood()); } @Test public void shouldAllowOverridingDefaultValues() throws Exception { // This is not an ImmutableMap so we can test null values. Map<String, Object> args = Maps.newHashMap(); args.put("something", "bar"); args.put("things", ImmutableList.of("qux", "quz")); args.put("more", 1234L); args.put("beGood", false); DtoWithDefaultValues built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDefaultValues.class, ImmutableSet.builder(), args); assertEquals("bar", built.getSomething()); assertEquals(ImmutableList.of("qux", "quz"), built.getThings()); assertEquals(1234, built.getMore()); assertFalse(built.getBeGood()); } @Test public void shouldResolveCollectionOfSourcePathsRelativeToTarget() throws Exception { DtoWithSetOfSourcePaths built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithSetOfSourcePaths.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "srcs", ImmutableList.of("main.py", "lib/__init__.py", "lib/manifest.py"))); ImmutableSet<String> observedValues = built .getSrcs() .stream() .map(input -> ((PathSourcePath) input).getRelativePath().toString()) .collect(MoreCollectors.toImmutableSet()); assertEquals( ImmutableSet.of( Paths.get("example/path/main.py").toString(), Paths.get("example/path/lib/__init__.py").toString(), Paths.get("example/path/lib/manifest.py").toString()), observedValues); } @Test(expected = HumanReadableException.class) public void bogusVisibilityGivesFriendlyError() throws Exception { ConstructorArgMarshaller.populateVisibilityPatterns( createCellRoots(filesystem), "visibility", ImmutableList.of(":marmosets"), TARGET); } @Test public void derivedMethodsAreIgnored() throws Exception { DtoWithDerivedAndOrdinaryMethods built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDerivedAndOrdinaryMethods.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("string", "tamarins")); assertEquals("tamarins", built.getString()); assertEquals("TAMARINS", built.getUpper()); assertEquals("constant", built.getConstant()); } @Test public void specifyingDerivedValuesIsIgnored() throws Exception { DtoWithDerivedAndOrdinaryMethods built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDerivedAndOrdinaryMethods.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "string", "tamarins", "upper", "WRONG")); assertEquals("tamarins", built.getString()); assertEquals("TAMARINS", built.getUpper()); } @Test public void defaultMethodFallsBackToDefault() throws Exception { InheritsFromHasDefaultMethod built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, InheritsFromHasDefaultMethod.class, ImmutableSet.builder(), ImmutableMap.of()); assertEquals("foo", built.getString()); } @Test public void defaultMethodCanBeSpecified() throws Exception { InheritsFromHasDefaultMethod built = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, InheritsFromHasDefaultMethod.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("string", "bar")); assertEquals("bar", built.getString()); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithString { abstract String getString(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithBoolean { abstract boolean getBooleanOne(); abstract boolean isBooleanTwo(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithListOfStrings { abstract List<String> getList(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithFakeDeps { @Hint(isDep = false) abstract Set<BuildTarget> getDeps(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithCollections { abstract Set<String> getSet(); abstract ImmutableSet<String> getImmutableSet(); @Value.NaturalOrder abstract SortedSet<String> getSortedSet(); @Value.NaturalOrder abstract ImmutableSortedSet<String> getImmutableSortedSet(); abstract List<String> getList(); abstract ImmutableList<String> getImmutableList(); abstract Map<String, String> getMap(); abstract ImmutableMap<String, String> getImmutableMap(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithOptionalSetOfStrings { abstract Optional<Set<String>> getStrings(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithSetOfStrings { abstract Set<String> getStrings(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithPath { abstract Path getPath(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithOptionalInteger { abstract Optional<Integer> getNumber(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractEmptyImmutableDto {} @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithBuildTargets { abstract BuildTarget getTarget(); abstract BuildTarget getLocal(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithLong { abstract long getNumber(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithSourcePaths { abstract SourcePath getFilePath(); abstract SourcePath getTargetPath(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithImmutableSortedSet { abstract ImmutableSortedSet<BuildTarget> getStuff(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithSetOfPaths { abstract Set<Path> getPaths(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithDepsAndNotDeps { abstract Set<BuildTarget> getDeps(); abstract Set<BuildTarget> getNotDeps(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithBuildTargetList { abstract BuildTarget getSingle(); abstract BuildTarget getSameBuildFileTarget(); abstract List<BuildTarget> getTargets(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithVariousTypes { abstract String getRequired(); abstract Optional<String> getNotRequired(); abstract int getNum(); abstract Optional<Long> getOptionalLong(); abstract boolean isNeeded(); abstract Optional<Boolean> isNotNeeded(); abstract SourcePath getASrcPath(); abstract Optional<SourcePath> getNotASrcPath(); abstract Path getAPath(); abstract Optional<Path> getNotAPath(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithOptionalValues { abstract Optional<String> getNoString(); abstract Optional<String> getDefaultString(); abstract Optional<SourcePath> getNoSourcePath(); abstract Optional<SourcePath> getDefaultSourcePath(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithDefaultValues { @Value.Default public String getSomething() { return "foo"; } @Value.Default public List<String> getThings() { return ImmutableList.of("bar"); } @Value.Default public int getMore() { return 365; } @Value.Default public Boolean getBeGood() { return true; } } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithSetOfSourcePaths { abstract ImmutableSortedSet<SourcePath> getSrcs(); } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithCheck { abstract String getString(); @Value.Check public void check() { Preconditions.checkState(!getString().equals("secrets"), "NOT THE SECRETS!"); } } @BuckStyleImmutable @Value.Immutable abstract static class AbstractDtoWithDerivedAndOrdinaryMethods { abstract String getString(); public String getConstant() { return "constant"; } @Value.Derived public String getUpper() { return getString().toUpperCase(); } } interface HasDefaultMethod { @Value.Default default String getString() { return "foo"; } } @BuckStyleImmutable @Value.Immutable interface AbstractInheritsFromHasDefaultMethod extends HasDefaultMethod {} }