Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,39 @@ jobs:
shell: bash -el {0}
strategy:
matrix:
os: [macos-14, ubuntu-latest]
os: [macos-14, ubuntu-latest, windows-latest]
python-version: ["3.11", "3.12", "3.13"]
exclude:
# Just run macos tests on one Python version
# Just run macos and windows tests on one Python version
- os: macos-14
python-version: "3.12"
- os: macos-14
python-version: "3.13"
- os: windows-latest
python-version: "3.12"
- os: windows-latest
python-version: "3.13"
steps:
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0

- name: Set up Miniconda with Python ${{ matrix.python-version }}
if: runner.os != 'Windows'
uses: conda-incubator/setup-miniconda@v3
with:
auto-update-conda: true
python-version: ${{ matrix.python-version }}
channels: conda-forge,bioconda

- name: Install conda test tools
if: runner.os != 'Windows'
run: conda install bcftools plink

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install test dependencies
run: |
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Intended Audience :: Science/Research",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
Expand All @@ -50,7 +51,7 @@ vcztools = "vcztools.cli:vcztools_main"
[dependency-groups]
test = [
"bio2zarr",
"cyvcf2",
"cyvcf2 ; sys_platform != 'win32'",
"obstore",
"pytest",
"pytest-cov",
Expand Down
5 changes: 5 additions & 0 deletions tests/test_bcftools_validation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pathlib
import subprocess
import sys

import click.testing as ct
import pytest
Expand All @@ -8,6 +9,10 @@

from .utils import assert_vcfs_close, vcz_path_cache

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


def run_bcftools(args: str, expect_error=False) -> tuple[str, str]:
"""
Expand Down
5 changes: 5 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pathlib
import sys
from unittest import mock

import click.testing as ct
Expand All @@ -9,6 +10,10 @@
from tests.utils import vcz_path_cache
from vcztools import provenance

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


@pytest.fixture
def vcz_path():
Expand Down
16 changes: 15 additions & 1 deletion tests/test_cpython_interface.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sys

import numpy as np
import pytest

Expand Down Expand Up @@ -78,6 +80,7 @@ def example_encoder(num_variants=1, num_samples=0, add_info=True):
return encoder


@pytest.mark.skipif(sys.platform == "win32", reason="Not implemented on Windows")
class TestPrintState:
def test_nomimal_case(self, tmp_path):
encoder = example_encoder()
Expand Down Expand Up @@ -440,7 +443,18 @@ def test_array_bad_alignment(self):
class TestUninitialised:
@pytest.mark.parametrize(
"name",
["add_info_field", "add_gt_field", "add_format_field", "print_state", "encode"],
[
"add_info_field",
"add_gt_field",
"add_format_field",
pytest.param(
"print_state",
marks=pytest.mark.skipif(
sys.platform == "win32", reason="Not implemented on Windows"
),
),
"encode",
],
)
def test_methods(self, name):
cls = _vcztools.VcfEncoder
Expand Down
5 changes: 5 additions & 0 deletions tests/test_filter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pathlib
import sys

import numpy as np
import numpy.testing as nt
Expand All @@ -9,6 +10,10 @@
from tests.utils import vcz_path_cache
from vcztools import filter as filter_mod

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


class TestFilterExpressionParser:
@pytest.fixture
Expand Down
5 changes: 5 additions & 0 deletions tests/test_plink_validation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import pathlib
import subprocess
import sys

import click.testing as ct
import pytest
Expand All @@ -9,6 +10,10 @@

from . import utils

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


def assert_files_identical(path1, path2):
"""
Expand Down
5 changes: 5 additions & 0 deletions tests/test_query.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pathlib
import re
import sys
from io import StringIO

import numpy as np
Expand All @@ -17,6 +18,10 @@
)
from vcztools.retrieval import variant_chunk_iter

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


def test_list_samples(tmp_path):
vcf_path = pathlib.Path("tests/data/vcf") / "sample.vcf.gz"
Expand Down
5 changes: 5 additions & 0 deletions tests/test_retrieval.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pathlib
import sys

import numpy.testing as nt
import pytest
Expand All @@ -9,6 +10,10 @@

from .utils import vcz_path_cache

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


def test_variant_chunk_iter():
original = pathlib.Path("tests/data/vcf") / "sample.vcf.gz"
Expand Down
5 changes: 5 additions & 0 deletions tests/test_stats.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pathlib
import sys
from io import StringIO

