From 983e3cfb4429a689d8165996bb0f926ea2cb5d42 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 14:06:16 +0800 Subject: [PATCH 1/6] Move virtualfile-related tests into a separate test file --- pygmt/tests/test_clib.py | 334 ---------------------- pygmt/tests/test_clib_virtualfiles.py | 384 ++++++++++++++++++++++++++ 2 files changed, 384 insertions(+), 334 deletions(-) create mode 100644 pygmt/tests/test_clib_virtualfiles.py diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 9f2c73857cd..c48fcbef649 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -5,12 +5,10 @@ """ import os from contextlib import contextmanager -from itertools import product from pathlib import Path import numpy as np import numpy.testing as npt -import pandas as pd import pytest import xarray as xr from packaging.version import Version @@ -348,338 +346,6 @@ def test_create_data_fails(): ) -def test_virtual_file(dtypes): - """ - Test passing in data via a virtual file with a Dataset. - """ - shape = (5, 3) - for dtype in dtypes: - with clib.Session() as lib: - family = "GMT_IS_DATASET|GMT_VIA_MATRIX" - geometry = "GMT_IS_POINT" - dataset = lib.create_data( - family=family, - geometry=geometry, - mode="GMT_CONTAINER_ONLY", - dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype - ) - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - lib.put_matrix(dataset, matrix=data) - # Add the dataset to a virtual file and pass it along to gmt info - vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset) - with lib.open_virtual_file(*vfargs) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) - expected = f": N = {shape[0]}\t{bounds}\n" - assert output == expected - - -def test_virtual_file_fails(): - """ - Check that opening and closing virtual files raises an exception for non- - zero return codes. - """ - vfargs = ( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - "GMT_IS_POINT", - "GMT_IN|GMT_IS_REFERENCE", - None, - ) - - # Mock Open_VirtualFile to test the status check when entering the context. - # If the exception is raised, the code won't get to the closing of the - # virtual file. - with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): - with pytest.raises(GMTCLibError): - with lib.open_virtual_file(*vfargs): - print("Should not get to this code") - - # Test the status check when closing the virtual file - # Mock the opening to return 0 (success) so that we don't open a file that - # we won't close later. - with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( - lib, "GMT_Close_VirtualFile", returns=1 - ): - with pytest.raises(GMTCLibError): - with lib.open_virtual_file(*vfargs): - pass - print("Shouldn't get to this code either") - - -def test_virtual_file_bad_direction(): - """ - Test passing an invalid direction argument. - """ - with clib.Session() as lib: - vfargs = ( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - "GMT_IS_POINT", - "GMT_IS_GRID", # The invalid direction argument - 0, - ) - with pytest.raises(GMTInvalidInput): - with lib.open_virtual_file(*vfargs): - print("This should have failed") - - -@pytest.mark.parametrize( - "array_func,kind", - [(np.array, "matrix"), (pd.DataFrame, "vector"), (xr.Dataset, "vector")], -) -def test_virtualfile_from_data_required_z_matrix(array_func, kind): - """ - Test that function works when third z column in a matrix is needed and - provided. - """ - shape = (5, 3) - dataframe = pd.DataFrame( - data=np.arange(shape[0] * shape[1]).reshape(shape), columns=["x", "y", "z"] - ) - data = array_func(dataframe) - with clib.Session() as lib: - with lib.virtualfile_from_data( - data=data, required_z=True, check_kind="vector" - ) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - [ - f"<{i.min():.0f}/{i.max():.0f}>" - for i in (dataframe.x, dataframe.y, dataframe.z) - ] - ) - expected = f"<{kind} memory>: N = {shape[0]}\t{bounds}\n" - assert output == expected - - -def test_virtualfile_from_data_required_z_matrix_missing(): - """ - Test that function fails when third z column in a matrix is needed but not - provided. - """ - data = np.ones((5, 2)) - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - with lib.virtualfile_from_data( - data=data, required_z=True, check_kind="vector" - ): - pass - - -def test_virtualfile_from_data_fail_non_valid_data(data): - """ - Should raise an exception if too few or too much data is given. - """ - # Test all combinations where at least one data variable - # is not given in the x, y case: - for variable in product([None, data[:, 0]], repeat=2): - # Filter one valid configuration: - if not any(item is None for item in variable): - continue - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=variable[0], - y=variable[1], - ) - - # Test all combinations where at least one data variable - # is not given in the x, y, z case: - for variable in product([None, data[:, 0]], repeat=3): - # Filter one valid configuration: - if not any(item is None for item in variable): - continue - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=variable[0], y=variable[1], z=variable[2], required_z=True - ) - - # Should also fail if given too much data - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - data=data, - ) - - -def test_virtualfile_from_vectors(dtypes): - """ - Test the automation for transforming vectors to virtual file dataset. - """ - size = 10 - for dtype in dtypes: - x = np.arange(size, dtype=dtype) - y = np.arange(size, size * 2, 1, dtype=dtype) - z = np.arange(size * 2, size * 3, 1, dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"<{i.min():.0f}/{i.max():.0f}>" for i in (x, y, z)]) - expected = f": N = {size}\t{bounds}\n" - assert output == expected - - -@pytest.mark.parametrize("dtype", [str, object]) -def test_virtualfile_from_vectors_one_string_or_object_column(dtype): - """ - Test passing in one column with string or object dtype into virtual file - dataset. - """ - size = 5 - x = np.arange(size, dtype=np.int32) - y = np.arange(size, size * 2, 1, dtype=np.int32) - strings = np.array(["a", "bc", "defg", "hijklmn", "opqrst"], dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, strings) as vfile: - with GMTTempFile() as outfile: - lib.call_module("convert", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - expected = "".join(f"{i}\t{j}\t{k}\n" for i, j, k in zip(x, y, strings)) - assert output == expected - - -@pytest.mark.parametrize("dtype", [str, object]) -def test_virtualfile_from_vectors_two_string_or_object_columns(dtype): - """ - Test passing in two columns of string or object dtype into virtual file - dataset. - """ - size = 5 - x = np.arange(size, dtype=np.int32) - y = np.arange(size, size * 2, 1, dtype=np.int32) - # Catch bug in https://github.com/GenericMappingTools/pygmt/pull/2719 - strings1 = np.array(["a", "bc", "def", "ghij", "klmnolooong"], dtype=dtype) - strings2 = np.array(["pqrst", "uvwx", "yz!", "@#", "$"], dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, strings1, strings2) as vfile: - with GMTTempFile() as outfile: - lib.call_module("convert", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - expected = "".join( - f"{h}\t{i}\t{j} {k}\n" for h, i, j, k in zip(x, y, strings1, strings2) - ) - assert output == expected - - -def test_virtualfile_from_vectors_transpose(): - """ - Test transforming matrix columns to virtual file dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (7, 5) - for dtype in dtypes: - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(*data.T) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} -C ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"{col.min():.0f}\t{col.max():.0f}" for col in data.T]) - expected = f"{bounds}\n" - assert output == expected - - -def test_virtualfile_from_vectors_diff_size(): - """ - Test the function fails for arrays of different sizes. - """ - x = np.arange(5) - y = np.arange(6) - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - with lib.virtualfile_from_vectors(x, y): - print("This should have failed") - - -def test_virtualfile_from_matrix(dtypes): - """ - Test transforming a matrix to virtual file dataset. - """ - shape = (7, 5) - for dtype in dtypes: - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with clib.Session() as lib: - with lib.virtualfile_from_matrix(data) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) - expected = f": N = {shape[0]}\t{bounds}\n" - assert output == expected - - -def test_virtualfile_from_matrix_slice(dtypes): - """ - Test transforming a slice of a larger array to virtual file dataset. - """ - shape = (10, 6) - for dtype in dtypes: - full_data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - rows = 5 - cols = 3 - data = full_data[:rows, :cols] - with clib.Session() as lib: - with lib.virtualfile_from_matrix(data) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) - expected = f": N = {rows}\t{bounds}\n" - assert output == expected - - -def test_virtualfile_from_vectors_pandas(dtypes): - """ - Pass vectors to a dataset using pandas Series. - """ - size = 13 - for dtype in dtypes: - data = pd.DataFrame( - data={ - "x": np.arange(size, dtype=dtype), - "y": np.arange(size, size * 2, 1, dtype=dtype), - "z": np.arange(size * 2, size * 3, 1, dtype=dtype), - } - ) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(data.x, data.y, data.z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - [f"<{i.min():.0f}/{i.max():.0f}>" for i in (data.x, data.y, data.z)] - ) - expected = f": N = {size}\t{bounds}\n" - assert output == expected - - -def test_virtualfile_from_vectors_arraylike(): - """ - Pass array-like vectors to a dataset. - """ - size = 13 - x = list(range(0, size, 1)) - y = tuple(range(size, size * 2, 1)) - z = range(size * 2, size * 3, 1) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - bounds = "\t".join([f"<{min(i):.0f}/{max(i):.0f}>" for i in (x, y, z)]) - expected = f": N = {size}\t{bounds}\n" - assert output == expected - - def test_extract_region_fails(): """ Check that extract region fails if nothing has been plotted. diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py new file mode 100644 index 00000000000..f6837165b2f --- /dev/null +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -0,0 +1,384 @@ +""" +Test the API functions related to virtual files. +""" +from contextlib import contextmanager +from itertools import product + +import numpy as np +import pandas as pd +import pytest +import xarray as xr +from pygmt import clib +from pygmt.exceptions import GMTCLibError, GMTInvalidInput +from pygmt.helpers import GMTTempFile + + +@contextmanager +def mock(session, func, returns=None, mock_func=None): + """ + Mock a GMT C API function to make it always return a given value. + + Used to test that exceptions are raised when API functions fail by + producing a NULL pointer as output or non-zero status codes. + + Needed because it's not easy to get some API functions to fail without + inducing a Segmentation Fault (which is a good thing because libgmt usually + only fails with errors). + """ + if mock_func is None: + + def mock_api_function(*args): # pylint: disable=unused-argument + """ + A mock GMT API function that always returns a given value. + """ + return returns + + mock_func = mock_api_function + + get_libgmt_func = session.get_libgmt_func + + def mock_get_libgmt_func(name, argtypes=None, restype=None): + """ + Return our mock function. + """ + if name == func: + return mock_func + return get_libgmt_func(name, argtypes, restype) + + setattr(session, "get_libgmt_func", mock_get_libgmt_func) + + yield + + setattr(session, "get_libgmt_func", get_libgmt_func) + + +def test_virtual_file(dtypes): + """ + Test passing in data via a virtual file with a Dataset. + """ + shape = (5, 3) + for dtype in dtypes: + with clib.Session() as lib: + family = "GMT_IS_DATASET|GMT_VIA_MATRIX" + geometry = "GMT_IS_POINT" + dataset = lib.create_data( + family=family, + geometry=geometry, + mode="GMT_CONTAINER_ONLY", + dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype + ) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + lib.put_matrix(dataset, matrix=data) + # Add the dataset to a virtual file and pass it along to gmt info + vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset) + with lib.open_virtual_file(*vfargs) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) + expected = f": N = {shape[0]}\t{bounds}\n" + assert output == expected + + +def test_virtual_file_fails(): + """ + Check that opening and closing virtual files raises an exception for non- + zero return codes. + """ + vfargs = ( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + "GMT_IS_POINT", + "GMT_IN|GMT_IS_REFERENCE", + None, + ) + + # Mock Open_VirtualFile to test the status check when entering the context. + # If the exception is raised, the code won't get to the closing of the + # virtual file. + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): + with pytest.raises(GMTCLibError): + with lib.open_virtual_file(*vfargs): + print("Should not get to this code") + + # Test the status check when closing the virtual file + # Mock the opening to return 0 (success) so that we don't open a file that + # we won't close later. + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( + lib, "GMT_Close_VirtualFile", returns=1 + ): + with pytest.raises(GMTCLibError): + with lib.open_virtual_file(*vfargs): + pass + print("Shouldn't get to this code either") + + +def test_virtual_file_bad_direction(): + """ + Test passing an invalid direction argument. + """ + with clib.Session() as lib: + vfargs = ( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + "GMT_IS_POINT", + "GMT_IS_GRID", # The invalid direction argument + 0, + ) + with pytest.raises(GMTInvalidInput): + with lib.open_virtual_file(*vfargs): + print("This should have failed") + + +@pytest.mark.parametrize( + "array_func,kind", + [(np.array, "matrix"), (pd.DataFrame, "vector"), (xr.Dataset, "vector")], +) +def test_virtualfile_from_data_required_z_matrix(array_func, kind): + """ + Test that function works when third z column in a matrix is needed and + provided. + """ + shape = (5, 3) + dataframe = pd.DataFrame( + data=np.arange(shape[0] * shape[1]).reshape(shape), columns=["x", "y", "z"] + ) + data = array_func(dataframe) + with clib.Session() as lib: + with lib.virtualfile_from_data( + data=data, required_z=True, check_kind="vector" + ) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + [ + f"<{i.min():.0f}/{i.max():.0f}>" + for i in (dataframe.x, dataframe.y, dataframe.z) + ] + ) + expected = f"<{kind} memory>: N = {shape[0]}\t{bounds}\n" + assert output == expected + + +def test_virtualfile_from_data_required_z_matrix_missing(): + """ + Test that function fails when third z column in a matrix is needed but not + provided. + """ + data = np.ones((5, 2)) + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + with lib.virtualfile_from_data( + data=data, required_z=True, check_kind="vector" + ): + pass + + +def test_virtualfile_from_data_fail_non_valid_data(data): + """ + Should raise an exception if too few or too much data is given. + """ + # Test all combinations where at least one data variable + # is not given in the x, y case: + for variable in product([None, data[:, 0]], repeat=2): + # Filter one valid configuration: + if not any(item is None for item in variable): + continue + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + lib.virtualfile_from_data( + x=variable[0], + y=variable[1], + ) + + # Test all combinations where at least one data variable + # is not given in the x, y, z case: + for variable in product([None, data[:, 0]], repeat=3): + # Filter one valid configuration: + if not any(item is None for item in variable): + continue + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + lib.virtualfile_from_data( + x=variable[0], y=variable[1], z=variable[2], required_z=True + ) + + # Should also fail if given too much data + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + lib.virtualfile_from_data( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + data=data, + ) + + +def test_virtualfile_from_vectors(dtypes): + """ + Test the automation for transforming vectors to virtual file dataset. + """ + size = 10 + for dtype in dtypes: + x = np.arange(size, dtype=dtype) + y = np.arange(size, size * 2, 1, dtype=dtype) + z = np.arange(size * 2, size * 3, 1, dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"<{i.min():.0f}/{i.max():.0f}>" for i in (x, y, z)]) + expected = f": N = {size}\t{bounds}\n" + assert output == expected + + +@pytest.mark.parametrize("dtype", [str, object]) +def test_virtualfile_from_vectors_one_string_or_object_column(dtype): + """ + Test passing in one column with string or object dtype into virtual file + dataset. + """ + size = 5 + x = np.arange(size, dtype=np.int32) + y = np.arange(size, size * 2, 1, dtype=np.int32) + strings = np.array(["a", "bc", "defg", "hijklmn", "opqrst"], dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, strings) as vfile: + with GMTTempFile() as outfile: + lib.call_module("convert", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + expected = "".join(f"{i}\t{j}\t{k}\n" for i, j, k in zip(x, y, strings)) + assert output == expected + + +@pytest.mark.parametrize("dtype", [str, object]) +def test_virtualfile_from_vectors_two_string_or_object_columns(dtype): + """ + Test passing in two columns of string or object dtype into virtual file + dataset. + """ + size = 5 + x = np.arange(size, dtype=np.int32) + y = np.arange(size, size * 2, 1, dtype=np.int32) + # Catch bug in https://github.com/GenericMappingTools/pygmt/pull/2719 + strings1 = np.array(["a", "bc", "def", "ghij", "klmnolooong"], dtype=dtype) + strings2 = np.array(["pqrst", "uvwx", "yz!", "@#", "$"], dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, strings1, strings2) as vfile: + with GMTTempFile() as outfile: + lib.call_module("convert", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + expected = "".join( + f"{h}\t{i}\t{j} {k}\n" for h, i, j, k in zip(x, y, strings1, strings2) + ) + assert output == expected + + +def test_virtualfile_from_vectors_transpose(): + """ + Test transforming matrix columns to virtual file dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (7, 5) + for dtype in dtypes: + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(*data.T) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} -C ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"{col.min():.0f}\t{col.max():.0f}" for col in data.T]) + expected = f"{bounds}\n" + assert output == expected + + +def test_virtualfile_from_vectors_diff_size(): + """ + Test the function fails for arrays of different sizes. + """ + x = np.arange(5) + y = np.arange(6) + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + with lib.virtualfile_from_vectors(x, y): + print("This should have failed") + + +def test_virtualfile_from_matrix(dtypes): + """ + Test transforming a matrix to virtual file dataset. + """ + shape = (7, 5) + for dtype in dtypes: + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + with clib.Session() as lib: + with lib.virtualfile_from_matrix(data) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) + expected = f": N = {shape[0]}\t{bounds}\n" + assert output == expected + + +def test_virtualfile_from_matrix_slice(dtypes): + """ + Test transforming a slice of a larger array to virtual file dataset. + """ + shape = (10, 6) + for dtype in dtypes: + full_data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + rows = 5 + cols = 3 + data = full_data[:rows, :cols] + with clib.Session() as lib: + with lib.virtualfile_from_matrix(data) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T]) + expected = f": N = {rows}\t{bounds}\n" + assert output == expected + + +def test_virtualfile_from_vectors_pandas(dtypes): + """ + Pass vectors to a dataset using pandas Series. + """ + size = 13 + for dtype in dtypes: + data = pd.DataFrame( + data={ + "x": np.arange(size, dtype=dtype), + "y": np.arange(size, size * 2, 1, dtype=dtype), + "z": np.arange(size * 2, size * 3, 1, dtype=dtype), + } + ) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(data.x, data.y, data.z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + [f"<{i.min():.0f}/{i.max():.0f}>" for i in (data.x, data.y, data.z)] + ) + expected = f": N = {size}\t{bounds}\n" + assert output == expected + + +def test_virtualfile_from_vectors_arraylike(): + """ + Pass array-like vectors to a dataset. + """ + size = 13 + x = list(range(0, size, 1)) + y = tuple(range(size, size * 2, 1)) + z = range(size * 2, size * 3, 1) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + bounds = "\t".join([f"<{min(i):.0f}/{max(i):.0f}>" for i in (x, y, z)]) + expected = f": N = {size}\t{bounds}\n" + assert output == expected From 20a5020ae6491de9be42f9675814c3551cdc26e8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 14:10:14 +0800 Subject: [PATCH 2/6] Remove an invalid pylint directive --- pygmt/tests/test_clib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index c48fcbef649..86a5327c40f 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -1,5 +1,4 @@ # pylint: disable=protected-access -# pylint: disable=redefined-outer-name """ Test the wrappers for the C API. """ From d2a14437d56bbb58600fd230f14be4b72d1b7118 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 14:22:50 +0800 Subject: [PATCH 3/6] Add the dtypes fixtures --- pygmt/tests/test_clib.py | 8 -------- pygmt/tests/test_clib_virtualfiles.py | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 86a5327c40f..b4680c5aade 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -34,14 +34,6 @@ def fixture_data(): return np.loadtxt(POINTS_DATA) -@pytest.fixture(scope="module", name="dtypes") -def fixture_dtypes(): - """ - List of supported numpy dtypes. - """ - return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() - - @contextmanager def mock(session, func, returns=None, mock_func=None): """ diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py index f6837165b2f..7fcd6a48216 100644 --- a/pygmt/tests/test_clib_virtualfiles.py +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -13,6 +13,14 @@ from pygmt.helpers import GMTTempFile +@pytest.fixture(scope="module", name="dtypes") +def fixture_dtypes(): + """ + List of supported numpy dtypes. + """ + return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() + + @contextmanager def mock(session, func, returns=None, mock_func=None): """ From a5cb5bca4e9abb77311233df554349ece1e90ce1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 15:11:24 +0800 Subject: [PATCH 4/6] Add the data fixture --- pygmt/tests/test_clib.py | 9 --------- pygmt/tests/test_clib_virtualfiles.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index b4680c5aade..f2bd5379489 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -23,15 +23,6 @@ from pygmt.helpers import GMTTempFile TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") - - -@pytest.fixture(scope="module", name="data") -def fixture_data(): - """ - Load the point data from the test file. - """ - return np.loadtxt(POINTS_DATA) @contextmanager diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py index 7fcd6a48216..68f46f214fe 100644 --- a/pygmt/tests/test_clib_virtualfiles.py +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -1,6 +1,7 @@ """ Test the API functions related to virtual files. """ +import os from contextlib import contextmanager from itertools import product @@ -12,6 +13,9 @@ from pygmt.exceptions import GMTCLibError, GMTInvalidInput from pygmt.helpers import GMTTempFile +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") + @pytest.fixture(scope="module", name="dtypes") def fixture_dtypes(): @@ -21,6 +25,14 @@ def fixture_dtypes(): return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() +@pytest.fixture(scope="module", name="data") +def fixture_data(): + """ + Load the point data from the test file. + """ + return np.loadtxt(POINTS_DATA) + + @contextmanager def mock(session, func, returns=None, mock_func=None): """ From e2e9bdac8ada506efa9f9cbbf35b8a43ce2848ea Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 15:55:37 +0800 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/tests/test_clib_virtualfiles.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py index 68f46f214fe..99e58f4de0a 100644 --- a/pygmt/tests/test_clib_virtualfiles.py +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -1,5 +1,5 @@ """ -Test the API functions related to virtual files. +Test the C API functions related to virtual files. """ import os from contextlib import contextmanager @@ -17,14 +17,6 @@ POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") -@pytest.fixture(scope="module", name="dtypes") -def fixture_dtypes(): - """ - List of supported numpy dtypes. - """ - return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() - - @pytest.fixture(scope="module", name="data") def fixture_data(): """ @@ -33,6 +25,14 @@ def fixture_data(): return np.loadtxt(POINTS_DATA) +@pytest.fixture(scope="module", name="dtypes") +def fixture_dtypes(): + """ + List of supported numpy dtypes. + """ + return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() + + @contextmanager def mock(session, func, returns=None, mock_func=None): """ @@ -205,10 +205,7 @@ def test_virtualfile_from_data_fail_non_valid_data(data): continue with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=variable[0], - y=variable[1], - ) + lib.virtualfile_from_data(x=variable[0], y=variable[1]) # Test all combinations where at least one data variable # is not given in the x, y, z case: From d5ef26eb8d6d05a55568e75ffd5b8c838eb17745 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 31 Oct 2023 16:00:51 +0800 Subject: [PATCH 6/6] Avoid duplicate the mock function in test_clib_virtualfiles.py --- pygmt/tests/test_clib_virtualfiles.py | 41 +-------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py index 99e58f4de0a..984ec187e87 100644 --- a/pygmt/tests/test_clib_virtualfiles.py +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -2,7 +2,6 @@ Test the C API functions related to virtual files. """ import os -from contextlib import contextmanager from itertools import product import numpy as np @@ -12,6 +11,7 @@ from pygmt import clib from pygmt.exceptions import GMTCLibError, GMTInvalidInput from pygmt.helpers import GMTTempFile +from pygmt.tests.test_clib import mock TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") @@ -33,45 +33,6 @@ def fixture_dtypes(): return "int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64".split() -@contextmanager -def mock(session, func, returns=None, mock_func=None): - """ - Mock a GMT C API function to make it always return a given value. - - Used to test that exceptions are raised when API functions fail by - producing a NULL pointer as output or non-zero status codes. - - Needed because it's not easy to get some API functions to fail without - inducing a Segmentation Fault (which is a good thing because libgmt usually - only fails with errors). - """ - if mock_func is None: - - def mock_api_function(*args): # pylint: disable=unused-argument - """ - A mock GMT API function that always returns a given value. - """ - return returns - - mock_func = mock_api_function - - get_libgmt_func = session.get_libgmt_func - - def mock_get_libgmt_func(name, argtypes=None, restype=None): - """ - Return our mock function. - """ - if name == func: - return mock_func - return get_libgmt_func(name, argtypes, restype) - - setattr(session, "get_libgmt_func", mock_get_libgmt_func) - - yield - - setattr(session, "get_libgmt_func", get_libgmt_func) - - def test_virtual_file(dtypes): """ Test passing in data via a virtual file with a Dataset.