diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 13e829df12fa..eb373ca0d94f 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -543,3 +543,10 @@ quartodoc: - Options - Repr - SQL + + - title: Contributing + desc: "Ibis Backend Developer Documentation" + package: ibis.backends.tests.base + contents: + - BackendTest + - ServiceBackendTest diff --git a/docs/contribute/05_maintainers_guide.qmd b/docs/contribute/04_maintainers_guide.qmd similarity index 100% rename from docs/contribute/05_maintainers_guide.qmd rename to docs/contribute/04_maintainers_guide.qmd diff --git a/docs/contribute/05_reference.qmd b/docs/contribute/05_reference.qmd new file mode 100644 index 000000000000..954da7bdb331 --- /dev/null +++ b/docs/contribute/05_reference.qmd @@ -0,0 +1,37 @@ +--- +title: "Test Class Reference" +--- + +This page provides a partial reference to the attributes, methods, properties +and class-level variables that are used to help configure a backend for the Ibis +test suite. + +Contributors are encouraged to look over the methods and class-level variables +in `ibis/backends/tests/base.py`. + +To add a new backend test configuration import one of `BackendTest` or +`ServiceBackendTest` into a `conftest.py` file with the path +`ibis/backends/{backend_name}/tests/conftest.py`. Then update / override the +relevant class-level variables and methods. + +```python +from ibis.backends.tests.base import BackendTest + +class TestConf(BackendTest): + """Backend-specific class with information for testing.""" + + supports_divide_by_zero = True + supports_floating_modulus = False + returned_timestamp_unit = "us" + supports_structs = True + supports_json = True + check_names = False + force_sort = True + + @staticmethod + def connect(*args, **kwargs): + ... +``` + +{{< include ../reference/BackendTest.qmd >}} +{{< include ../reference/ServiceBackendTest.qmd >}} diff --git a/ibis/backends/bigquery/tests/conftest.py b/ibis/backends/bigquery/tests/conftest.py index e842a4f47024..c765333c7246 100644 --- a/ibis/backends/bigquery/tests/conftest.py +++ b/ibis/backends/bigquery/tests/conftest.py @@ -34,7 +34,7 @@ class TestConf(BackendTest): supports_structs = True supports_json = True check_names = False - force_sort_before_comparison = True + force_sort = True deps = ("google.cloud.bigquery",) @staticmethod diff --git a/ibis/backends/clickhouse/tests/conftest.py b/ibis/backends/clickhouse/tests/conftest.py index 618f97a3b633..9f5d4cccfeec 100644 --- a/ibis/backends/clickhouse/tests/conftest.py +++ b/ibis/backends/clickhouse/tests/conftest.py @@ -29,7 +29,7 @@ class TestConf(ServiceBackendTest): supported_to_timestamp_units = {"s"} supports_floating_modulus = False supports_json = False - force_sort_before_comparison = True + force_sort = True rounding_method = "half_to_even" data_volume = "/var/lib/clickhouse/user_files/ibis" service_name = "clickhouse" diff --git a/ibis/backends/flink/tests/conftest.py b/ibis/backends/flink/tests/conftest.py index 92b412ebbeb0..a8d32f9b4d1e 100644 --- a/ibis/backends/flink/tests/conftest.py +++ b/ibis/backends/flink/tests/conftest.py @@ -11,7 +11,7 @@ class TestConf(BackendTest): supports_structs = False - force_sort_before_comparison = True + force_sort = True deps = "pandas", "pyflink" @staticmethod diff --git a/ibis/backends/impala/tests/conftest.py b/ibis/backends/impala/tests/conftest.py index 4e46b646c6e1..abeb810b3d4c 100644 --- a/ibis/backends/impala/tests/conftest.py +++ b/ibis/backends/impala/tests/conftest.py @@ -31,7 +31,7 @@ class TestConf(BackendTest): returned_timestamp_unit = "s" supports_structs = False supports_json = False - force_sort_before_comparison = True + force_sort = True deps = "fsspec", "requests", "impala" def _load_data(self, **_: Any) -> None: diff --git a/ibis/backends/tests/base.py b/ibis/backends/tests/base.py index 1fdb96663b28..8b9ae9a0ca77 100644 --- a/ibis/backends/tests/base.py +++ b/ibis/backends/tests/base.py @@ -59,7 +59,7 @@ class BackendTest(abc.ABC): "Whether special handling is needed for running a multi-process pytest run." supports_tpch: bool = False "Child class defines a `load_tpch` method that loads the required TPC-H tables into a connection." - force_sort_before_comparison = False + force_sort = False "Sort results before comparing against reference computation." rounding_method: Literal["away_from_zero", "half_to_even"] = "away_from_zero" "Name of round method to use for rounding test comparisons." @@ -163,6 +163,7 @@ def load_data( @classmethod def skip_if_missing_deps(cls) -> None: + """Add an `importorskip` for any missing dependencies.""" for dep in cls.deps: pytest.importorskip(dep) @@ -176,7 +177,11 @@ def postload(self, **_): # noqa: B027 def assert_series_equal( cls, left: pd.Series, right: pd.Series, *args: Any, **kwargs: Any ) -> None: - if cls.force_sort_before_comparison: + """Compare two Pandas Series, optionally ignoring order, dtype, and column name. + + `force_sort`, `check_dtype`, and `check_names` are set as class-level variables. + """ + if cls.force_sort: left = left.sort_values().reset_index(drop=True) right = right.sort_values().reset_index(drop=True) kwargs.setdefault("check_dtype", cls.check_dtype) @@ -187,7 +192,11 @@ def assert_series_equal( def assert_frame_equal( cls, left: pd.DataFrame, right: pd.DataFrame, *args: Any, **kwargs: Any ) -> None: - if cls.force_sort_before_comparison: + """Compare two Pandas DataFrames optionally ignoring order, and dtype. + + `force_sort`, and `check_dtype` are set as class-level variables. + """ + if cls.force_sort: columns = list(set(left.columns) & set(right.columns)) left = left.sort_values(by=columns) right = right.sort_values(by=columns) @@ -344,9 +353,14 @@ class ServiceBackendTest(BackendTest): @property @abc.abstractmethod def test_files(self) -> Iterable[Path]: + """Returns an iterable of test files to load into a Docker container before testing.""" ... def preload(self): + """Use `docker compose cp` to copy all files from `test_files` into a container. + + `service_name` and `data_volume` are set as class-level variables. + """ service = self.service_name data_volume = self.data_volume with concurrent.futures.ThreadPoolExecutor() as e: