Skip to content
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

Convert ndarray scalars to Span<T> using the buffer protocol #187

Merged
merged 55 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b778aa8
Add numpy as a dependency for integration tests
tonybaloney Sep 5, 2024
73a9e3d
Expose ndarray scalars using the Python Buffer protocol
tonybaloney Sep 5, 2024
082c9b4
Add float scalars and test
tonybaloney Sep 5, 2024
7ffeb1a
Update src/CSnakes.Runtime/Python/IPyBuffer.cs
tonybaloney Sep 5, 2024
5cae82f
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
d339e8d
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
9dc0ddc
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
cb54930
Update src/CSnakes.Runtime/CPython/Buffer.cs
tonybaloney Sep 5, 2024
9e1f0c1
Update src/CSnakes.Runtime/CPython/Buffer.cs
tonybaloney Sep 5, 2024
0693a1b
Marshall format into a string
tonybaloney Sep 5, 2024
7f52a09
Add more conversions
tonybaloney Sep 5, 2024
6130c13
Using single-point precision for the float32 test
tonybaloney Sep 5, 2024
be317a8
Add byte-order and alignment accessor
tonybaloney Sep 5, 2024
9225cb7
Add the common format string types to the class
tonybaloney Sep 5, 2024
f44a778
Fix the struct sequence and add a 2D test
tonybaloney Sep 5, 2024
ff706ef
Add support for 2D vectors using the performance community extensions
tonybaloney Sep 6, 2024
31bab09
Add a test to verify that is writable
tonybaloney Sep 6, 2024
1840a78
Add some length and size checks
tonybaloney Sep 6, 2024
ec1f92f
Platform independent marshalling
tonybaloney Sep 6, 2024
d4077ab
Remove the Python 3.12 checks
tonybaloney Sep 6, 2024
8ee47fa
Add int8 int16 and readonly span
tonybaloney Sep 6, 2024
514454e
Use concrete readonly span implementations
tonybaloney Sep 6, 2024
8e4db0b
Test bytes and bytearray
tonybaloney Sep 6, 2024
deb3fde
Add a gIL to the initial buffer fetch and releases
tonybaloney Sep 6, 2024
e201c90
Explicitly request C Contiguous arrays incase someone tries to give t…
tonybaloney Sep 6, 2024
3f11ee0
Update Buffer.cs
tonybaloney Sep 6, 2024
80d9e7e
Update Buffer.cs
tonybaloney Sep 6, 2024
5d2333e
Update PyBuffer.cs
tonybaloney Sep 6, 2024
5d3d2c2
Update PyBuffer.cs
tonybaloney Sep 6, 2024
3cc7f97
Test ndarray transposition (non-contiguous array) errors, slicings an…
tonybaloney Sep 6, 2024
547db87
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 6, 2024
75c0abb
Simplify types in API. Add bool, nint and unint conversions
tonybaloney Sep 16, 2024
d93295d
Apply suggestions from code review
tonybaloney Sep 16, 2024
49e06ac
Fix byte order enum parsing
tonybaloney Sep 16, 2024
c4f7948
Platform agnostic implementations
tonybaloney Sep 16, 2024
dabe33a
Add documentation
tonybaloney Sep 16, 2024
aacea48
Merge branch 'main' into numpy_tests
tonybaloney Sep 16, 2024
71d91f0
Make buffer span methods generic and move the convenience methods to …
tonybaloney Sep 17, 2024
3718b78
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 17, 2024
ecf8100
Simplify names
tonybaloney Sep 17, 2024
f9f4bfa
Rename extension class
tonybaloney Sep 17, 2024
d6369b3
Tightening up the code a little
aaronpowell Sep 17, 2024
ec1d3c9
Update docs/buffers.md
tonybaloney Sep 17, 2024
d889bbb
Make a big red box with the warning about editing values
tonybaloney Sep 17, 2024
2c18f85
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 17, 2024
aa110f1
Add remark about contiguous arrays
tonybaloney Sep 17, 2024
befea5c
Rebase from main
tonybaloney Sep 18, 2024
144e30c
Reference Tensor<T>
RussKie Sep 18, 2024
6bb3666
Merge pull request #213 from RussKie/numpy_tests
tonybaloney Sep 18, 2024
8b4dc70
Implement tensor spans
tonybaloney Sep 18, 2024
cdea255
Add extension methods
tonybaloney Sep 18, 2024
835ae1d
Add API type
tonybaloney Sep 18, 2024
3b652ea
Merge remote-tracking branch 'origin/main' into numpy_tests
tonybaloney Sep 18, 2024
f9ea39a
try LD_PRELOAD On tests
tonybaloney Sep 18, 2024
8471a7a
Use generic lib name
tonybaloney Sep 18, 2024
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
60 changes: 60 additions & 0 deletions src/CSnakes.Runtime/CPython/Buffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using CSnakes.Runtime.Python;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
using System.Runtime.InteropServices;

