blob: d2993ec6e95dad8a06e179ab5053c3af836c72f4 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import array
from datetime import date, datetime, timezone
import pytest
from nanoarrow._array import CArrayBuilder
from nanoarrow._utils import NanoarrowException
from nanoarrow.c_schema import c_schema_view
import nanoarrow as na
from nanoarrow import device
def test_c_array_from_c_array():
c_array = na.c_array([1, 2, 3], na.int32())
c_array_from_c_array = na.c_array(c_array)
assert c_array_from_c_array.length == c_array.length
assert c_array_from_c_array.buffers == c_array.buffers
assert list(c_array.view().buffer(1)) == [1, 2, 3]
def test_c_array_from_capsule_protocol():
class CArrayWrapper:
def __init__(self, obj):
self.obj = obj
def __arrow_c_array__(self, *args, **kwargs):
return self.obj.__arrow_c_array__(*args, **kwargs)
c_array = na.c_array([1, 2, 3], na.int32())
c_array_wrapper = CArrayWrapper(c_array)
c_array_from_protocol = na.c_array(c_array_wrapper)
assert c_array_from_protocol.length == c_array.length
assert c_array_from_protocol.buffers == c_array.buffers
assert list(c_array_from_protocol.view().buffer(1)) == [1, 2, 3]
def test_c_array_from_old_pyarrow():
# Simulate a pyarrow Array with no __arrow_c_array__
class MockLegacyPyarrowArray:
def __init__(self, obj):
self.obj = obj
def _export_to_c(self, *args):
return self.obj._export_to_c(*args)
MockLegacyPyarrowArray.__module__ = "pyarrow.lib"
pa = pytest.importorskip("pyarrow")
array = MockLegacyPyarrowArray(pa.array([1, 2, 3], pa.int32()))
c_array = na.c_array(array)
assert c_array.length == 3
assert c_array.schema.format == "i"
assert list(c_array.view().buffer(1)) == [1, 2, 3]
# Make sure that this heuristic won't result in trying to import
# something else that has an _export_to_c method
with pytest.raises(TypeError, match="Can't resolve ArrayBuilder"):
not_array = pa.int32()
assert hasattr(not_array, "_export_to_c")
na.c_array(not_array)
def test_c_array_from_bare_capsule():
c_array = na.c_array([1, 2, 3], na.int32())
# Check from bare capsule without supplying a schema
schema_capsule, array_capsule = c_array.__arrow_c_array__()
del schema_capsule
c_array_from_capsule = na.c_array(array_capsule)
assert c_array_from_capsule.length == c_array.length
assert c_array_from_capsule.buffers == c_array.buffers
# Check from bare capsule supplying a schema
schema_capsule, array_capsule = c_array.__arrow_c_array__()
c_array_from_capsule = na.c_array(array_capsule, schema_capsule)
assert c_array_from_capsule.length == c_array.length
assert c_array_from_capsule.buffers == c_array.buffers
assert list(c_array_from_capsule.view().buffer(1)) == [1, 2, 3]
def test_c_array_type_not_supported():
msg = "Can't resolve ArrayBuilder for object of type NoneType"
with pytest.raises(TypeError, match=msg):
na.c_array(None)
def test_c_array_slice():
array = na.c_array([1, 2, 3], na.int32())
assert array.offset == 0
assert array.length == 3
array2 = array[:]
assert array.offset == 0
assert array.length == 3
assert array.buffers == array2.buffers
array2 = array[:2]
assert array2.offset == 0
assert array2.length == 2
array2 = array[:-1]
assert array2.offset == 0
assert array2.length == 2
array2 = array[1:]
assert array2.offset == 1
assert array2.length == 2
array2 = array[-2:]
assert array2.offset == 1
assert array2.length == 2
def test_c_array_slice_errors():
array = na.c_array([1, 2, 3], na.int32())
with pytest.raises(TypeError):
array[None]
with pytest.raises(IndexError):
array[4:]
with pytest.raises(IndexError):
array[:4]
with pytest.raises(IndexError):
array[1:0]
def test_c_array_shallow_copy():
import gc
import platform
from nanoarrow._utils import get_pyobject_buffer_count
if platform.python_implementation() == "PyPy":
pytest.skip(
"Reference counting/garbage collection is non-deterministic on PyPy"
)
gc.collect()
initial_ref_count = get_pyobject_buffer_count()
# Create an array with children
array = na.c_array_from_buffers(
na.struct({"col1": na.int32(), "col2": na.int64()}),
3,
[None],
children=[na.c_array([1, 2, 3], na.int32()), na.c_array([4, 5, 6], na.int32())],
move=True,
)
# The move=True should have prevented a shallow copy of the children
# when constructing the array.
assert get_pyobject_buffer_count() == initial_ref_count
# Force a shallow copy via the array protocol and ensure we saved
# references to two additional buffers.
_, col1_capsule = array.child(0).__arrow_c_array__()
assert get_pyobject_buffer_count() == (initial_ref_count + 1)
_, col2_capsule = array.child(1).__arrow_c_array__()
assert get_pyobject_buffer_count() == (initial_ref_count + 2)
# Ensure that the references can be removed
del col1_capsule
assert get_pyobject_buffer_count() == (initial_ref_count + 1)
del col2_capsule
assert get_pyobject_buffer_count() == initial_ref_count
def test_c_array_builder_init():
builder = CArrayBuilder.allocate()
with pytest.raises(RuntimeError, match="CArrayBuilder is not initialized"):
builder.is_empty()
builder.init_from_type(na.Type.INT32.value)
assert builder.is_empty()
with pytest.raises(RuntimeError, match="CArrayBuilder is already initialized"):
builder.init_from_type(na.Type.INT32.value)
with pytest.raises(RuntimeError, match="CArrayBuilder is already initialized"):
builder.init_from_schema(na.c_schema(na.int32()))
def test_c_array_from_pybuffer_uint8():
data = b"abcdefg"
c_array = na.c_array(data)
assert c_array.length == len(data)
assert c_array.null_count == 0
assert c_array.offset == 0
assert c_schema_view(c_array.schema).type == "uint8"
c_array_view = c_array.view()
assert list(c_array_view.buffer(1)) == list(data)
def test_c_array_from_pybuffer_string():
data = b"abcdefg"
buffer = na.c_buffer(data)._set_format("c")
c_array = na.c_array(buffer)
assert c_array.length == len(data)
assert c_array.null_count == 0
assert c_array.offset == 0
assert c_schema_view(c_array.schema).type == "int8"
c_array_view = c_array.view()
assert list(c_array_view.buffer(1)) == list(data)
def test_c_array_from_pybuffer_fixed_size_binary():
items = [b"abcd", b"efgh", b"ijkl"]
packed = b"".join(items)
buffer = na.c_buffer(packed)._set_format("4s")
c_array = na.c_array(buffer)
assert c_array.length == len(items)
assert c_array.null_count == 0
assert c_array.offset == 0
assert c_schema_view(c_array.schema).type == "fixed_size_binary"
assert c_schema_view(c_array.schema).fixed_size == 4
c_array_view = c_array.view()
assert list(c_array_view.buffer(1)) == items
def test_c_array_from_pybuffer_numpy():
np = pytest.importorskip("numpy")
data = np.array([1, 2, 3], dtype=np.int32)
c_array = na.c_array(data)
assert c_array.length == len(data)
assert c_array.null_count == 0
assert c_array.offset == 0
assert c_schema_view(c_array.schema).type == "int32"
c_array_view = c_array.view()
assert list(c_array_view.buffer(1)) == list(data)
def test_c_array_from_iterable_empty():
empty_string = na.c_array([], na.string())
assert empty_string.length == 0
assert empty_string.null_count == 0
assert empty_string.offset == 0
assert empty_string.n_buffers == 3
array_view = empty_string.view()
assert len(array_view.buffer(0)) == 0
assert len(array_view.buffer(1)) == 0
assert len(array_view.buffer(2)) == 0
def test_c_array_from_iterable_string():
string = na.c_array(["abc", None, "defg"], na.string())
assert string.length == 3
assert string.null_count == 1
array_view = string.view()
assert len(array_view.buffer(0)) == 1
assert len(array_view.buffer(1)) == 4
assert len(array_view.buffer(2)) == 7
# Check an item that is not a str()
with pytest.raises(ValueError):
na.c_array([b"1234"], na.string())
def test_c_array_from_iterable_string_view():
string = na.c_array(
["abc", None, "a string longer than 12 bytes"], na.string_view()
)
assert string.length == 3
assert string.null_count == 1
assert string.n_buffers == 4
array_view = string.view()
assert len(array_view.buffer(0)) == 1
assert bytes(array_view.buffer(2)) == b"a string longer than 12 bytes"
assert list(array_view.buffer(3)) == [len("a string longer than 12 bytes")]
# Make sure this also works when all strings are inlined (i.e., no variadic buffers)
string = na.c_array(["abc", None, "short string"], na.string_view())
assert string.length == 3
assert string.null_count == 1
assert string.n_buffers == 3
array_view = string.view()
assert len(array_view.buffer(0)) == 1
assert len(array_view.buffer(1)) == 3
assert len(bytes(array_view.buffer(1))) == 3 * 16
assert list(array_view.buffer(2)) == []
def test_c_array_from_iterable_bytes():
string = na.c_array([b"abc", None, b"defg"], na.binary())
assert string.length == 3
assert string.null_count == 1
array_view = string.view()
assert len(array_view.buffer(0)) == 1
assert len(array_view.buffer(1)) == 4
assert len(array_view.buffer(2)) == 7
with pytest.raises(ValueError):
na.c_array(["1234"], na.binary())
buf_not_bytes = na.c_buffer([1, 2, 3], na.int32())
with pytest.raises(ValueError, match="Can't append buffer with itemsize != 1"):
na.c_array([buf_not_bytes], na.binary())
np = pytest.importorskip("numpy")
buf_2d = np.ones((2, 2))
with pytest.raises(ValueError, match="Can't append buffer with dimensions != 1"):
na.c_array([buf_2d], na.binary())
def test_c_array_from_iterable__view():
string = na.c_array(
[b"abc", None, b"a string longer than 12 bytes"], na.binary_view()
)
assert string.length == 3
assert string.null_count == 1
assert string.n_buffers == 4
array_view = string.view()
assert len(array_view.buffer(0)) == 1
assert bytes(array_view.buffer(2)) == b"a string longer than 12 bytes"
assert list(array_view.buffer(3)) == [len("a string longer than 12 bytes")]
def test_c_array_from_iterable_non_empty_nullable_without_nulls():
c_array = na.c_array([1, 2, 3], na.int32())
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [1, 2, 3]
def test_c_array_from_iterable_non_empty_non_nullable():
c_array = na.c_array([1, 2, 3], na.int32(nullable=False))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [1, 2, 3]
def test_c_array_from_iterable_int_with_nulls():
c_array = na.c_array([1, None, 3], na.int32())
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1)) == [1, 0, 3]
def test_c_array_from_iterable_float_with_nulls():
c_array = na.c_array([1, None, 3], na.float64())
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1)) == [1.0, 0.0, 3.0]
def test_c_array_from_iterable_bool_with_nulls():
c_array = na.c_array([True, None, False], na.bool_())
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1).elements()) == [True, False, False] + [False] * 5
def test_c_array_from_iterable_fixed_size_binary_with_nulls():
c_array = na.c_array([b"1234", None, b"5678"], na.fixed_size_binary(4))
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1)) == [b"1234", b"\x00\x00\x00\x00", b"5678"]
def test_c_array_from_iterable_day_time_interval_with_nulls():
c_array = na.c_array([(1, 2), None, (3, 4)], na.interval_day_time())
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1)) == [(1, 2), (0, 0), (3, 4)]
def test_c_array_from_iterable_month_day_nano_interval_with_nulls():
c_array = na.c_array([(1, 2, 3), None, (4, 5, 6)], na.interval_month_day_nano())
assert c_array.length == 3
assert c_array.null_count == 1
view = c_array.view()
assert list(view.buffer(0).elements()) == [True, False, True] + [False] * 5
assert list(view.buffer(1)) == [(1, 2, 3), (0, 0, 0), (4, 5, 6)]
def test_c_array_from_iterable_error():
with pytest.raises(ValueError, match="schema is required"):
na.c_array([1, 2, 3])
def test_c_array_from_buffers():
c_array = na.c_array_from_buffers(na.uint8(), 5, [None, b"12345"])
assert c_array.length == 5
assert c_array.null_count == 0
assert c_array.offset == 0
array_view = c_array.view()
assert array_view.storage_type == "uint8"
assert bytes(array_view.buffer(0)) == b""
assert bytes(array_view.buffer(1)) == b"12345"
def test_c_array_from_buffers_null_count():
# Ensure null_count is not calculated if explicitly set
c_array = na.c_array_from_buffers(
na.uint8(), 7, [b"\xff", b"12345678"], null_count=1
)
assert c_array.null_count == 1
# Zero nulls, explicit bitmap
c_array = na.c_array_from_buffers(na.uint8(), 8, [b"\xff", b"12345678"])
assert c_array.null_count == 0
# All nulls, explicit bitmap
c_array = na.c_array_from_buffers(na.uint8(), 8, [b"\x00", b"12345678"])
assert c_array.null_count == 8
# Ensure offset is considered in null count calculation
c_array = na.c_array_from_buffers(na.uint8(), 7, [b"\xff", b"12345678"], offset=1)
assert c_array.null_count == 0
c_array = na.c_array_from_buffers(na.uint8(), 7, [b"\x00", b"12345678"], offset=1)
assert c_array.null_count == 7
# Ensure we don't access out-of-bounds memory to calculate the bitmap
with pytest.raises(ValueError, match="Expected validity bitmap >= 2 bytes"):
na.c_array_from_buffers(na.uint8(), 9, [b"\x00", b"123456789"])
def test_c_array_from_buffers_recursive():
c_array = na.c_array_from_buffers(
na.struct([na.uint8()]), 5, [None], children=[b"12345"]
)
assert c_array.length == 5
assert c_array.n_children == 1
array_view = c_array.view()
assert bytes(array_view.child(0).buffer(1)) == b"12345"
with pytest.raises(ValueError, match="Expected 1 children but got 0"):
na.c_array_from_buffers(na.struct([na.uint8()]), 5, [], children=[])
with pytest.raises(
NotImplementedError,
match="Validation for array with children is not implemented",
):
na.c_array_from_buffers(
na.struct([na.uint8()]),
5,
[None],
children=[b"12345"],
validation_level="minimal",
)
def test_c_array_from_buffers_validation():
# Should fail with all validation levels except none
for validation_level in ("full", "default", "minimal"):
with pytest.raises(
NanoarrowException,
match="Expected int32 array buffer 1 to have size >= 4 bytes",
):
na.c_array_from_buffers(
na.int32(), 1, [None, b"123"], validation_level=validation_level
)
c_array = na.c_array_from_buffers(
na.int32(), 1, [None, b"123"], validation_level="none"
)
assert c_array.length == 1
# Should only fail with validation levels of "full", and "default"
for validation_level in ("full", "default"):
with pytest.raises(
NanoarrowException,
match="Expected string array buffer 2 to have size >= 2 bytes",
):
na.c_array_from_buffers(
na.string(),
2,
[None, na.c_buffer([0, 1, 2], na.int32()), na.c_buffer(b"a")],
validation_level=validation_level,
)
for validation_level in ("minimal", "none"):
c_array = na.c_array_from_buffers(
na.string(),
2,
[None, na.c_buffer([0, 1, 2], na.int32()), na.c_buffer(b"a")],
validation_level=validation_level,
)
assert c_array.length == 2
# Should only fail with validation level "full"
with pytest.raises(
NanoarrowException,
match="Expected element size >= 0",
):
na.c_array_from_buffers(
na.string(),
2,
[None, na.c_buffer([0, 100, 2], na.int32()), na.c_buffer(b"ab")],
validation_level="full",
)
for validation_level in ("minimal", "none"):
c_array = na.c_array_from_buffers(
na.string(),
2,
[None, na.c_buffer([0, 100, 2], na.int32()), na.c_buffer(b"ab")],
validation_level=validation_level,
)
assert c_array.length == 2
def test_c_array_from_buffers_device():
c_device_array = na.c_array_from_buffers(
na.uint8(), 5, [None, b"12345"], device=device.cpu()
)
assert isinstance(c_device_array, device.CDeviceArray)
c_array = na.c_array(c_device_array)
assert c_array.length == 5
assert bytes(c_array.view().buffer(1)) == b"12345"
def test_c_array_timestamp_seconds():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp()))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp()))
d3 = int(round(datetime(2005, 3, 4, tzinfo=timezone.utc).timestamp()))
c_array = na.c_array([d1, d2, d3], na.timestamp("s"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_timestamp_seconds_from_pybuffer():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp()))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp()))
d3 = int(round(datetime(2005, 3, 4, tzinfo=timezone.utc).timestamp()))
c_array = na.c_array(array.array("q", [d1, d2, d3]), na.timestamp("s"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_timestamp_milliseconds():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp() * 1e3))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp() * 1e3))
d3 = int(round(datetime(2005, 3, 4, tzinfo=timezone.utc).timestamp() * 1e3))
c_array = na.c_array([d1, d2, d3], na.timestamp("ms"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_timestamp_milliseconds_from_pybuffer():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp() * 1e3))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp() * 1e3))
d3 = int(round(datetime(2005, 3, 4, tzinfo=timezone.utc).timestamp() * 1e3))
c_array = na.c_array(array.array("q", [d1, d2, d3]), na.timestamp("ms"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_timestamp_microseconds():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp() * 1e6))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp() * 1e6))
d3 = int(round(datetime(2005, 3, 4, tzinfo=timezone.utc).timestamp() * 1e6))
c_array = na.c_array([d1, d2, d3], na.timestamp("us"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_timestamp_nanoseconds():
d1 = int(round(datetime(1970, 1, 1, tzinfo=timezone.utc).timestamp() * 1e9))
d2 = int(round(datetime(1985, 12, 31, tzinfo=timezone.utc).timestamp() * 1e9))
d3 = int(round(datetime(2005, 3, 4).timestamp() * 1e9))
c_array = na.c_array([d1, d2, d3], na.timestamp("ns"))
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [d1, d2, d3]
def test_c_array_duration():
unix_epoch = date(1970, 1, 1)
d1, d2, d3 = date(1970, 1, 2), date(1970, 1, 3), date(1970, 1, 4)
d1_duration_in_ms = int(round((d1 - unix_epoch).total_seconds() * 1e3))
d2_duration_in_ms = int(round((d2 - unix_epoch).total_seconds() * 1e3))
d3_duration_in_ms = int(round((d3 - unix_epoch).total_seconds() * 1e3))
c_array = na.c_array(
[d1_duration_in_ms, d2_duration_in_ms, d3_duration_in_ms], na.duration("ms")
)
assert c_array.length == 3
assert c_array.null_count == 0
view = c_array.view()
assert list(view.buffer(0)) == []
assert list(view.buffer(1)) == [
d1_duration_in_ms,
d2_duration_in_ms,
d3_duration_in_ms,
]
def test_device_array_errors(cuda_device):
if not cuda_device:
pytest.skip("CUDA device not available")
# Check that we can't create a CUDA array from CPU buffers
with pytest.raises(ValueError, match="are not identical"):
na.c_array_from_buffers(
na.int64(),
3,
[None, na.c_buffer([1, 2, 3], na.int64())],
device=cuda_device,
)
# Check that we can't create a CUDA array from CPU children
with pytest.raises(ValueError, match="are not identical"):
na.c_array_from_buffers(
na.struct([na.int64()]),
length=0,
buffers=[None],
children=[na.c_array([], na.int64())],
device=cuda_device,
)
def test_array_from_dlpack_cuda(cuda_device):
from nanoarrow.device import CDeviceArray, DeviceType
cp = pytest.importorskip("cupy")
if not cuda_device:
pytest.skip("CUDA device not available")
gpu_validity = cp.array([255], cp.uint8)
gpu_array = cp.array([1, 2, 3], cp.int64)
c_array = na.c_array_from_buffers(
na.int64(),
3,
[gpu_validity, gpu_array],
move=True,
device=cuda_device,
)
assert isinstance(c_array, CDeviceArray)
assert c_array.device_type == DeviceType.CUDA
assert c_array.device_id == 0
c_array_view = c_array.view()
assert c_array_view.storage_type == "int64"
assert c_array_view.buffer(0).device == cuda_device
assert c_array_view.buffer(1).device == cuda_device
assert len(c_array_view) == 3
# Make sure we don't attempt accessing a GPU buffer to calculate the null count
assert c_array_view.null_count == -1
# Check that buffers made it all the way through
cp.testing.assert_array_equal(cp.from_dlpack(c_array_view.buffer(1)), gpu_array)
# Also check a nested array
c_array_struct = na.c_array_from_buffers(
na.struct([na.int64()]),
3,
buffers=[None],
children=[c_array],
move=True,
device=cuda_device,
)
assert c_array_struct.device_type == DeviceType.CUDA
assert c_array_struct.device_id == 0
c_array_view_struct = c_array_struct.view()
assert c_array_view_struct.storage_type == "struct"
assert c_array_view_struct.buffer(0).device == cuda_device
c_array_view_child = c_array_view_struct.child(0)
assert c_array_view_child.buffer(0).device == cuda_device
assert c_array_view_child.buffer(1).device == cuda_device
cp.testing.assert_array_equal(
cp.from_dlpack(c_array_view_child.buffer(1)), gpu_array
)