-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathtests.cpp
196 lines (162 loc) · 5.91 KB
/
tests.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <iostream>
#include <clocale>
#include <thread>
#include <cppy3/cppy3.hpp>
#if CPPY3_BUILT_WITH_NUMPY
#include <cppy3/cppy3_numpy.hpp>
#endif
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#ifndef TEST_UNICODE_CONVERTERS
#define TEST_UNICODE_CONVERTERS 1
#endif
TEST_CASE( "Utils", "" ) {
#if TEST_UNICODE_CONVERTERS
SECTION( "unicode converters" ) {
const std::string utf8Str("зачем вы посетили нас в глуши забытого селенья");
const std::wstring unicodeStr(L"зачем вы посетили нас в глуши забытого селенья");
#if 1
constexpr char locale_name[] = "en_US.UTF-8";
setlocale( LC_ALL, locale_name );
std::locale::global(std::locale(locale_name));
std::wcin.imbue(std::locale());
std::wcout.imbue(std::locale());
std::ios_base::sync_with_stdio(false);
#else
#include <codecvt>
std::ios_base::sync_with_stdio(false);
std::locale utf8(std::locale(), new std::codecvt_utf8<wchar_t>);
std::wcout.imbue(utf8);
#endif
std::cout << utf8Str << std::endl;
std::cout << "Unicode->UTF8:" << cppy3::WideToUTF8(unicodeStr) << std::endl;
std::wcout << unicodeStr << std::endl;
std::wcout << "UTF8->Unicode:" << cppy3::UTF8ToWide(utf8Str) << std::endl;
REQUIRE(cppy3::WideToUTF8(unicodeStr) == utf8Str);
REQUIRE(cppy3::UTF8ToWide(utf8Str) == unicodeStr);
}
#endif
}
TEST_CASE( "cppy3: Embedding Python into C++ code", "main funcs" ) {
// create interpreter
cppy3::PythonVM instance;
SECTION("c++ -> python -> c++ variables injection/extraction") {
// inject C++ -> python
cppy3::Main().injectVar<int>("a", 2);
cppy3::Main().injectVar<int>("b", 2);
cppy3::exec("assert a + b == 4");
cppy3::exec("print('sum is', a + b)");
// extract python -> C++
const cppy3::Var sum = cppy3::eval("a + b");
REQUIRE(sum.type() == cppy3::Var::LONG);
REQUIRE(sum.toLong() == 4);
REQUIRE(sum.toString() == L"4");
REQUIRE(!cppy3::error());
try {
// extract casting integer as double is forbidden
sum.toDouble();
REQUIRE(false); // unreachable code, expect an exception
} catch (const cppy3::PythonException& e) {
REQUIRE(e.info.reason == L"variable is not a real type");
}
// python var assign name from c++ -> python
cppy3::Main().inject("sum_var", sum);
cppy3::exec("assert sum_var == 4");
// cast to float in python
cppy3::eval("sum_var = float(sum_var)");
// can extract in c++ as double
double sum_var = 0;
cppy3::Main().getVar<double>("sum_var", sum_var);
REQUIRE(abs(sum_var - 4.0) < 10e-10);
// unicode strings inject / extract via exec / eval
const std::wstring unicodeStr = L"юникод smile ☺";
cppy3::exec(L"uu = '" + unicodeStr + L"'");
const cppy3::Var uVar = cppy3::eval("uu");
REQUIRE(uVar.toString() == unicodeStr);
// unicode string inject / extract via converters
cppy3::Main().injectVar<std::wstring>("uVar2", unicodeStr);
cppy3::exec("print('uVar2:', uVar2)");
std::wstring uVar2;
cppy3::Main().getVar<std::wstring>("uVar2", uVar2);
REQUIRE(uVar2 == unicodeStr);
}
SECTION("python -> c++ exception forwarding") {
try {
// throw excepton in python
cppy3::exec("raise Exception('test-exception')");
REQUIRE( false ); // unreachable code
} catch (const cppy3::PythonException& e) {
// catch in c++
REQUIRE(e.info.type == L"<class 'Exception'>");
REQUIRE(e.info.reason == L"test-exception");
REQUIRE(e.info.trace.size() > 0);
REQUIRE(std::string(e.what()).size() > 0);
}
// exception has been poped from python layer
REQUIRE(!cppy3::error());
}
#if CPPY3_BUILT_WITH_NUMPY
SECTION("numpy ndarray support") {
cppy3::importNumpy();
cppy3::exec("import numpy");
cppy3::exec("print('numpy version {}'.format(numpy.version.full_version))");
// create numpy ndarray in C
double cData[2] = {3.14, 42};
// create copy
cppy3::NDArray<double> a(cData, 2, 1);
// wrap cData without copying
cppy3::NDArray<double> b;
b.wrap(cData, 2, 1);
REQUIRE(a(1, 0) == cData[1]);
REQUIRE(b(1, 0) == cData[1]);
// inject into python __main__ namespace
cppy3::Main().inject("a", a);
cppy3::Main().inject("b", b);
cppy3::exec("print('a: {} {}'.format(type(a), a))");
cppy3::exec("print('b: {} {}'.format(type(b), b))");
cppy3::exec("assert type(a) == numpy.ndarray, 'expect injected instance'");
cppy3::exec("assert numpy.all(a == b), 'expect cData'");
// modify b from python (b is a shared ndarray over cData)
cppy3::exec("b[0] = 100500");
REQUIRE(b(0, 0) == 100500);
REQUIRE(cData[0] == 100500);
}
#endif
SECTION("test Scoped GIL Lock / Release") {
// initially Python GIL is locked
REQUIRE(cppy3::GILLocker::isLocked());
// add variable
cppy3::exec("a = []");
cppy3::List a = cppy3::List(cppy3::lookupObject(cppy3::getMainModule(), L"a"));
REQUIRE(a.size() == 0);
// create thread that changes the variable a in a different thread
const std::string threadScript = R"(
import threading
def thread_main():
global a
a.append(42)
t = threading.Thread(target=thread_main, daemon=True)
t.start()
)";
std::cout << threadScript << std::endl;
cppy3::exec(threadScript);
{
// release GIL on this thread
cppy3::ScopedGILRelease gilRelease;
REQUIRE(!cppy3::GILLocker::isLocked());
// and wait thread changes the variable
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
// lock GIL again before accessing python objects
cppy3::GILLocker locker;
REQUIRE(cppy3::GILLocker::isLocked());
// ensure that variable has been changed
cppy3::exec("assert a == [42], a");
REQUIRE(a.size() == 1);
REQUIRE((a[0]).toLong() == 42);
}
// GIL is released again
REQUIRE(!cppy3::GILLocker::isLocked());
}
}
}