namespace CSnakes.Runtime.CPython;
internal unsafe partial class CPythonAPI
{
private const int PyBUF_SIMPLE = 0;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
private const int PyBUF_WRITABLE = 0x0001;
private const int PyBUF_FORMAT = 0x0004;
private const int PyBUF_ND = 0x0008;
private const int PyBUF_STRIDES = (0x0010 | PyBUF_ND);
private const int PyBUF_C_CONTIGUOUS = (0x0020 | PyBUF_STRIDES);
private const int PyBUF_F_CONTIGUOUS = (0x0040 | PyBUF_STRIDES);
private const int PyBUF_ANY_CONTIGUOUS = (0x0080 | PyBUF_STRIDES);

[StructLayout(LayoutKind.Sequential)]
internal struct Py_buffer
{
public IntPtr buf;
public IntPtr obj;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
public nint len;
public Int32 @readonly;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
public nint itemsize;
public char* format;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
public Int32 ndim;
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
public nint* shape;
public nint* strides;
public nint* suboffsets;
public nint* @internal;
}

public static bool IsBuffer(PyObject p)
{
return PyObject_CheckBuffer(p) == 1;
}
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

internal static Py_buffer GetBuffer(PyObject p)
{
Py_buffer view = default;
if (PyObject_GetBuffer(p, &view, PyBUF_FORMAT) != 0)
{
throw PyObject.ThrowPythonExceptionAsClrException();
}
return view;
}

internal static void ReleaseBuffer(Py_buffer view)
{
PyBuffer_Release(&view);
}
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

[LibraryImport(PythonLibraryName)]
private static partial int PyObject_CheckBuffer(PyObject ob);

[LibraryImport(PythonLibraryName)]
private static partial int PyObject_GetBuffer(PyObject ob, Py_buffer* view, int flags);
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

[LibraryImport(PythonLibraryName)]
private static partial void PyBuffer_Release(Py_buffer* view);
}
1 change: 1 addition & 0 deletions src/CSnakes.Runtime/PyObjectTypeConverter.KnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ internal partial class PyObjectTypeConverter
static readonly Type dictionaryType = typeof(IReadOnlyDictionary<,>);
static readonly Type pyObjectType = typeof(PyObject);
static readonly Type generatorIteratorType = typeof(IGeneratorIterator<,,>);
static readonly Type bufferType = typeof(IPyBuffer);
}
5 changes: 5 additions & 0 deletions src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public static object PyObjectToManagedType(PyObject pyObject, Type destinationTy
return ConvertToList(pyObject, destinationType);
}

if (CPythonAPI.IsBuffer(pyObject) && destinationType.IsAssignableTo(bufferType))
{
return new PyBuffer(pyObject);
}

throw new InvalidCastException($"Attempting to cast {destinationType} from {pyObject.GetPythonType()}");
}

Expand Down
12 changes: 12 additions & 0 deletions src/CSnakes.Runtime/Python/IPyBuffer.cs
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CSnakes.Runtime.Python;
public interface IPyBuffer
{
Int64 Length { get; }

bool Scalar { get; }

Span<Int32> AsInt32Scalar();
Span<Int64> AsInt64Scalar();
Span<UInt32> AsUInt32Scalar();
Span<UInt64> AsUInt64Scalar();
}
65 changes: 65 additions & 0 deletions src/CSnakes.Runtime/Python/PyBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using CSnakes.Runtime.CPython;

namespace CSnakes.Runtime.Python;
internal class PyBuffer : IPyBuffer
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly CPythonAPI.Py_buffer _buffer;

public PyBuffer(PyObject exporter)
{
_buffer = CPythonAPI.GetBuffer(exporter);
}

public void Dispose()
{
CPythonAPI.ReleaseBuffer(_buffer);
}
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

public Int64 Length => _buffer.len;

public bool Scalar => _buffer.ndim == 0;

