From 5bb2566f7985162c74ea88af7e19f259f1c72436 Mon Sep 17 00:00:00 2001 From: Tom Minka <8955276+tminka@users.noreply.github.com> Date: Thu, 31 Dec 2020 22:02:24 +0000 Subject: [PATCH 1/4] Python tests can now be debugged by running them as embedded tests within NUnit. Added PythonTestsRunner project and extra build actions. --- .github/workflows/main.yml | 6 +- pythonnet.sln | 26 ++++++ .../Python.PythonTestsRunner.csproj | 25 ++++++ src/python_tests_runner/PythonTestRunner.cs | 82 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/python_tests_runner/Python.PythonTestsRunner.csproj create mode 100644 src/python_tests_runner/PythonTestRunner.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8137d0b0a..3ca6a27bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,9 +49,13 @@ jobs: - name: Python Tests run: pytest - - name: Run Embedding tests + - name: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython + - name: Python tests runner + run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ + if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython + # TODO: Run perf tests # TODO: Run mono tests on Windows? diff --git a/pythonnet.sln b/pythonnet.sln index fcad97d5c..e0fbeede7 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{BC426F42 tools\geninterop\geninterop.py = tools\geninterop\geninterop.py EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PythonTestsRunner", "src\python_tests_runner\Python.PythonTestsRunner.csproj", "{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,6 +131,30 @@ Global {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x64.Build.0 = Release|x64 {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.ActiveCfg = Release|x86 {4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.Build.0 = Release|x86 + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x64.Build.0 = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x86.Build.0 = Debug|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|Any CPU.Build.0 = Release|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x64.ActiveCfg = Release|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x64.Build.0 = Release|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x86.ActiveCfg = Release|Any CPU + {6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x86.Build.0 = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x64.ActiveCfg = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x64.Build.0 = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x86.ActiveCfg = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x86.Build.0 = Debug|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|Any CPU.Build.0 = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.ActiveCfg = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.Build.0 = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.ActiveCfg = Release|Any CPU + {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj new file mode 100644 index 000000000..2d6544614 --- /dev/null +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -0,0 +1,25 @@ + + + + net472;netcoreapp3.1 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + 1.0.0 + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs new file mode 100644 index 000000000..79b15700e --- /dev/null +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.PythonTestsRunner +{ + public class PythonTestRunner + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + /// + /// Selects the Python tests to be run as embedded tests. + /// + /// + static IEnumerable PythonTestCases() + { + // Add the test that you want to debug here. + yield return new[] { "test_enum", "test_enum_standard_attrs" }; + yield return new[] { "test_generic", "test_missing_generic_type" }; + } + + /// + /// Runs a test in src/tests/*.py as an embedded test. This facilitates debugging. + /// + /// The file name without extension + /// The name of the test method + [TestCaseSource(nameof(PythonTestCases))] + public void RunPythonTest(string testFile, string testName) + { + // Find the tests directory + string folder = typeof(PythonTestRunner).Assembly.Location; + while (Path.GetFileName(folder) != "src") + { + folder = Path.GetDirectoryName(folder); + } + folder = Path.Combine(folder, "tests"); + string path = Path.Combine(folder, testFile + ".py"); + if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path); + + // We could use 'import' below, but importlib gives more helpful error messages than 'import' + // https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly + // Because the Python tests sometimes have relative imports, the module name must be inside the tests package + PythonEngine.Exec($@" +import sys +import os +sys.path.append(os.path.dirname(r'{folder}')) +sys.path.append(os.path.join(r'{folder}', 'fixtures')) +import clr +clr.AddReference('Python.Test') +import tests +module_name = 'tests.{testFile}' +file_path = r'{path}' +import importlib.util +spec = importlib.util.spec_from_file_location(module_name, file_path) +module = importlib.util.module_from_spec(spec) +sys.modules[module_name] = module +try: + spec.loader.exec_module(module) +except ImportError as error: + raise ImportError(str(error) + ' when sys.path=' + os.pathsep.join(sys.path)) +module.{testName}() +"); + } + } +} From 0ccc443d905afa56f04ec48ffdfddf61b3a13955 Mon Sep 17 00:00:00 2001 From: Tom Minka <8955276+tminka@users.noreply.github.com> Date: Mon, 4 Jan 2021 23:06:48 +0000 Subject: [PATCH 2/4] ImportHook preserves the original exception message when an import fails --- .github/workflows/main.yml | 4 ++-- src/runtime/importhook.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ca6a27bb..7c53d7522 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,9 +53,9 @@ jobs: run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/ if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython - - name: Python tests runner + - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ - if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython + if: ${{ matrix.os == 'windows' }} # Not working for others right now # TODO: Run perf tests # TODO: Run mono tests on Windows? diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index d8f7e4dcc..c5c81e0c3 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -291,6 +291,9 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) // We don't support them anyway return IntPtr.Zero; } + // Save the exception + var originalException = new PythonException(); + var originalExceptionMessage = originalException.ToString(); // Otherwise, just clear the it. Exceptions.Clear(); @@ -342,7 +345,7 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) ManagedType mt = tail.GetAttribute(name, true); if (!(mt is ModuleObject)) { - Exceptions.SetError(Exceptions.ImportError, $"No module named {name}"); + Exceptions.SetError(Exceptions.ImportError, originalExceptionMessage); return IntPtr.Zero; } if (head == null) From 0a88f27fa19959a6cf0c7f0ca43e5c1720839a39 Mon Sep 17 00:00:00 2001 From: Tom Minka <8955276+tminka@users.noreply.github.com> Date: Tue, 5 Jan 2021 00:27:36 +0000 Subject: [PATCH 3/4] Use PythonException.Restore --- src/runtime/importhook.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index c5c81e0c3..af6174188 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -293,7 +293,6 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) } // Save the exception var originalException = new PythonException(); - var originalExceptionMessage = originalException.ToString(); // Otherwise, just clear the it. Exceptions.Clear(); @@ -345,7 +344,7 @@ public static IntPtr __import__(IntPtr self, IntPtr args, IntPtr kw) ManagedType mt = tail.GetAttribute(name, true); if (!(mt is ModuleObject)) { - Exceptions.SetError(Exceptions.ImportError, originalExceptionMessage); + originalException.Restore(); return IntPtr.Zero; } if (head == null) From 4ac44fee0edaa5da94c18266bc5126921a2e60a6 Mon Sep 17 00:00:00 2001 From: Tom Minka <8955276+tminka@users.noreply.github.com> Date: Tue, 5 Jan 2021 00:31:53 +0000 Subject: [PATCH 4/4] Updated CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1442075ef..53c45f419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ details about the cause of the failure - Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects - Fixed objects returned by enumerating `PyObject` being disposed too soon - Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException +- `import` may now raise errors with more detail than "No module named X" ### Removed pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy