From 5afff95f4c93dbd05d019da4c50ca4f070aa9d03 Mon Sep 17 00:00:00 2001 From: Jerome Kelleher Date: Sun, 14 Jul 2024 21:58:43 +0100 Subject: [PATCH] Initial start on cpython interface testing --- tests/test_cpython_interface.py | 53 +++++++++++++++++++++++++++++++++ vcztools/_vcztoolsmodule.c | 6 ++-- 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 tests/test_cpython_interface.py diff --git a/tests/test_cpython_interface.py b/tests/test_cpython_interface.py new file mode 100644 index 0000000..f406c5a --- /dev/null +++ b/tests/test_cpython_interface.py @@ -0,0 +1,53 @@ +import pytest +import numpy as np + +from vcztools import _vcztools + +FIXED_FIELD_NAMES = ["chrom", "pos", "id", "qual", "ref", "alt", "filter"] + + +def example_fixed_data(num_variants, num_samples=0): + chrom = np.array(["X"] * num_variants, dtype="S") + pos = np.arange(num_variants, dtype=np.int32) + id = np.array(["."] * num_variants, dtype="S").reshape((num_variants, 1)) + ref = np.array(["A"] * num_variants, dtype="S") + alt = np.array(["T"] * num_variants, dtype="S").reshape((num_variants, 1)) + qual = np.arange(num_variants, dtype=np.float32) + filter_ = np.ones(num_variants, dtype=bool).reshape((num_variants, 1)) + filter_id = np.array(["PASS"], dtype="S") + return { + "chrom": chrom, + "pos": pos, + "id": id, + "qual": qual, + "ref": ref, + "alt": alt, + "filter": filter_, + "filter_ids": filter_id, + } + + +class TestTypeChecking: + @pytest.mark.parametrize("name", FIXED_FIELD_NAMES) + def test_field_incorrect_length(self, name): + num_variants = 5 + data = example_fixed_data(num_variants) + data[name] = data[name][1:] + with pytest.raises(ValueError, match=f"Array {name.upper()} must have "): + _vcztools.VcfEncoder(num_variants, 0, **data) + + @pytest.mark.parametrize("name", FIXED_FIELD_NAMES) + def test_field_incorrect_dtype(self, name): + num_variants = 5 + data = example_fixed_data(num_variants) + data[name] = np.zeros(data[name].shape, dtype=np.int64) + with pytest.raises(ValueError, match=f"Wrong dtype for {name.upper()}"): + _vcztools.VcfEncoder(num_variants, 0, **data) + + @pytest.mark.parametrize("name", FIXED_FIELD_NAMES) + def test_field_incorrect_type(self, name): + num_variants = 5 + data = example_fixed_data(num_variants) + data[name] = "A Python string" + with pytest.raises(TypeError, match=f"must be numpy.ndarray"): + _vcztools.VcfEncoder(num_variants, 0, **data) diff --git a/vcztools/_vcztoolsmodule.c b/vcztools/_vcztoolsmodule.c index 077b782..e9c484d 100644 --- a/vcztools/_vcztoolsmodule.c +++ b/vcztools/_vcztoolsmodule.c @@ -119,7 +119,7 @@ VcfEncoder_store_fixed_array(VcfEncoder *self, PyArrayObject *array, const char goto out; } if (PyArray_DTYPE(array)->type_num != type) { - PyErr_Format(PyExc_ValueError, "Array %s is not of the correct type", name); + PyErr_Format(PyExc_ValueError, "Wrong dtype for %s", name); goto out; } @@ -189,7 +189,7 @@ VcfEncoder_init(VcfEncoder *self, PyObject *args, PyObject *kwds) goto out; } - /* NOTE: we generalise this pattern for CHROM also to save a bit of time + /* NOTE: we could generalise this pattern for CHROM also to save a bit of time * in building numpy String arrays */ assert(PyArray_CheckExact(filter_ids)); if (!PyArray_CHKFLAGS(filter_ids, NPY_ARRAY_IN_ARRAY)) { @@ -209,7 +209,7 @@ VcfEncoder_init(VcfEncoder *self, PyObject *args, PyObject *kwds) if (VcfEncoder_add_array(self, "IDS/", "FILTER", filter_ids) != 0) { goto out; } - if (VcfEncoder_store_fixed_array(self, filter, "filter", NPY_BOOL, 2, num_variants) + if (VcfEncoder_store_fixed_array(self, filter, "FILTER", NPY_BOOL, 2, num_variants) != 0) { goto out; }