forked from pythonnet/pythonnet
-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathTestFloatToIntConversion.cs
More file actions
258 lines (214 loc) · 8.56 KB
/
Copy pathTestFloatToIntConversion.cs
File metadata and controls
258 lines (214 loc) · 8.56 KB
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
using NUnit.Framework;
using Python.Runtime;
namespace Python.EmbeddingTest
{
/// <summary>
/// Passing a Python float where a .NET integer is expected.
///
/// A float that holds an integral value (e.g. 5.0) is accepted and converted;
/// a non-integral float (e.g. 5.5) is rejected rather than silently truncated.
/// This must hold regardless of whether the target method/constructor has a
/// single signature or several overloads (the latter reproduces Lean's
/// RangeConsolidator(period), which has two int-first constructor overloads).
/// </summary>
public class TestFloatToIntConversion
{
private PyModule _module;
private const string TestModule = @"
from clr import AddReference
AddReference(""Python.EmbeddingTest"")
from Python.EmbeddingTest import IntTaker, OverloadedIntTaker
def single_ctor(value):
return IntTaker(value).Value
def single_method(value):
return IntTaker(0).Echo(value)
def overloaded_ctor(value):
return OverloadedIntTaker(value).Value
def overloaded_method(value):
return OverloadedIntTaker(0).Echo(value)
def single_named(value):
return IntTaker(0).ComputeValue(value)
def overloaded_named(value):
return OverloadedIntTaker(0).ComputeRange(value)
def single_params(value):
return IntTaker(0).ComputeScaled(value)
class FloatSubclass(float):
# numpy.float64-like: a float subclass
pass
class FloatLike:
# numpy.float32-like: float and (truncating) int conversions, no __index__
def __init__(self, v):
self._v = v
def __float__(self):
return float(self._v)
def __int__(self):
return int(self._v)
class IndexLike:
# numpy.int64-like: a true integer type advertising __index__
def __init__(self, v):
self._v = v
def __index__(self):
return int(self._v)
def __float__(self):
return float(self._v)
def single_ctor_float_subclass(value):
return IntTaker(FloatSubclass(value)).Value
def overloaded_ctor_float_subclass(value):
return OverloadedIntTaker(FloatSubclass(value)).Value
def single_ctor_float_like(value):
return IntTaker(FloatLike(value)).Value
def overloaded_ctor_float_like(value):
return OverloadedIntTaker(FloatLike(value)).Value
def single_ctor_index_like(value):
return IntTaker(IndexLike(value)).Value
";
[OneTimeSetUp]
public void Setup()
{
PythonEngine.Initialize();
_module = PyModule.FromString("float_to_int_module", TestModule);
}
[OneTimeTearDown]
public void TearDown()
{
_module.Dispose();
PythonEngine.Shutdown();
}
private int Call(string func, double value)
{
using (Py.GIL())
using (var arg = value.ToPython())
{
return _module.InvokeMethod(func, arg).As<int>();
}
}
// An integral-valued float is accepted and converted, single or overloaded.
[TestCase("single_ctor")]
[TestCase("single_method")]
[TestCase("overloaded_ctor")]
[TestCase("overloaded_method")]
public void IntegralFloat_IsAccepted(string func)
{
Assert.AreEqual(5, Call(func, 5.0));
}
// A non-integral float is rejected (no silent truncation) for every target.
[TestCase("single_ctor")]
[TestCase("single_method")]
[TestCase("overloaded_ctor")]
[TestCase("overloaded_method")]
public void NonIntegralFloat_IsRejected(string func)
{
var ex = Assert.Throws<PythonException>(() => Call(func, 5.5));
Assert.AreEqual("TypeError", ex.Type.Name);
}
// Float subclasses (e.g. numpy.float64) follow the plain-float rule.
[TestCase("single_ctor_float_subclass")]
[TestCase("overloaded_ctor_float_subclass")]
public void IntegralFloatSubclass_IsAccepted(string func)
{
Assert.AreEqual(5, Call(func, 5.0));
}
[TestCase("single_ctor_float_subclass")]
[TestCase("overloaded_ctor_float_subclass")]
public void NonIntegralFloatSubclass_IsRejected(string func)
{
var ex = Assert.Throws<PythonException>(() => Call(func, 5.5));
Assert.AreEqual("TypeError", ex.Type.Name);
}
// __float__-only numbers (e.g. numpy.float32) follow the plain-float rule.
[TestCase("single_ctor_float_like")]
[TestCase("overloaded_ctor_float_like")]
public void IntegralFloatLike_IsAccepted(string func)
{
Assert.AreEqual(5, Call(func, 5.0));
}
[TestCase("single_ctor_float_like")]
[TestCase("overloaded_ctor_float_like")]
public void NonIntegralFloatLike_IsRejected(string func)
{
var ex = Assert.Throws<PythonException>(() => Call(func, 5.5));
Assert.AreEqual("TypeError", ex.Type.Name);
}
// __index__ types (e.g. numpy.int64) are integers, not float-like.
[Test]
public void IndexLike_IsAccepted()
{
Assert.AreEqual(5, Call("single_ctor_index_like", 5.0));
}
// When no overload matches, the error should hint the expected signature(s).
[Test]
public void ErrorMessage_SingleOverload_ShowsExpectedSignature()
{
var ex = Assert.Throws<PythonException>(() => Call("single_ctor", 5.5));
StringAssert.Contains("The expected signature is:", ex.Message);
StringAssert.Contains("value: int", ex.Message);
}
[Test]
public void ErrorMessage_MultipleOverloads_ListsCandidates()
{
var ex = Assert.Throws<PythonException>(() => Call("overloaded_ctor", 5.5));
// The int overload is surfaced, hinting an integer was expected. The
// PyObject overload is skipped (it carries no type information), which
// leaves a single hinted signature here.
StringAssert.Contains("The expected signature is:", ex.Message);
StringAssert.Contains("range: int", ex.Message);
StringAssert.DoesNotContain("volume_selector", ex.Message);
}
// The hinted signatures use the snake_case name Python callers use, not the
// original C# name.
[Test]
public void ErrorMessage_SingleOverload_UsesSnakeCaseMethodName()
{
var ex = Assert.Throws<PythonException>(() => Call("single_named", 5.5));
StringAssert.Contains("compute_value(", ex.Message);
StringAssert.DoesNotContain("ComputeValue", ex.Message);
}
[Test]
public void ErrorMessage_MultipleOverloads_UseSnakeCaseMethodName()
{
var ex = Assert.Throws<PythonException>(() => Call("overloaded_named", 5.5));
StringAssert.Contains("compute_range(", ex.Message);
StringAssert.DoesNotContain("ComputeRange", ex.Message);
}
// The hinted signatures also snake_case the parameter names.
[Test]
public void ErrorMessage_SignatureParameters_AreSnakeCase()
{
var ex = Assert.Throws<PythonException>(() => Call("single_params", 5.5));
StringAssert.Contains("scale_factor", ex.Message);
StringAssert.DoesNotContain("scaleFactor", ex.Message);
}
}
public class IntTaker
{
public int Value { get; }
public IntTaker(int value)
{
Value = value;
}
public int Echo(int value) => value;
public int ComputeValue(int value) => value;
public int ComputeScaled(int scaleFactor) => scaleFactor;
}
/// <summary>
/// Mimics Lean's RangeConsolidator: two overloads that both take an int first
/// parameter, differing only in the (defaulted) later parameters. This forces the
/// binder through its overload-disambiguation path.
/// </summary>
public class OverloadedIntTaker
{
public int Value { get; }
public OverloadedIntTaker(int range, System.Func<int, int> selector = null)
{
Value = range;
}
public OverloadedIntTaker(int range, PyObject selector, PyObject volumeSelector = null)
{
Value = range;
}
public int Echo(int value, System.Func<int, int> selector = null) => value;
public int Echo(int value, PyObject selector, PyObject other = null) => value;
public int ComputeRange(int value, System.Func<int, int> selector = null) => value;
public int ComputeRange(int value, PyObject selector, PyObject other = null) => value;
}
}