Skip to content

Commit

Permalink
Add basic native task unit tests.
Browse files Browse the repository at this point in the history
Running a `./pants lint` across the whole repo revealed an issue
accessing a non-existant `self.linker.platform` attribute in
`LinkSharedLibraries`. Add basic unit tests to exercise task execution
with a full cache miss and then a full hit.
  • Loading branch information
jsirois committed Jul 25, 2018
1 parent 9e30b20 commit 28c24d9
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 13 deletions.
21 changes: 12 additions & 9 deletions src/python/pants/backend/native/tasks/link_shared_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def product_types(cls):

@classmethod
def prepare(cls, options, round_manager):
super(LinkSharedLibraries, cls).prepare(options, round_manager)
round_manager.require(NativeTargetDependencies)
round_manager.require(ObjectFiles)
round_manager.require(NativeExternalLibraryFetch.NativeExternalLibraryFiles)
Expand Down Expand Up @@ -70,6 +71,11 @@ def _cpp_toolchain(self):
def linker(self):
return self._cpp_toolchain.cpp_linker

@memoized_property
def platform(self):
# FIXME: convert this to a v2 engine dependency injection.
return Platform.create()

def _retrieve_single_product_at_target_base(self, product_mapping, target):
self.context.log.debug("product_mapping: {}".format(product_mapping))
self.context.log.debug("target: {}".format(target))
Expand All @@ -87,14 +93,11 @@ def execute(self):

all_shared_libs_by_name = {}

# FIXME: convert this to a v2 engine dependency injection.
platform = Platform.create()

with self.invalidated(targets_providing_artifacts,
invalidate_dependents=True) as invalidation_check:
for vt in invalidation_check.all_vts:
if vt.valid:
shared_library = self._retrieve_shared_lib_from_cache(vt, platform)
shared_library = self._retrieve_shared_lib_from_cache(vt)
else:
# FIXME: We need to partition links based on proper dependency edges and not
# perform a link to every native_external_library for all targets in the closure.
Expand All @@ -117,10 +120,10 @@ def execute(self):

shared_libs_product.add(vt.target, vt.target.target_base).append(shared_library)

def _retrieve_shared_lib_from_cache(self, vt, platform):
def _retrieve_shared_lib_from_cache(self, vt):
native_artifact = vt.target.ctypes_native_library
path_to_cached_lib = os.path.join(
vt.results_dir, native_artifact.as_shared_lib(platform))
vt.results_dir, native_artifact.as_shared_lib(self.platform))
if not os.path.isfile(path_to_cached_lib):
raise self.LinkSharedLibrariesError("The shared library at {} does not exist!"
.format(path_to_cached_lib))
Expand Down Expand Up @@ -164,14 +167,14 @@ def _execute_link_request(self, link_request):
raise self.LinkSharedLibrariesError("No object files were provided in request {}!"
.format(link_request))

