Skip to content

Commit 936ca44

Browse files
committed
Proof of concept: pytest
1 parent 5289af5 commit 936ca44

13 files changed

Lines changed: 784 additions & 11 deletions

.appveyor.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ platform:
55
- x86
66
- x64
77
environment:
8-
CTEST_OUTPUT_ON_FAILURE: 1
98
matrix:
109
- CONDA: 27
1110
- CONDA: 35
@@ -16,12 +15,12 @@ install:
1615
if ($env:PLATFORM -eq "x64") { $env:PYTHON = "$env:PYTHON-x64" }
1716
$env:PATH = "C:\Python$env:PYTHON\;C:\Python$env:PYTHON\Scripts\;$env:PATH"
1817
pip install --disable-pip-version-check --user --upgrade pip wheel
19-
pip install numpy scipy
18+
pip install pytest numpy scipy
2019
} elseif ($env:CONDA) {
2120
if ($env:CONDA -eq "27") { $env:CONDA = "" }
2221
if ($env:PLATFORM -eq "x64") { $env:CONDA = "$env:CONDA-x64" }
2322
$env:PATH = "C:\Miniconda$env:CONDA\;C:\Miniconda$env:CONDA\Scripts\;$env:PATH"
24-
conda install -y -q numpy scipy
23+
conda install -y -q pytest numpy scipy
2524
}
2625
- ps: |
2726
Start-FileDownload 'http://bitbucket.org/eigen/eigen/get/3.2.9.zip'
@@ -30,4 +29,4 @@ install:
3029
build_script:
3130
- cmake -A "%CMAKE_ARCH%"
3231
- set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
33-
- cmake --build . --config Release --target check -- /v:m /logger:%MSBuildLogger%
32+
- cmake --build . --config Release --target pytest -- /v:m /logger:%MSBuildLogger%

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ CMakeFiles
33
Makefile
44
cmake_install.cmake
55
.DS_Store
6-
/example/example.so
7-
/example/example.cpython*.so
8-
/example/example.pyd
9-
/example/example*.dll
6+
*.so
7+
*.pyd
8+
*.dll
109
*.sln
1110
*.sdf
1211
*.opensdf
@@ -31,3 +30,4 @@ MANIFEST
3130
.DS_Store
3231
/dist
3332
/build
33+
.cache/

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ install:
4646
python$PMAJOR -m virtualenv venv
4747
fi
4848
source venv/bin/activate
49-
pip install numpy scipy
49+
pip install pytest numpy scipy
5050
- |
5151
wget -q -O eigen.tar.gz https://bitbucket.org/eigen/eigen/get/3.2.9.tar.gz
5252
tar xzf eigen.tar.gz
5353
export CMAKE_INCLUDE_PATH=eigen-eigen-dc6cfdf9bcec
5454
script:
5555
- cmake -DPYBIND11_PYTHON_VERSION=$PYTHON -DPYBIND11_CPP_STANDARD=-std=c++$CPP
56-
- CTEST_OUTPUT_ON_FAILURE=TRUE make check -j 2
56+
- make pytest -j 2

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ if (PYBIND11_TEST)
148148
enable_testing()
149149
add_subdirectory(example)
150150
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION> DEPENDS example)
151+
add_subdirectory(tests)
152+
add_custom_target(pytest COMMAND ${PYTHON_EXECUTABLE} -m pytest
153+
DEPENDS pybind11_tests WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests)
151154
endif()
152155

153156
if (PYBIND11_INSTALL)

example/example-buffers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Matrix {
3131
}
3232

3333
~Matrix() {
34-
std::cout << "Freeing a " << m_rows << "x" << m_cols << " matrix " << std::endl;
34+
std::cout << "Freeing a " << m_rows << "x" << m_cols << " matrix" << std::endl;
3535
delete[] m_data;
3636
}
3737

