/* * Copyright 2015-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.python; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeThat; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.config.Config; import com.facebook.buck.config.Configs; import com.facebook.buck.cxx.CxxBuckConfig; import com.facebook.buck.cxx.CxxPlatformUtils; import com.facebook.buck.cxx.NativeLinkStrategy; import com.facebook.buck.io.ExecutableFinder; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.DefaultCellPathResolver; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.testutil.integration.ProjectWorkspace; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.environment.Architecture; import com.facebook.buck.util.environment.Platform; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Optional; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class PythonBinaryIntegrationTest { @Parameterized.Parameters(name = "{0}(dir={1}),{2},sandbox_sources={3}") public static Collection<Object[]> data() { ImmutableList.Builder<Object[]> validPermutations = ImmutableList.builder(); for (PythonBuckConfig.PackageStyle packageStyle : PythonBuckConfig.PackageStyle.values()) { for (boolean pexDirectory : new boolean[] {true, false}) { if (packageStyle == PythonBuckConfig.PackageStyle.INPLACE && pexDirectory) { continue; } for (NativeLinkStrategy linkStrategy : NativeLinkStrategy.values()) { for (boolean sandboxSource : new boolean[] {true, false}) { validPermutations.add( new Object[] {packageStyle, pexDirectory, linkStrategy, sandboxSource}); } } } } return validPermutations.build(); } @Parameterized.Parameter public PythonBuckConfig.PackageStyle packageStyle; @Parameterized.Parameter(value = 1) public boolean pexDirectory; @Parameterized.Parameter(value = 2) public NativeLinkStrategy nativeLinkStrategy; @Parameterized.Parameter(value = 3) public boolean sandboxSources; @Rule public TemporaryPaths tmp = new TemporaryPaths(); public ProjectWorkspace workspace; @Before public void setUp() throws InterruptedException, IOException { workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "python_binary", tmp); workspace.setUp(); String pexFlags = pexDirectory ? "--directory" : ""; workspace.writeContentsToPath( "[python]\n" + " package_style = " + packageStyle.toString().toLowerCase() + "\n" + " native_link_strategy = " + nativeLinkStrategy.toString().toLowerCase() + "\n" + " pex_flags = " + pexFlags + "\n" + "[cxx]\n" + " sandbox_sources=" + sandboxSources, ".buckconfig"); PythonBuckConfig config = getPythonBuckConfig(); assertThat(config.getPackageStyle(), equalTo(packageStyle)); assertThat(config.getNativeLinkStrategy(), equalTo(nativeLinkStrategy)); } @Test public void nonComponentDepsArePreserved() throws IOException { workspace.runBuckBuild("//:bin-with-extra-dep").assertSuccess(); workspace.getBuildLog().assertTargetBuiltLocally("//:extra"); } @Test public void executionThroughSymlink() throws IOException, InterruptedException { assumeThat(Platform.detect(), Matchers.oneOf(Platform.MACOS, Platform.LINUX)); workspace.runBuckBuild("//:bin").assertSuccess(); String output = workspace .runBuckCommand("targets", "--show-output", "//:bin") .assertSuccess() .getStdout() .trim(); Path link = workspace.getPath("link"); Files.createSymbolicLink( link, workspace.getPath(Splitter.on(" ").splitToList(output).get(1)).toAbsolutePath()); ProcessExecutor.Result result = workspace.runCommand(getPythonBuckConfig().getPythonInterpreter(), link.toString()); assertThat( result.getStdout().orElse("") + result.getStderr().orElse(""), result.getExitCode(), equalTo(0)); } @Test public void commandLineArgs() throws IOException { ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", ":bin", "HELLO WORLD").assertSuccess(); assertThat(result.getStdout(), containsString("HELLO WORLD")); } @Test public void testOutput() throws IOException { workspace.runBuckBuild("//:bin").assertSuccess(); File output = workspace.getPath("buck-out/gen/bin.pex").toFile(); if (pexDirectory) { assertTrue(output.isDirectory()); } else { assertTrue(output.isFile()); } } @Test public void nativeLibraries() throws IOException { assumeThat(packageStyle, equalTo(PythonBuckConfig.PackageStyle.INPLACE)); assumeThat( "TODO(8667197): Native libs currently don't work on El Capitan", Platform.detect(), not(equalTo(Platform.MACOS))); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", ":bin-with-native-libs").assertSuccess(); assertThat(result.getStdout(), containsString("HELLO WORLD")); } @Test public void runFromGenrule() throws IOException { workspace.runBuckBuild(":gen").assertSuccess(); } @Test public void arg0IsPreserved() throws IOException { workspace.writeContentsToPath("import sys; print(sys.argv[0])", "main.py"); String arg0 = workspace.runBuckCommand("run", ":bin").assertSuccess().getStdout().trim(); String output = workspace .runBuckCommand("targets", "--show-output", "//:bin") .assertSuccess() .getStdout() .trim(); assertThat(arg0, endsWith(Splitter.on(" ").splitToList(output).get(1))); } @Test public void nativeLibsEnvVarIsPreserved() throws IOException { BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); assumeThat( "TODO(8667197): Native libs currently don't work on El Capitan", Platform.detect(), not(equalTo(Platform.MACOS))); String nativeLibsEnvVarName = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())) .getLd() .resolve(resolver) .searchPathEnvVar(); String originalNativeLibsEnvVar = "something"; workspace.writeContentsToPath( String.format("import os; print(os.environ.get('%s'))", nativeLibsEnvVarName), "main_with_native_libs.py"); // Pre-set library path. String nativeLibsEnvVar = workspace .runBuckCommandWithEnvironmentOverridesAndContext( workspace.getPath(""), Optional.empty(), ImmutableMap.of(nativeLibsEnvVarName, originalNativeLibsEnvVar), "run", ":bin-with-native-libs") .assertSuccess() .getStdout() .trim(); assertThat(nativeLibsEnvVar, equalTo(originalNativeLibsEnvVar)); // Empty library path. nativeLibsEnvVar = workspace .runBuckCommandWithEnvironmentOverridesAndContext( workspace.getPath(""), Optional.empty(), ImmutableMap.of(), "run", ":bin-with-native-libs") .assertSuccess() .getStdout() .trim(); assertThat(nativeLibsEnvVar, equalTo("None")); } @Test public void sysPathDoesNotIncludeWorkingDir() throws IOException { workspace.writeContentsToPath("import sys; print(sys.path[0])", "main.py"); String sysPath0 = workspace.runBuckCommand("run", ":bin").assertSuccess().getStdout().trim(); assertThat(sysPath0, not(equalTo(""))); } @Test public void binaryIsCachedProperly() throws IOException { // Verify that the flow of build, upload to cache, clean, then re-build (and potentially // fetching from cache) results in a usable binary. workspace.writeContentsToPath("print('hello world')", "main.py"); workspace.enableDirCache(); workspace.runBuckBuild(":bin").assertSuccess(); workspace.runBuckCommand("clean").assertSuccess(); String stdout = workspace.runBuckCommand("run", ":bin").assertSuccess().getStdout().trim(); assertThat(stdout, equalTo("hello world")); } @Test public void externalPexToolAffectsRuleKey() throws IOException { assumeThat(packageStyle, equalTo(PythonBuckConfig.PackageStyle.STANDALONE)); ProjectWorkspace.ProcessResult firstResult = workspace.runBuckCommand( "targets", "-c", "python.path_to_pex=//:pex_tool", "--show-rulekey", "//:bin"); String firstRuleKey = firstResult.assertSuccess().getStdout().trim(); workspace.writeContentsToPath("changes", "pex_tool.sh"); ProjectWorkspace.ProcessResult secondResult = workspace.runBuckCommand( "targets", "-c", "python.path_to_pex=//:pex_tool", "--show-rulekey", "//:bin"); String secondRuleKey = secondResult.assertSuccess().getStdout().trim(); assertThat(secondRuleKey, not(equalTo(firstRuleKey))); } @Test public void multiplePythonHomes() throws Exception { assumeThat(Platform.detect(), not(Matchers.is(Platform.WINDOWS))); ProjectWorkspace.ProcessResult result = workspace.runBuckBuild( "-c", "python#a.library=//:platform_a", "-c", "python#b.library=//:platform_b", "//:binary_with_extension_a", "//:binary_with_extension_b"); result.assertSuccess(); } @Test public void mainModuleNameIsSetProperly() throws Exception { assumeThat(packageStyle, not(Matchers.is(PythonBuckConfig.PackageStyle.STANDALONE))); workspace.runBuckCommand("run", "//:main_module_bin").assertSuccess(); } @Test public void disableCachingForPackagedBinaries() throws IOException { assumeThat(packageStyle, Matchers.is(PythonBuckConfig.PackageStyle.STANDALONE)); workspace.enableDirCache(); workspace.runBuckBuild("-c", "python.cache_binaries=false", ":bin").assertSuccess(); workspace.runBuckCommand("clean").assertSuccess(); workspace.runBuckBuild("-c", "python.cache_binaries=false", ":bin").assertSuccess(); workspace.getBuildLog().assertTargetBuiltLocally("//:bin"); } /** * Test a bug where a C/C++ library that is transitively excluded by a `python_library` containing * native extensions (in this case, it has to be a 2nd-order dep of the `python_library`) but * which is also a direct dependency of another Python rule, causes the node to be processed as * both a linkable root and an excluded rule, causing an internal omnibus failure. */ @Test public void omnibusExcludedNativeLinkableRoot() throws InterruptedException, IOException { assumeThat(nativeLinkStrategy, Matchers.is(NativeLinkStrategy.MERGED)); workspace .runBuckCommand("targets", "--show-output", "//omnibus_excluded_root:bin") .assertSuccess(); } private PythonBuckConfig getPythonBuckConfig() throws InterruptedException, IOException { Config rawConfig = Configs.createDefaultConfig(tmp.getRoot()); BuckConfig buckConfig = new BuckConfig( rawConfig, new ProjectFilesystem(tmp.getRoot()), Architecture.detect(), Platform.detect(), ImmutableMap.copyOf(System.getenv()), new DefaultCellPathResolver(tmp.getRoot(), rawConfig)); return new PythonBuckConfig(buckConfig, new ExecutableFinder()); } }