platform = Platform.create()
linker = link_request.linker
native_artifact = link_request.native_artifact
output_dir = link_request.output_dir
resulting_shared_lib_path = os.path.join(output_dir, native_artifact.as_shared_lib(platform))
resulting_shared_lib_path = os.path.join(output_dir,
native_artifact.as_shared_lib(self.platform))
# We are executing in the results_dir, so get absolute paths for everything.
cmd = ([linker.exe_filename] +
platform.resolve_platform_specific(self._SHARED_CMDLINE_ARGS) +
self.platform.resolve_platform_specific(self._SHARED_CMDLINE_ARGS) +
linker.extra_args +
link_request.external_libs_info.get_third_party_lib_args() +
['-o', os.path.abspath(resulting_shared_lib_path)] +
Expand Down
10 changes: 8 additions & 2 deletions src/python/pants/backend/native/tasks/native_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pants.backend.native.targets.native_library import NativeLibrary
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants.backend.native.tasks.native_task import NativeTask
from pants.base.build_environment import get_buildroot
from pants.base.exceptions import TaskError
from pants.base.workunit import WorkUnit, WorkUnitLabel
from pants.build_graph.dependency_context import DependencyContext
Expand Down Expand Up @@ -60,6 +61,11 @@ class NativeCompile(NativeTask, AbstractClass):
def product_types(cls):
return [ObjectFiles, NativeTargetDependencies]

@classmethod
def prepare(cls, options, round_manager):
super(NativeCompile, cls).prepare(options, round_manager)
round_manager.require(NativeExternalLibraryFetch.NativeExternalLibraryFiles)

@property
def cache_target_dirs(self):
return True
Expand Down Expand Up @@ -138,7 +144,7 @@ def execute(self):
# This may be calculated many times for a target, so we memoize it.
@memoized_method
def _include_dirs_for_target(self, target):
return target.sources_relative_to_target_base().rel_root
return os.path.join(get_buildroot(), target.target_base)

class NativeSourcesByType(datatype(['rel_root', 'headers', 'sources'])): pass

Expand Down Expand Up @@ -172,7 +178,7 @@ def get_sources_headers_for_target(self, target):
"Conflicting filenames:\n{}"
.format(target.address.spec, target.alias(), '\n'.join(duplicate_filename_err_msgs)))

return [os.path.join(rel_root, src) for src in target_relative_sources]
return [os.path.join(get_buildroot(), rel_root, src) for src in target_relative_sources]

# FIXME(#5951): expand `Executable` to cover argv generation (where an `Executable` is subclassed
# to modify or extend the argument list, as declaratively as possible) to remove
Expand Down
18 changes: 18 additions & 0 deletions tests/python/pants_test/backend/native/tasks/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
python_tests(
dependencies=[
':native_task_test_base',
'src/python/pants/backend/native/targets',
'src/python/pants/backend/native/tasks',
],
tags={'platform_specific_behavior'},
)