tests/CMakeLists.txt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Set a default build configuration if none is specified. 'MinSizeRel' produces the smallest binaries
2+
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
3+
message(STATUS "Setting build type to 'MinSizeRel' as none was specified.")
4+
set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING "Choose the type of build." FORCE)
5+
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
6+
"MinSizeRel" "RelWithDebInfo")
7+
endif()
8+
9+
set(PYBIND11_EXAMPLES
10+
../example/example-methods-and-attributes.cpp
11+
../example/example-python-types.cpp
12+
../example/example-operator-overloading.cpp
13+
../example/example-constants-and-functions.cpp
14+
../example/example-callbacks.cpp
15+
../example/example-sequences-and-iterators.cpp
16+
../example/example-buffers.cpp
17+
../example/example-smart-ptr.cpp
18+
../example/example-modules.cpp
19+
../example/example-numpy-vectorize.cpp
20+
../example/example-arg-keywords-and-defaults.cpp
21+
../example/example-virtual-functions.cpp
22+
../example/example-keep-alive.cpp
23+
../example/example-opaque-types.cpp
24+
../example/example-pickling.cpp
25+
../example/example-inheritance.cpp
26+
../example/example-stl-binder-vector.cpp
27+
../example/example-eval.cpp
28+
../example/example-custom-exceptions.cpp
29+
../example/issues.cpp
30+
)
31+
32+
# Check if Eigen is available
33+
find_package(Eigen3 QUIET)
34+
35+
if(EIGEN3_FOUND)
36+
list(APPEND PYBIND11_EXAMPLES ../example/eigen.cpp)
37+
message(STATUS "Building Eigen v${EIGEN3_VERSION} testcase")
38+
else()
39+
message(STATUS "NOT Building Eigen testcase")
40+
endif()
41+
42+
# Create the binding library
43+
pybind11_add_module(pybind11_tests pybind11_tests.cpp ${PYBIND11_EXAMPLES})
44+
pybind11_enable_warnings(pybind11_tests)
45+
46+
if(EIGEN3_FOUND)
47+
target_include_directories(pybind11_tests PRIVATE ${EIGEN3_INCLUDE_DIR})
48+
target_compile_definitions(pybind11_tests PRIVATE -DPYBIND11_TEST_EIGEN)
49+
endif()
50+
51+
# Always write the output file directly into the 'example' directory (even on MSVC)
52+
set(CompilerFlags
53+
LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_RELEASE LIBRARY_OUTPUT_DIRECTORY_DEBUG
54+
LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO
55+
RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_RELEASE RUNTIME_OUTPUT_DIRECTORY_DEBUG
56+
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO)
57+
58+
foreach(CompilerFlag ${CompilerFlags})
59+
set_target_properties(pybind11_tests PROPERTIES ${CompilerFlag} ${PROJECT_SOURCE_DIR}/tests)
60+
endforeach()
61+
62+
if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel")
63+
target_compile_definitions(pybind11_tests PRIVATE -DPYBIND11_TEST_RELAXED)
64+
endif()

tests/conftest.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import pytest
2+
import textwrap
3+
import difflib
4+
5+
6+
def _strip_and_dedent(s):
7+
"""For triple-quote strings"""
8+
return textwrap.dedent(s.lstrip('\n').rstrip())
9+
10+
11+
def _split_and_sort(s):
12+
"""For output which does not require specific line order"""
13+
return sorted(_strip_and_dedent(s).splitlines())
14+
15+
16+
def _make_explanation(a, b):
17+
"""The arguments are List[str]"""
18+
return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
19+
20+
21+
class Output(object):
22+
"""Basic output and comparison"""
23+
def __init__(self, string):
24+
self.string = string
25+
self.explanation = []
26+
27+
def __str__(self):
28+
return self.string
29+
30+
def __eq__(self, other):
31+
a = self.string.strip()
32+
b = _strip_and_dedent(other)
33+
if a == b:
34+
return True
35+
else:
36+
self.explanation = _make_explanation(a.splitlines(), b.splitlines())
37+
return False
38+
39+
40+
class Unordered(Output):
41+
"""Custom comparison for output without strict line ordering"""
42+
def __eq__(self, other):
43+
a = _split_and_sort(self.string)
44+
b = _split_and_sort(other)
45+
if a == b:
46+
return True
47+
else:
48+
self.explanation = _make_explanation(a, b)
49+
return False
50+
51+
52+
class Relaxed(Unordered):
53+
"""Like Unordered, but may also be ignored completely for some compilers"""
54+
try:
55+
# noinspection PyUnresolvedReferences
56+
from pybind11_tests import relaxed
57+
is_relaxed = True
58+
except ImportError:
59+
is_relaxed = False
60+
61+
def __eq__(self, other):
62+
if self.is_relaxed:
63+
return True
64+
else:
65+
return super(self.__class__, self).__eq__(other)
66+
67+
68+
class Capture(object):
69+
def __init__(self, capfd):
70+
self.capfd = capfd
71+
self.out = ""
72+
self.err = ""
73+
74+
def __enter__(self):
75+
self.capfd.readouterr()
76+
return self
77+
78+
def __exit__(self, *_):
79+
self.out, self.err = self.capfd.readouterr()
80+
81+
@property
82+
def output(self):
83+
return Output(self.out)
84+
85+
@property
86+
def unordered(self):
87+
return Unordered(self.out)
88+
89+
@property
90+
def relaxed(self):
91+
return Relaxed(self.out)
92+
93+
94+
@pytest.fixture
95+
def capture(capfd):
96+
"""Extended `capfd` with context manager and custom equality operators"""
97+
return Capture(capfd)
98+
99+
100+
def _sanitize_docstring(s):
101+
s = s.strip()
102+
s = s.replace("unicode", "str")
103+
s = s.replace("pybind11_tests.", "m.")
104+
return s
105+
106+
107+
class Doc(object):
108+
def __init__(self, thing):
109+
self.doc = _sanitize_docstring(thing.__doc__)
110+
self.explanation = []
111+
112+
def __eq__(self, other):
113+
a = self.doc
114+
b = _strip_and_dedent(other)
115+
if a == b:
116+
return True
117+
else:
118+
self.explanation = _make_explanation(a.splitlines(), b.splitlines())
119+
return False
120+
121+
122+
@pytest.fixture
123+
def doc():
124+
"""Sanitize docstrings and add custom failure explanation"""
125+
return Doc
126+
127+
128+
# noinspection PyUnusedLocal
129+
def pytest_assertrepr_compare(op, left, right):
130+
"""Hook to insert custom failure explanation"""
131+
if hasattr(left, 'explanation'):
132+
return left.explanation