import pytest
Expand All @@ -9,6 +10,10 @@

from .utils import vcz_path_cache

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


def test_nrecords():
original = pathlib.Path("tests/data/vcf") / "sample.vcf.gz"
Expand Down
3 changes: 3 additions & 0 deletions tests/test_tskit_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
with various outputs.
"""

import sys

import bio2zarr.plink as p2z
import bio2zarr.tskit as ts2z
import bio2zarr.vcf as v2z
Expand Down Expand Up @@ -205,6 +207,7 @@ def fx_simple_ts(tmp_path):
# handled)


@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows")
class TestVcfRoundTrip:
def assert_bio2zarr_rt(self, tmp_path, tskit_vcz):
vcf_path = tmp_path / "out.vcf"
Expand Down
5 changes: 5 additions & 0 deletions tests/test_vcf_roundtrip.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pathlib
import sys

import pytest

Expand All @@ -7,6 +8,10 @@

from .utils import assert_vcfs_close

pytestmark = pytest.mark.skipif(
sys.platform == "win32", reason="Not supported on Windows"
)


@pytest.mark.parametrize(
"vcf_file",
Expand Down
4 changes: 3 additions & 1 deletion tests/test_vcf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
import pytest
import zarr
from bio2zarr import vcf
from cyvcf2 import VCF
from numpy.testing import assert_array_equal

from vcztools.constants import INT_FILL, INT_MISSING
from vcztools.vcf_writer import _compute_info_fields, c_chunk_to_vcf, write_vcf

from .utils import assert_vcfs_close, to_vcz_icechunk, vcz_path_cache

cyvcf2 = pytest.importorskip("cyvcf2")
VCF = cyvcf2.VCF


@pytest.mark.parametrize("output_is_path", [True, False])
@pytest.mark.parametrize("zarr_backend_storage", [None, "fsspec", "obstore"])
Expand Down
5 changes: 3 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pathlib import Path
from typing import Any

import cyvcf2
import numpy as np
import xarray as xr
import zarr
Expand All @@ -30,8 +29,10 @@ def load_dataset(


@contextmanager
def open_vcf(path) -> Iterator[cyvcf2.VCF]:
def open_vcf(path) -> Iterator:
"""A context manager for opening a VCF file."""
import cyvcf2 # noqa: PLC0415

vcf = cyvcf2.VCF(path)
try:
yield vcf
Expand Down
14 changes: 7 additions & 7 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions vcztools/_vcztoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ handle_library_error(int err)
}
}

/* The dup/fdopen/fclose pattern used by make_file does not work on Windows
* because _dup shares the underlying OS handle rather than duplicating it.
* When fclose closes the duped FILE*, it invalidates the original handle,
* causing an access violation when Python later closes its file object.
* Since print_state is only a debugging tool, we simply exclude it on Windows. */
#ifndef _WIN32

static FILE *
make_file(PyObject *fileobj, const char *mode)
{
Expand Down Expand Up @@ -60,6 +67,8 @@ make_file(PyObject *fileobj, const char *mode)
return ret;
}

#endif /* !_WIN32 */

/*===================================================================
* VcfEncoder
*===================================================================
Expand Down Expand Up @@ -474,6 +483,7 @@ VcfEncoder_encode(VcfEncoder *self, PyObject *args)
return ret;
}

#ifndef _WIN32
static PyObject *
VcfEncoder_print_state(VcfEncoder *self, PyObject *args)
{
Expand All @@ -500,6 +510,8 @@ VcfEncoder_print_state(VcfEncoder *self, PyObject *args)
return ret;
}

#endif /* !_WIN32 */

/* Return a copy of the dictionary of arrays providing the memory backing.
* Note that we return copy of the Dictionary here so that the arrays themselves
* can't be removed from it.
Expand All @@ -524,10 +536,12 @@ static PyGetSetDef VcfEncoder_getsetters[] = {
};

static PyMethodDef VcfEncoder_methods[] = {
#ifndef _WIN32
{ .ml_name = "print_state",
.ml_meth = (PyCFunction) VcfEncoder_print_state,
.ml_flags = METH_VARARGS,
.ml_doc = "Debug method to print out the low-level state" },
#endif
{ .ml_name = "add_info_field",
.ml_meth = (PyCFunction) VcfEncoder_add_info_field,
.ml_flags = METH_VARARGS,
Expand Down
Loading