python_library(
name='native_task_test_base',
sources=['native_task_test_base.py'],
dependencies=[
'src/python/pants/backend/native',
'src/python/pants/backend/native/targets',
'tests/python/pants_test:task_test_base',
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

from textwrap import dedent

from pants.backend.native import register
from pants.backend.native.targets.native_library import CppLibrary
from pants_test.task_test_base import TaskTestBase


class NativeTaskTestBase(TaskTestBase):

@classmethod
def rules(cls):
return super(NativeTaskTestBase, cls).rules() + register.rules()

def create_simple_cpp_library(self, **kwargs):
self.create_file('src/cpp/test/test.hpp', contents=dedent("""
#ifndef __TEST_HPP__
#define __TEST_HPP__
int test(int);
extern "C" int test_exported(int);
#endif
"""))
self.create_file('src/cpp/test/test.cpp', contents=dedent("""
#include "test.hpp"
int test(int x) {
return x / 137;
}
extern "C" int test_exported(int x) {
return test(x * 42);
}
"""))
return self.make_target(spec='src/cpp/test',
target_type=CppLibrary,
sources=['test.hpp', 'test.cpp'],
**kwargs)
57 changes: 57 additions & 0 deletions tests/python/pants_test/backend/native/tasks/test_c_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

import os
from textwrap import dedent

from pants.backend.native.targets.native_library import CLibrary
from pants.backend.native.tasks.c_compile import CCompile
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants_test.backend.native.tasks.native_task_test_base import NativeTaskTestBase


class CCompileTest(NativeTaskTestBase):
@classmethod
def task_type(cls):
return CCompile

def create_simple_c_library(self, **kwargs):
self.create_file('src/c/test/test.h', contents=dedent("""
#ifndef __TEST_H__
#define __TEST_H__
int test(int);
#endif
"""))
self.create_file('src/c/test/test.c', contents=dedent("""
#include "test.h"
int test(int x) {
return x / 137;
}
"""))
return self.make_target(spec='src/c/test',
target_type=CLibrary,
sources=['test.h', 'test.c'],
**kwargs)

def test_caching(self):
c = self.create_simple_c_library()

native_elf_fetch_task_type = self.synthesize_task_subtype(NativeExternalLibraryFetch,
'native_elf_fetch_scope')

context = self.context(target_roots=[c], for_task_types=[native_elf_fetch_task_type])

native_elf_fetch = native_elf_fetch_task_type(context,
os.path.join(self.pants_workdir,
'native_elf_fetch'))
native_elf_fetch.execute()

c_compile = self.create_task(context)
c_compile.execute()
c_compile.execute()
34 changes: 34 additions & 0 deletions tests/python/pants_test/backend/native/tasks/test_cpp_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

import os

from pants.backend.native.tasks.cpp_compile import CppCompile
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants_test.backend.native.tasks.native_task_test_base import NativeTaskTestBase


class CppCompileTest(NativeTaskTestBase):
@classmethod
def task_type(cls):
return CppCompile

def test_caching(self):
cpp = self.create_simple_cpp_library()

native_elf_fetch_task_type = self.synthesize_task_subtype(NativeExternalLibraryFetch,
'native_elf_fetch_scope')

context = self.context(target_roots=[cpp], for_task_types=[native_elf_fetch_task_type])

native_elf_fetch = native_elf_fetch_task_type(context,
os.path.join(self.pants_workdir,
'native_elf_fetch'))
native_elf_fetch.execute()

cpp_compile = self.create_task(context)
cpp_compile.execute()
cpp_compile.execute()
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

import os

from pants.backend.native.targets.native_artifact import NativeArtifact
from pants.backend.native.tasks.cpp_compile import CppCompile
from pants.backend.native.tasks.link_shared_libraries import LinkSharedLibraries
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants_test.backend.native.tasks.native_task_test_base import NativeTaskTestBase


class LinkSharedLibrariesTest(NativeTaskTestBase):
@classmethod
def task_type(cls):
return LinkSharedLibraries

def test_caching(self):
cpp = self. create_simple_cpp_library(ctypes_native_library=NativeArtifact(lib_name='test'),)

native_elf_fetch_task_type = self.synthesize_task_subtype(NativeExternalLibraryFetch,
'native_elf_fetch_scope')
cpp_compile_task_type = self.synthesize_task_subtype(CppCompile, 'cpp_compile_scope')
context = self.context(target_roots=[cpp],
for_task_types=[native_elf_fetch_task_type, cpp_compile_task_type])

native_elf_fetch = native_elf_fetch_task_type(context,
os.path.join(self.pants_workdir,
'native_elf_fetch'))
native_elf_fetch.execute()

cpp_compile = cpp_compile_task_type(context, os.path.join(self.pants_workdir, 'cpp_compile'))
cpp_compile.execute()

link_shared_libraries = self.create_task(context)
link_shared_libraries.execute()
link_shared_libraries.execute()
9 changes: 7 additions & 2 deletions tests/python/pants_test/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,16 @@ def alias_groups(cls):
"""
return BuildFileAliases(targets={'target': Target})

@classmethod
def rules(cls):
# Required for sources_for:
return [RootRule(SourcesField)]

@classmethod
def build_config(cls):
build_config = BuildConfiguration()
build_config.register_aliases(cls.alias_groups())
build_config.register_rules(cls.rules())
return build_config

def setUp(self):
Expand Down Expand Up @@ -369,8 +375,7 @@ def _init_engine(cls):
native=init_native(),
build_configuration=cls.build_config(),
build_ignore_patterns=None,
# Required for sources_for:
rules=[RootRule(SourcesField)],
rules=cls.build_config().rules(),
).new_session()
cls._scheduler = graph_session.scheduler_session
cls._build_graph, cls._address_mapper = graph_session.create_build_graph(
Expand Down

0 comments on commit 28c24d9

Please sign in to comment.