/*
* Copyright 2014-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 com.facebook.buck.testutil.HasConsecutiveItemsMatcher.hasConsecutiveItems;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import com.facebook.buck.cli.FakeBuckConfig;
import com.facebook.buck.io.AlwaysFoundExecutableFinder;
import com.facebook.buck.io.ExecutableFinder;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.timing.FakeClock;
import com.facebook.buck.util.FakeProcess;
import com.facebook.buck.util.FakeProcessExecutor;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.ProcessExecutor;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
public class PythonBuckConfigTest {
@Rule public TemporaryPaths temporaryFolder = new TemporaryPaths();
@Rule public TemporaryPaths temporaryFolder2 = new TemporaryPaths();
@Test
public void testGetPythonVersion() throws Exception {
PythonVersion version =
PythonBuckConfig.extractPythonVersion(
Paths.get("usr", "bin", "python"), new ProcessExecutor.Result(0, "", "CPython 2 7\n"));
assertEquals("CPython 2.7", version.toString());
}
@Test
public void whenToolsPythonIsExecutableFileThenItIsUsed() throws IOException {
Path configPythonFile = temporaryFolder.newExecutableFile("python");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of(
"python",
ImmutableMap.of(
"interpreter", configPythonFile.toAbsolutePath().toString())))
.build(),
new ExecutableFinder());
assertEquals(
"Should return path to temp file.",
configPythonFile.toAbsolutePath().toString(),
config.getPythonInterpreter());
}
@Test(expected = HumanReadableException.class)
public void whenToolsPythonDoesNotExistThenItIsNotUsed() throws IOException {
String invalidPath = temporaryFolder.getRoot().toAbsolutePath() + "DoesNotExist";
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(ImmutableMap.of("python", ImmutableMap.of("interpreter", invalidPath)))
.build(),
new ExecutableFinder());
config.getPythonInterpreter();
fail("Should throw exception as python config is invalid.");
}
@Test(expected = HumanReadableException.class)
public void whenToolsPythonIsNonExecutableFileThenItIsNotUsed() throws IOException {
assumeThat(
"On windows all files are executable.", Platform.detect(), is(not(Platform.WINDOWS)));
Path configPythonFile = temporaryFolder.newFile("python");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of(
"python",
ImmutableMap.of(
"interpreter", configPythonFile.toAbsolutePath().toString())))
.build(),
new ExecutableFinder());
config.getPythonInterpreter();
fail("Should throw exception as python config is invalid.");
}
@Test(expected = HumanReadableException.class)
public void whenToolsPythonIsExecutableDirectoryThenItIsNotUsed() throws IOException {
Path configPythonFile = temporaryFolder.newFolder("python");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of(
"python",
ImmutableMap.of(
"interpreter", configPythonFile.toAbsolutePath().toString())))
.build(),
new ExecutableFinder());
config.getPythonInterpreter();
fail("Should throw exception as python config is invalid.");
}
@Test
public void whenPythonOnPathIsExecutableFileThenItIsUsed() throws IOException {
temporaryFolder.newExecutableFile("python");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setEnvironment(
ImmutableMap.<String, String>builder()
.put("PATH", temporaryFolder.getRoot().toAbsolutePath().toString())
.put("PATHEXT", "")
.build())
.build(),
new ExecutableFinder());
config.getPythonInterpreter();
}
@Test
public void whenPythonPlusExtensionOnPathIsExecutableFileThenItIsUsed() throws IOException {
temporaryFolder.newExecutableFile("my-py.exe");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(ImmutableMap.of("python", ImmutableMap.of("interpreter", "my-py")))
.setEnvironment(
ImmutableMap.<String, String>builder()
.put("PATH", temporaryFolder.getRoot().toAbsolutePath().toString())
.put("PATHEXT", ".exe")
.build())
.build(),
new ExecutableFinder(Platform.WINDOWS));
config.getPythonInterpreter();
}
@Test
public void whenPython2OnPathThenItIsUsed() throws IOException {
temporaryFolder.newExecutableFile("python");
Path python2 = temporaryFolder.newExecutableFile("python2");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setEnvironment(
ImmutableMap.<String, String>builder()
.put("PATH", temporaryFolder.getRoot().toAbsolutePath().toString())
.put("PATHEXT", "")
.build())
.build(),
new ExecutableFinder());
assertEquals(
"Should return path to python2.",
python2.toAbsolutePath().toString(),
config.getPythonInterpreter());
}
@Test(expected = HumanReadableException.class)
public void whenPythonOnPathNotFoundThenThrow() throws IOException {
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of("python", ImmutableMap.of("interpreter", "does-not-exist")))
.setEnvironment(
ImmutableMap.<String, String>builder()
.put("PATH", temporaryFolder.getRoot().toAbsolutePath().toString())
.put("PATHEXT", "")
.build())
.build(),
new ExecutableFinder());
config.getPythonInterpreter();
fail("Should throw an exception when Python isn't found.");
}
@Test
public void whenMultiplePythonExecutablesOnPathFirstIsUsed() throws IOException {
Path pythonA = temporaryFolder.newExecutableFile("python2");
temporaryFolder2.newExecutableFile("python2");
String path =
temporaryFolder.getRoot().toAbsolutePath()
+ File.pathSeparator
+ temporaryFolder2.getRoot().toAbsolutePath();
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setEnvironment(
ImmutableMap.<String, String>builder()
.put("PATH", path)
.put("PATHEXT", "")
.build())
.build(),
new ExecutableFinder());
assertEquals(
"Should return the first path",
config.getPythonInterpreter(),
pythonA.toAbsolutePath().toString());
}
@Test
public void testPathToPexExecuterUsesConfigSetting() throws IOException {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
Path projectDir = Files.createTempDirectory("project");
Path pexExecuter = Paths.get("pex-exectuter");
ProjectFilesystem projectFilesystem =
new FakeProjectFilesystem(new FakeClock(0), projectDir, ImmutableSet.of(pexExecuter));
Files.createFile(projectFilesystem.resolve(pexExecuter));
assertTrue(
"Should be able to set file executable",
projectFilesystem.resolve(pexExecuter).toFile().setExecutable(true));
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of(
"python", ImmutableMap.of("path_to_pex_executer", pexExecuter.toString())))
.setFilesystem(projectFilesystem)
.build(),
new ExecutableFinder());
assertThat(
config.getPexExecutor(resolver).get().getCommandPrefix(pathResolver),
Matchers.contains(pexExecuter.toString()));
}
@Test
public void testGetPyrunVersion() throws Exception {
PythonVersion version =
PythonBuckConfig.extractPythonVersion(
Paths.get("non", "important", "path"),
new ProcessExecutor.Result(0, "", "CPython 2 7\n"));
assertEquals("CPython 2.7", version.toString());
}
@Test
public void testGetWindowsVersion() throws Exception {
String output = "CPython 2 7\r\n";
PythonVersion version =
PythonBuckConfig.extractPythonVersion(
Paths.get("non", "important", "path"), new ProcessExecutor.Result(0, "", output));
assertThat(version.toString(), Matchers.equalTo("CPython 2.7"));
}
@Test
public void testDefaultPythonLibrary() throws InterruptedException {
BuildTarget library = BuildTargetFactory.newInstance("//:library");
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of("python", ImmutableMap.of("library", library.toString())))
.build(),
new AlwaysFoundExecutableFinder());
assertThat(
config
.getDefaultPythonPlatform(
new FakeProcessExecutor(
Functions.constant(new FakeProcess(0, "CPython 2 7", "")), new TestConsole()))
.getCxxLibrary(),
Matchers.equalTo(Optional.of(library)));
}
@Test
public void testPexArgs() throws Exception {
PythonBuckConfig config =
new PythonBuckConfig(
FakeBuckConfig.builder()
.setSections(
ImmutableMap.of("python", ImmutableMap.of("pex_flags", "--hello --world")))
.build(),
new AlwaysFoundExecutableFinder());
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
assertThat(
config
.getPexTool(resolver)
.getCommandPrefix(new SourcePathResolver(new SourcePathRuleFinder(resolver))),
hasConsecutiveItems("--hello", "--world"));
}
}