From 17289d073580d3b1d733bfd8478df5e70bea26e3 Mon Sep 17 00:00:00 2001 From: haoxuanjng-lang Date: Fri, 9 Oct 2026 14:22:56 +0800 Subject: [PATCH 1/3] fix: generate signed byte types for VT_I1 --- comtypes/test/test_cpp_safearray.py | 14 ++------ comtypes/test/test_tlbparser.py | 51 +++++++++++++++++++++++++++++ comtypes/tools/tlbparser.py | 3 +- 3 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 comtypes/test/test_tlbparser.py diff --git a/comtypes/test/test_cpp_safearray.py b/comtypes/test/test_cpp_safearray.py index 1c8e77d2f..f39d5b575 100644 --- a/comtypes/test/test_cpp_safearray.py +++ b/comtypes/test/test_cpp_safearray.py @@ -35,6 +35,7 @@ def setUp(self): ) # Fixed expected values come from the C++ fixture, not Python input. self.cases_1d = [ + (self.server.GetInt8Array, "int8", (-7, 0, 42, -7, 0, 42)), (self.server.GetUint8Array, "uint8", (0, 42, 255, 0, 42, 255)), (self.server.GetInt16Array, "int16", (-7, 0, 42, -7, 0, 42)), (self.server.GetUint16Array, "uint16", (0, 42, 65535, 0, 42, 65535)), @@ -63,6 +64,7 @@ def setUp(self): ] # SAFEARRAY storage is column-major; the fixture has two rows. self.cases_2d = [ + (self.server.GetInt8Array, "int8", ((-7, 42, 0), (0, -7, 42))), (self.server.GetUint8Array, "uint8", ((0, 255, 42), (42, 0, 255))), (self.server.GetInt16Array, "int16", ((-7, 42, 0), (0, -7, 42))), (self.server.GetUint16Array, "uint16", ((0, 65535, 42), (42, 0, 65535))), @@ -140,18 +142,6 @@ def test_ndarray_values_2d(self): numpy.testing.assert_array_equal(result, expected) self.assertEqual(method(2, 6), expected) - def test_int8_codegen_1d(self): - # VT_I1 is currently parsed as c_char (see #935), whose pointer slice - # returns bytes. Check the real COM/code-generator path and raw bytes; - # signed int8/NumPy dtype assertions await the separate c_char/c_byte fix. - result = self.server.GetInt8Array(1, 6) - self.assertEqual(result, (249, 0, 42, 249, 0, 42)) - - def test_int8_codegen_2d(self): - # As above, 249 is the raw byte for -7, not a signed dtype assertion. - result = self.server.GetInt8Array(2, 6) - self.assertEqual(result, ((249, 42, 0), (0, 249, 42))) - if __name__ == "__main__": unittest.main() diff --git a/comtypes/test/test_tlbparser.py b/comtypes/test/test_tlbparser.py new file mode 100644 index 000000000..1f3e4327a --- /dev/null +++ b/comtypes/test/test_tlbparser.py @@ -0,0 +1,51 @@ +import ctypes +import unittest + +from comtypes import automation, typeinfo +from comtypes.tools.codegenerator.helpers import TypeNamer +from comtypes.tools.tlbparser import Parser + + +class ByteTypeTest(unittest.TestCase): + def make_ctype(self, desc): + # Exercise the ctypes expression used in generated COM signatures. + typ = Parser().make_type(desc, None) + namespace = dict(vars(ctypes), STRING=ctypes.c_char_p) + return eval(TypeNamer()(typ), namespace) + + def test_signed_byte(self): + desc = typeinfo.TYPEDESC() + desc.vt = automation.VT_I1 + ctype = self.make_ctype(desc) + for value in (-128, -7, 0, 127): + with self.subTest(value=value): + self.assertEqual(ctype(value).value, value) + + def test_signed_byte_pointer(self): + element = typeinfo.TYPEDESC() + element.vt = automation.VT_I1 + desc = typeinfo.TYPEDESC() + desc.vt = automation.VT_PTR + desc._.lptdesc = ctypes.pointer(element) + ctype = self.make_ctype(desc) + value = ctypes.c_byte(-7) + pointer = ctypes.cast(ctypes.pointer(value), ctype) + self.assertEqual(pointer.contents.value, -7) + + def test_unsigned_byte(self): + desc = typeinfo.TYPEDESC() + desc.vt = automation.VT_UI1 + ctype = self.make_ctype(desc) + for value in (0, 42, 255): + with self.subTest(value=value): + self.assertEqual(ctype(value).value, value) + + def test_narrow_string(self): + desc = typeinfo.TYPEDESC() + desc.vt = automation.VT_LPSTR + ctype = self.make_ctype(desc) + self.assertEqual(ctype(b"hello").value, b"hello") + + +if __name__ == "__main__": + unittest.main() diff --git a/comtypes/tools/tlbparser.py b/comtypes/tools/tlbparser.py index cad940d7f..3bc194e76 100644 --- a/comtypes/tools/tlbparser.py +++ b/comtypes/tools/tlbparser.py @@ -21,6 +21,7 @@ def PTR(typ): # basic C data types, with size and alignment in bits char_type = typedesc.FundamentalType("char", 8, 8) +schar_type = typedesc.FundamentalType("signed char", 8, 8) uchar_type = typedesc.FundamentalType("unsigned char", 8, 8) wchar_t_type = typedesc.FundamentalType("wchar_t", 16, 16) short_type = typedesc.FundamentalType("short int", 16, 16) @@ -81,7 +82,7 @@ def midlSAFEARRAY(typ): automation.VT_VARIANT: VARIANT_type, # 12 automation.VT_UNKNOWN: PTR(IUNKNOWN_type), # 13 automation.VT_DECIMAL: DECIMAL_type, # 14 - automation.VT_I1: char_type, # 16 + automation.VT_I1: schar_type, # 16 automation.VT_UI1: uchar_type, # 17 automation.VT_UI2: ushort_type, # 18 automation.VT_UI4: ulong_type, # 19 From 8b796736d0f2b1aba44265b8fb55fa5c100f3470 Mon Sep 17 00:00:00 2001 From: haoxuanjng-lang Date: Sat, 10 Oct 2026 18:01:45 +0800 Subject: [PATCH 2/3] test: exercise byte types across native COM boundary --- comtypes/test/test_cpp_byte_echo.py | 64 +++++++++++++++++++ comtypes/test/test_tlbparser.py | 51 --------------- source/CppTestSrv/CoByteEchoTest.cpp | 74 ++++++++++++++++++++++ source/CppTestSrv/CoByteEchoTest.h | 19 ++++++ source/CppTestSrv/CoNumericSafearrayTest.h | 2 - source/CppTestSrv/MAKEFILE | 7 +- source/CppTestSrv/SERVER.CPP | 7 ++ source/CppTestSrv/SERVER.IDL | 23 +++++++ 8 files changed, 193 insertions(+), 54 deletions(-) create mode 100644 comtypes/test/test_cpp_byte_echo.py delete mode 100644 comtypes/test/test_tlbparser.py create mode 100644 source/CppTestSrv/CoByteEchoTest.cpp create mode 100644 source/CppTestSrv/CoByteEchoTest.h diff --git a/comtypes/test/test_cpp_byte_echo.py b/comtypes/test/test_cpp_byte_echo.py new file mode 100644 index 000000000..c008723b3 --- /dev/null +++ b/comtypes/test/test_cpp_byte_echo.py @@ -0,0 +1,64 @@ +"""Byte and string round trips through generated interfaces and native COM.""" + +import unittest +from ctypes import byref, c_char_p, c_void_p, cast + +from comtypes import CLSCTX_LOCAL_SERVER +from comtypes.client import CreateObject, GetModule +from comtypes.malloc import _CoTaskMemFree + +try: + GetModule(("{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}", 1, 0, 0)) + from comtypes.gen.ComtypesCppTestSrvLib import CoByteEchoTest, IByteEchoTest + + IMPORT_SERVER_FAILED = False +except (ImportError, OSError): + IMPORT_SERVER_FAILED = True + + +@unittest.skipIf(IMPORT_SERVER_FAILED, "Requires the C++ out-of-process COM server.") +class ByteEchoTest(unittest.TestCase): + def setUp(self): + self.server = CreateObject( + CoByteEchoTest, + clsctx=CLSCTX_LOCAL_SERVER, + interface=IByteEchoTest, + ) + + def test_negative_int8(self): + for value in (-128, -7, -1): + with self.subTest(value=value): + self.assertEqual(self.server.EchoInt8(value), value) + + def test_nonnegative_int8(self): + for value in (0, 1, 127): + with self.subTest(value=value): + self.assertEqual(self.server.EchoInt8(value), value) + + def test_uint8(self): + for value in (0, 1, 42, 128, 255): + with self.subTest(value=value): + self.assertEqual(self.server.EchoUint8(value), value) + + def test_lpstr(self): + for value in (b"", b"hello", b"\x80\xff"): + with self.subTest(value=value): + result = c_char_p() + try: + # Use the generated raw method to retain the allocated address. + # The high-level result is bytes, which loses that address. + self.server._IByteEchoTest__com_EchoLpStr(value, byref(result)) + self.assertEqual(result.value, value) + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + @unittest.expectedFailure + def test_legacy_char_pointer_bytes(self): + # Plain char* was previously inferred as STRING. Changing VT_I1 to + # signed char loses this implicit bytes input, unlike explicit LPSTR. + # Keep the compatibility regression visible for the review decision. + self.assertEqual(self.server.ReadCharPointer(b"h"), ord("h")) + + +if __name__ == "__main__": + unittest.main() diff --git a/comtypes/test/test_tlbparser.py b/comtypes/test/test_tlbparser.py deleted file mode 100644 index 1f3e4327a..000000000 --- a/comtypes/test/test_tlbparser.py +++ /dev/null @@ -1,51 +0,0 @@ -import ctypes -import unittest - -from comtypes import automation, typeinfo -from comtypes.tools.codegenerator.helpers import TypeNamer -from comtypes.tools.tlbparser import Parser - - -class ByteTypeTest(unittest.TestCase): - def make_ctype(self, desc): - # Exercise the ctypes expression used in generated COM signatures. - typ = Parser().make_type(desc, None) - namespace = dict(vars(ctypes), STRING=ctypes.c_char_p) - return eval(TypeNamer()(typ), namespace) - - def test_signed_byte(self): - desc = typeinfo.TYPEDESC() - desc.vt = automation.VT_I1 - ctype = self.make_ctype(desc) - for value in (-128, -7, 0, 127): - with self.subTest(value=value): - self.assertEqual(ctype(value).value, value) - - def test_signed_byte_pointer(self): - element = typeinfo.TYPEDESC() - element.vt = automation.VT_I1 - desc = typeinfo.TYPEDESC() - desc.vt = automation.VT_PTR - desc._.lptdesc = ctypes.pointer(element) - ctype = self.make_ctype(desc) - value = ctypes.c_byte(-7) - pointer = ctypes.cast(ctypes.pointer(value), ctype) - self.assertEqual(pointer.contents.value, -7) - - def test_unsigned_byte(self): - desc = typeinfo.TYPEDESC() - desc.vt = automation.VT_UI1 - ctype = self.make_ctype(desc) - for value in (0, 42, 255): - with self.subTest(value=value): - self.assertEqual(ctype(value).value, value) - - def test_narrow_string(self): - desc = typeinfo.TYPEDESC() - desc.vt = automation.VT_LPSTR - ctype = self.make_ctype(desc) - self.assertEqual(ctype(b"hello").value, b"hello") - - -if __name__ == "__main__": - unittest.main() diff --git a/source/CppTestSrv/CoByteEchoTest.cpp b/source/CppTestSrv/CoByteEchoTest.cpp new file mode 100644 index 000000000..a990f42d4 --- /dev/null +++ b/source/CppTestSrv/CoByteEchoTest.cpp @@ -0,0 +1,74 @@ +#include +#include +#include "CoByteEchoTest.h" + +HRESULT CByteEchoTest::CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) +{ + if (pUnknownOuter != NULL) + { + return CLASS_E_NOAGGREGATION ; + } + *ppNewComponent = new CByteEchoTest(pUnknownOuter) ; + return S_OK ; +} + +HRESULT __stdcall CByteEchoTest::NondelegatingQueryInterface(const IID& iid, void** ppv) +{ + if (iid == IID_IByteEchoTest) + { + return FinishQI(static_cast(this), ppv) ; + } + return CUnknown::NondelegatingQueryInterface(iid, ppv) ; +} + +HRESULT __stdcall CByteEchoTest::EchoInt8(char value, char* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = value ; + return S_OK ; +} + +HRESULT __stdcall CByteEchoTest::EchoUint8(byte value, byte* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = value ; + return S_OK ; +} + +HRESULT __stdcall CByteEchoTest::EchoLpStr(LPSTR value, LPSTR* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = NULL ; + if (value == NULL) + { + return E_INVALIDARG ; + } + size_t size = strlen(value) + 1 ; + *result = static_cast(CoTaskMemAlloc(size)) ; + if (*result == NULL) + { + return E_OUTOFMEMORY ; + } + memcpy(*result, value, size) ; + return S_OK ; +} + +HRESULT __stdcall CByteEchoTest::ReadCharPointer(unsigned char* value, byte* result) +{ + if (value == NULL || result == NULL) + { + return E_POINTER ; + } + // A raw char* marshals one character, not a NUL-terminated string. + *result = static_cast(*value) ; + return S_OK ; +} diff --git a/source/CppTestSrv/CoByteEchoTest.h b/source/CppTestSrv/CoByteEchoTest.h new file mode 100644 index 000000000..6e84aa8cb --- /dev/null +++ b/source/CppTestSrv/CoByteEchoTest.h @@ -0,0 +1,19 @@ +// A separate COM component for byte and narrow-string round trips. +#include "CUnknown.h" +#include "Iface.h" + +class CByteEchoTest : public CUnknown, public IByteEchoTest +{ +public: + static HRESULT CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) ; + +private: + DECLARE_IUNKNOWN + virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, void** ppv) ; + virtual HRESULT __stdcall EchoInt8(char value, char* result) ; + virtual HRESULT __stdcall EchoUint8(byte value, byte* result) ; + virtual HRESULT __stdcall EchoLpStr(LPSTR value, LPSTR* result) ; + virtual HRESULT __stdcall ReadCharPointer(unsigned char* value, byte* result) ; + + CByteEchoTest(IUnknown* pUnknownOuter) : CUnknown(pUnknownOuter) {} ; +} ; diff --git a/source/CppTestSrv/CoNumericSafearrayTest.h b/source/CppTestSrv/CoNumericSafearrayTest.h index 821a811bf..7224e01e3 100644 --- a/source/CppTestSrv/CoNumericSafearrayTest.h +++ b/source/CppTestSrv/CoNumericSafearrayTest.h @@ -12,8 +12,6 @@ class CNumericSafearrayTest : public CUnknown, public INumericSafearrayTest virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, void** ppv) ; // Interface INumericSafearrayTest - // VT_I1 has COM/code-generator smoke coverage; NumPy dtype coverage awaits - // the separate c_char/c_byte issue (#935). virtual HRESULT __stdcall GetInt8Array(short dimensions, long count, SAFEARRAY** result) ; virtual HRESULT __stdcall GetUint8Array(short dimensions, long count, SAFEARRAY** result) ; virtual HRESULT __stdcall GetInt16Array(short dimensions, long count, SAFEARRAY** result) ; diff --git a/source/CppTestSrv/MAKEFILE b/source/CppTestSrv/MAKEFILE index 4c02592dc..0b87aaba6 100644 --- a/source/CppTestSrv/MAKEFILE +++ b/source/CppTestSrv/MAKEFILE @@ -41,7 +41,7 @@ guids.obj : guids.c # Component/server source files # -server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h +server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h CoByteEchoTest.h cl $(CPP_FLAGS) server.cpp CoComtypesDispRecordParamTest.obj : CoComtypesDispRecordParamTest.cpp \ @@ -56,6 +56,10 @@ CoNumericSafearrayTest.obj : CoNumericSafearrayTest.cpp \ CoNumericSafearrayTest.h iface.h CUnknown.h cl $(CPP_FLAGS) CoNumericSafearrayTest.cpp +CoByteEchoTest.obj : CoByteEchoTest.cpp \ + CoByteEchoTest.h iface.h CUnknown.h + cl $(CPP_FLAGS) CoByteEchoTest.cpp + # # Helper classes # @@ -86,6 +90,7 @@ SERVER_OBJS = Server.obj \ CoComtypesDispRecordParamTest.obj \ CoComtypesDispSafearrayParamTest.obj \ CoNumericSafearrayTest.obj \ + CoByteEchoTest.obj \ Registry.obj \ Cfactory.obj \ Cunknown.obj \ diff --git a/source/CppTestSrv/SERVER.CPP b/source/CppTestSrv/SERVER.CPP index 0db4c5410..6c17ee4a4 100644 --- a/source/CppTestSrv/SERVER.CPP +++ b/source/CppTestSrv/SERVER.CPP @@ -11,6 +11,7 @@ #include "CoComtypesDispRecordParamTest.h" #include "CoComtypesDispSafearrayParamTest.h" #include "CoNumericSafearrayTest.h" +#include "CoByteEchoTest.h" /////////////////////////////////////////////////////////// @@ -54,6 +55,12 @@ CFactoryData g_FactoryDataArray[] = L"Comtypes.NumericSafearrayTest.1", L"Comtypes.NumericSafearrayTest", &LIBID_ComtypesCppTestSrvLib, + NULL, 0}, + {&CLSID_CoByteEchoTest, CByteEchoTest::CreateInstance, + L"Comtypes component for byte and narrow-string round-trip tests", + L"Comtypes.ByteEchoTest.1", + L"Comtypes.ByteEchoTest", + &LIBID_ComtypesCppTestSrvLib, NULL, 0} } ; int g_cFactoryDataEntries diff --git a/source/CppTestSrv/SERVER.IDL b/source/CppTestSrv/SERVER.IDL index 365071a06..09843e033 100644 --- a/source/CppTestSrv/SERVER.IDL +++ b/source/CppTestSrv/SERVER.IDL @@ -88,6 +88,20 @@ interface INumericSafearrayTest : IUnknown HRESULT GetFloat64Array([in] short dimensions, [in] long count, [out, retval] SAFEARRAY(double)* result); } ; +// A separate interface for scalar input/output and string compatibility. +[ + object, + uuid(047E34C1-33A8-4DAA-A1E5-B941756A8319), + oleautomation +] +interface IByteEchoTest : IUnknown +{ + HRESULT EchoInt8([in] small value, [out, retval] small* result); + HRESULT EchoUint8([in] byte value, [out, retval] byte* result); + HRESULT EchoLpStr([in] LPSTR value, [out, retval] LPSTR* result); + HRESULT ReadCharPointer([in] char* value, [out, retval] byte* result); +} ; + // Interface IDispSafearrayParamTest [ uuid(4097A6D0-A111-40E2-BD0B-177B775A9496) @@ -145,4 +159,13 @@ library ComtypesCppTestSrvLib { [default] interface INumericSafearrayTest ; } ; + + [ + uuid(E07D71E8-C8A5-4B76-9A88-688DB10CF712), + helpstring("Comtypes component for byte and narrow-string round-trip tests.") + ] + coclass CoByteEchoTest + { + [default] interface IByteEchoTest ; + } ; } ; From a98cbab106f022b0ed13ad85463096d6ea9a7c6a Mon Sep 17 00:00:00 2001 From: haoxuanjng-lang Date: Wed, 14 Oct 2026 14:07:39 +0800 Subject: [PATCH 3/3] test: separate legacy and proposed byte mapping cases --- comtypes/test/test_cpp_byte_compatibility.py | 99 +++++++++++++------- 1 file changed, 67 insertions(+), 32 deletions(-) diff --git a/comtypes/test/test_cpp_byte_compatibility.py b/comtypes/test/test_cpp_byte_compatibility.py index a22471447..bde368c97 100644 --- a/comtypes/test/test_cpp_byte_compatibility.py +++ b/comtypes/test/test_cpp_byte_compatibility.py @@ -28,7 +28,7 @@ @unittest.skipIf(IMPORT_SERVER_FAILED, "Requires the C++ out-of-process COM server.") -class ByteCompatibilityTest(unittest.TestCase): +class _NativeByteTest(unittest.TestCase): def setUp(self): self.server = CreateObject( CoByteCompatibilityTest, @@ -36,6 +36,8 @@ def setUp(self): interface=IByteCompatibilityTest, ) + +class ByteCompatibilityTest(_NativeByteTest): def test_negative_int8(self): for value in (-128, -7, -1): with self.subTest(value=value): @@ -65,49 +67,82 @@ def test_lpstr(self): finally: _CoTaskMemFree(cast(result, c_void_p)) + +class LegacyMappingCompatibilityTest(_NativeByteTest): + """Calls accepted by the old mapping that regress with the proposed mapping.""" + @unittest.expectedFailure - def test_legacy_char_pointer_bytes(self): + def test_char_pointer_bytes_input(self): # Plain char* was previously inferred as STRING. Changing VT_I1 to # signed char loses this implicit bytes input, unlike explicit LPSTR. # Keep the compatibility regression visible for the review decision. - self.assertEqual(self.server.ReadCharPointer(b"h"), ord("h")) - - def test_char_pointer_ctypes_inputs(self): - for value in ( - pointer(c_byte(ord("h"))), - byref(c_byte(ord("h"))), - (c_byte * 1)(ord("h")), - ): + self.assertEqual(self.server.ReadCharPointer(b"h"), 104) + + @unittest.expectedFailure + def test_character_bytes_output_buffer(self): + # Old STRING output accepts a char buffer. Reserve the terminator + # locally because a plain char* marshals only one character. + result = create_string_buffer(2) + self.server._IByteCompatibilityTest__com_GetCharacterBytes(104, result) + self.assertEqual(result.value, b"h") + + +class ProposedMappingCompatibilityTest(_NativeByteTest): + """Numeric arguments and results supported by the proposed signed mapping.""" + + def test_char_pointer_input(self): + for value, expected in ((0, 0), (104, 104), (-128, 128), (-1, 255)): with self.subTest(value=value): - self.assertEqual(self.server.ReadCharPointer(value), ord("h")) + self.assertEqual( + self.server.ReadCharPointer(pointer(c_byte(value))), expected + ) + + def test_char_reference_input(self): + for value, expected in ((0, 0), (104, 104), (-128, 128), (-1, 255)): + with self.subTest(value=value): + self.assertEqual( + self.server.ReadCharPointer(byref(c_byte(value))), expected + ) + + def test_char_array_input(self): + for value, expected in ((0, 0), (104, 104), (-128, 128), (-1, 255)): + with self.subTest(value=value): + self.assertEqual( + self.server.ReadCharPointer((c_byte * 1)(value)), expected + ) def test_character_pointer_output(self): - for value in (0, 1, ord("h"), 128, 255): + for value, expected in ((0, 0), (1, 1), (104, 104), (128, -128), (255, -1)): with self.subTest(value=value): # Plain char* output uses the signed numeric mapping as well. - self.assertEqual( - self.server.GetCharacterBytes(value), - value if value < 128 else value - 256, + self.assertEqual(self.server.GetCharacterBytes(value), expected) + + def test_character_pointer_output_buffer(self): + for value, expected in ((0, 0), (104, 104), (128, -128), (255, -1)): + with self.subTest(value=value): + result = pointer(c_byte()) + self.server._IByteCompatibilityTest__com_GetCharacterBytes( + value, result ) + self.assertEqual(result.contents.value, expected) - def test_character_ctypes_output_buffers(self): - for value in (0, ord("h"), 128, 255): - for result in (c_byte(), (c_byte * 1)()): - with self.subTest(value=value, result=result): - argument = byref(result) if isinstance(result, c_byte) else result - self.server._IByteCompatibilityTest__com_GetCharacterBytes( - value, argument - ) - actual = result.value if isinstance(result, c_byte) else result[0] - self.assertEqual(actual, value if value < 128 else value - 256) + def test_character_reference_output_buffer(self): + for value, expected in ((0, 0), (104, 104), (128, -128), (255, -1)): + with self.subTest(value=value): + result = c_byte() + self.server._IByteCompatibilityTest__com_GetCharacterBytes( + value, byref(result) + ) + self.assertEqual(result.value, expected) - @unittest.expectedFailure - def test_legacy_character_bytes_output(self): - # Old STRING output accepts a char buffer. Reserve the terminator - # locally because a plain char* marshals only one character. - result = create_string_buffer(2) - self.server._IByteCompatibilityTest__com_GetCharacterBytes(ord("h"), result) - self.assertEqual(result.value, b"h") + def test_character_array_output_buffer(self): + for value, expected in ((0, 0), (104, 104), (128, -128), (255, -1)): + with self.subTest(value=value): + result = (c_byte * 1)() + self.server._IByteCompatibilityTest__com_GetCharacterBytes( + value, result + ) + self.assertEqual(result[0], expected) if __name__ == "__main__":