From bbd714f8a64da52ee8bc8ee04edce61f86dde897 Mon Sep 17 00:00:00 2001 From: Marcus Bannerman Date: Tue, 13 Apr 2021 15:11:06 +0100 Subject: [PATCH] Added python testing ready for development --- .gitmodules | 3 + CMakeLists.txt | 20 +-- Jenkinsfile | 129 +++++++++++------- extern/pybind11 | 1 + pyproject.toml | 8 ++ pysrc/stator/__init__.py | 1 + pysrc/stator/core.cpp | 32 +++++ pytest.ini | 2 + setup.cfg | 2 + setup.py | 50 +++++++ {testing => tests}/geometry_shapes.cpp | 0 {testing => tests}/orphan_static_list.cpp | 0 {testing => tests}/stack_vector.cpp | 0 {testing => tests}/symbolic_ad.cpp | 0 {testing => tests}/symbolic_generic.cpp | 0 {testing => tests}/symbolic_integration.cpp | 0 {testing => tests}/symbolic_numeric.cpp | 0 {testing => tests}/symbolic_parser.cpp | 0 .../symbolic_poly_solve_roots.cpp | 0 {testing => tests}/symbolic_poly_taylor.cpp | 0 {testing => tests}/symbolic_polynomial.cpp | 0 {testing => tests}/symbolic_runtime.cpp | 0 tests/test_stator.py | 10 ++ 23 files changed, 201 insertions(+), 57 deletions(-) create mode 160000 extern/pybind11 create mode 100644 pyproject.toml create mode 100644 pysrc/stator/__init__.py create mode 100644 pysrc/stator/core.cpp create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 setup.py rename {testing => tests}/geometry_shapes.cpp (100%) rename {testing => tests}/orphan_static_list.cpp (100%) rename {testing => tests}/stack_vector.cpp (100%) rename {testing => tests}/symbolic_ad.cpp (100%) rename {testing => tests}/symbolic_generic.cpp (100%) rename {testing => tests}/symbolic_integration.cpp (100%) rename {testing => tests}/symbolic_numeric.cpp (100%) rename {testing => tests}/symbolic_parser.cpp (100%) rename {testing => tests}/symbolic_poly_solve_roots.cpp (100%) rename {testing => tests}/symbolic_poly_taylor.cpp (100%) rename {testing => tests}/symbolic_polynomial.cpp (100%) rename {testing => tests}/symbolic_runtime.cpp (100%) create mode 100644 tests/test_stator.py diff --git a/.gitmodules b/.gitmodules index 162ca85..1bb3984 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = extern/eigen url = https://gitlab.com/libeigen/eigen.git branch = branches/3.4 +[submodule "extern/pybind11"] + path = extern/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 887b0dc..ed1a9b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,14 @@ -cmake_minimum_required (VERSION 2.6 FATAL_ERROR) +cmake_minimum_required (VERSION 3.0) if (NOT CMAKE_BUILD_TYPE) message(STATUS "No build type selected, default to Release") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo" FORCE) endif() -project(stator) #Project name +project(stator VERSION 1.0) #Project name enable_testing() #Enable build of test executables and 'make test' command include(CTest) - -IF(WIN32) -ENDIF(WIN32) - ########## RELEASE MODE if(MSVC) #MSVC has crazy warnings for -Wall, we'll build up support to the higher warning levels @@ -32,7 +28,7 @@ if(MSVC) SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") SET(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /bigobj") SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj") -SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /bigobj") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /bigobj") else() add_compile_options(-Wall) endif() @@ -133,13 +129,12 @@ endif(DOXYGEN_FOUND) ######### TEST TARGETS ###################################################################### function(stator_test name) #Registers a unit-test - add_executable(${name} ${CMAKE_CURRENT_SOURCE_DIR}/testing/${name}.cpp) + add_executable(${name} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${name}.cpp) add_test(${name} ${name}) endfunction(stator_test) add_executable(symbolic_example ${CMAKE_CURRENT_SOURCE_DIR}/examples/symbolic_example.cpp) - #stator_test(orphan_static_list) stator_test(stack_vector) @@ -155,3 +150,10 @@ if(CXX11_TEMPLATE_TYPEDEFS) stator_test(symbolic_ad) #stator_test(symbolic_integration) endif() + +###################################################################### +########## PYTHON INTERFACE +###################################################################### +add_subdirectory(extern/pybind11) +pybind11_add_module(core pysrc/stator/core.cpp) +target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}) diff --git a/Jenkinsfile b/Jenkinsfile index bbb6e85..63938c9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ spec: containers: - name: dind image: docker:dind - args: ["--registry-mirror=https://registry.bansci.com"] + args: ["--registry-mirror","https://registry.bansci.com"] tty: true securityContext: privileged: true @@ -39,66 +39,99 @@ spec: } } } - stage('CMake configure') { - steps{ - container('dind') { - script { - dockerBuildImage.inside { - dir('build') { - sh "cmake .." + parallel { + stage('C++/CMake build and test') { + stage('CMake configure') { + steps{ + container('dind') { + script { + dockerBuildImage.inside { + dir('build') { + sh "cmake .." + } + } } } } } - } - } - stage('Make the C++ library') { - steps{ - container('dind') { - script { - dockerBuildImage.inside { - dir('build') { - sh "make -j2" + stage('Make the C++ library') { + steps{ + container('dind') { + script { + dockerBuildImage.inside { + dir('build') { + sh "make -j2" + } + } } } } } - } - } - stage('Test the C++ library') { - steps{ - container('dind') { - script { - dockerBuildImage.inside { - dir('build') { - sh 'ctest -j2 --no-compress-output -T Test' + stage('Test the C++ library') { + steps{ + container('dind') { + script { + dockerBuildImage.inside { + dir('build') { + sh 'ctest -j2 --no-compress-output -T Test' + } + } } } } + post { + always { + archiveArtifacts ( + artifacts: 'build/Testing/**/*.xml', + fingerprint: true, + ) + // Process the CTest xml output with the xUnit plugin + xunit ( + testTimeMargin: '3000', + thresholdMode: 1, + thresholds: [ + skipped(failureThreshold: '0'), + failed(failureThreshold: '0') + ], + tools: [CTest( + pattern: 'build/Testing/**/*.xml', + deleteOutputFiles: true, + failIfNotNew: false, + skipNoTestFiles: true, + stopProcessingIfError: true + )] + ) + } + } } } - post { - always { - archiveArtifacts ( - artifacts: 'build/Testing/**/*.xml', - fingerprint: true, - ) - // Process the CTest xml output with the xUnit plugin - xunit ( - testTimeMargin: '3000', - thresholdMode: 1, - thresholds: [ - skipped(failureThreshold: '0'), - failed(failureThreshold: '0') - ], - tools: [CTest( - pattern: 'build/Testing/**/*.xml', - deleteOutputFiles: true, - failIfNotNew: false, - skipNoTestFiles: true, - stopProcessingIfError: true - )] - ) + stage('Python build and test') { + stage('setuptools build') { + steps{ + container('dind') { + script { + dockerBuildImage.inside { + sh "python3 setup.py build" + } + } + } + } + } + stage('Unit Tests') { + steps { + container('dind') { + script { + dockerBuildImage.inside { + sh 'PYTHONUNBUFFERED=1 py.test --junitxml test_results.xml' + } + } + } + } + post { + always { + junit 'test_results.xml' + } + } } } } diff --git a/extern/pybind11 b/extern/pybind11 new file mode 160000 index 0000000..8de7772 --- /dev/null +++ b/extern/pybind11 @@ -0,0 +1 @@ +Subproject commit 8de7772cc72daca8e947b79b83fea46214931604 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fe3d51d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "pybind11>=2.6.0", +] + +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/pysrc/stator/__init__.py b/pysrc/stator/__init__.py new file mode 100644 index 0000000..00ae908 --- /dev/null +++ b/pysrc/stator/__init__.py @@ -0,0 +1 @@ +from stator.core import * diff --git a/pysrc/stator/core.cpp b/pysrc/stator/core.cpp new file mode 100644 index 0000000..002a6de --- /dev/null +++ b/pysrc/stator/core.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +PYBIND11_MODULE(core, m) +{ + py::class_(m, "Expr") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def("__repr__", +[](const sym::Expr& self) { return stator::repr(self); }) + .def("__str__", +[](const sym::Expr& self) { return stator::repr(self); }) + .def("latex", +[](const sym::Expr& self) { return stator::repr >(self); }) + .def("simplify", +[](const sym::Expr& self) { return sym::simplify(self); }) + .def("__add__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l+r); }) + .def("__radd__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l+r); }) + .def("__sub__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l-r); }) + .def("__rsub__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l-r); }) + .def("__mul__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l*r); }) + .def("__rmul__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l*r); }) + .def("__div__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l/r); }) + .def("__rdiv__", +[](const sym::Expr& l, const sym::Expr& r) { return sym::Expr(l/r); }) + .def(py::self / py::self) + ; + + py::implicitly_convertible(); + py::implicitly_convertible(); +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..40fdd88 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = extern build diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9af7e6f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ea176c3 --- /dev/null +++ b/setup.py @@ -0,0 +1,50 @@ +from setuptools import setup, find_packages +import sys,os + +# Track down the pybind11 directory, add it to path, import +# requirements, then remove it again. +DIR = os.path.abspath(os.path.dirname(__file__)) +sys.path.append(os.path.join(DIR, "extern", "pybind11")) +from pybind11.setup_helpers import Pybind11Extension, build_ext +from pybind11 import get_cmake_dir +del sys.path[-1] + + +__version__ = "0.0.1" + +# The main interface is through Pybind11Extension. +# * You can add cxx_std=11/14/17, and then build_ext can be removed. +# * You can set include_pybind11=false to add the include directory yourself, +# say from a submodule. +# +# Note: +# Sort input source files if you glob sources to ensure bit-for-bit +# reproducible builds (https://github.com/pybind/python_example/pull/53) + +ext_modules = [ + Pybind11Extension("stator.core", + ["pysrc/stator/core.cpp"], + # Example: passing in the version to the compiled code + define_macros = [('VERSION_INFO', __version__)], + include_dirs=['.', 'extern/eigen'] + ), +] + +setup( + name="stator", + version=__version__, + author="Marcus Bannerman", + author_email="m.bannerman@gmail.com", + url="https://github.com/toastedcrumpets/stator", + description="The python stator interface.", + long_description="", + packages=find_packages('pysrc'), + ext_modules=ext_modules, + extras_require={"test": "pytest"}, + # Currently, build_ext only provides an optional "highest supported C++ + # level" feature, but in the future it may provide more features. + cmdclass={"build_ext": build_ext}, + setup_requires=['pytest-runner'], + tests_require=['pytest'], + zip_safe=False, +) diff --git a/testing/geometry_shapes.cpp b/tests/geometry_shapes.cpp similarity index 100% rename from testing/geometry_shapes.cpp rename to tests/geometry_shapes.cpp diff --git a/testing/orphan_static_list.cpp b/tests/orphan_static_list.cpp similarity index 100% rename from testing/orphan_static_list.cpp rename to tests/orphan_static_list.cpp diff --git a/testing/stack_vector.cpp b/tests/stack_vector.cpp similarity index 100% rename from testing/stack_vector.cpp rename to tests/stack_vector.cpp diff --git a/testing/symbolic_ad.cpp b/tests/symbolic_ad.cpp similarity index 100% rename from testing/symbolic_ad.cpp rename to tests/symbolic_ad.cpp diff --git a/testing/symbolic_generic.cpp b/tests/symbolic_generic.cpp similarity index 100% rename from testing/symbolic_generic.cpp rename to tests/symbolic_generic.cpp diff --git a/testing/symbolic_integration.cpp b/tests/symbolic_integration.cpp similarity index 100% rename from testing/symbolic_integration.cpp rename to tests/symbolic_integration.cpp diff --git a/testing/symbolic_numeric.cpp b/tests/symbolic_numeric.cpp similarity index 100% rename from testing/symbolic_numeric.cpp rename to tests/symbolic_numeric.cpp diff --git a/testing/symbolic_parser.cpp b/tests/symbolic_parser.cpp similarity index 100% rename from testing/symbolic_parser.cpp rename to tests/symbolic_parser.cpp diff --git a/testing/symbolic_poly_solve_roots.cpp b/tests/symbolic_poly_solve_roots.cpp similarity index 100% rename from testing/symbolic_poly_solve_roots.cpp rename to tests/symbolic_poly_solve_roots.cpp diff --git a/testing/symbolic_poly_taylor.cpp b/tests/symbolic_poly_taylor.cpp similarity index 100% rename from testing/symbolic_poly_taylor.cpp rename to tests/symbolic_poly_taylor.cpp diff --git a/testing/symbolic_polynomial.cpp b/tests/symbolic_polynomial.cpp similarity index 100% rename from testing/symbolic_polynomial.cpp rename to tests/symbolic_polynomial.cpp diff --git a/testing/symbolic_runtime.cpp b/tests/symbolic_runtime.cpp similarity index 100% rename from testing/symbolic_runtime.cpp rename to tests/symbolic_runtime.cpp diff --git a/tests/test_stator.py b/tests/test_stator.py new file mode 100644 index 0000000..1c28efb --- /dev/null +++ b/tests/test_stator.py @@ -0,0 +1,10 @@ +import unittest +import stator +import stator.core + +class Testfit(unittest.TestCase): + def test_interface(self): + e = stator.core.Expr("1+1") + +if __name__ == "__main__": + unittest.main()