tests/pybind11_tests.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
example/example.cpp -- pybind example plugin
3+
4+
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
10+
#include "../example/example.h"
11+
12+
void init_ex_methods_and_attributes(py::module &);
13+
void init_ex_python_types(py::module &);
14+
void init_ex_operator_overloading(py::module &);
15+
void init_ex_constants_and_functions(py::module &);
16+
void init_ex_callbacks(py::module &);
17+
void init_ex_sequences_and_iterators(py::module &);
18+
void init_ex_buffers(py::module &);
19+
void init_ex_smart_ptr(py::module &);
20+
void init_ex_modules(py::module &);
21+
void init_ex_numpy_vectorize(py::module &);
22+
void init_ex_arg_keywords_and_defaults(py::module &);
23+
void init_ex_virtual_functions(py::module &);
24+
void init_ex_keep_alive(py::module &);
25+
void init_ex_opaque_types(py::module &);
26+
void init_ex_pickling(py::module &);
27+
void init_ex_inheritance(py::module &);
28+
void init_ex_stl_binder_vector(py::module &);
29+
void init_ex_eval(py::module &);
30+
void init_ex_custom_exceptions(py::module &);
31+
void init_issues(py::module &);
32+
33+
#if defined(PYBIND11_TEST_EIGEN)
34+
void init_eigen(py::module &);
35+
#endif
36+
37+
PYBIND11_PLUGIN(pybind11_tests) {
38+
py::module m("pybind11_tests", "pybind example plugin");
39+
40+
init_ex_methods_and_attributes(m);
41+
init_ex_python_types(m);
42+
init_ex_operator_overloading(m);
43+
init_ex_constants_and_functions(m);
44+
init_ex_callbacks(m);
45+
init_ex_sequences_and_iterators(m);
46+
init_ex_buffers(m);
47+
init_ex_smart_ptr(m);
48+
init_ex_modules(m);
49+
init_ex_numpy_vectorize(m);
50+
init_ex_arg_keywords_and_defaults(m);
51+
init_ex_virtual_functions(m);
52+
init_ex_keep_alive(m);
53+
init_ex_opaque_types(m);
54+
init_ex_pickling(m);
55+
init_ex_inheritance(m);
56+
init_ex_stl_binder_vector(m);
57+
init_ex_eval(m);
58+
init_ex_custom_exceptions(m);
59+
init_issues(m);
60+
61+
#if defined(PYBIND11_TEST_EIGEN)
62+
init_eigen(m);
63+
#endif
64+
65+
#if defined(PYBIND11_TEST_RELAXED)
66+
m.attr("relaxed") = py::cast(true);
67+
#endif
68+
69+
return m.ptr();
70+
}

tests/test_buffers.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pytest
2+
try:
3+
import numpy as np
4+
except ImportError:
5+
np = None
6+
7+
from pybind11_tests import Matrix
8+
9+
10+
@pytest.mark.skipif(not np, reason="numpy is not installed")
11+
def test_buffers(capture):
12+
with capture:
13+
m = Matrix(5, 5)
14+
assert capture.relaxed == "Value constructor: Creating a 5x5 matrix"
15+
16+
assert m[2, 3] == 0
17+
m[2, 3] = 4
18+
assert m[2, 3] == 4
19+
20+
m2 = np.array(m, copy=False)
21+
assert m2.shape == (5, 5)
22+
assert m2.sum() == 4
23+
assert m2[2, 3] == 4
24+
m2[2, 3] = 5
25+
assert m2[2, 3] == 5
26+
27+
m3 = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32)
28+
with capture:
29+
m4 = Matrix(m3)
30+
assert capture.relaxed == "Value constructor: Creating a 2x3 matrix"
31+
32+
for i in range(m4.rows()):
33+
for j in range(m4.cols()):
34+
assert m3[i, j] == m4[i, j]
35+
36+
with capture:
37+
del m, m2, m3, m4
38+
assert capture.relaxed == """
39+
Freeing a 2x3 matrix
40+
Freeing a 5x5 matrix
41+
"""

0 commit comments

Comments
 (0)