/* * 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.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; 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.AbstractDescriptionArg; 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.MoreCollectors; 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 org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @SuppressWarnings("unused") // Many unused fields in sample DTO objects. public class ConstructorArgMarshallerFieldBasedTest { public static final BuildTarget TARGET = BuildTargetFactory.newInstance("//example/path:three"); private Path basePath; private ConstructorArgMarshaller marshaller; private ProjectFilesystem filesystem; @Rule public ExpectedException mExpected = 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 dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithString.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("string", "cheese")); assertEquals("cheese", dto.string); } @Test public void shouldPopulateABooleanValue() throws Exception { DtoWithBoolean dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithBoolean.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("value", true)); assertTrue(dto.value); } @Test public void shouldPopulateBuildTargetValues() throws Exception { DtoWithBuildTargets dto = 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"), dto.target); assertEquals( BuildTargetFactory.newInstance(filesystem.getRootPath(), "//example/path:fish"), dto.local); } @Test public void shouldPopulateANumericValue() throws Exception { DtoWithLong dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithLong.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("number", 42L)); assertEquals(42, dto.number); } @Test public void shouldPopulateSourcePaths() throws Exception { ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); BuildTarget target = BuildTargetFactory.newInstance("//example/path:peas"); DtoWithSourcePaths dto = 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")), dto.filePath); assertEquals(new DefaultBuildTargetSourcePath(target), dto.targetPath); } @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 dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithImmutableSortedSet.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of( "deps", ImmutableList.of("//please/go:here", ":there"))); assertEquals(ImmutableSortedSet.of(t2, t1), dto.deps); } @Test public void shouldPopulateSets() throws Exception { DtoWithSetOfPaths dto = 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")), dto.paths); } @Test public void shouldPopulateLists() throws Exception { DtoWithListOfStrings dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithListOfStrings.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("list", ImmutableList.of("alpha", "beta"))); assertEquals(ImmutableList.of("alpha", "beta"), dto.list); } @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 = ImmutableMap.of( "deps", ImmutableList.of(dep), "notdeps", ImmutableList.of(notDep)); ImmutableSet.Builder<BuildTarget> declaredDeps = ImmutableSet.builder(); DtoWithDepsAndNotDeps dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDepsAndNotDeps.class, declaredDeps, args); assertEquals(ImmutableSet.of(declaredDep), declaredDeps.build()); } @Test public void fieldsWithIsDepEqualsFalseHintAreNotTreatedAsDeps() throws Exception { final String dep = "//should/be:ignored"; Map<String, Object> args = ImmutableMap.of("deps", ImmutableList.of(dep)); ImmutableSet.Builder<BuildTarget> declaredDeps = ImmutableSet.builder(); DtoWithFakeDeps dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithFakeDeps.class, declaredDeps, args); assertEquals(ImmutableSet.of(), declaredDeps.build()); } @Test public void optionalCollectionsWithoutAValueWillBeSetToAnEmptyOptionalCollection() throws Exception { Map<String, Object> args = ImmutableMap.of(); // Deliberately not populating args DtoWithOptionalSetOfStrings dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalSetOfStrings.class, ImmutableSet.builder(), args); assertEquals(Optional.empty(), dto.strings); } @Test(expected = ParamInfoException.class) public void shouldBeAnErrorToAttemptToSetASingleValueToACollection() throws Exception { DtoWithString dto = 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 { DtoWithSetOfStrings dto = 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 { DtoWithSetOfStrings dto = 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 dto = 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(), dto.path); } @Test(expected = RuntimeException.class) public void lowerBoundGenericTypesCauseAnException() throws Exception { class Dto { public List<? super BuildTarget> nope; } Dto dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, Dto.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("nope", ImmutableList.of("//will/not:happen"))); } @Test public void shouldSetBuildTargetParameters() throws Exception { DtoWithBuildTargetList dto = 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, dto.single); assertEquals(cake, dto.sameBuildFileTarget); assertEquals(ImmutableList.of(cake, cheese), dto.targets); } @Test public void specifyingZeroIsNotConsideredOptional() throws Exception { DtoWithOptionalInteger dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalInteger.class, ImmutableSet.builder(), ImmutableMap.<String, Object>of("number", 0)); assertTrue(dto.number.isPresent()); assertEquals(Optional.of(0), dto.number); } @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 dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithVariousTypes.class, ImmutableSet.builder(), args); assertEquals("cheese", dto.required); assertEquals("cake", dto.notRequired.get()); assertEquals(42, dto.num); assertEquals(Optional.of(88L), dto.optionalLong); assertTrue(dto.needed); assertEquals(Optional.empty(), dto.notNeeded); DefaultBuildTargetSourcePath expected = new DefaultBuildTargetSourcePath(target); assertEquals(expected, dto.aSrcPath); assertEquals(Paths.get("example/path/NotFile.java"), dto.notAPath.get()); } @Test public void shouldPopulateDefaultValuesAsBeingAbsent() 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 dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithOptionalValues.class, ImmutableSet.builder(), args); assertEquals(Optional.empty(), dto.noString); assertEquals(Optional.empty(), dto.defaultString); assertEquals(Optional.empty(), dto.noSourcePath); assertEquals(Optional.empty(), dto.defaultSourcePath); } @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); args.put("another", null); DtoWithDefaultValues dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDefaultValues.class, ImmutableSet.builder(), args); assertThat(dto.something, is("foo")); assertThat(dto.things, is(ImmutableList.of("bar"))); assertThat(dto.another, is(365)); assertThat(dto.beGood, is(true)); } @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("another", 1234L); args.put("beGood", false); DtoWithDefaultValues dto = marshaller.populate( createCellRoots(filesystem), filesystem, TARGET, DtoWithDefaultValues.class, ImmutableSet.builder(), args); assertThat(dto.something, is("bar")); assertThat(dto.things, is(ImmutableList.of("qux", "quz"))); assertThat(dto.another, is(1234)); assertThat(dto.beGood, is(false)); } @Test public void shouldResolveCollectionOfSourcePaths() throws Exception { BuildTarget target = BuildTargetFactory.newInstance("//example/path:manifest"); DtoWithSetOfSourcePaths dto = 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 = dto.srcs .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); } public static class DtoWithString extends AbstractDescriptionArg { public String string; } public static class DtoWithListOfStrings extends AbstractDescriptionArg { public List<String> list; } public static class DtoWithSetOfStrings extends AbstractDescriptionArg { public Set<String> strings; } public static class DtoWithOptionalSetOfStrings extends AbstractDescriptionArg { public Optional<Set<String>> strings; } public static class DtoWithPath extends AbstractDescriptionArg { public Path path; } public static class DtoWithSetOfPaths extends AbstractDescriptionArg { public Set<Path> paths; } public static class DtoWithBoolean extends AbstractDescriptionArg { public boolean value; } public static class DtoWithFakeDeps extends AbstractDescriptionArg { @Hint(isDep = false) public Optional<Set<BuildTarget>> deps; } public static class DtoWithOptionalInteger extends AbstractDescriptionArg { public Optional<Integer> number; } public static class EmptyDto extends AbstractDescriptionArg {} public static class DtoWithBuildTargets extends AbstractDescriptionArg { public BuildTarget target; public BuildTarget local; } public static class DtoWithBuildTargetList extends AbstractDescriptionArg { public BuildTarget single; public BuildTarget sameBuildFileTarget; public List<BuildTarget> targets; } public static class DtoWithSourcePaths extends AbstractDescriptionArg { public SourcePath filePath; public SourcePath targetPath; } public static class DtoWithDepsAndNotDeps extends AbstractDescriptionArg { public Optional<Set<BuildTarget>> deps; public Optional<Set<BuildTarget>> notdeps; } public static class DtoWithImmutableSortedSet extends AbstractDescriptionArg { public ImmutableSortedSet<BuildTarget> deps; } public static class DtoWithSetOfSourcePaths extends AbstractDescriptionArg { public ImmutableSortedSet<SourcePath> srcs; } public static class DtoWithLong extends AbstractDescriptionArg { public long number; } public static class DtoWithVariousTypes extends AbstractDescriptionArg { public String required; public Optional<String> notRequired; public int num; public Optional<Long> optionalLong; public boolean needed; public Optional<Boolean> notNeeded; public SourcePath aSrcPath; public Optional<SourcePath> notASrcPath; public Path aPath; public Optional<Path> notAPath; } public static class DtoWithOptionalValues extends AbstractDescriptionArg { public Optional<String> noString; public Optional<String> defaultString; public Optional<SourcePath> noSourcePath; public Optional<SourcePath> defaultSourcePath; } public static class DtoWithDefaultValues extends AbstractDescriptionArg { public String something = "foo"; public List<String> things = ImmutableList.of("bar"); public int another = 365; public Boolean beGood = true; } }