/* * Copyright 2012-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.cli; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.facebook.buck.io.MorePathsForTests; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.integration.ProjectWorkspace; import com.facebook.buck.testutil.integration.ProjectWorkspace.ProcessResult; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.concurrent.ResourceAmounts; import com.facebook.buck.util.environment.Architecture; import com.facebook.buck.util.environment.Platform; import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import org.easymock.EasyMock; import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; public class BuckConfigTest { @Rule public TemporaryPaths temporaryFolder = new TemporaryPaths(); /** * Ensure that whichever alias is listed first in the file is the one used in the reverse map if * the value appears multiple times. */ @Test public void testGetBasePathToAliasMap() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader1 = new StringReader( Joiner.on('\n') .join( "[alias]", "fb4a = //java/com/example:fbandroid", "katana = //java/com/example:fbandroid")); BuckConfig config1 = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader1); assertEquals( ImmutableMap.of(Paths.get("java/com/example"), "fb4a"), config1.getBasePathToAliasMap()); assertEquals( ImmutableMap.of( "fb4a", "//java/com/example:fbandroid", "katana", "//java/com/example:fbandroid"), config1.getEntriesForSection("alias")); Reader reader2 = new StringReader( Joiner.on('\n') .join( "[alias]", "katana = //java/com/example:fbandroid", "fb4a = //java/com/example:fbandroid")); BuckConfig config2 = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader2); assertEquals( ImmutableMap.of(Paths.get("java/com/example"), "katana"), config2.getBasePathToAliasMap()); assertEquals( ImmutableMap.of( "fb4a", "//java/com/example:fbandroid", "katana", "//java/com/example:fbandroid"), config2.getEntriesForSection("alias")); Reader noAliasesReader = new StringReader(""); BuckConfig noAliasesConfig = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, noAliasesReader); assertEquals(ImmutableMap.of(), noAliasesConfig.getBasePathToAliasMap()); assertEquals(ImmutableMap.of(), noAliasesConfig.getEntriesForSection("alias")); } @Test public void testConstructorThrowsForMalformedBuildTarget() throws InterruptedException, IOException { Reader reader = new StringReader(Joiner.on('\n').join("[alias]", "fb4a = :fb4a")); ProjectFilesystem projectFilesystem = EasyMock.createMock(ProjectFilesystem.class); EasyMock.replay(projectFilesystem); try { BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); fail("Should have thrown HumanReadableException."); } catch (HumanReadableException e) { assertEquals("Path in :fb4a must start with //", e.getHumanReadableErrorMessage()); } EasyMock.verify(projectFilesystem); } @Test public void testConstructorWithNonExistentBasePath() throws InterruptedException, IOException { Reader reader = new StringReader(Joiner.on('\n').join("[alias]", "katana = //java/com/example:fb4a")); // BuckConfig should allow nonexistent targets without throwing. BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); } @Test public void testGetBuildTargetForAlias() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader = new StringReader( Joiner.on('\n') .join( "[alias]", "foo = //java/com/example:foo", "bar = //java/com/example:bar", "baz = //java/com/example:foo //java/com/example:bar", "bash = ")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); assertEquals( ImmutableSet.of("//java/com/example:foo"), config.getBuildTargetForAliasAsString("foo")); assertEquals( ImmutableSet.of("//java/com/example:bar"), config.getBuildTargetForAliasAsString("bar")); assertEquals( ImmutableSet.of("//java/com/example:foo", "//java/com/example:bar"), config.getBuildTargetForAliasAsString("baz")); assertEquals(ImmutableSet.of(), config.getBuildTargetForAliasAsString("bash")); // Flavors on alias. assertEquals( ImmutableSet.of("//java/com/example:foo#src_jar"), config.getBuildTargetForAliasAsString("foo#src_jar")); assertEquals( ImmutableSet.of("//java/com/example:bar#fl1,fl2"), config.getBuildTargetForAliasAsString("bar#fl1,fl2")); assertEquals( ImmutableSet.of("//java/com/example:foo#fl1,fl2", "//java/com/example:bar#fl1,fl2"), config.getBuildTargetForAliasAsString("baz#fl1,fl2")); assertEquals(ImmutableSet.of(), config.getBuildTargetForAliasAsString("bash#fl1,fl2")); assertEquals( "Invalid alias names, such as build targets, should be tolerated by this method.", ImmutableSet.of(), config.getBuildTargetForAliasAsString("//java/com/example:foo")); assertEquals(ImmutableSet.of(), config.getBuildTargetForAliasAsString("notathing")); assertEquals(ImmutableSet.of(), config.getBuildTargetForAliasAsString("notathing#src_jar")); Reader noAliasesReader = new StringReader(""); BuckConfig noAliasesConfig = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, noAliasesReader); assertEquals(ImmutableSet.of(), noAliasesConfig.getBuildTargetForAliasAsString("foo")); assertEquals(ImmutableSet.of(), noAliasesConfig.getBuildTargetForAliasAsString("bar")); assertEquals(ImmutableSet.of(), noAliasesConfig.getBuildTargetForAliasAsString("baz")); } @Test public void testGetBuildTargetListResolvesAliases() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader = new StringReader( Joiner.on('\n') .join( "[alias]", "foo = //java/com/example:foo", "[section]", "some_list = \\", "foo, \\", "//java/com/example:bar")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); ImmutableList<String> expected = ImmutableList.of("//java/com/example:foo", "//java/com/example:bar"); ImmutableList<String> result = ImmutableList.copyOf( FluentIterable.from(config.getBuildTargetList("section", "some_list")) .transform(Object::toString)); assertThat(result, Matchers.equalTo(expected)); } /** Ensures that all public methods of BuckConfig return reasonable values for an empty config. */ @Test public void testEmptyConfig() { BuckConfig emptyConfig = FakeBuckConfig.builder().build(); assertEquals(ImmutableMap.<String, String>of(), emptyConfig.getEntriesForSection("alias")); assertEquals(ImmutableSet.of(), emptyConfig.getBuildTargetForAliasAsString("fb4a")); assertEquals(ImmutableMap.<Path, String>of(), emptyConfig.getBasePathToAliasMap()); } @Test public void testValidateAliasName() { BuckConfig.validateAliasName("f"); BuckConfig.validateAliasName("_"); BuckConfig.validateAliasName("F"); BuckConfig.validateAliasName("fb4a"); BuckConfig.validateAliasName("FB4A"); BuckConfig.validateAliasName("FB4_"); try { BuckConfig.validateAliasName(""); fail("Should have thrown HumanReadableException"); } catch (HumanReadableException e) { assertEquals("Alias cannot be the empty string.", e.getHumanReadableErrorMessage()); } try { BuckConfig.validateAliasName("42meaningOfLife"); fail("Should have thrown HumanReadableException"); } catch (HumanReadableException e) { assertEquals("Not a valid alias: 42meaningOfLife.", e.getHumanReadableErrorMessage()); } } @Test public void testReferentialAliases() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader = new StringReader( Joiner.on('\n') .join( "[alias]", "foo = //java/com/example:foo", "bar = //java/com/example:bar", "foo_codename = foo", "", "# Do not delete these: automation builds require these aliases to exist!", "automation_foo = foo_codename", "automation_bar = bar")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); assertEquals( ImmutableSet.of("//java/com/example:foo"), config.getBuildTargetForAliasAsString("foo")); assertEquals( ImmutableSet.of("//java/com/example:bar"), config.getBuildTargetForAliasAsString("bar")); assertEquals( ImmutableSet.of("//java/com/example:foo"), config.getBuildTargetForAliasAsString("foo_codename")); assertEquals( ImmutableSet.of("//java/com/example:foo"), config.getBuildTargetForAliasAsString("automation_foo")); assertEquals( ImmutableSet.of("//java/com/example:bar"), config.getBuildTargetForAliasAsString("automation_bar")); assertEquals(ImmutableSet.of(), config.getBuildTargetForAliasAsString("baz")); } @Test public void testUnresolvedAliasThrows() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader = new StringReader( Joiner.on('\n').join("[alias]", "foo = //java/com/example:foo", "bar = food")); try { BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); fail("Should have thrown HumanReadableException."); } catch (HumanReadableException e) { assertEquals("No alias for: food.", e.getHumanReadableErrorMessage()); } } @Test public void testDuplicateAliasDefinitionThrows() throws InterruptedException, IOException, NoSuchBuildTargetException { Reader reader = new StringReader( Joiner.on('\n') .join("[alias]", "foo = //java/com/example:foo", "foo = //java/com/example:foo")); try { BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); fail("Should have thrown HumanReadableException."); } catch (HumanReadableException e) { assertEquals( "Throw an exception if there are duplicate definitions for an alias, " + "even if the values are the same.", "Duplicate definition for foo in the [alias] section of your .buckconfig or " + ".buckconfig.local.", e.getHumanReadableErrorMessage()); } } @Test public void testExcludedLabels() throws InterruptedException, IOException { Reader reader = new StringReader(Joiner.on('\n').join("[test]", "excluded_labels = windows, linux")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); assertEquals( ImmutableList.of("windows", "linux"), config.getDefaultRawExcludedLabelSelectors()); } @Test public void testResolveNullPathThatMayBeOutsideTheProjectFilesystem() throws IOException { BuckConfig config = createFromText(""); assertNull(config.resolvePathThatMayBeOutsideTheProjectFilesystem(null)); } @Test public void testResolveAbsolutePathThatMayBeOutsideTheProjectFilesystem() throws IOException { BuckConfig config = createFromText(""); assertEquals( MorePathsForTests.rootRelativePath("foo/bar"), config.resolvePathThatMayBeOutsideTheProjectFilesystem( MorePathsForTests.rootRelativePath("foo/bar"))); } @Test public void testResolveRelativePathThatMayBeOutsideTheProjectFilesystem() throws IOException { BuckConfig config = createFromText(""); assertEquals( MorePathsForTests.rootRelativePath("project/foo/bar"), config.resolvePathThatMayBeOutsideTheProjectFilesystem(Paths.get("../foo/bar"))); } @Test public void testResolveHomeDirPathThatMayBeOutsideTheProjectFilesystem() throws IOException { BuckConfig config = createFromText(""); Path homePath = Paths.get("").getFileSystem().getPath(System.getProperty("user.home")); assertEquals( homePath.resolve("foo/bar"), config.resolvePathThatMayBeOutsideTheProjectFilesystem(Paths.get("~/foo/bar"))); } @Test public void testBuckPyIgnorePaths() throws IOException { ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "buck_py_ignore_paths", temporaryFolder); workspace.setUp(); ProcessResult result = workspace.runBuckCommand("test", "--all"); result.assertSuccess("buck test --all should exit cleanly"); } @Test public void testGetDefaultTestTimeoutMillis() throws InterruptedException, IOException { assertEquals(0L, FakeBuckConfig.builder().build().getDefaultTestTimeoutMillis()); Reader reader = new StringReader(Joiner.on('\n').join("[test]", "timeout = 54321")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); assertEquals(54321L, config.getDefaultTestTimeoutMillis()); } @Test public void testGetAndroidTargetSdkWithSpaces() throws IOException { BuckConfig config = createFromText("[android]", "target = Google Inc.:Google APIs:16"); assertEquals("Google Inc.:Google APIs:16", config.getValue("android", "target").get()); } @Test public void testCreateAnsi() { BuckConfig windowsConfig = FakeBuckConfig.builder() .setArchitecture(Architecture.X86_64) .setPlatform(Platform.WINDOWS) .build(); // "auto" on Windows is equivalent to "never". assertFalse(windowsConfig.createAnsi(Optional.empty()).isAnsiTerminal()); assertFalse(windowsConfig.createAnsi(Optional.of("auto")).isAnsiTerminal()); assertTrue(windowsConfig.createAnsi(Optional.of("always")).isAnsiTerminal()); assertFalse(windowsConfig.createAnsi(Optional.of("never")).isAnsiTerminal()); BuckConfig linuxConfig = FakeBuckConfig.builder() .setArchitecture(Architecture.I386) .setPlatform(Platform.LINUX) .build(); // We don't test "auto" on Linux, because the behavior would depend on how the test was run. assertTrue(linuxConfig.createAnsi(Optional.of("always")).isAnsiTerminal()); assertFalse(linuxConfig.createAnsi(Optional.of("never")).isAnsiTerminal()); } @Test public void getEnvUsesSuppliedEnvironment() { String name = "SOME_ENVIRONMENT_VARIABLE"; String value = "SOME_VALUE"; BuckConfig config = FakeBuckConfig.builder().setEnvironment(ImmutableMap.of(name, value)).build(); String[] expected = {value}; assertArrayEquals("Should match value in environment.", expected, config.getEnv(name, ":")); } private BuckConfig createFromText(String... lines) throws IOException { ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(MorePathsForTests.rootRelativePath("project/root")); StringReader reader = new StringReader(Joiner.on('\n').join(lines)); return BuckConfigTestUtils.createFromReader( reader, projectFilesystem, Architecture.detect(), Platform.detect(), ImmutableMap.copyOf(System.getenv())); } @Test public void testShouldSetNumberOfThreadsFromBuckConfig() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("threads", "3"))) .build(); assertThat(buckConfig.getNumThreads(), Matchers.equalTo(3)); } @Test public void testDefaultsNumberOfBuildThreadsToOneAndAQuarterTheNumberOfAvailableProcessors() { BuckConfig buckConfig = FakeBuckConfig.builder().build(); assertThat( buckConfig.getNumThreads(), Matchers.equalTo(Runtime.getRuntime().availableProcessors())); } @Test public void testDefaultsNumberOfBuildThreadsSpecified() { BuckConfig buckConfig = FakeBuckConfig.builder().build(); assertThat(buckConfig.getNumThreads(42), Matchers.equalTo(42)); } @Test public void testBuildThreadsRatioSanityCheck() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "1"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(10), Matchers.equalTo(10)); } @Test public void testBuildThreadsRatioGreaterThanZero() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "0.00001"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(1), Matchers.equalTo(1)); } @Test public void testBuildThreadsRatioRoundsUp() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "0.3"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(4), Matchers.equalTo(2)); } @Test public void testNonZeroBuildThreadsRatio() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "0.1"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(1), Matchers.equalTo(1)); } @Test public void testZeroBuildThreadsRatio() { try { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "0"))) .build(); buckConfig.getDefaultMaximumNumberOfThreads(1); } catch (HumanReadableException e) { assertThat( e.getHumanReadableErrorMessage(), Matchers.startsWith("thread_core_ratio must be greater than zero")); } } @Test public void testLessThanZeroBuildThreadsRatio() { try { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("build", ImmutableMap.of("thread_core_ratio", "-0.1"))) .build(); buckConfig.getDefaultMaximumNumberOfThreads(1); } catch (HumanReadableException e) { assertThat( e.getHumanReadableErrorMessage(), Matchers.startsWith("thread_core_ratio must be greater than zero")); } } @Test public void testBuildThreadsRatioWithReservedCores() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of( "thread_core_ratio", "1", "thread_core_ratio_reserved_cores", "2"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(10), Matchers.equalTo(8)); } @Test public void testCappedBuildThreadsRatio() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of( "thread_core_ratio", "0.5", "thread_core_ratio_max_threads", "4"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(10), Matchers.equalTo(4)); } @Test public void testFloorLimitedBuildThreadsRatio() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of( "thread_core_ratio", "0.25", "thread_core_ratio_min_threads", "6"))) .build(); assertThat(buckConfig.getDefaultMaximumNumberOfThreads(10), Matchers.equalTo(6)); } @Test public void testEqualsForDaemonRestart() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of("threads", "3"), "cxx", ImmutableMap.of("cc", "/some_location/gcc"))) .build(); BuckConfig buckConfigMoreThreads = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of("threads", "4"), "cxx", ImmutableMap.of("cc", "/some_location/gcc"))) .build(); BuckConfig buckConfigDifferentCompiler = FakeBuckConfig.builder() .setSections( ImmutableMap.of( "build", ImmutableMap.of("threads", "3"), "cxx", ImmutableMap.of("cc", "/some_location/clang"))) .build(); assertFalse(buckConfig.equals(buckConfigMoreThreads)); assertFalse(buckConfig.equals(buckConfigDifferentCompiler)); assertTrue(buckConfig.equalsForDaemonRestart(buckConfigMoreThreads)); assertFalse(buckConfig.equalsForDaemonRestart(buckConfigDifferentCompiler)); assertFalse(buckConfigMoreThreads.equalsForDaemonRestart(buckConfigDifferentCompiler)); } @Test public void hasUserDefinedValueReturnsTrueForEmptySetting() { BuckConfig buckConfig = FakeBuckConfig.builder() .setSections(ImmutableMap.of("cache", ImmutableMap.of("mode", ""))) .build(); assertTrue(buckConfig.hasUserDefinedValue("cache", "mode")); } @Test public void hasUserDefinedValueReturnsFalseForNoSetting() { BuckConfig buckConfig = FakeBuckConfig.builder().setSections(ImmutableMap.of()).build(); assertFalse(buckConfig.hasUserDefinedValue("cache", "mode")); } @Test public void testGettingResourceAmountsPerRuleType() throws InterruptedException, IOException { Reader reader = new StringReader( Joiner.on('\n') .join("[resources_per_rule]", "some_rule = 1, 20, 3, 4", "other_rule = 4,30,2,1")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); ImmutableMap<String, ResourceAmounts> result = config.getResourceAmountsPerRuleType(); assertEquals( ImmutableMap.of( "some_rule", ResourceAmounts.of(1, 20, 3, 4), "other_rule", ResourceAmounts.of(4, 30, 2, 1)), result); } @Test public void testInvalidResourceAmountsConfiguration() throws InterruptedException, IOException { Reader reader = new StringReader( Joiner.on('\n') .join( "[resources_per_rule]", "some_rule = 1, 20, 3, 4", "other_rule = 4,30,2,1", "wrong_config = 1,2,3,4,5,6,7,8,9,0")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); try { config.getResourceAmountsPerRuleType(); } catch (IllegalArgumentException e) { assertThat( e.getMessage(), Matchers.containsString( "Buck config entry [resources_per_rule].wrong_config" + " contains 10 values, but expected to contain 4 values in the following order: " + "cpu, memory, disk_io, network_io")); return; } assertThat("IllegalArgumentException should be thrown", Matchers.equalTo("")); } @Test public void testGetMap() throws InterruptedException, IOException { Reader reader = new StringReader(Joiner.on('\n').join("[section]", "args_map = key0=>val0,key1=>val1")); BuckConfig config = BuckConfigTestUtils.createWithDefaultFilesystem(temporaryFolder, reader); assertEquals( ImmutableMap.of("key0", "val0", "key1", "val1"), config.getMap("section", "args_map")); } }