private unsafe void EnsureFormat(char format)
{
if (!_buffer.format[0].Equals(format))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private unsafe void EnsureFormat(char format)
{
if (!_buffer.format[0].Equals(format))
private unsafe void EnsureFormat(char format)
{
if (!_buffer.format[0] == (byte)format)

{
throw new InvalidOperationException($"Buffer is not a {format}, it is {_buffer.format[0]}");
}
}

private void EnsureScalar()
{
if (!Scalar)
{
throw new InvalidOperationException("Buffer is not a scalar");
}
}
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

public unsafe Span<Int32> AsInt32Scalar()
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
{
EnsureScalar();
EnsureFormat('l'); // TODO: i is also valid
return new Span<Int32>((void*)_buffer.buf, (int)(Length / 4));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return new Span<Int32>((void*)_buffer.buf, (int)(Length / 4));
return new Span<Int32>((void*)_buffer.buf, (int)(Length / sizeof(int)));

}

public unsafe Span<UInt32> AsUInt32Scalar()
{
EnsureScalar();
EnsureFormat('L'); // TODO: I is also valid
return new Span<UInt32>((void*)_buffer.buf, (int)(Length / 4));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return new Span<UInt32>((void*)_buffer.buf, (int)(Length / 4));
return new Span<UInt32>((void*)_buffer.buf, (int)(Length / sizeof(uint)));

}

public unsafe Span<Int64> AsInt64Scalar()
{
EnsureScalar();
EnsureFormat('q');
return new Span<Int64>((void*)_buffer.buf, (int)(Length / 8));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return new Span<Int64>((void*)_buffer.buf, (int)(Length / 8));
return new Span<Int64>((void*)_buffer.buf, (int)(Length / sizeof(long)));

}

public unsafe Span<UInt64> AsUInt64Scalar()
{
EnsureScalar();
EnsureFormat('Q');
return new Span<UInt64>((void*)_buffer.buf, (int)(Length / 8));
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 1 addition & 1 deletion src/CSnakes.Tests/GeneratedSignatureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public class GeneratedSignatureTests(TestEnvironment testEnv) : IClassFixture<Te
[InlineData("def hello(a: int = 2147483648) -> None:\n ...\n", "void Hello(long a = 2147483648L)")]
[InlineData("def hello(a: Optional[int] = None) -> None:\n ...\n", "void Hello(long? a = null)")]
[InlineData("def hello(a: typing.List[int], b: typing.Dict[str, int]) -> typing.Tuple[str, int]:\n ...\n", "public (string, long) Hello(IReadOnlyList<long> a, IReadOnlyDictionary<string, long> b)")]
[InlineData("def hello() -> Buffer:\n ...\n", "IPyBuffer Hello()")]
public void TestGeneratedSignature(string code, string expected)
{

var tempName = string.Format("{0}_{1:N}", "test", Guid.NewGuid().ToString("N"));
File.WriteAllText(Path.Combine(testEnv.TempDir, $"{tempName}.py"), code);

Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Tests/TypeReflectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private static void ParsingTestInternal(string pythonType, string expectedType)
var result = PythonParser.PythonTypeDefinitionTokenizer.TryParse(tokens);
Assert.True(result.HasValue);
Assert.NotNull(result.Value);
var reflectedType = TypeReflection.AsPredefinedType(result.Value);
var reflectedType = TypeReflection.AsPredefinedType(result.Value, TypeReflection.ConversionDirection.FromPython);
Assert.Equal(expectedType, reflectedType.ToString());
}
}
4 changes: 2 additions & 2 deletions src/CSnakes/Reflection/ArgumentReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class ArgumentReflection
TypeSyntax reflectedType = parameter.ParameterType switch
{
PythonFunctionParameterType.Star => ArrayPyObject,
PythonFunctionParameterType.DoubleStar => TypeReflection.AsPredefinedType(DictStrAny),
PythonFunctionParameterType.Normal => TypeReflection.AsPredefinedType(parameter.Type),
PythonFunctionParameterType.DoubleStar => TypeReflection.AsPredefinedType(DictStrAny, TypeReflection.ConversionDirection.ToPython),
PythonFunctionParameterType.Normal => TypeReflection.AsPredefinedType(parameter.Type, TypeReflection.ConversionDirection.ToPython),
_ => throw new NotImplementedException()
};

Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes/Reflection/MethodReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str
}
else
{
returnSyntax = TypeReflection.AsPredefinedType(returnPythonType);
returnSyntax = TypeReflection.AsPredefinedType(returnPythonType, TypeReflection.ConversionDirection.FromPython);
}

// Step 3: Build arguments
Expand Down Expand Up @@ -107,7 +107,7 @@ public static MethodDefinition FromMethod(PythonFunctionDefinition function, str
ReturnStatementSyntax returnExpression = returnSyntax switch
{
TypeSyntax s when s is PredefinedTypeSyntax p && p.Keyword.IsKind(SyntaxKind.VoidKeyword) => ReturnStatement(null),
TypeSyntax s when s is IdentifierNameSyntax => ReturnStatement(IdentifierName("__result_pyObject")),
TypeSyntax s when s is IdentifierNameSyntax && ((IdentifierNameSyntax)s)?.Identifier.ValueText == "PyObject" => ReturnStatement(IdentifierName("__result_pyObject")),
_ => ProcessMethodWithReturnType(returnSyntax, parameterGenericArgs)
};

Expand Down
67 changes: 39 additions & 28 deletions src/CSnakes/Reflection/TypeReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,77 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using CSnakes.Parser.Types;
using System.ComponentModel;

namespace CSnakes.Reflection;

public static class TypeReflection
{
public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType)
public enum ConversionDirection
{
ToPython,
FromPython
}

public static TypeSyntax AsPredefinedType(PythonTypeSpec pythonType, ConversionDirection direction)
{
// If type is an alias, e.g. "list[int]", "list[float]", etc.
if (pythonType.HasArguments())
{
// Get last occurrence of ] in pythonType
return pythonType.Name.Replace("typing.", "") switch
{
"list" => CreateListType(pythonType.Arguments[0]),
"List" => CreateListType(pythonType.Arguments[0]),
"Tuple" => CreateTupleType(pythonType.Arguments),
"tuple" => CreateTupleType(pythonType.Arguments),
"dict" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1]),
"Dict" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1]),
"Mapping" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1]),
"Sequence" => CreateListType(pythonType.Arguments[0]),
"Optional" => AsPredefinedType(pythonType.Arguments[0]),
"Generator" => CreateGeneratorType(pythonType.Arguments[0], pythonType.Arguments[1], pythonType.Arguments[2]),
"list" => CreateListType(pythonType.Arguments[0], direction),
"List" => CreateListType(pythonType.Arguments[0], direction),
"Tuple" => CreateTupleType(pythonType.Arguments, direction),
"tuple" => CreateTupleType(pythonType.Arguments, direction),
"dict" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1], direction),
"Dict" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1], direction),
"Mapping" => CreateDictionaryType(pythonType.Arguments[0], pythonType.Arguments[1], direction),
"Sequence" => CreateListType(pythonType.Arguments[0], direction),
"Optional" => AsPredefinedType(pythonType.Arguments[0], direction),
"Generator" => CreateGeneratorType(pythonType.Arguments[0], pythonType.Arguments[1], pythonType.Arguments[2], direction),

