Skip to content

Commit 75c49fe

Browse files
committed
Python tests can now be debugged by running them as embedded tests within NUnit.
Added PythonTestsRunner project and extra build actions.
1 parent 1f40564 commit 75c49fe

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

.github/workflows/main.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ jobs:
4949
- name: Python Tests
5050
run: pytest
5151

52-
- name: Run Embedding tests
52+
- name: Embedding tests
5353
run: dotnet test --runtime any-${{ matrix.platform }} src/embed_tests/
5454
if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython
5555

56+
- name: Python tests runner
57+
run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/
58+
if: ${{ matrix.os != 'macos' }} # Not working right now, doesn't find libpython
59+
5660
# TODO: Run perf tests
5761
# TODO: Run mono tests on Windows?

pythonnet.sln

+26
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{BC426F42
4747
tools\geninterop\geninterop.py = tools\geninterop\geninterop.py
4848
EndProjectSection
4949
EndProject
50+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PythonTestsRunner", "src\python_tests_runner\Python.PythonTestsRunner.csproj", "{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}"
51+
EndProject
5052
Global
5153
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5254
Debug|Any CPU = Debug|Any CPU
@@ -129,6 +131,30 @@ Global
129131
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x64.Build.0 = Release|x64
130132
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.ActiveCfg = Release|x86
131133
{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}.Release|x86.Build.0 = Release|x86
134+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
135+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|Any CPU.Build.0 = Debug|Any CPU
136+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x64.ActiveCfg = Debug|Any CPU
137+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x64.Build.0 = Debug|Any CPU
138+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x86.ActiveCfg = Debug|Any CPU
139+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Debug|x86.Build.0 = Debug|Any CPU
140+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|Any CPU.ActiveCfg = Release|Any CPU
141+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|Any CPU.Build.0 = Release|Any CPU
142+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x64.ActiveCfg = Release|Any CPU
143+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x64.Build.0 = Release|Any CPU
144+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x86.ActiveCfg = Release|Any CPU
145+
{6CF9EEA0-F865-4536-AABA-739AE3DA971E}.Release|x86.Build.0 = Release|Any CPU
146+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
147+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|Any CPU.Build.0 = Debug|Any CPU
148+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x64.ActiveCfg = Debug|Any CPU
149+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x64.Build.0 = Debug|Any CPU
150+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x86.ActiveCfg = Debug|Any CPU
151+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Debug|x86.Build.0 = Debug|Any CPU
152+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|Any CPU.ActiveCfg = Release|Any CPU
153+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|Any CPU.Build.0 = Release|Any CPU
154+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.ActiveCfg = Release|Any CPU
155+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.Build.0 = Release|Any CPU
156+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.ActiveCfg = Release|Any CPU
157+
{35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.Build.0 = Release|Any CPU
132158
EndGlobalSection
133159
GlobalSection(SolutionProperties) = preSolution
134160
HideSolutionNode = FALSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\runtime\Python.Runtime.csproj" />
9+
</ItemGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="NUnit" Version="3.*" />
13+
<PackageReference Include="NUnit3TestAdapter" Version="3.*">
14+
<PrivateAssets>all</PrivateAssets>
15+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
16+
</PackageReference>
17+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
18+
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Condition="$(MSBuildRuntimeType) == 'Core'">
19+
<Version>1.0.0</Version>
20+
<PrivateAssets>all</PrivateAssets>
21+
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
22+
</PackageReference>
23+
</ItemGroup>
24+
25+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
8+
using NUnit.Framework;
9+
10+
using Python.Runtime;
11+
12+
namespace Python.PythonTestsRunner
13+
{
14+
public class PythonTestRunner
15+
{
16+
[OneTimeSetUp]
17+
public void SetUp()
18+
{
19+
PythonEngine.Initialize();
20+
}
21+
22+
[OneTimeTearDown]
23+
public void Dispose()
24+
{
25+
PythonEngine.Shutdown();
26+
}
27+
28+
/// <summary>
29+
/// Selects the Python tests to be run as embedded tests.
30+
/// </summary>
31+
/// <returns></returns>
32+
static IEnumerable<string[]> PythonTestCases()
33+
{
34+
// Add the test that you want to debug here.
35+
yield return new[] { "test_enum", "test_enum_standard_attrs" };
36+
yield return new[] { "test_generic", "test_missing_generic_type" };
37+
}
38+
39+
/// <summary>
40+
/// Runs a test in src/tests/*.py as an embedded test. This facilitates debugging.
41+
/// </summary>
42+
/// <param name="testFile">The file name without extension</param>
43+
/// <param name="testName">The name of the test method</param>
44+
[TestCaseSource(nameof(PythonTestCases))]
45+
public void RunPythonTest(string testFile, string testName)
46+
{
47+
// Find the tests directory
48+
string folder = typeof(PythonTestRunner).Assembly.Location;
49+
while (Path.GetFileName(folder) != "src")
50+
{
51+
folder = Path.GetDirectoryName(folder);
52+
}
53+
folder = Path.Combine(folder, "tests");
54+
string path = Path.Combine(folder, testFile + ".py");
55+
if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path);
56+
57+
// We could use 'import' below, but importlib gives more helpful error messages than 'import'
58+
// https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
59+
// Because the Python tests sometimes have relative imports, the module name must be inside the tests package
60+
PythonEngine.Exec($@"
61+
import sys
62+
import os
63+
sys.path.append(os.path.dirname(r'{folder}'))
64+
sys.path.append(os.path.join(r'{folder}', 'fixtures'))
65+
import clr
66+
clr.AddReference('Python.Test')
67+
import tests
68+
module_name = 'tests.{testFile}'
69+
file_path = r'{path}'
70+
import importlib.util
71+
spec = importlib.util.spec_from_file_location(module_name, file_path)
72+
module = importlib.util.module_from_spec(spec)
73+
sys.modules[module_name] = module
74+
try:
75+
spec.loader.exec_module(module)
76+
except ImportError as error:
77+
raise ImportError(str(error) + ' when sys.path=' + os.pathsep.join(sys.path))
78+
module.{testName}()
79+
");
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)