| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8bb66fd commit a7af7a8
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,104 @@ | |||
| 1 | + using System; | ||
| 2 | + using System.Collections; | ||
| 3 | + using System.Collections.Generic; | ||
| 4 | + | ||
| 5 | + using NUnit.Framework; | ||
| 6 | + | ||
| 7 | + using Python.Runtime; | ||
| 8 | + | ||
| 9 | + namespace Python.EmbeddingTest | ||
| 10 | + { | ||
| 11 | + /// <summary> | ||
| 12 | + /// A failure while converting a CLR value for Python has to surface as a | ||
| 13 | + /// Python exception that Python code can catch, rather than as a CLR exception | ||
| 14 | + /// unwinding through the interpreter. | ||
| 15 | + /// </summary> | ||
| 16 | + public class TestConversionErrors | ||
| 17 | + { | ||
| 18 | + internal const string Failure = "conversion failed on purpose"; | ||
| 19 | + | ||
| 20 | + [SetUp] | ||
| 21 | + public void SetUp() => PyObjectConversions.RegisterEncoder(new FailingConversionEncoder()); | ||
| 22 | + | ||
| 23 | + [TearDown] | ||
| 24 | + public void TearDown() => PyObjectConversions.Reset(); | ||
| 25 | + | ||
| 26 | + [TestCase("host.Method()", TestName = "MethodReturn")] | ||
| 27 | + [TestCase("host.OutParam(None)", TestName = "OutParameterAndReturn")] | ||
| 28 | + [TestCase("host.OutOnly(None)", TestName = "OutParameterOnly")] | ||
| 29 | + [TestCase("list(host.Iterate())", TestName = "IteratorItem")] | ||
| 30 | + [TestCase("host.Array1[0]", TestName = "ArrayItem")] | ||
| 31 | + [TestCase("host.Array2[0, 0]", TestName = "MultidimensionalArrayItem")] | ||
| 32 | + [TestCase("host[0]", TestName = "IndexerResult")] | ||
| 33 | + public void ConversionFailureIsCatchableInPython(string expression) | ||
| 34 | + { | ||
| 35 | + Assert.That(CaughtInPython(expression), Does.Contain(Failure)); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + [Test] | ||
| 39 | + public void ThrowingCurrentIsCatchableInPython() | ||
| 40 | + { | ||
| 41 | + Assert.That( | ||
| 42 | + CaughtInPython("list(host.ThrowingEnumerable())"), | ||
| 43 | + Does.Contain(ThrowingCurrentEnumerator.Message)); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + /// <summary> | ||
| 47 | + /// Evaluates <paramref name="expression"/> inside a Python try/except and | ||
| 48 | + /// returns what Python caught. If the failure escapes Python instead, the | ||
| 49 | + /// call throws and the test fails. | ||
| 50 | + /// </summary> | ||
| 51 | + static string CaughtInPython(string expression) | ||
| 52 | + { | ||
| 53 | + using var scope = Py.CreateScope(); | ||
| 54 | + scope.Set("host", new ConversionErrorHost()); | ||
| 55 | + scope.Exec($@" | ||
| 56 | + def attempt(): | ||
| 57 | + try: | ||
| 58 | + {expression} | ||
| 59 | + except Exception as e: | ||
| 60 | + return str(e) | ||
| 61 | + return 'nothing raised' | ||
| 62 | + "); | ||
| 63 | + return scope.Eval<string>("attempt()"); | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public class UnconvertibleValue { } | ||
| 68 | + | ||
| 69 | + class FailingConversionEncoder : IPyObjectEncoder | ||
| 70 | + { | ||
| 71 | + public bool CanEncode(Type type) => type == typeof(UnconvertibleValue); | ||
| 72 | + | ||
| 73 | + public PyObject TryEncode(object value) => | ||
| 74 | + throw new InvalidOperationException(TestConversionErrors.Failure); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + /// <summary>Hands an unconvertible value to Python through each boundary.</summary> | ||
| 78 | + public class ConversionErrorHost | ||
| 79 | + { | ||
| 80 | + public UnconvertibleValue Method() => new(); | ||
| 81 | + public int OutParam(out UnconvertibleValue value) { value = new(); return 1; } | ||
| 82 | + public void OutOnly(out UnconvertibleValue value) => value = new(); | ||
| 83 | + public IEnumerable<UnconvertibleValue> Iterate() { yield return new(); } | ||
| 84 | + public UnconvertibleValue[] Array1 = { new() }; | ||
| 85 | + public UnconvertibleValue[,] Array2 = { { new() } }; | ||
| 86 | + public UnconvertibleValue this[int index] => new(); | ||
| 87 | + public IEnumerable ThrowingEnumerable() => new ThrowingCurrentEnumerable(); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + class ThrowingCurrentEnumerable : IEnumerable | ||
| 91 | + { | ||
| 92 | + public IEnumerator GetEnumerator() => new ThrowingCurrentEnumerator(); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + class ThrowingCurrentEnumerator : IEnumerator | ||
| 96 | + { | ||
| 97 | + public const string Message = "Current failed on purpose"; | ||
| 98 | + bool _moved; | ||
| 99 | + | ||
| 100 | + public object Current => throw new InvalidOperationException(Message); | ||
| 101 | + public bool MoveNext() => !_moved && (_moved = true); | ||
| 102 | + public void Reset() => _moved = false; | ||
| 103 | + } | ||
| 104 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -964,43 +964,54 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a | |||
| 964 | 964 | ||
| 965 | 965 | var returnType = binding.info.IsConstructor ? typeof(void) : ((MethodInfo)binding.info).ReturnType; | |
| 966 | 966 | ||
| 967 | - if (binding.outs > 0) | ||
| 967 | + // Converting the results can fail too - a type refused by an | ||
| 968 | + // IClrTypeFilter, for one - and that must become a Python exception | ||
| 969 | + // rather than a CLR exception unwinding through the interpreter | ||
| 970 | + try | ||
| 968 | 971 | { | |
| 969 | - ParameterInfo[] pi = binding.info.GetParameters(); | ||
| 970 | - int c = pi.Length; | ||
| 971 | - var n = 0; | ||
| 972 | - | ||
| 973 | - bool isVoid = returnType == typeof(void); | ||
| 974 | - int tupleSize = binding.outs + (isVoid ? 0 : 1); | ||
| 975 | - using var t = Runtime.PyTuple_New(tupleSize); | ||
| 976 | - if (!isVoid) | ||
| 972 | + if (binding.outs > 0) | ||
| 977 | 973 | { | |
| 978 | - using var v = Converter.ToPython(result, returnType); | ||
| 979 | - Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal()); | ||
| 980 | - n++; | ||
| 981 | - } | ||
| 982 | - | ||
| 983 | - for (var i = 0; i < c; i++) | ||
| 984 | - { | ||
| 985 | - Type pt = pi[i].ParameterType; | ||
| 986 | - if (pt.IsByRef) | ||
| 974 | + ParameterInfo[] pi = binding.info.GetParameters(); | ||
| 975 | + int c = pi.Length; | ||
| 976 | + var n = 0; | ||
| 977 | + | ||
| 978 | + bool isVoid = returnType == typeof(void); | ||
| 979 | + int tupleSize = binding.outs + (isVoid ? 0 : 1); | ||
| 980 | + using var t = Runtime.PyTuple_New(tupleSize); | ||
| 981 | + if (!isVoid) | ||
| 987 | 982 | { | |
| 988 | - using var v = Converter.ToPython(binding.args[i], pt.GetElementType()); | ||
| 983 | + using var v = Converter.ToPython(result, returnType); | ||
| 989 | 984 | Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal()); | |
| 990 | 985 | n++; | |
| 991 | 986 | } | |
| 992 | - } | ||
| 993 | 987 | ||
| 994 | - if (binding.outs == 1 && returnType == typeof(void)) | ||
| 995 | - { | ||
| 996 | - BorrowedReference item = Runtime.PyTuple_GetItem(t.Borrow(), 0); | ||
| 997 | - return new NewReference(item); | ||
| 988 | + for (var i = 0; i < c; i++) | ||
| 989 | + { | ||
| 990 | + Type pt = pi[i].ParameterType; | ||
| 991 | + if (pt.IsByRef) | ||
| 992 | + { | ||
| 993 | + using var v = Converter.ToPython(binding.args[i], pt.GetElementType()); | ||
| 994 | + Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal()); | ||
| 995 | + n++; | ||
| 996 | + } | ||
| 997 | + } | ||
| 998 | + | ||
| 999 | + if (binding.outs == 1 && returnType == typeof(void)) | ||
| 1000 | + { | ||
| 1001 | + BorrowedReference item = Runtime.PyTuple_GetItem(t.Borrow(), 0); | ||
| 1002 | + return new NewReference(item); | ||
| 1003 | + } | ||
| 1004 | + | ||
| 1005 | + return new NewReference(t.Borrow()); | ||
| 998 | 1006 | } | |
| 999 | 1007 | ||
| 1000 | - return new NewReference(t.Borrow()); | ||
| 1008 | + return Converter.ToPython(result, returnType); | ||
| 1009 | + } | ||
| 1010 | + catch (Exception e) | ||
| 1011 | + { | ||
| 1012 | + Exceptions.SetError(e); | ||
| 1013 | + return default; | ||
| 1001 | 1014 | } | |
| 1002 | - | ||
| 1003 | - return Converter.ToPython(result, returnType); | ||
| 1004 | 1015 | } | |
| 1005 | 1016 | } | |
| 1006 | 1017 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -191,7 +191,7 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference | |||
| 191 | 191 | ||
| 192 | 192 | value = items.GetValue(index); | |
| 193 | 193 | ||
| 194 | - return Converter.ToPython(value, itemType); | ||
| 194 | + return ItemToPython(value, itemType); | ||
| 195 | 195 | } | |
| 196 | 196 | ||
| 197 | 197 | // Multi-dimensional arrays can be indexed a la: list[1, 2, 3]. | |
@@ -238,7 +238,22 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference | |||
| 238 | 238 | ||
| 239 | 239 | value = items.GetValue(indices); | |
| 240 | 240 | ||
| 241 | - return Converter.ToPython(value, itemType); | ||
| 241 | + return ItemToPython(value, itemType); | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + // The conversion can throw - for a type refused by an IClrTypeFilter, say - | ||
| 245 | + // and that must become a Python exception rather than unwinding through it | ||
| 246 | + static NewReference ItemToPython(object? value, Type itemType) | ||
| 247 | + { | ||
| 248 | + try | ||
| 249 | + { | ||
| 250 | + return Converter.ToPython(value, itemType); | ||
| 251 | + } | ||
| 252 | + catch (Exception e) | ||
| 253 | + { | ||
| 254 | + Exceptions.SetError(e); | ||
| 255 | + return default; | ||
| 256 | + } | ||
| 242 | 257 | } | |
| 243 | 258 | ||
| 244 | 259 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -42,8 +42,18 @@ public static NewReference tp_iternext(BorrowedReference ob) | |||
| 42 | 42 | Exceptions.SetError(e); | |
| 43 | 43 | return default; | |
| 44 | 44 | } | |
| 45 | - object item = self.iter.Current; | ||
| 46 | - return Converter.ToPython(item, self.elemType); | ||
| 45 | + // Current and the conversion can both throw - the latter for a type | ||
| 46 | + // refused by an IClrTypeFilter - and must not unwind through Python | ||
| 47 | + try | ||
| 48 | + { | ||
| 49 | + object item = self.iter.Current; | ||
| 50 | + return Converter.ToPython(item, self.elemType); | ||
| 51 | + } | ||
| 52 | + catch (Exception e) | ||
| 53 | + { | ||
| 54 | + Exceptions.SetError(e); | ||
| 55 | + return default; | ||
| 56 | + } | ||
| 47 | 57 | } | |
| 48 | 58 | ||
| 49 | 59 | public static NewReference tp_iter(BorrowedReference ob) => new (ob); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments