/*
* 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.junit.Assert.assertThat;
import com.facebook.buck.cli.FakeBuckConfig;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxLibraryBuilder;
import com.facebook.buck.cxx.CxxPlatformUtils;
import com.facebook.buck.cxx.CxxTestUtils;
import com.facebook.buck.cxx.NativeLinkStrategy;
import com.facebook.buck.cxx.PrebuiltCxxLibraryBuilder;
import com.facebook.buck.io.MorePaths;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.python.CxxPythonExtensionBuilder;
import com.facebook.buck.python.PythonBinaryDescription;
import com.facebook.buck.python.PythonEnvironment;
import com.facebook.buck.python.PythonLibraryBuilder;
import com.facebook.buck.python.PythonPlatform;
import com.facebook.buck.python.PythonVersion;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.CommandTool;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FakeSourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SourceWithFlags;
import com.facebook.buck.rules.SymlinkTree;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.coercer.PatternMatchedCollection;
import com.facebook.buck.rules.coercer.SourceList;
import com.facebook.buck.testutil.FakeProjectFilesystem;
import com.facebook.buck.testutil.TargetGraphFactory;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Pattern;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class LuaBinaryDescriptionTest {
private static final BuildTarget PYTHON2_DEP_TARGET =
BuildTargetFactory.newInstance("//:python2_dep");
private static final PythonPlatform PY2 =
PythonPlatform.of(
InternalFlavor.of("py2"),
new PythonEnvironment(Paths.get("python2"), PythonVersion.of("CPython", "2.6")),
Optional.of(PYTHON2_DEP_TARGET));
private static final BuildTarget PYTHON3_DEP_TARGET =
BuildTargetFactory.newInstance("//:python3_dep");
private static final PythonPlatform PY3 =
PythonPlatform.of(
InternalFlavor.of("py3"),
new PythonEnvironment(Paths.get("python3"), PythonVersion.of("CPython", "3.5")),
Optional.of(PYTHON3_DEP_TARGET));
@Rule public ExpectedException expectedException = ExpectedException.none();
@Test
public void mainModule() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
LuaBinary binary =
new LuaBinaryBuilder(BuildTargetFactory.newInstance("//:rule"))
.setMainModule("hello.world")
.build(resolver);
assertThat(binary.getMainModule(), Matchers.equalTo("hello.world"));
}
@Test
public void extensionOverride() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
LuaBinary binary =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:rule"),
FakeLuaConfig.DEFAULT.withExtension(".override"))
.setMainModule("main")
.build(resolver);
assertThat(
pathResolver.getRelativePath(binary.getSourcePathToOutput()).toString(),
Matchers.endsWith(".override"));
}
@Test
public void toolOverride() throws Exception {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
Tool override = new CommandTool.Builder().addArg("override").build();
LuaBinary binary =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:rule"),
FakeLuaConfig.DEFAULT.withLua(override).withExtension(".override"))
.setMainModule("main")
.build(resolver);
assertThat(binary.getLua(), Matchers.is(override));
}
@Test
public void versionLessNativeLibraryExtension() throws Exception {
CxxLibraryBuilder cxxLibraryBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:lib"))
.setSoname("libfoo.so.1.0")
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("hello.c"))));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:rule"),
FakeLuaConfig.DEFAULT.withPackageStyle(LuaConfig.PackageStyle.INPLACE))
.setMainModule("main")
.setDeps(ImmutableSortedSet.of(cxxLibraryBuilder.getTarget()));
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(cxxLibraryBuilder.build(), binaryBuilder.build()),
new DefaultTargetNodeToBuildRuleTransformer());
cxxLibraryBuilder.build(resolver);
binaryBuilder.build(resolver);
SymlinkTree tree =
resolver.getRuleWithType(
LuaBinaryDescription.getNativeLibsSymlinkTreeTarget(binaryBuilder.getTarget()),
SymlinkTree.class);
assertThat(
tree.getLinks().keySet(),
Matchers.hasItem(tree.getProjectFilesystem().getPath("libfoo.so")));
}
@Test
public void duplicateIdenticalModules() throws Exception {
LuaLibraryBuilder libraryABuilder =
new LuaLibraryBuilder(BuildTargetFactory.newInstance("//:a"))
.setSrcs(ImmutableSortedMap.of("foo.lua", new FakeSourcePath("test")));
LuaLibraryBuilder libraryBBuilder =
new LuaLibraryBuilder(BuildTargetFactory.newInstance("//:b"))
.setSrcs(ImmutableSortedMap.of("foo.lua", new FakeSourcePath("test")));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(BuildTargetFactory.newInstance("//:rule"))
.setMainModule("hello.world")
.setDeps(
ImmutableSortedSet.of(libraryABuilder.getTarget(), libraryBBuilder.getTarget()));
ProjectFilesystem filesystem = new FakeProjectFilesystem();
TargetGraph targetGraph =
TargetGraphFactory.newInstance(
libraryABuilder.build(), libraryBBuilder.build(), binaryBuilder.build());
BuildRuleResolver resolver =
new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer());
libraryABuilder.build(resolver, filesystem, targetGraph);
libraryBBuilder.build(resolver, filesystem, targetGraph);
binaryBuilder.build(resolver, filesystem, targetGraph);
}
@Test
public void duplicateConflictingModules() throws Exception {
LuaLibraryBuilder libraryABuilder =
new LuaLibraryBuilder(BuildTargetFactory.newInstance("//:a"))
.setSrcs(ImmutableSortedMap.of("foo.lua", new FakeSourcePath("foo")));
LuaLibraryBuilder libraryBBuilder =
new LuaLibraryBuilder(BuildTargetFactory.newInstance("//:b"))
.setSrcs(ImmutableSortedMap.of("foo.lua", new FakeSourcePath("bar")));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(BuildTargetFactory.newInstance("//:rule"))
.setMainModule("hello.world")
.setDeps(
ImmutableSortedSet.of(libraryABuilder.getTarget(), libraryBBuilder.getTarget()));
ProjectFilesystem filesystem = new FakeProjectFilesystem();
TargetGraph targetGraph =
TargetGraphFactory.newInstance(
libraryABuilder.build(), libraryBBuilder.build(), binaryBuilder.build());
BuildRuleResolver resolver =
new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer());
libraryABuilder.build(resolver, filesystem, targetGraph);
libraryBBuilder.build(resolver, filesystem, targetGraph);
expectedException.expect(HumanReadableException.class);
expectedException.expectMessage(Matchers.containsString("conflicting modules for foo.lua"));
binaryBuilder.build(resolver, filesystem, targetGraph);
}
@Test
public void pythonDeps() throws Exception {
PythonLibraryBuilder pythonLibraryBuilder =
new PythonLibraryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setSrcs(
SourceList.ofUnnamedSources(ImmutableSortedSet.of(new FakeSourcePath("foo.py"))));
LuaBinaryBuilder luaBinaryBuilder =
new LuaBinaryBuilder(BuildTargetFactory.newInstance("//:rule"))
.setMainModule("hello.world")
.setDeps(ImmutableSortedSet.of(pythonLibraryBuilder.getTarget()));
TargetGraph targetGraph =
TargetGraphFactory.newInstance(pythonLibraryBuilder.build(), luaBinaryBuilder.build());
ProjectFilesystem filesystem = new FakeProjectFilesystem();
BuildRuleResolver resolver =
new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer());
pythonLibraryBuilder.build(resolver, filesystem, targetGraph);
LuaBinary luaBinary = luaBinaryBuilder.build(resolver, filesystem, targetGraph);
assertThat(luaBinary.getComponents().getPythonModules().keySet(), Matchers.hasItem("foo.py"));
}
@Test
public void platformDeps() throws Exception {
FlavorDomain<PythonPlatform> pythonPlatforms = FlavorDomain.of("Python Platform", PY2, PY3);
CxxBuckConfig cxxBuckConfig = new CxxBuckConfig(FakeBuckConfig.builder().build());
CxxLibraryBuilder py2LibBuilder = new CxxLibraryBuilder(PYTHON2_DEP_TARGET);
CxxLibraryBuilder py3LibBuilder = new CxxLibraryBuilder(PYTHON3_DEP_TARGET);
CxxLibraryBuilder py2CxxLibraryBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:py2_library"))
.setSoname("libpy2.so")
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("hello.c"))));
CxxLibraryBuilder py3CxxLibraryBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:py3_library"))
.setSoname("libpy3.so")
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("hello.c"))));
CxxPythonExtensionBuilder cxxPythonExtensionBuilder =
new CxxPythonExtensionBuilder(
BuildTargetFactory.newInstance("//:extension"),
pythonPlatforms,
cxxBuckConfig,
CxxTestUtils.createDefaultPlatforms())
.setPlatformDeps(
PatternMatchedCollection.<ImmutableSortedSet<BuildTarget>>builder()
.add(
Pattern.compile(PY2.getFlavor().toString()),
ImmutableSortedSet.of(py2CxxLibraryBuilder.getTarget()))
.add(
Pattern.compile(PY3.getFlavor().toString()),
ImmutableSortedSet.of(py3CxxLibraryBuilder.getTarget()))
.build());
LuaBinaryBuilder luaBinaryBuilder =
new LuaBinaryBuilder(
new LuaBinaryDescription(
FakeLuaConfig.DEFAULT,
cxxBuckConfig,
CxxPlatformUtils.DEFAULT_PLATFORM,
CxxPlatformUtils.DEFAULT_PLATFORMS,
pythonPlatforms),
BuildTargetFactory.newInstance("//:binary"))
.setMainModule("main")
.setDeps(ImmutableSortedSet.of(cxxPythonExtensionBuilder.getTarget()));
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(
py2LibBuilder.build(),
py3LibBuilder.build(),
py2CxxLibraryBuilder.build(),
py3CxxLibraryBuilder.build(),
cxxPythonExtensionBuilder.build(),
luaBinaryBuilder.build()),
new DefaultTargetNodeToBuildRuleTransformer());
py2LibBuilder.build(resolver);
py3LibBuilder.build(resolver);
py2CxxLibraryBuilder.build(resolver);
py3CxxLibraryBuilder.build(resolver);
cxxPythonExtensionBuilder.build(resolver);
LuaBinary luaBinary = luaBinaryBuilder.build(resolver);
LuaPackageComponents components = luaBinary.getComponents();
assertThat(components.getNativeLibraries().keySet(), Matchers.hasItem("libpy2.so"));
assertThat(
components.getNativeLibraries().keySet(), Matchers.not(Matchers.hasItem("libpy3.so")));
}
@Test
public void pythonInitIsRuntimeDepForInPlaceBinary() throws Exception {
PythonLibraryBuilder pythonLibraryBuilder =
new PythonLibraryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setSrcs(
SourceList.ofUnnamedSources(ImmutableSortedSet.of(new FakeSourcePath("foo.py"))));
LuaBinaryBuilder luaBinaryBuilder =
new LuaBinaryBuilder(BuildTargetFactory.newInstance("//:rule"))
.setMainModule("hello.world")
.setPackageStyle(LuaConfig.PackageStyle.INPLACE)
.setDeps(ImmutableSortedSet.of(pythonLibraryBuilder.getTarget()));
TargetGraph targetGraph =
TargetGraphFactory.newInstance(pythonLibraryBuilder.build(), luaBinaryBuilder.build());
ProjectFilesystem filesystem = new FakeProjectFilesystem();
BuildRuleResolver resolver =
new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer());
pythonLibraryBuilder.build(resolver, filesystem, targetGraph);
LuaBinary luaBinary = luaBinaryBuilder.build(resolver, filesystem, targetGraph);
assertThat(
luaBinary.getRuntimeDeps().collect(MoreCollectors.toImmutableSet()),
Matchers.hasItem(PythonBinaryDescription.getEmptyInitTarget(luaBinary.getBuildTarget())));
}
@Test
public void transitiveNativeDepsUsingMergedNativeLinkStrategy() throws Exception {
CxxLibraryBuilder transitiveCxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep"))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c"))));
CxxLibraryBuilder cxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c"))))
.setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget()));
CxxLibraryBuilder cxxBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c"))))
.setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget()));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:bin"),
FakeLuaConfig.DEFAULT.withNativeLinkStrategy(NativeLinkStrategy.MERGED));
binaryBuilder.setMainModule("main");
binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget()));
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(
transitiveCxxDepBuilder.build(),
cxxDepBuilder.build(),
cxxBuilder.build(),
binaryBuilder.build()),
new DefaultTargetNodeToBuildRuleTransformer());
transitiveCxxDepBuilder.build(resolver);
cxxDepBuilder.build(resolver);
cxxBuilder.build(resolver);
LuaBinary binary = binaryBuilder.build(resolver);
assertThat(
Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString),
Matchers.containsInAnyOrder("libomnibus.so", "libcxx.so"));
}
@Test
public void transitiveNativeDepsUsingSeparateNativeLinkStrategy() throws Exception {
CxxLibraryBuilder transitiveCxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep"))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c"))));
CxxLibraryBuilder cxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c"))))
.setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget()));
CxxLibraryBuilder cxxBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c"))))
.setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget()));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:bin"),
FakeLuaConfig.DEFAULT.withNativeLinkStrategy(NativeLinkStrategy.SEPARATE));
binaryBuilder.setMainModule("main");
binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget()));
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(
transitiveCxxDepBuilder.build(),
cxxDepBuilder.build(),
cxxBuilder.build(),
binaryBuilder.build()),
new DefaultTargetNodeToBuildRuleTransformer());
transitiveCxxDepBuilder.build(resolver);
cxxDepBuilder.build(resolver);
cxxBuilder.build(resolver);
LuaBinary binary = binaryBuilder.build(resolver);
assertThat(
Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString),
Matchers.containsInAnyOrder("libtransitive_dep.so", "libdep.so", "libcxx.so"));
}
@Test
public void transitiveDepsOfNativeStarterDepsAreIncludedInMergedNativeLinkStrategy()
throws Exception {
CxxLibraryBuilder transitiveCxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:transitive_dep"))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("transitive_dep.c"))));
CxxLibraryBuilder cxxDepBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:dep"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("dep.c"))))
.setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget()));
CxxLibraryBuilder cxxBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:cxx"))
.setSrcs(ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("cxx.c"))))
.setDeps(ImmutableSortedSet.of(cxxDepBuilder.getTarget()));
CxxLibraryBuilder nativeStarterCxxBuilder =
new CxxLibraryBuilder(BuildTargetFactory.newInstance("//:native_starter"))
.setSrcs(
ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("native_starter.c"))))
.setDeps(ImmutableSortedSet.of(transitiveCxxDepBuilder.getTarget()));
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:bin"),
FakeLuaConfig.DEFAULT.withNativeLinkStrategy(NativeLinkStrategy.MERGED));
binaryBuilder.setMainModule("main");
binaryBuilder.setDeps(ImmutableSortedSet.of(cxxBuilder.getTarget()));
binaryBuilder.setNativeStarterLibrary(nativeStarterCxxBuilder.getTarget());
BuildRuleResolver resolver =
new BuildRuleResolver(
TargetGraphFactory.newInstance(
transitiveCxxDepBuilder.build(),
cxxDepBuilder.build(),
cxxBuilder.build(),
nativeStarterCxxBuilder.build(),
binaryBuilder.build()),
new DefaultTargetNodeToBuildRuleTransformer());
transitiveCxxDepBuilder.build(resolver);
cxxDepBuilder.build(resolver);
cxxBuilder.build(resolver);
nativeStarterCxxBuilder.build(resolver);
LuaBinary binary = binaryBuilder.build(resolver);
assertThat(
Iterables.transform(binary.getComponents().getNativeLibraries().keySet(), Object::toString),
Matchers.containsInAnyOrder("libomnibus.so", "libcxx.so"));
}
@Test
public void pythonExtensionDepUsingMergedNativeLinkStrategy() throws Exception {
FlavorDomain<PythonPlatform> pythonPlatforms = FlavorDomain.of("Python Platform", PY2);
PrebuiltCxxLibraryBuilder python2Builder =
new PrebuiltCxxLibraryBuilder(PYTHON2_DEP_TARGET)
.setProvided(true)
.setExportedLinkerFlags(ImmutableList.of("-lpython2"));
CxxPythonExtensionBuilder extensionBuilder =
new CxxPythonExtensionBuilder(
BuildTargetFactory.newInstance("//:extension"),
pythonPlatforms,
new CxxBuckConfig(FakeBuckConfig.builder().build()),
CxxPlatformUtils.DEFAULT_PLATFORMS);
extensionBuilder.setBaseModule("hello");
LuaBinaryBuilder binaryBuilder =
new LuaBinaryBuilder(
BuildTargetFactory.newInstance("//:bin"),
FakeLuaConfig.DEFAULT.withNativeLinkStrategy(NativeLinkStrategy.MERGED),
CxxPlatformUtils.DEFAULT_CONFIG,
CxxPlatformUtils.DEFAULT_PLATFORM,
CxxPlatformUtils.DEFAULT_PLATFORMS,
pythonPlatforms);
binaryBuilder.setMainModule("main");
binaryBuilder.setDeps(ImmutableSortedSet.of(extensionBuilder.getTarget()));
TargetGraph targetGraph =
TargetGraphFactory.newInstance(
python2Builder.build(), extensionBuilder.build(), binaryBuilder.build());
ProjectFilesystem filesystem = new FakeProjectFilesystem();
BuildRuleResolver resolver =
new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer());
python2Builder.build(resolver, filesystem, targetGraph);
extensionBuilder.build(resolver, filesystem, targetGraph);
LuaBinary binary = binaryBuilder.build(resolver, filesystem, targetGraph);
assertThat(binary.getComponents().getNativeLibraries().entrySet(), Matchers.empty());
assertThat(
Iterables.transform(binary.getComponents().getPythonModules().keySet(), Object::toString),
Matchers.hasItem(MorePaths.pathWithPlatformSeparators("hello/extension.so")));
}
}