rubyx-py 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.toml +19 -0
- data/README.md +469 -0
- data/ext/rubyx/Cargo.toml +19 -0
- data/ext/rubyx/extconf.rb +22 -0
- data/ext/rubyx/src/async_gen.rs +1298 -0
- data/ext/rubyx/src/context.rs +812 -0
- data/ext/rubyx/src/convert.rs +1498 -0
- data/ext/rubyx/src/eval.rs +377 -0
- data/ext/rubyx/src/exception.rs +184 -0
- data/ext/rubyx/src/future.rs +126 -0
- data/ext/rubyx/src/import.rs +34 -0
- data/ext/rubyx/src/lib.rs +4212 -0
- data/ext/rubyx/src/nonblocking_stream.rs +1422 -0
- data/ext/rubyx/src/pipe_notify.rs +232 -0
- data/ext/rubyx/src/python/sync_adapter.py +31 -0
- data/ext/rubyx/src/python_api.rs +6029 -0
- data/ext/rubyx/src/python_ffi.rs +18 -0
- data/ext/rubyx/src/python_finder.rs +119 -0
- data/ext/rubyx/src/python_guard.rs +25 -0
- data/ext/rubyx/src/ruby_helpers.rs +74 -0
- data/ext/rubyx/src/rubyx_object.rs +1931 -0
- data/ext/rubyx/src/rubyx_stream.rs +950 -0
- data/ext/rubyx/src/stream.rs +713 -0
- data/ext/rubyx/src/test_helpers.rs +351 -0
- data/lib/generators/rubyx/install_generator.rb +24 -0
- data/lib/generators/rubyx/templates/rubyx_initializer.rb +17 -0
- data/lib/rubyx/context.rb +27 -0
- data/lib/rubyx/error.rb +30 -0
- data/lib/rubyx/rails.rb +105 -0
- data/lib/rubyx/railtie.rb +20 -0
- data/lib/rubyx/uv.rb +261 -0
- data/lib/rubyx/version.rb +4 -0
- data/lib/rubyx-py.rb +1 -0
- data/lib/rubyx.rb +136 -0
- metadata +123 -0
|
@@ -0,0 +1,1498 @@
|
|
|
1
|
+
use crate::python_api::PythonApi;
|
|
2
|
+
use crate::python_ffi::PyObject;
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use thiserror::Error;
|
|
5
|
+
|
|
6
|
+
#[derive(Error, Debug)]
|
|
7
|
+
#[allow(dead_code)]
|
|
8
|
+
pub enum ConvertError {
|
|
9
|
+
#[error("Type error: expected {expected}, got {got}")]
|
|
10
|
+
TypeError { expected: &'static str, got: String },
|
|
11
|
+
|
|
12
|
+
#[error("Integer overflow")]
|
|
13
|
+
Overflow,
|
|
14
|
+
|
|
15
|
+
#[error("Python error: {0}")]
|
|
16
|
+
PythonError(String),
|
|
17
|
+
|
|
18
|
+
#[error("Encoding error")]
|
|
19
|
+
EncodingError,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// pub trait ToPython {
|
|
23
|
+
// fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError>;
|
|
24
|
+
// }
|
|
25
|
+
#[allow(dead_code)]
|
|
26
|
+
pub trait ToPython {
|
|
27
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[allow(dead_code)]
|
|
31
|
+
pub trait FromPython: Sized {
|
|
32
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl ToPython for i64 {
|
|
36
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
37
|
+
let obj = api.long_from_i64(*self);
|
|
38
|
+
if obj.is_null() {
|
|
39
|
+
// Check if Python failed to create object
|
|
40
|
+
Err(ConvertError::PythonError("Failed to create int".into()))
|
|
41
|
+
} else {
|
|
42
|
+
Ok(obj)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
impl FromPython for i64 {
|
|
47
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
48
|
+
if obj.is_null() {
|
|
49
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
50
|
+
}
|
|
51
|
+
// Type check
|
|
52
|
+
if !api.is_long(obj) {
|
|
53
|
+
return Err(ConvertError::TypeError {
|
|
54
|
+
expected: "int",
|
|
55
|
+
got: "other".to_string(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract value after type check passes
|
|
60
|
+
let value = api.long_to_i64(obj);
|
|
61
|
+
|
|
62
|
+
// Check for overflow
|
|
63
|
+
if value == -1 && api.has_error() {
|
|
64
|
+
// Need to clear the error before checking for overflow again
|
|
65
|
+
api.clear_error();
|
|
66
|
+
return Err(ConvertError::Overflow);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Ok(value)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
impl ToPython for f64 {
|
|
73
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
74
|
+
let obj = api.float_from_f64(*self);
|
|
75
|
+
if obj.is_null() {
|
|
76
|
+
return Err(ConvertError::PythonError("Failed to create float".into()));
|
|
77
|
+
}
|
|
78
|
+
Ok(obj)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
impl FromPython for f64 {
|
|
83
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
84
|
+
if obj.is_null() {
|
|
85
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
86
|
+
}
|
|
87
|
+
if !api.is_float(obj) {
|
|
88
|
+
return Err(ConvertError::TypeError {
|
|
89
|
+
expected: "float",
|
|
90
|
+
got: "other".to_string(),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
let value = api.float_to_f64(obj);
|
|
94
|
+
if value == -1.0 && api.has_error() {
|
|
95
|
+
api.clear_error();
|
|
96
|
+
return Err(ConvertError::PythonError(
|
|
97
|
+
"Failed to convert from Python float".into(),
|
|
98
|
+
));
|
|
99
|
+
}
|
|
100
|
+
Ok(value)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl ToPython for bool {
|
|
105
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
106
|
+
let obj = api.bool_from_bool(*self);
|
|
107
|
+
api.incref(obj);
|
|
108
|
+
Ok(obj)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl FromPython for bool {
|
|
113
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
114
|
+
if obj.is_null() {
|
|
115
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
116
|
+
}
|
|
117
|
+
api.bool_to_bool(obj).map_err(|_| ConvertError::TypeError {
|
|
118
|
+
expected: "bool",
|
|
119
|
+
got: "other".to_string(),
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl ToPython for &str {
|
|
125
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
126
|
+
let obj = api.string_from_str(self);
|
|
127
|
+
if obj.is_null() {
|
|
128
|
+
Err(ConvertError::PythonError("Failed to create str".into()))
|
|
129
|
+
} else {
|
|
130
|
+
Ok(obj)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
impl ToPython for String {
|
|
136
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
137
|
+
self.as_str().to_python(api)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
impl FromPython for String {
|
|
142
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
143
|
+
if obj.is_null() {
|
|
144
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
145
|
+
}
|
|
146
|
+
if !api.is_string(obj) {
|
|
147
|
+
return Err(ConvertError::TypeError {
|
|
148
|
+
expected: "str",
|
|
149
|
+
got: "other".to_string(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
api.string_to_string(obj).ok_or(ConvertError::EncodingError)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl<T: ToPython> ToPython for Option<T> {
|
|
157
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
158
|
+
match self {
|
|
159
|
+
Some(value) => value.to_python(api),
|
|
160
|
+
None => {
|
|
161
|
+
api.incref(api.py_none);
|
|
162
|
+
Ok(api.py_none)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
impl<T: ToPython> ToPython for Vec<T> {
|
|
169
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
170
|
+
let py_list = api.list_new(self.len() as isize);
|
|
171
|
+
if py_list.is_null() {
|
|
172
|
+
return Err(ConvertError::PythonError("Failed to create list".into()));
|
|
173
|
+
}
|
|
174
|
+
for (index, item) in self.iter().enumerate() {
|
|
175
|
+
let py_item = item.to_python(api)?;
|
|
176
|
+
let result = api.list_set_item(py_list, index as isize, py_item);
|
|
177
|
+
if result != 0 {
|
|
178
|
+
// PyList_SetItem failed - it did NOT steal the reference
|
|
179
|
+
api.decref(py_item);
|
|
180
|
+
api.decref(py_list);
|
|
181
|
+
return Err(ConvertError::PythonError("Failed to set list item".into()));
|
|
182
|
+
}
|
|
183
|
+
// Success: reference was stolen, don't decref py_item
|
|
184
|
+
}
|
|
185
|
+
Ok(py_list)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
impl<T: FromPython> FromPython for Vec<T> {
|
|
190
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
191
|
+
if obj.is_null() {
|
|
192
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
193
|
+
}
|
|
194
|
+
if !api.list_check(obj) {
|
|
195
|
+
return Err(ConvertError::TypeError {
|
|
196
|
+
expected: "list",
|
|
197
|
+
got: "other".to_string(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
let size = api.list_size(obj) as usize;
|
|
201
|
+
let mut list = Vec::with_capacity(size);
|
|
202
|
+
|
|
203
|
+
for index in 0..size {
|
|
204
|
+
let py_item = api.list_get_item(obj, index as isize);
|
|
205
|
+
if py_item.is_null() {
|
|
206
|
+
return Err(ConvertError::PythonError("Failed to get list item".into()));
|
|
207
|
+
}
|
|
208
|
+
let item = T::from_python(py_item, api)?;
|
|
209
|
+
list.push(item);
|
|
210
|
+
}
|
|
211
|
+
Ok(list)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
impl<K: ToPython, V: ToPython> ToPython for HashMap<K, V> {
|
|
215
|
+
fn to_python(&self, api: &PythonApi) -> Result<*mut PyObject, ConvertError> {
|
|
216
|
+
let dict = api.dict_new();
|
|
217
|
+
if dict.is_null() {
|
|
218
|
+
return Err(ConvertError::PythonError(
|
|
219
|
+
"Failed to create dictionary".into(),
|
|
220
|
+
));
|
|
221
|
+
}
|
|
222
|
+
for (key, value) in self.iter() {
|
|
223
|
+
let py_key = match key.to_python(api) {
|
|
224
|
+
Ok(k) => k,
|
|
225
|
+
Err(e) => {
|
|
226
|
+
api.decref(dict);
|
|
227
|
+
return Err(e);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
let py_value = match value.to_python(api) {
|
|
231
|
+
Ok(v) => v,
|
|
232
|
+
Err(e) => {
|
|
233
|
+
api.decref(dict);
|
|
234
|
+
api.decref(py_key);
|
|
235
|
+
return Err(e);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
let result = api.dict_set_item(dict, py_key, py_value);
|
|
239
|
+
// PyDict_SetItem did NOT steal the reference
|
|
240
|
+
api.decref(py_key);
|
|
241
|
+
api.decref(py_value);
|
|
242
|
+
if result == -1 {
|
|
243
|
+
api.decref(dict);
|
|
244
|
+
return Err(ConvertError::PythonError(
|
|
245
|
+
"Failed to set dictionary item".into(),
|
|
246
|
+
));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
Ok(dict)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
impl<K: FromPython + Eq + std::hash::Hash, V: FromPython> FromPython for HashMap<K, V> {
|
|
254
|
+
fn from_python(obj: *mut PyObject, api: &PythonApi) -> Result<Self, ConvertError> {
|
|
255
|
+
if obj.is_null() {
|
|
256
|
+
return Err(ConvertError::PythonError("Null object".into()));
|
|
257
|
+
}
|
|
258
|
+
if !api.dict_check(obj) {
|
|
259
|
+
return Err(ConvertError::TypeError {
|
|
260
|
+
expected: "dict",
|
|
261
|
+
got: "other".to_string(),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
let mut map = HashMap::new();
|
|
265
|
+
let mut position = 0;
|
|
266
|
+
let mut key: *mut PyObject = std::ptr::null_mut();
|
|
267
|
+
let mut value: *mut PyObject = std::ptr::null_mut();
|
|
268
|
+
while api.dict_next(obj, &mut position, &mut key, &mut value) {
|
|
269
|
+
let key = K::from_python(key, api)?;
|
|
270
|
+
let value = V::from_python(value, api)?;
|
|
271
|
+
map.insert(key, value);
|
|
272
|
+
}
|
|
273
|
+
Ok(map)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[cfg(test)]
|
|
278
|
+
mod tests {
|
|
279
|
+
use super::*;
|
|
280
|
+
use crate::test_helpers::skip_if_no_python;
|
|
281
|
+
use serial_test::serial;
|
|
282
|
+
|
|
283
|
+
mod i64_tests {
|
|
284
|
+
use super::*;
|
|
285
|
+
|
|
286
|
+
#[test]
|
|
287
|
+
#[serial]
|
|
288
|
+
fn test_i64_to_python() {
|
|
289
|
+
let Some(guard) = skip_if_no_python() else {
|
|
290
|
+
return;
|
|
291
|
+
};
|
|
292
|
+
let api = guard.api();
|
|
293
|
+
|
|
294
|
+
let py_obj = 42i64.to_python(api).unwrap();
|
|
295
|
+
assert!(!py_obj.is_null());
|
|
296
|
+
|
|
297
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
298
|
+
assert_eq!(back, 42);
|
|
299
|
+
|
|
300
|
+
api.decref(py_obj);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[test]
|
|
304
|
+
#[serial]
|
|
305
|
+
fn test_i64_negative() {
|
|
306
|
+
let Some(guard) = skip_if_no_python() else {
|
|
307
|
+
return;
|
|
308
|
+
};
|
|
309
|
+
let api = guard.api();
|
|
310
|
+
|
|
311
|
+
let py_obj = (-123i64).to_python(api).unwrap();
|
|
312
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
313
|
+
assert_eq!(back, -123);
|
|
314
|
+
|
|
315
|
+
api.decref(py_obj);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
#[serial]
|
|
320
|
+
fn test_i64_max() {
|
|
321
|
+
let Some(guard) = skip_if_no_python() else {
|
|
322
|
+
return;
|
|
323
|
+
};
|
|
324
|
+
let api = guard.api();
|
|
325
|
+
|
|
326
|
+
let py_obj = i64::MAX.to_python(api).unwrap();
|
|
327
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
328
|
+
assert_eq!(back, i64::MAX);
|
|
329
|
+
|
|
330
|
+
api.decref(py_obj);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[test]
|
|
334
|
+
#[serial]
|
|
335
|
+
fn test_i64_min() {
|
|
336
|
+
let Some(guard) = skip_if_no_python() else {
|
|
337
|
+
return;
|
|
338
|
+
};
|
|
339
|
+
let api = guard.api();
|
|
340
|
+
|
|
341
|
+
let py_obj = i64::MIN.to_python(api).unwrap();
|
|
342
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
343
|
+
assert_eq!(back, i64::MIN);
|
|
344
|
+
|
|
345
|
+
api.decref(py_obj);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#[test]
|
|
349
|
+
#[serial]
|
|
350
|
+
fn test_i64_zero() {
|
|
351
|
+
let Some(guard) = skip_if_no_python() else {
|
|
352
|
+
return;
|
|
353
|
+
};
|
|
354
|
+
let api = guard.api();
|
|
355
|
+
|
|
356
|
+
let py_obj = 0i64.to_python(api).unwrap();
|
|
357
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
358
|
+
assert_eq!(back, 0);
|
|
359
|
+
|
|
360
|
+
api.decref(py_obj);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
mod f64_tests {
|
|
365
|
+
use super::*;
|
|
366
|
+
|
|
367
|
+
#[test]
|
|
368
|
+
#[serial]
|
|
369
|
+
fn test_f64_to_python() {
|
|
370
|
+
let Some(guard) = skip_if_no_python() else {
|
|
371
|
+
return;
|
|
372
|
+
};
|
|
373
|
+
let api = guard.api();
|
|
374
|
+
|
|
375
|
+
let py_obj = std::f64::consts::PI.to_python(api).unwrap();
|
|
376
|
+
assert!(!py_obj.is_null());
|
|
377
|
+
|
|
378
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
379
|
+
assert!((back - std::f64::consts::PI).abs() < 1e-10);
|
|
380
|
+
|
|
381
|
+
api.decref(py_obj);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#[test]
|
|
385
|
+
#[serial]
|
|
386
|
+
fn test_f64_negative() {
|
|
387
|
+
let Some(guard) = skip_if_no_python() else {
|
|
388
|
+
return;
|
|
389
|
+
};
|
|
390
|
+
let api = guard.api();
|
|
391
|
+
|
|
392
|
+
let py_obj = (-2.5f64).to_python(api).unwrap();
|
|
393
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
394
|
+
assert!((back - (-2.5)).abs() < 1e-10);
|
|
395
|
+
|
|
396
|
+
api.decref(py_obj);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#[test]
|
|
400
|
+
#[serial]
|
|
401
|
+
fn test_f64_zero() {
|
|
402
|
+
let Some(guard) = skip_if_no_python() else {
|
|
403
|
+
return;
|
|
404
|
+
};
|
|
405
|
+
let api = guard.api();
|
|
406
|
+
|
|
407
|
+
let py_obj = 0.0f64.to_python(api).unwrap();
|
|
408
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
409
|
+
assert_eq!(back, 0.0);
|
|
410
|
+
|
|
411
|
+
api.decref(py_obj);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
mod bool_tests {
|
|
416
|
+
use super::*;
|
|
417
|
+
|
|
418
|
+
#[test]
|
|
419
|
+
#[serial]
|
|
420
|
+
fn test_bool_true_to_python() {
|
|
421
|
+
let Some(guard) = skip_if_no_python() else {
|
|
422
|
+
return;
|
|
423
|
+
};
|
|
424
|
+
let api = guard.api();
|
|
425
|
+
|
|
426
|
+
let py_obj = true.to_python(api).unwrap();
|
|
427
|
+
assert_eq!(py_obj, api.py_true);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
#[test]
|
|
431
|
+
#[serial]
|
|
432
|
+
fn test_bool_false_to_python() {
|
|
433
|
+
let Some(guard) = skip_if_no_python() else {
|
|
434
|
+
return;
|
|
435
|
+
};
|
|
436
|
+
let api = guard.api();
|
|
437
|
+
|
|
438
|
+
let py_obj = false.to_python(api).unwrap();
|
|
439
|
+
assert_eq!(py_obj, api.py_false);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
#[serial]
|
|
444
|
+
fn test_bool_roundtrip() {
|
|
445
|
+
let Some(guard) = skip_if_no_python() else {
|
|
446
|
+
return;
|
|
447
|
+
};
|
|
448
|
+
let api = guard.api();
|
|
449
|
+
|
|
450
|
+
let py_true = true.to_python(api).unwrap();
|
|
451
|
+
let back_true = bool::from_python(py_true, api).unwrap();
|
|
452
|
+
assert!(back_true);
|
|
453
|
+
|
|
454
|
+
let py_false = false.to_python(api).unwrap();
|
|
455
|
+
let back_false = bool::from_python(py_false, api).unwrap();
|
|
456
|
+
assert!(!back_false);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
mod string_tests {
|
|
461
|
+
use super::*;
|
|
462
|
+
|
|
463
|
+
#[test]
|
|
464
|
+
#[serial]
|
|
465
|
+
fn test_string_to_python() {
|
|
466
|
+
let Some(guard) = skip_if_no_python() else {
|
|
467
|
+
return;
|
|
468
|
+
};
|
|
469
|
+
let api = guard.api();
|
|
470
|
+
|
|
471
|
+
let py_obj = "hello world".to_python(api).unwrap();
|
|
472
|
+
assert!(!py_obj.is_null());
|
|
473
|
+
|
|
474
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
475
|
+
assert_eq!(back, "hello world");
|
|
476
|
+
|
|
477
|
+
api.decref(py_obj);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#[test]
|
|
481
|
+
#[serial]
|
|
482
|
+
fn test_empty_string() {
|
|
483
|
+
let Some(guard) = skip_if_no_python() else {
|
|
484
|
+
return;
|
|
485
|
+
};
|
|
486
|
+
let api = guard.api();
|
|
487
|
+
|
|
488
|
+
let py_obj = "".to_python(api).unwrap();
|
|
489
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
490
|
+
assert_eq!(back, "");
|
|
491
|
+
|
|
492
|
+
api.decref(py_obj);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[test]
|
|
496
|
+
#[serial]
|
|
497
|
+
fn test_unicode_string() {
|
|
498
|
+
let Some(guard) = skip_if_no_python() else {
|
|
499
|
+
return;
|
|
500
|
+
};
|
|
501
|
+
let api = guard.api();
|
|
502
|
+
|
|
503
|
+
let py_obj = "hello".to_python(api).unwrap();
|
|
504
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
505
|
+
assert_eq!(back, "hello");
|
|
506
|
+
|
|
507
|
+
api.decref(py_obj);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
#[test]
|
|
511
|
+
#[serial]
|
|
512
|
+
fn test_string_with_spaces() {
|
|
513
|
+
let Some(guard) = skip_if_no_python() else {
|
|
514
|
+
return;
|
|
515
|
+
};
|
|
516
|
+
let api = guard.api();
|
|
517
|
+
|
|
518
|
+
let py_obj = " spaces ".to_python(api).unwrap();
|
|
519
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
520
|
+
assert_eq!(back, " spaces ");
|
|
521
|
+
|
|
522
|
+
api.decref(py_obj);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
mod option_tests {
|
|
527
|
+
use super::*;
|
|
528
|
+
|
|
529
|
+
#[test]
|
|
530
|
+
#[serial]
|
|
531
|
+
fn test_none_to_python() {
|
|
532
|
+
let Some(guard) = skip_if_no_python() else {
|
|
533
|
+
return;
|
|
534
|
+
};
|
|
535
|
+
let api = guard.api();
|
|
536
|
+
|
|
537
|
+
let py_obj = Option::<i64>::None.to_python(api).unwrap();
|
|
538
|
+
assert_eq!(py_obj, api.py_none);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
#[test]
|
|
542
|
+
#[serial]
|
|
543
|
+
fn test_some_to_python() {
|
|
544
|
+
let Some(guard) = skip_if_no_python() else {
|
|
545
|
+
return;
|
|
546
|
+
};
|
|
547
|
+
let api = guard.api();
|
|
548
|
+
|
|
549
|
+
let py_obj = Some(42i64).to_python(api).unwrap();
|
|
550
|
+
assert_ne!(py_obj, api.py_none);
|
|
551
|
+
|
|
552
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
553
|
+
assert_eq!(back, 42);
|
|
554
|
+
|
|
555
|
+
api.decref(py_obj);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
mod type_error_tests {
|
|
560
|
+
use super::*;
|
|
561
|
+
|
|
562
|
+
#[test]
|
|
563
|
+
#[serial]
|
|
564
|
+
fn test_string_from_int_fails() {
|
|
565
|
+
let Some(guard) = skip_if_no_python() else {
|
|
566
|
+
return;
|
|
567
|
+
};
|
|
568
|
+
let api = guard.api();
|
|
569
|
+
|
|
570
|
+
let py_int = 42i64.to_python(api).unwrap();
|
|
571
|
+
let result = String::from_python(py_int, api);
|
|
572
|
+
|
|
573
|
+
assert!(result.is_err());
|
|
574
|
+
match result.unwrap_err() {
|
|
575
|
+
ConvertError::TypeError { expected, .. } => {
|
|
576
|
+
assert_eq!(expected, "str");
|
|
577
|
+
}
|
|
578
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
api.decref(py_int);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
#[serial]
|
|
586
|
+
fn test_int_from_string_fails() {
|
|
587
|
+
let Some(guard) = skip_if_no_python() else {
|
|
588
|
+
return;
|
|
589
|
+
};
|
|
590
|
+
let api = guard.api();
|
|
591
|
+
|
|
592
|
+
let py_str = "not a number".to_python(api).unwrap();
|
|
593
|
+
let result = i64::from_python(py_str, api);
|
|
594
|
+
|
|
595
|
+
assert!(result.is_err());
|
|
596
|
+
match result.unwrap_err() {
|
|
597
|
+
ConvertError::TypeError { expected, .. } => {
|
|
598
|
+
assert_eq!(expected, "int");
|
|
599
|
+
}
|
|
600
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
api.decref(py_str);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
#[test]
|
|
607
|
+
#[serial]
|
|
608
|
+
fn test_int_from_float_fails() {
|
|
609
|
+
let Some(guard) = skip_if_no_python() else {
|
|
610
|
+
return;
|
|
611
|
+
};
|
|
612
|
+
let api = guard.api();
|
|
613
|
+
|
|
614
|
+
let py_float = std::f64::consts::PI.to_python(api).unwrap();
|
|
615
|
+
let result = i64::from_python(py_float, api);
|
|
616
|
+
|
|
617
|
+
assert!(result.is_err());
|
|
618
|
+
match result.unwrap_err() {
|
|
619
|
+
ConvertError::TypeError { expected, .. } => {
|
|
620
|
+
assert_eq!(expected, "int");
|
|
621
|
+
}
|
|
622
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
api.decref(py_float);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
#[test]
|
|
629
|
+
#[serial]
|
|
630
|
+
fn test_int_from_bool_succeeds() {
|
|
631
|
+
let Some(guard) = skip_if_no_python() else {
|
|
632
|
+
return;
|
|
633
|
+
};
|
|
634
|
+
let api = guard.api();
|
|
635
|
+
|
|
636
|
+
let py_true = true.to_python(api).unwrap();
|
|
637
|
+
assert_eq!(i64::from_python(py_true, api).unwrap(), 1);
|
|
638
|
+
|
|
639
|
+
let py_false = false.to_python(api).unwrap();
|
|
640
|
+
assert_eq!(i64::from_python(py_false, api).unwrap(), 0);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
#[test]
|
|
644
|
+
#[serial]
|
|
645
|
+
fn test_float_from_int_fails() {
|
|
646
|
+
let Some(guard) = skip_if_no_python() else {
|
|
647
|
+
return;
|
|
648
|
+
};
|
|
649
|
+
let api = guard.api();
|
|
650
|
+
|
|
651
|
+
let py_int = 42i64.to_python(api).unwrap();
|
|
652
|
+
let result = f64::from_python(py_int, api);
|
|
653
|
+
|
|
654
|
+
assert!(result.is_err());
|
|
655
|
+
match result.unwrap_err() {
|
|
656
|
+
ConvertError::TypeError { expected, .. } => {
|
|
657
|
+
assert_eq!(expected, "float");
|
|
658
|
+
}
|
|
659
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
api.decref(py_int);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
#[test]
|
|
666
|
+
#[serial]
|
|
667
|
+
fn test_float_from_string_fails() {
|
|
668
|
+
let Some(guard) = skip_if_no_python() else {
|
|
669
|
+
return;
|
|
670
|
+
};
|
|
671
|
+
let api = guard.api();
|
|
672
|
+
|
|
673
|
+
let py_str = "3.14".to_python(api).unwrap();
|
|
674
|
+
let result = f64::from_python(py_str, api);
|
|
675
|
+
|
|
676
|
+
assert!(result.is_err());
|
|
677
|
+
match result.unwrap_err() {
|
|
678
|
+
ConvertError::TypeError { expected, .. } => {
|
|
679
|
+
assert_eq!(expected, "float");
|
|
680
|
+
}
|
|
681
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
api.decref(py_str);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
#[test]
|
|
688
|
+
#[serial]
|
|
689
|
+
fn test_float_from_bool_fails() {
|
|
690
|
+
let Some(guard) = skip_if_no_python() else {
|
|
691
|
+
return;
|
|
692
|
+
};
|
|
693
|
+
let api = guard.api();
|
|
694
|
+
|
|
695
|
+
let py_bool = true.to_python(api).unwrap();
|
|
696
|
+
let result = f64::from_python(py_bool, api);
|
|
697
|
+
|
|
698
|
+
assert!(result.is_err());
|
|
699
|
+
match result.unwrap_err() {
|
|
700
|
+
ConvertError::TypeError { expected, .. } => {
|
|
701
|
+
assert_eq!(expected, "float");
|
|
702
|
+
}
|
|
703
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
#[test]
|
|
708
|
+
#[serial]
|
|
709
|
+
fn test_bool_from_int_fails() {
|
|
710
|
+
let Some(guard) = skip_if_no_python() else {
|
|
711
|
+
return;
|
|
712
|
+
};
|
|
713
|
+
let api = guard.api();
|
|
714
|
+
|
|
715
|
+
let py_int = 1i64.to_python(api).unwrap();
|
|
716
|
+
let result = bool::from_python(py_int, api);
|
|
717
|
+
|
|
718
|
+
assert!(result.is_err());
|
|
719
|
+
match result.unwrap_err() {
|
|
720
|
+
ConvertError::TypeError { expected, .. } => {
|
|
721
|
+
assert_eq!(expected, "bool");
|
|
722
|
+
}
|
|
723
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
api.decref(py_int);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
#[test]
|
|
730
|
+
#[serial]
|
|
731
|
+
fn test_bool_from_string_fails() {
|
|
732
|
+
let Some(guard) = skip_if_no_python() else {
|
|
733
|
+
return;
|
|
734
|
+
};
|
|
735
|
+
let api = guard.api();
|
|
736
|
+
|
|
737
|
+
let py_str = "true".to_python(api).unwrap();
|
|
738
|
+
let result = bool::from_python(py_str, api);
|
|
739
|
+
|
|
740
|
+
assert!(result.is_err());
|
|
741
|
+
match result.unwrap_err() {
|
|
742
|
+
ConvertError::TypeError { expected, .. } => {
|
|
743
|
+
assert_eq!(expected, "bool");
|
|
744
|
+
}
|
|
745
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
api.decref(py_str);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
#[test]
|
|
752
|
+
#[serial]
|
|
753
|
+
fn test_string_from_float_fails() {
|
|
754
|
+
let Some(guard) = skip_if_no_python() else {
|
|
755
|
+
return;
|
|
756
|
+
};
|
|
757
|
+
let api = guard.api();
|
|
758
|
+
|
|
759
|
+
let py_float = std::f64::consts::PI.to_python(api).unwrap();
|
|
760
|
+
let result = String::from_python(py_float, api);
|
|
761
|
+
|
|
762
|
+
assert!(result.is_err());
|
|
763
|
+
match result.unwrap_err() {
|
|
764
|
+
ConvertError::TypeError { expected, .. } => {
|
|
765
|
+
assert_eq!(expected, "str");
|
|
766
|
+
}
|
|
767
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
api.decref(py_float);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
#[test]
|
|
774
|
+
#[serial]
|
|
775
|
+
fn test_string_from_bool_fails() {
|
|
776
|
+
let Some(guard) = skip_if_no_python() else {
|
|
777
|
+
return;
|
|
778
|
+
};
|
|
779
|
+
let api = guard.api();
|
|
780
|
+
|
|
781
|
+
let py_bool = true.to_python(api).unwrap();
|
|
782
|
+
let result = String::from_python(py_bool, api);
|
|
783
|
+
|
|
784
|
+
assert!(result.is_err());
|
|
785
|
+
match result.unwrap_err() {
|
|
786
|
+
ConvertError::TypeError { expected, .. } => {
|
|
787
|
+
assert_eq!(expected, "str");
|
|
788
|
+
}
|
|
789
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
#[test]
|
|
794
|
+
#[serial]
|
|
795
|
+
fn test_int_from_none_fails() {
|
|
796
|
+
let Some(guard) = skip_if_no_python() else {
|
|
797
|
+
return;
|
|
798
|
+
};
|
|
799
|
+
let api = guard.api();
|
|
800
|
+
|
|
801
|
+
let result = i64::from_python(api.py_none, api);
|
|
802
|
+
|
|
803
|
+
assert!(result.is_err());
|
|
804
|
+
match result.unwrap_err() {
|
|
805
|
+
ConvertError::TypeError { expected, .. } => {
|
|
806
|
+
assert_eq!(expected, "int");
|
|
807
|
+
}
|
|
808
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
#[test]
|
|
813
|
+
#[serial]
|
|
814
|
+
fn test_float_from_none_fails() {
|
|
815
|
+
let Some(guard) = skip_if_no_python() else {
|
|
816
|
+
return;
|
|
817
|
+
};
|
|
818
|
+
let api = guard.api();
|
|
819
|
+
|
|
820
|
+
let result = f64::from_python(api.py_none, api);
|
|
821
|
+
|
|
822
|
+
assert!(result.is_err());
|
|
823
|
+
match result.unwrap_err() {
|
|
824
|
+
ConvertError::TypeError { expected, .. } => {
|
|
825
|
+
assert_eq!(expected, "float");
|
|
826
|
+
}
|
|
827
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
#[test]
|
|
832
|
+
#[serial]
|
|
833
|
+
fn test_bool_from_none_fails() {
|
|
834
|
+
let Some(guard) = skip_if_no_python() else {
|
|
835
|
+
return;
|
|
836
|
+
};
|
|
837
|
+
let api = guard.api();
|
|
838
|
+
|
|
839
|
+
let result = bool::from_python(api.py_none, api);
|
|
840
|
+
|
|
841
|
+
assert!(result.is_err());
|
|
842
|
+
match result.unwrap_err() {
|
|
843
|
+
ConvertError::TypeError { expected, .. } => {
|
|
844
|
+
assert_eq!(expected, "bool");
|
|
845
|
+
}
|
|
846
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
#[test]
|
|
851
|
+
#[serial]
|
|
852
|
+
fn test_string_from_none_fails() {
|
|
853
|
+
let Some(guard) = skip_if_no_python() else {
|
|
854
|
+
return;
|
|
855
|
+
};
|
|
856
|
+
let api = guard.api();
|
|
857
|
+
|
|
858
|
+
let result = String::from_python(api.py_none, api);
|
|
859
|
+
|
|
860
|
+
assert!(result.is_err());
|
|
861
|
+
match result.unwrap_err() {
|
|
862
|
+
ConvertError::TypeError { expected, .. } => {
|
|
863
|
+
assert_eq!(expected, "str");
|
|
864
|
+
}
|
|
865
|
+
e => panic!("Expected TypeError, got {:?}", e),
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
mod f64_edge_cases {
|
|
871
|
+
use super::*;
|
|
872
|
+
|
|
873
|
+
#[test]
|
|
874
|
+
#[serial]
|
|
875
|
+
fn test_f64_max() {
|
|
876
|
+
let Some(guard) = skip_if_no_python() else {
|
|
877
|
+
return;
|
|
878
|
+
};
|
|
879
|
+
let api = guard.api();
|
|
880
|
+
|
|
881
|
+
let py_obj = f64::MAX.to_python(api).unwrap();
|
|
882
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
883
|
+
assert_eq!(back, f64::MAX);
|
|
884
|
+
|
|
885
|
+
api.decref(py_obj);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
#[test]
|
|
889
|
+
#[serial]
|
|
890
|
+
fn test_f64_min() {
|
|
891
|
+
let Some(guard) = skip_if_no_python() else {
|
|
892
|
+
return;
|
|
893
|
+
};
|
|
894
|
+
let api = guard.api();
|
|
895
|
+
|
|
896
|
+
let py_obj = f64::MIN.to_python(api).unwrap();
|
|
897
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
898
|
+
assert_eq!(back, f64::MIN);
|
|
899
|
+
|
|
900
|
+
api.decref(py_obj);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
#[test]
|
|
904
|
+
#[serial]
|
|
905
|
+
fn test_f64_min_positive() {
|
|
906
|
+
let Some(guard) = skip_if_no_python() else {
|
|
907
|
+
return;
|
|
908
|
+
};
|
|
909
|
+
let api = guard.api();
|
|
910
|
+
|
|
911
|
+
let py_obj = f64::MIN_POSITIVE.to_python(api).unwrap();
|
|
912
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
913
|
+
assert_eq!(back, f64::MIN_POSITIVE);
|
|
914
|
+
|
|
915
|
+
api.decref(py_obj);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
#[test]
|
|
919
|
+
#[serial]
|
|
920
|
+
fn test_f64_infinity() {
|
|
921
|
+
let Some(guard) = skip_if_no_python() else {
|
|
922
|
+
return;
|
|
923
|
+
};
|
|
924
|
+
let api = guard.api();
|
|
925
|
+
|
|
926
|
+
let py_obj = f64::INFINITY.to_python(api).unwrap();
|
|
927
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
928
|
+
assert!(back.is_infinite() && back.is_sign_positive());
|
|
929
|
+
|
|
930
|
+
api.decref(py_obj);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
#[test]
|
|
934
|
+
#[serial]
|
|
935
|
+
fn test_f64_neg_infinity() {
|
|
936
|
+
let Some(guard) = skip_if_no_python() else {
|
|
937
|
+
return;
|
|
938
|
+
};
|
|
939
|
+
let api = guard.api();
|
|
940
|
+
|
|
941
|
+
let py_obj = f64::NEG_INFINITY.to_python(api).unwrap();
|
|
942
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
943
|
+
assert!(back.is_infinite() && back.is_sign_negative());
|
|
944
|
+
|
|
945
|
+
api.decref(py_obj);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
#[test]
|
|
949
|
+
#[serial]
|
|
950
|
+
fn test_f64_nan() {
|
|
951
|
+
let Some(guard) = skip_if_no_python() else {
|
|
952
|
+
return;
|
|
953
|
+
};
|
|
954
|
+
let api = guard.api();
|
|
955
|
+
|
|
956
|
+
let py_obj = f64::NAN.to_python(api).unwrap();
|
|
957
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
958
|
+
assert!(back.is_nan());
|
|
959
|
+
|
|
960
|
+
api.decref(py_obj);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
#[test]
|
|
964
|
+
#[serial]
|
|
965
|
+
fn test_f64_negative_zero() {
|
|
966
|
+
let Some(guard) = skip_if_no_python() else {
|
|
967
|
+
return;
|
|
968
|
+
};
|
|
969
|
+
let api = guard.api();
|
|
970
|
+
|
|
971
|
+
let neg_zero = -0.0f64;
|
|
972
|
+
let py_obj = neg_zero.to_python(api).unwrap();
|
|
973
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
974
|
+
assert_eq!(back, 0.0);
|
|
975
|
+
|
|
976
|
+
api.decref(py_obj);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
#[test]
|
|
980
|
+
#[serial]
|
|
981
|
+
fn test_f64_pi() {
|
|
982
|
+
let Some(guard) = skip_if_no_python() else {
|
|
983
|
+
return;
|
|
984
|
+
};
|
|
985
|
+
let api = guard.api();
|
|
986
|
+
|
|
987
|
+
let py_obj = std::f64::consts::PI.to_python(api).unwrap();
|
|
988
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
989
|
+
assert!((back - std::f64::consts::PI).abs() < 1e-15);
|
|
990
|
+
|
|
991
|
+
api.decref(py_obj);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
#[test]
|
|
995
|
+
#[serial]
|
|
996
|
+
fn test_f64_very_small() {
|
|
997
|
+
let Some(guard) = skip_if_no_python() else {
|
|
998
|
+
return;
|
|
999
|
+
};
|
|
1000
|
+
let api = guard.api();
|
|
1001
|
+
|
|
1002
|
+
let small = 1e-300f64;
|
|
1003
|
+
let py_obj = small.to_python(api).unwrap();
|
|
1004
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
1005
|
+
assert_eq!(back, small);
|
|
1006
|
+
|
|
1007
|
+
api.decref(py_obj);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
#[test]
|
|
1011
|
+
#[serial]
|
|
1012
|
+
fn test_f64_very_large() {
|
|
1013
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1014
|
+
return;
|
|
1015
|
+
};
|
|
1016
|
+
let api = guard.api();
|
|
1017
|
+
|
|
1018
|
+
let large = 1e300f64;
|
|
1019
|
+
let py_obj = large.to_python(api).unwrap();
|
|
1020
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
1021
|
+
assert_eq!(back, large);
|
|
1022
|
+
|
|
1023
|
+
api.decref(py_obj);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
mod string_edge_cases {
|
|
1028
|
+
use super::*;
|
|
1029
|
+
|
|
1030
|
+
#[test]
|
|
1031
|
+
#[serial]
|
|
1032
|
+
fn test_string_unicode_emoji() {
|
|
1033
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1034
|
+
return;
|
|
1035
|
+
};
|
|
1036
|
+
let api = guard.api();
|
|
1037
|
+
|
|
1038
|
+
let py_obj = "Hello 🎉🚀💻".to_python(api).unwrap();
|
|
1039
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1040
|
+
assert_eq!(back, "Hello 🎉🚀💻");
|
|
1041
|
+
|
|
1042
|
+
api.decref(py_obj);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
#[test]
|
|
1046
|
+
#[serial]
|
|
1047
|
+
fn test_string_unicode_cjk() {
|
|
1048
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1049
|
+
return;
|
|
1050
|
+
};
|
|
1051
|
+
let api = guard.api();
|
|
1052
|
+
|
|
1053
|
+
let py_obj = "日本語 中文 한국어".to_python(api).unwrap();
|
|
1054
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1055
|
+
assert_eq!(back, "日本語 中文 한국어");
|
|
1056
|
+
|
|
1057
|
+
api.decref(py_obj);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
#[test]
|
|
1061
|
+
#[serial]
|
|
1062
|
+
fn test_string_unicode_mixed() {
|
|
1063
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1064
|
+
return;
|
|
1065
|
+
};
|
|
1066
|
+
let api = guard.api();
|
|
1067
|
+
|
|
1068
|
+
let py_obj = "café résumé naïve".to_python(api).unwrap();
|
|
1069
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1070
|
+
assert_eq!(back, "café résumé naïve");
|
|
1071
|
+
|
|
1072
|
+
api.decref(py_obj);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
#[test]
|
|
1076
|
+
#[serial]
|
|
1077
|
+
fn test_string_with_newlines() {
|
|
1078
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1079
|
+
return;
|
|
1080
|
+
};
|
|
1081
|
+
let api = guard.api();
|
|
1082
|
+
|
|
1083
|
+
let py_obj = "line1\nline2\nline3".to_python(api).unwrap();
|
|
1084
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1085
|
+
assert_eq!(back, "line1\nline2\nline3");
|
|
1086
|
+
|
|
1087
|
+
api.decref(py_obj);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
#[test]
|
|
1091
|
+
#[serial]
|
|
1092
|
+
fn test_string_with_tabs() {
|
|
1093
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1094
|
+
return;
|
|
1095
|
+
};
|
|
1096
|
+
let api = guard.api();
|
|
1097
|
+
|
|
1098
|
+
let py_obj = "col1\tcol2\tcol3".to_python(api).unwrap();
|
|
1099
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1100
|
+
assert_eq!(back, "col1\tcol2\tcol3");
|
|
1101
|
+
|
|
1102
|
+
api.decref(py_obj);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
#[test]
|
|
1106
|
+
#[serial]
|
|
1107
|
+
fn test_string_with_null_byte() {
|
|
1108
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1109
|
+
return;
|
|
1110
|
+
};
|
|
1111
|
+
let api = guard.api();
|
|
1112
|
+
|
|
1113
|
+
let py_obj = "before\0after".to_python(api).unwrap();
|
|
1114
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1115
|
+
assert_eq!(back, "before\0after");
|
|
1116
|
+
|
|
1117
|
+
api.decref(py_obj);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
#[test]
|
|
1121
|
+
#[serial]
|
|
1122
|
+
fn test_string_special_chars() {
|
|
1123
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1124
|
+
return;
|
|
1125
|
+
};
|
|
1126
|
+
let api = guard.api();
|
|
1127
|
+
|
|
1128
|
+
let py_obj = r#"quotes: "double" 'single' \backslash"#.to_python(api).unwrap();
|
|
1129
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1130
|
+
assert_eq!(back, r#"quotes: "double" 'single' \backslash"#);
|
|
1131
|
+
|
|
1132
|
+
api.decref(py_obj);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
#[test]
|
|
1136
|
+
#[serial]
|
|
1137
|
+
fn test_string_long() {
|
|
1138
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1139
|
+
return;
|
|
1140
|
+
};
|
|
1141
|
+
let api = guard.api();
|
|
1142
|
+
|
|
1143
|
+
let long_str = "a".repeat(10000);
|
|
1144
|
+
let py_obj = long_str.as_str().to_python(api).unwrap();
|
|
1145
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1146
|
+
assert_eq!(back, long_str);
|
|
1147
|
+
|
|
1148
|
+
api.decref(py_obj);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
#[test]
|
|
1152
|
+
#[serial]
|
|
1153
|
+
fn test_string_only_whitespace() {
|
|
1154
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1155
|
+
return;
|
|
1156
|
+
};
|
|
1157
|
+
let api = guard.api();
|
|
1158
|
+
|
|
1159
|
+
let py_obj = " \t\n\r ".to_python(api).unwrap();
|
|
1160
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1161
|
+
assert_eq!(back, " \t\n\r ");
|
|
1162
|
+
|
|
1163
|
+
api.decref(py_obj);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
mod option_edge_cases {
|
|
1168
|
+
use super::*;
|
|
1169
|
+
|
|
1170
|
+
#[test]
|
|
1171
|
+
#[serial]
|
|
1172
|
+
fn test_option_some_f64() {
|
|
1173
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1174
|
+
return;
|
|
1175
|
+
};
|
|
1176
|
+
let api = guard.api();
|
|
1177
|
+
|
|
1178
|
+
let py_obj = Some(std::f64::consts::PI).to_python(api).unwrap();
|
|
1179
|
+
assert_ne!(py_obj, api.py_none);
|
|
1180
|
+
|
|
1181
|
+
let back = f64::from_python(py_obj, api).unwrap();
|
|
1182
|
+
assert!((back - std::f64::consts::PI).abs() < 1e-10);
|
|
1183
|
+
|
|
1184
|
+
api.decref(py_obj);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
#[test]
|
|
1188
|
+
#[serial]
|
|
1189
|
+
fn test_option_some_bool() {
|
|
1190
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1191
|
+
return;
|
|
1192
|
+
};
|
|
1193
|
+
let api = guard.api();
|
|
1194
|
+
|
|
1195
|
+
let py_obj = Some(true).to_python(api).unwrap();
|
|
1196
|
+
assert_ne!(py_obj, api.py_none);
|
|
1197
|
+
assert_eq!(py_obj, api.py_true);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
#[test]
|
|
1201
|
+
#[serial]
|
|
1202
|
+
fn test_option_some_string() {
|
|
1203
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1204
|
+
return;
|
|
1205
|
+
};
|
|
1206
|
+
let api = guard.api();
|
|
1207
|
+
|
|
1208
|
+
let py_obj = Some("hello").to_python(api).unwrap();
|
|
1209
|
+
assert_ne!(py_obj, api.py_none);
|
|
1210
|
+
|
|
1211
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1212
|
+
assert_eq!(back, "hello");
|
|
1213
|
+
|
|
1214
|
+
api.decref(py_obj);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
#[test]
|
|
1218
|
+
#[serial]
|
|
1219
|
+
fn test_option_none_f64() {
|
|
1220
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1221
|
+
return;
|
|
1222
|
+
};
|
|
1223
|
+
let api = guard.api();
|
|
1224
|
+
|
|
1225
|
+
let py_obj = Option::<f64>::None.to_python(api).unwrap();
|
|
1226
|
+
assert_eq!(py_obj, api.py_none);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
#[test]
|
|
1230
|
+
#[serial]
|
|
1231
|
+
fn test_option_none_bool() {
|
|
1232
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1233
|
+
return;
|
|
1234
|
+
};
|
|
1235
|
+
let api = guard.api();
|
|
1236
|
+
|
|
1237
|
+
let py_obj = Option::<bool>::None.to_python(api).unwrap();
|
|
1238
|
+
assert_eq!(py_obj, api.py_none);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
#[test]
|
|
1242
|
+
#[serial]
|
|
1243
|
+
fn test_option_none_string() {
|
|
1244
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1245
|
+
return;
|
|
1246
|
+
};
|
|
1247
|
+
let api = guard.api();
|
|
1248
|
+
|
|
1249
|
+
let py_obj = Option::<&str>::None.to_python(api).unwrap();
|
|
1250
|
+
assert_eq!(py_obj, api.py_none);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
#[test]
|
|
1254
|
+
#[serial]
|
|
1255
|
+
fn test_option_some_zero_is_not_none() {
|
|
1256
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1257
|
+
return;
|
|
1258
|
+
};
|
|
1259
|
+
let api = guard.api();
|
|
1260
|
+
|
|
1261
|
+
let py_obj = Some(0i64).to_python(api).unwrap();
|
|
1262
|
+
assert_ne!(py_obj, api.py_none);
|
|
1263
|
+
|
|
1264
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1265
|
+
assert_eq!(back, 0);
|
|
1266
|
+
|
|
1267
|
+
api.decref(py_obj);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
#[test]
|
|
1271
|
+
#[serial]
|
|
1272
|
+
fn test_option_some_empty_string_is_not_none() {
|
|
1273
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1274
|
+
return;
|
|
1275
|
+
};
|
|
1276
|
+
let api = guard.api();
|
|
1277
|
+
|
|
1278
|
+
let py_obj = Some("").to_python(api).unwrap();
|
|
1279
|
+
assert_ne!(py_obj, api.py_none);
|
|
1280
|
+
|
|
1281
|
+
let back = String::from_python(py_obj, api).unwrap();
|
|
1282
|
+
assert_eq!(back, "");
|
|
1283
|
+
|
|
1284
|
+
api.decref(py_obj);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
#[test]
|
|
1288
|
+
#[serial]
|
|
1289
|
+
fn test_option_some_false_is_not_none() {
|
|
1290
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1291
|
+
return;
|
|
1292
|
+
};
|
|
1293
|
+
let api = guard.api();
|
|
1294
|
+
|
|
1295
|
+
let py_obj = Some(false).to_python(api).unwrap();
|
|
1296
|
+
assert_ne!(py_obj, api.py_none);
|
|
1297
|
+
assert_eq!(py_obj, api.py_false);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
mod i64_edge_cases {
|
|
1302
|
+
use super::*;
|
|
1303
|
+
|
|
1304
|
+
#[test]
|
|
1305
|
+
#[serial]
|
|
1306
|
+
fn test_i64_one() {
|
|
1307
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1308
|
+
return;
|
|
1309
|
+
};
|
|
1310
|
+
let api = guard.api();
|
|
1311
|
+
|
|
1312
|
+
let py_obj = 1i64.to_python(api).unwrap();
|
|
1313
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1314
|
+
assert_eq!(back, 1);
|
|
1315
|
+
|
|
1316
|
+
api.decref(py_obj);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
#[test]
|
|
1320
|
+
#[serial]
|
|
1321
|
+
fn test_i64_negative_one_error_indicator_edge_case() {
|
|
1322
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1323
|
+
return;
|
|
1324
|
+
};
|
|
1325
|
+
let api = guard.api();
|
|
1326
|
+
|
|
1327
|
+
let py_obj = (-1i64).to_python(api).unwrap();
|
|
1328
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1329
|
+
assert_eq!(back, -1);
|
|
1330
|
+
|
|
1331
|
+
api.decref(py_obj);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
#[test]
|
|
1335
|
+
#[serial]
|
|
1336
|
+
fn test_i64_large_positive() {
|
|
1337
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1338
|
+
return;
|
|
1339
|
+
};
|
|
1340
|
+
let api = guard.api();
|
|
1341
|
+
|
|
1342
|
+
let value = 9_000_000_000_000_000_000i64;
|
|
1343
|
+
let py_obj = value.to_python(api).unwrap();
|
|
1344
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1345
|
+
assert_eq!(back, value);
|
|
1346
|
+
|
|
1347
|
+
api.decref(py_obj);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
#[test]
|
|
1351
|
+
#[serial]
|
|
1352
|
+
fn test_i64_large_negative() {
|
|
1353
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1354
|
+
return;
|
|
1355
|
+
};
|
|
1356
|
+
let api = guard.api();
|
|
1357
|
+
|
|
1358
|
+
let value = -9_000_000_000_000_000_000i64;
|
|
1359
|
+
let py_obj = value.to_python(api).unwrap();
|
|
1360
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1361
|
+
assert_eq!(back, value);
|
|
1362
|
+
|
|
1363
|
+
api.decref(py_obj);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
#[test]
|
|
1367
|
+
#[serial]
|
|
1368
|
+
fn test_i64_max_minus_one() {
|
|
1369
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1370
|
+
return;
|
|
1371
|
+
};
|
|
1372
|
+
let api = guard.api();
|
|
1373
|
+
|
|
1374
|
+
let py_obj = (i64::MAX - 1).to_python(api).unwrap();
|
|
1375
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1376
|
+
assert_eq!(back, i64::MAX - 1);
|
|
1377
|
+
|
|
1378
|
+
api.decref(py_obj);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
#[test]
|
|
1382
|
+
#[serial]
|
|
1383
|
+
fn test_i64_min_plus_one() {
|
|
1384
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1385
|
+
return;
|
|
1386
|
+
};
|
|
1387
|
+
let api = guard.api();
|
|
1388
|
+
|
|
1389
|
+
let py_obj = (i64::MIN + 1).to_python(api).unwrap();
|
|
1390
|
+
let back = i64::from_python(py_obj, api).unwrap();
|
|
1391
|
+
assert_eq!(back, i64::MIN + 1);
|
|
1392
|
+
|
|
1393
|
+
api.decref(py_obj);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
#[test]
|
|
1398
|
+
#[serial]
|
|
1399
|
+
fn test_vec_to_python() {
|
|
1400
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1401
|
+
return;
|
|
1402
|
+
};
|
|
1403
|
+
let api = guard.api();
|
|
1404
|
+
|
|
1405
|
+
let vec = vec![1i64, 2, 3];
|
|
1406
|
+
let py_list = vec.to_python(api).unwrap();
|
|
1407
|
+
assert!(!py_list.is_null());
|
|
1408
|
+
assert!(api.list_check(py_list));
|
|
1409
|
+
assert_eq!(api.list_size(py_list), 3);
|
|
1410
|
+
|
|
1411
|
+
let back: Vec<i64> = Vec::from_python(py_list, api).unwrap();
|
|
1412
|
+
assert_eq!(back, vec![1, 2, 3]);
|
|
1413
|
+
|
|
1414
|
+
api.decref(py_list);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
#[test]
|
|
1418
|
+
#[serial]
|
|
1419
|
+
fn test_hashmap_to_python() {
|
|
1420
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1421
|
+
return;
|
|
1422
|
+
};
|
|
1423
|
+
let api = guard.api();
|
|
1424
|
+
|
|
1425
|
+
let mut map = HashMap::new();
|
|
1426
|
+
map.insert("key".to_string(), 42i64);
|
|
1427
|
+
map.insert("another".to_string(), 100i64);
|
|
1428
|
+
|
|
1429
|
+
let py_dict = map.to_python(api).unwrap();
|
|
1430
|
+
assert!(!py_dict.is_null());
|
|
1431
|
+
assert!(api.dict_check(py_dict));
|
|
1432
|
+
assert_eq!(api.dict_size(py_dict), 2);
|
|
1433
|
+
|
|
1434
|
+
let back: HashMap<String, i64> = HashMap::from_python(py_dict, api).unwrap();
|
|
1435
|
+
assert_eq!(back.get("key"), Some(&42));
|
|
1436
|
+
assert_eq!(back.get("another"), Some(&100));
|
|
1437
|
+
|
|
1438
|
+
api.decref(py_dict);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
#[test]
|
|
1442
|
+
#[serial]
|
|
1443
|
+
fn test_nested_collections() {
|
|
1444
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1445
|
+
return;
|
|
1446
|
+
};
|
|
1447
|
+
let api = guard.api();
|
|
1448
|
+
|
|
1449
|
+
let nested = vec![vec![1i64, 2], vec![3, 4]];
|
|
1450
|
+
let py_obj = nested.to_python(api).unwrap();
|
|
1451
|
+
assert!(!py_obj.is_null());
|
|
1452
|
+
|
|
1453
|
+
let back: Vec<Vec<i64>> = Vec::from_python(py_obj, api).unwrap();
|
|
1454
|
+
assert_eq!(back, vec![vec![1, 2], vec![3, 4]]);
|
|
1455
|
+
|
|
1456
|
+
api.decref(py_obj);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
#[test]
|
|
1460
|
+
#[serial]
|
|
1461
|
+
fn test_empty_vec() {
|
|
1462
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1463
|
+
return;
|
|
1464
|
+
};
|
|
1465
|
+
let api = guard.api();
|
|
1466
|
+
|
|
1467
|
+
let empty: Vec<i64> = vec![];
|
|
1468
|
+
let py_list = empty.to_python(api).unwrap();
|
|
1469
|
+
assert!(!py_list.is_null());
|
|
1470
|
+
assert!(api.list_check(py_list));
|
|
1471
|
+
assert_eq!(api.list_size(py_list), 0);
|
|
1472
|
+
|
|
1473
|
+
let back: Vec<i64> = Vec::from_python(py_list, api).unwrap();
|
|
1474
|
+
assert!(back.is_empty());
|
|
1475
|
+
|
|
1476
|
+
api.decref(py_list);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
#[test]
|
|
1480
|
+
#[serial]
|
|
1481
|
+
fn test_empty_hashmap() {
|
|
1482
|
+
let Some(guard) = skip_if_no_python() else {
|
|
1483
|
+
return;
|
|
1484
|
+
};
|
|
1485
|
+
let api = guard.api();
|
|
1486
|
+
|
|
1487
|
+
let empty: HashMap<String, i64> = HashMap::new();
|
|
1488
|
+
let py_dict = empty.to_python(api).unwrap();
|
|
1489
|
+
assert!(!py_dict.is_null());
|
|
1490
|
+
assert!(api.dict_check(py_dict));
|
|
1491
|
+
assert_eq!(api.dict_size(py_dict), 0);
|
|
1492
|
+
|
|
1493
|
+
let back: HashMap<String, i64> = HashMap::from_python(py_dict, api).unwrap();
|
|
1494
|
+
assert!(back.is_empty());
|
|
1495
|
+
|
|
1496
|
+
api.decref(py_dict);
|
|
1497
|
+
}
|
|
1498
|
+
}
|