/* * 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.lua; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeThat; import static org.junit.Assume.assumeTrue; 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.CxxPlatform; import com.facebook.buck.cxx.DefaultCxxPlatforms; import com.facebook.buck.cxx.NativeLinkStrategy; import com.facebook.buck.io.ExecutableFinder; import com.facebook.buck.io.FakeExecutableFinder; 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.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.testutil.FakeProjectFilesystem; import com.facebook.buck.testutil.ParameterizedTests; 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.Console; import com.facebook.buck.util.DefaultProcessExecutor; import com.facebook.buck.util.ObjectMappers; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.ProcessExecutorParams; import com.facebook.buck.util.environment.Architecture; import com.facebook.buck.util.environment.Platform; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; 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 LuaBinaryIntegrationTest { private ProjectWorkspace workspace; private Path lua; private boolean luaDevel; @Parameterized.Parameters(name = "{0} {1} sandbox_sources={2}") public static Collection<Object[]> data() { return ParameterizedTests.getPermutations( Arrays.asList(LuaBinaryDescription.StarterType.values()), Arrays.asList(NativeLinkStrategy.values()), ImmutableList.of(true, false)); } @Parameterized.Parameter public LuaBinaryDescription.StarterType starterType; @Parameterized.Parameter(value = 1) public NativeLinkStrategy nativeLinkStrategy; @Parameterized.Parameter(value = 2) public boolean sandboxSources; @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Before public void setUp() throws Exception { // We don't currently support windows. assumeThat(Platform.detect(), Matchers.not(Platform.WINDOWS)); // Verify that a Lua interpreter is available on the system. LuaBuckConfig fakeConfig = new LuaBuckConfig(FakeBuckConfig.builder().build(), new ExecutableFinder()); Optional<Path> luaOptional = fakeConfig.getSystemLua(); assumeTrue(luaOptional.isPresent()); lua = luaOptional.get(); // Try to detect if a Lua devel package is available, which is needed to C/C++ support. BuildRuleResolver resolver = new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()); CxxPlatform cxxPlatform = DefaultCxxPlatforms.build( Platform.detect(), new FakeProjectFilesystem(), new CxxBuckConfig(FakeBuckConfig.builder().build())); ProcessExecutorParams params = ProcessExecutorParams.builder() .setCommand( ImmutableList.<String>builder() .addAll( cxxPlatform .getCc() .resolve(resolver) .getCommandPrefix( new SourcePathResolver(new SourcePathRuleFinder(resolver)))) .add("-includelua.h", "-E", "-") .build()) .setRedirectInput(ProcessBuilder.Redirect.PIPE) .build(); ProcessExecutor executor = new DefaultProcessExecutor(Console.createNullConsole()); ProcessExecutor.LaunchedProcess launchedProcess = executor.launchProcess(params); launchedProcess.getOutputStream().close(); int exitCode = executor.waitForLaunchedProcess(launchedProcess).getExitCode(); luaDevel = exitCode == 0; if (starterType == LuaBinaryDescription.StarterType.NATIVE) { assumeTrue("Lua devel package required for native starter", luaDevel); } // Setup the workspace. workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "lua_binary", tmp); workspace.setUp(); workspace.writeContentsToPath( Joiner.on(System.lineSeparator()) .join( ImmutableList.of( "[lua]", " starter_type = " + starterType.toString().toLowerCase(), " native_link_strategy = " + nativeLinkStrategy.toString().toLowerCase(), "[cxx]", " sandbox_sources =" + sandboxSources)), ".buckconfig"); LuaBuckConfig config = getLuaBuckConfig(); assertThat(config.getStarterType(), Matchers.equalTo(Optional.of(starterType))); assertThat(config.getNativeLinkStrategy(), Matchers.equalTo(nativeLinkStrategy)); } @Test public void stdout() throws Exception { workspace.writeContentsToPath("require 'os'; io.stdout:write('hello world')", "simple.lua"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", "//:simple").assertSuccess(); assertThat( result.getStdout() + result.getStderr(), result.getStdout().trim(), Matchers.equalTo("hello world")); } @Test public void stderr() throws Exception { workspace.writeContentsToPath("require 'os'; io.stderr:write('hello world')", "simple.lua"); Path path = workspace.buildAndReturnOutput("//:simple"); ProcessExecutor.Result result = workspace.runCommand(path.toString()); assertThat( result.getStdout().orElse("") + result.getStderr().orElse(""), result.getStderr().orElse("").trim(), Matchers.endsWith("hello world")); } @Test public void errorCode() throws Exception { workspace.writeContentsToPath("require 'os'\nos.exit(5)", "simple.lua"); workspace.runBuckBuild("//:simple").assertSuccess(); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", "//:simple"); assertThat(result.getExitCode(), Matchers.equalTo(5)); } @Test public void error() throws Exception { workspace.writeContentsToPath("blah blah garbage", "simple.lua"); workspace.runBuckBuild("//:simple").assertSuccess(); workspace.runBuckCommand("run", "//:simple").assertFailure(); } @Test public void args() throws Exception { workspace.writeContentsToPath("for i=-1,#arg do print(arg[i]) end", "simple.lua"); Path arg0 = workspace.buildAndReturnOutput("//:simple"); // no args... ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", "//:simple").assertSuccess(); assertThat( result.getStdout() + result.getStderr(), Splitter.on(System.lineSeparator()).splitToList(result.getStdout().trim()), Matchers.contains( ImmutableList.of( Matchers.anyOf(Matchers.equalTo(lua.toString()), Matchers.equalTo("nil")), Matchers.endsWith(arg0.toString())))); // with args... result = workspace.runBuckCommand("run", "//:simple", "--", "hello", "world").assertSuccess(); assertThat( result.getStdout() + result.getStderr(), Splitter.on(System.lineSeparator()).splitToList(result.getStdout().trim()), Matchers.contains( ImmutableList.of( Matchers.anyOf(Matchers.equalTo(lua.toString()), Matchers.equalTo("nil")), Matchers.endsWith(arg0.toString()), Matchers.equalTo("hello"), Matchers.equalTo("world")))); } @Test public void nativeExtension() throws Exception { assumeTrue(luaDevel); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", "//:native").assertSuccess(); assertThat( result.getStdout() + result.getStderr(), result.getStdout().trim(), Matchers.equalTo("hello world")); } @Test public void nativeExtensionWithDep() throws Exception { assumeThat(starterType, Matchers.not(Matchers.equalTo(LuaBinaryDescription.StarterType.PURE))); assumeTrue(luaDevel); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("run", "//:native_with_dep").assertSuccess(); assertThat( result.getStdout() + result.getStderr(), result.getStdout().trim(), Matchers.equalTo("hello world")); } @Test public void packagedFormat() throws Exception { Path output = workspace.buildAndReturnOutput( "-c", "lua.package_style=standalone", "-c", "lua.packager=//:packager", "//:simple"); ImmutableMap<String, ImmutableMap<String, String>> components = ObjectMappers.readValue( output.toFile(), new TypeReference<ImmutableMap<String, ImmutableMap<String, String>>>() {}); assertThat(components.get("modules").keySet(), Matchers.equalTo(ImmutableSet.of("simple.lua"))); } @Test @SuppressWarnings("PMD.UseAssertEqualsInsteadOfAssertTrue") public void switchingBetweenPacakgedFormats() throws Exception { // Run an inital build using the standalone packaging style. String standaloneFirst = workspace.getFileContents( workspace.buildAndReturnOutput( "-c", "lua.package_style=standalone", "-c", "lua.packager=//:packager", "//:simple")); // Now rebuild with just changing to an in-place packaging style. String inplaceFirst = workspace.getFileContents( workspace.buildAndReturnOutput("-c", "lua.package_style=inplace", "//:simple")); // Now rebuild again, switching back to standalone, and verify the output matches the original // build's output. String standaloneSecond = workspace.getFileContents( workspace.buildAndReturnOutput( "-c", "lua.package_style=standalone", "-c", "lua.packager=//:packager", "//:simple")); assertTrue(standaloneFirst.equals(standaloneSecond)); // Now rebuild again, switching back to in-place, and verify the output matches the original // build's output. String inplaceSecond = workspace.getFileContents( workspace.buildAndReturnOutput("-c", "lua.package_style=inplace", "//:simple")); assertTrue(inplaceFirst.equals(inplaceSecond)); } @Test public void cxxLuaExtensionWithIncludeDirs() throws IOException { assumeTrue("", sandboxSources && starterType == LuaBinaryDescription.StarterType.NATIVE); workspace.runBuckBuild("//with_includes:native_with_extension").assertSuccess(); } @Test public void cxxLuaExtensionWithoutIncludeDirs() throws IOException { assumeTrue("", sandboxSources && starterType == LuaBinaryDescription.StarterType.NATIVE); workspace.replaceFileContents("with_includes/BUCK", "include_dirs", "#"); ProjectWorkspace.ProcessResult luaBinaryResult = workspace.runBuckBuild("//with_includes:native_with_extension"); luaBinaryResult.assertFailure(); assertThat(luaBinaryResult.getStderr(), containsString("extension.h")); } private LuaBuckConfig getLuaBuckConfig() throws InterruptedException, IOException { Config rawConfig = Configs.createDefaultConfig(tmp.getRoot()); BuckConfig buckConfig = new BuckConfig( rawConfig, new ProjectFilesystem(tmp.getRoot()), Architecture.detect(), Platform.detect(), ImmutableMap.of(), new DefaultCellPathResolver(tmp.getRoot(), rawConfig)); return new LuaBuckConfig(buckConfig, new FakeExecutableFinder(ImmutableList.of())); } }