// Todo more types... see https://docs.python.org/3/library/stdtypes.html#standard-generic-classes
_ => SyntaxFactory.ParseTypeName("PyObject"),
};
}
return pythonType.Name switch
return (pythonType.Name, direction) switch
{
"int" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)),
"str" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)),
"float" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)),
"bool" => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
"bytes" => SyntaxFactory.ParseTypeName("byte[]"),
("int", _) => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)),
("str", _) => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)),
("float", _) => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)),
("bool", _) => SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
("bytes", _) => SyntaxFactory.ParseTypeName("byte[]"),
("Buffer", ConversionDirection.FromPython) => SyntaxFactory.ParseTypeName("IPyBuffer"),
("typing.Buffer", ConversionDirection.FromPython) => SyntaxFactory.ParseTypeName("IPyBuffer"),
("collections.abc.Buffer", ConversionDirection.FromPython) => SyntaxFactory.ParseTypeName("IPyBuffer"),
_ => SyntaxFactory.ParseTypeName("PyObject"),
};
}

private static TypeSyntax CreateDictionaryType(PythonTypeSpec keyType, PythonTypeSpec valueType) => CreateGenericType("IReadOnlyDictionary", [
AsPredefinedType(keyType),
AsPredefinedType(valueType)
private static TypeSyntax CreateDictionaryType(PythonTypeSpec keyType, PythonTypeSpec valueType, ConversionDirection direction) => CreateGenericType("IReadOnlyDictionary", [
AsPredefinedType(keyType, direction),
AsPredefinedType(valueType, direction)
]);

private static TypeSyntax CreateGeneratorType(PythonTypeSpec yieldType, PythonTypeSpec sendType, PythonTypeSpec returnType) => CreateGenericType("IGeneratorIterator", [
AsPredefinedType(yieldType),
AsPredefinedType(sendType),
AsPredefinedType(returnType)
private static TypeSyntax CreateGeneratorType(PythonTypeSpec yieldType, PythonTypeSpec sendType, PythonTypeSpec returnType, ConversionDirection direction) => CreateGenericType("IGeneratorIterator", [
AsPredefinedType(yieldType, direction),
AsPredefinedType(sendType, direction),
AsPredefinedType(returnType, direction)
]);

private static TypeSyntax CreateTupleType(PythonTypeSpec[] tupleTypes)
private static TypeSyntax CreateTupleType(PythonTypeSpec[] tupleTypes, ConversionDirection direction)
{
if (tupleTypes.Length == 1)
{
return CreateGenericType("ValueTuple", tupleTypes.Select(AsPredefinedType));
return CreateGenericType("ValueTuple", tupleTypes.Select(t => AsPredefinedType(t, direction)));
}

IEnumerable<TupleElementSyntax> tupleTypeSyntaxGroups = tupleTypes.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / 7)
.Select(x => x.Select(v => v.Value))
.Select(typeSpecs => typeSpecs.Select(AsPredefinedType))
.Select(typeSpecs => typeSpecs.Select(t => AsPredefinedType(t, direction)))
.SelectMany(item => item.Select(SyntaxFactory.TupleElement));

return SyntaxFactory.TupleType(
Expand All @@ -70,7 +81,7 @@ private static TypeSyntax CreateTupleType(PythonTypeSpec[] tupleTypes)
SyntaxFactory.Token(SyntaxKind.CloseParenToken));
}

private static TypeSyntax CreateListType(PythonTypeSpec genericOf) => CreateGenericType("IReadOnlyList", [AsPredefinedType(genericOf)]);
private static TypeSyntax CreateListType(PythonTypeSpec genericOf, ConversionDirection direction) => CreateGenericType("IReadOnlyList", [AsPredefinedType(genericOf, direction)]);

internal static TypeSyntax CreateGenericType(string typeName, IEnumerable<TypeSyntax> genericArguments) =>
SyntaxFactory.GenericName(
Expand Down
24 changes: 24 additions & 0 deletions src/Integration.Tests/BufferTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Integration.Tests;

public class BufferTests : IntegrationTestBase
{
[Fact]
public void TestSimpleBuffer()
{
// SKip if < Python 3.12
if (new Version(Env.Version.Split(' ')[0]) < new Version(3, 11, 0))
{
return;
}
var testModule = Env.TestBuffer();
var bufferObject = testModule.TestSimpleBuffer();
Assert.Equal(20, bufferObject.Length); // 5 * sizeof(int)
Assert.True(bufferObject.Scalar);

// Check the buffer contents
Span<Int32> result = bufferObject.AsInt32Scalar();
Assert.Equal((int)1, result[0]);
Assert.Equal((int)5, result[4]);

}
}
6 changes: 5 additions & 1 deletion src/Integration.Tests/Integration.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
Expand All @@ -10,6 +10,7 @@
<None Remove="python\requirements.txt" />
<None Remove="python\test_args.py" />
<None Remove="python\test_basic.py" />
<None Remove="python\test_buffer.py" />
<None Remove="python\test_defaults.py" />
<None Remove="python\test_dependency.py" />
<None Remove="python\test_dicts.py" />
Expand All @@ -29,6 +30,9 @@
<AdditionalFiles Include="python\test_basic.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
<AdditionalFiles Include="python\test_buffer.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
<AdditionalFiles Include="python\test_defaults.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
Expand Down
4 changes: 3 additions & 1 deletion src/Integration.Tests/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
httpx
httpx
numpy
typing-extensions
10 changes: 10 additions & 0 deletions src/Integration.Tests/python/test_buffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
try:
from collections.abc import Buffer
except ImportError:
from typing_extensions import Buffer

import numpy as np


def test_simple_buffer() -> Buffer:
return np.array([1, 2, 3, 4, 5], dtype=np.int32)
Loading