Skip to content

Python tests can now be debugged by running them as embedded tests within NUnit #1341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Python tests can now be debugged by running them as embedded tests wi…
…thin NUnit.

Added PythonTestsRunner project and extra build actions.
  • Loading branch information
tminka committed Jan 5, 2021
commit 5bb2566f7985162c74ea88af7e19f259f1c72436
6 changes: 5 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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?
26 changes: 26 additions & 0 deletions pythonnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/python_tests_runner/Python.PythonTestsRunner.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\runtime\Python.Runtime.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="NUnit" Version="3.*" />
<PackageReference Include="NUnit3TestAdapter" Version="3.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Condition="$(MSBuildRuntimeType) == 'Core'">
<Version>1.0.0</Version>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
82 changes: 82 additions & 0 deletions src/python_tests_runner/PythonTestRunner.cs
Original file line number Diff line number Diff line change
@@ -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();
}

/// <summary>
/// Selects the Python tests to be run as embedded tests.
/// </summary>
/// <returns></returns>
static IEnumerable<string[]> 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" };
}

/// <summary>
/// Runs a test in src/tests/*.py as an embedded test. This facilitates debugging.
/// </summary>
/// <param name="testFile">The file name without extension</param>
/// <param name="testName">The name of the test method</param>
[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}()
");
}
}
}