Skip to content

Commit

Permalink
test: remove broken marker, better define notimpl and notyet (#…
Browse files Browse the repository at this point in the history
…9688)

inspired by #9535

The broken mark had the meaning of "this broke for some reason, but it
wasn't the fault of this PR, so lets ignore it for now". I find this
only meaningful in the context of that individual PR though: as soon as
the PR is merged, the usefulness drops to near 0. I come across this
mark as I'm reading code and I have no idea what action I should take.
notimpl, notyet, and never are much more useful, they signal where the
fault lies and what to do about it.

I went through and converted all the instances of broken to one of these
other 3 marks. It was fairly fast and definitely error prone. I could
see the argument for converting these all to plain xfails when we aren't
sure. I'm not sure how bad it is for these marks to be incorrect.

I tweaked the prose that describes these 3 marks. I would love your
editing of them.
  • Loading branch information
NickCrews authored Jul 29, 2024
1 parent f89aa32 commit 20ceee5
Show file tree
Hide file tree
Showing 25 changed files with 208 additions and 242 deletions.
12 changes: 6 additions & 6 deletions docs/contribute/02_workflow.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ all ordinary and edge cases.
Pytest markers can be used to assert that a test should fail or raise a specific error.
We use a number of pytest markers in ibis:

- `pytest.mark.notimpl`: the backend can do a thing, we haven't mapped the op
- `pytest.mark.notyet`: the backend cannot do a thing, but might in the future
- `pytest.mark.never`: the backend will never support this / pass this test (common example
here is a test running on sqlite that relies on strong typing)
- `pytest.mark.broken`: this test broke and it's demonstrably unrelated to the PR I'm working
on and fixing it shouldn't block this PR from going in (but we should fix it up pronto)
- `pytest.mark.notimpl`: We can implement/fix/workaround this on the ibis side, but haven't yet.
- `pytest.mark.notyet`: This requires the backend to implement/fix something.
We can't/won't do it on the ibis side.
- `pytest.mark.never`: The backend will never support this / pass this test.
We shouldn't have any hope of trying to fix this.
A common example here is a test running on sqlite that relies on strong typing.

Refrain from using a generic marker like `pytest.mark.xfail`.

Expand Down
16 changes: 0 additions & 16 deletions ibis/backends/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,6 @@ def _filter_none_from_raises(kwargs):
kwargs = _filter_none_from_raises(kwargs)
item.add_marker(pytest.mark.xfail(**kwargs))

# Something has been exposed as broken by a new test and it shouldn't be
# imperative for a contributor to fix it just because they happened to
# bring it to attention -- USE SPARINGLY
for marker in item.iter_markers(name="broken"):
if backend in marker.args[0]:
if (
item.location[0] in FIlES_WITH_STRICT_EXCEPTION_CHECK
and "raises" not in marker.kwargs.keys()
):
raise ValueError("broken requires a raises")

kwargs = marker.kwargs.copy()
kwargs.setdefault("reason", f"Feature is failing on {backend}")
kwargs = _filter_none_from_raises(kwargs)
item.add_marker(pytest.mark.xfail(**kwargs))

for marker in item.iter_markers(name="xfail_version"):
kwargs = marker.kwargs.copy()
kwargs = _filter_none_from_raises(kwargs)
Expand Down
26 changes: 13 additions & 13 deletions ibis/backends/tests/test_aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def mean_and_std(v):
lambda t, where: t.bool_col[where].any(),
id="any",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'any'",
Expand All @@ -292,7 +292,7 @@ def mean_and_std(v):
lambda t, where: ~t.bool_col[where].any(),
id="notany",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'notany'",
Expand All @@ -305,7 +305,7 @@ def mean_and_std(v):
lambda t, where: ~t.bool_col[where].any(),
id="any_negate",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'any'",
Expand All @@ -318,7 +318,7 @@ def mean_and_std(v):
lambda t, where: t.bool_col[where].all(),
id="all",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'all'",
Expand All @@ -330,7 +330,7 @@ def mean_and_std(v):
lambda t, where: ~t.bool_col[where].all(),
id="notall",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'notall'",
Expand All @@ -343,7 +343,7 @@ def mean_and_std(v):
lambda t, where: ~t.bool_col[where].all(),
id="all_negate",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["druid"],
raises=AttributeError,
reason="'IntegerColumn' object has no attribute 'all'",
Expand All @@ -361,7 +361,7 @@ def mean_and_std(v):
lambda t, where: (t.int_col > 0)[where].sum(),
id="bool_sum",
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["oracle"],
raises=OracleDatabaseError,
reason="ORA-02000: missing AS keyword",
Expand Down Expand Up @@ -538,7 +538,7 @@ def mean_and_std(v):
["impala", "mysql", "sqlite", "mssql", "druid", "oracle", "exasol"],
raises=com.OperationNotDefinedError,
),
pytest.mark.broken(
pytest.mark.notimpl(
["dask"],
raises=(AttributeError, TypeError),
reason=(
Expand Down Expand Up @@ -809,12 +809,12 @@ def test_count_distinct_star(alltypes, df, ibis_cond, pandas_cond):
reason="backend implements approximate quantiles",
raises=com.OperationNotDefinedError,
),
pytest.mark.broken(
pytest.mark.never(
["pyspark"],
reason="backend implements approximate quantiles",
raises=AssertionError,
),
pytest.mark.broken(
pytest.mark.never(
["dask"],
reason="backend implements approximate quantiles",
raises=AssertionError,
Expand All @@ -824,7 +824,7 @@ def test_count_distinct_star(alltypes, df, ibis_cond, pandas_cond):
reason="backend doesn't implement approximate quantiles yet",
raises=com.OperationNotDefinedError,
),
pytest.mark.broken(
pytest.mark.notimpl(
["risingwave"],
reason="Invalid input syntax: direct arg in `percentile_cont` must be castable to float64",
raises=PsycoPg2InternalError,
Expand Down Expand Up @@ -1132,7 +1132,7 @@ def test_median(alltypes, df):
raises=ClickHouseDatabaseError,
reason="doesn't support median of strings",
)
@pytest.mark.broken(
@pytest.mark.notyet(
["pyspark"], raises=AssertionError, reason="pyspark returns null for string median"
)
@pytest.mark.notimpl(["dask"], raises=(AssertionError, NotImplementedError, TypeError))
Expand Down Expand Up @@ -1301,7 +1301,7 @@ def test_topk_op(alltypes, df):
)
],
)
@pytest.mark.broken(
@pytest.mark.notyet(
["druid"], raises=PyDruidProgrammingError, reason="Java NullPointerException"
)
@pytest.mark.notimpl(
Expand Down
38 changes: 21 additions & 17 deletions ibis/backends/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def test_array_slice(backend, start, stop):
@pytest.mark.notimpl(
["datafusion", "flink", "polars", "sqlite"], raises=com.OperationNotDefinedError
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["risingwave"],
raises=PsycoPg2InternalError,
reason="TODO(Kexiang): seems a bug",
Expand Down Expand Up @@ -460,7 +460,7 @@ def test_array_slice(backend, start, stop):
],
ids=["lambda", "partial", "deferred"],
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["risingwave"],
raises=PsycoPg2InternalError,
reason="TODO(Kexiang): seems a bug",
Expand Down Expand Up @@ -542,7 +542,7 @@ def test_array_filter(con, input, output, predicate):
"x",
1,
marks=[
pytest.mark.broken(
pytest.mark.notimpl(
["flink"],
raises=Py4JJavaError,
reason="unknown; NPE during execution",
Expand Down Expand Up @@ -574,7 +574,7 @@ def test_array_contains(backend, con, col, value):
raises=Py4JJavaError,
reason="SQL validation failed; Flink does not support ARRAY[]", # https://issues.apache.org/jira/browse/FLINK-20578
),
pytest.mark.broken(
pytest.mark.notyet(
["datafusion"],
raises=Exception,
reason="Internal error: start_from index out of bounds",
Expand Down Expand Up @@ -647,7 +647,7 @@ def test_array_remove(con, a):
raises=(AssertionError, GoogleBadRequest),
reason="bigquery doesn't support null elements in arrays",
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["risingwave"], raises=AssertionError, reason="TODO(Kexiang): seems a bug"
)
@pytest.mark.notyet(
Expand Down Expand Up @@ -681,7 +681,7 @@ def test_array_unique(con, input, expected):
["flink", "polars"],
raises=com.OperationNotDefinedError,
)
@pytest.mark.broken(
@pytest.mark.notyet(
["risingwave"],
raises=AssertionError,
reason="Refer to https://github.com/risingwavelabs/risingwave/issues/14735",
Expand Down Expand Up @@ -752,7 +752,7 @@ def test_array_union(con, a, b, expected_array):
@pytest.mark.notimpl(
["sqlite"], raises=com.UnsupportedBackendType, reason="Unsupported type: Array..."
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["risingwave"],
raises=AssertionError,
reason="TODO(Kexiang): seems a bug",
Expand Down Expand Up @@ -793,7 +793,7 @@ def test_array_intersect(con, data):
@pytest.mark.notimpl(["postgres"], raises=PsycoPg2SyntaxError)
@pytest.mark.notimpl(["risingwave"], raises=PsycoPg2InternalError)
@pytest.mark.notimpl(["datafusion"], raises=com.OperationNotDefinedError)
@pytest.mark.broken(
@pytest.mark.notimpl(
["trino"], reason="inserting maps into structs doesn't work", raises=TrinoUserError
)
def test_unnest_struct(con):
Expand All @@ -813,10 +813,10 @@ def test_unnest_struct(con):
@pytest.mark.notimpl(["postgres"], raises=PsycoPg2SyntaxError)
@pytest.mark.notimpl(["risingwave"], raises=PsycoPg2InternalError)
@pytest.mark.notimpl(["datafusion"], raises=com.OperationNotDefinedError)
@pytest.mark.broken(
@pytest.mark.notimpl(
["trino"], reason="inserting maps into structs doesn't work", raises=TrinoUserError
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["flink"], reason="flink unnests a and b as separate columns", raises=Py4JJavaError
)
def test_unnest_struct_with_multiple_fields(con):
Expand Down Expand Up @@ -914,7 +914,7 @@ def test_zip_null(con, fn):
reason="pyspark doesn't seem to support field selection on explode",
raises=PySparkAnalysisException,
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["trino"], reason="inserting maps into structs doesn't work", raises=TrinoUserError
)
@pytest.mark.notyet(
Expand Down Expand Up @@ -1002,7 +1002,7 @@ def flatten_data():
reason="Arrays are never nullable",
raises=AssertionError,
),
pytest.mark.broken(
pytest.mark.notimpl(
["polars"],
raises=TypeError,
reason="comparison of nested arrays doesn't work in pandas testing module",
Expand Down Expand Up @@ -1220,8 +1220,10 @@ def swap(token):
"-1h",
id="neg_inner",
marks=[
pytest.mark.broken(
["polars"], raises=AssertionError, reason="returns an empty array"
pytest.mark.notimpl(
["polars"],
raises=(AssertionError, TypeError),
reason="returns an empty array",
),
pytest.mark.notimpl(
["risingwave"],
Expand Down Expand Up @@ -1329,8 +1331,10 @@ def test_repr_timestamp_array(con, monkeypatch):
["datafusion", "flink", "polars"],
raises=com.OperationNotDefinedError,
)
@pytest.mark.broken(["pandas"], raises=ValueError, reason="reindex on duplicate values")
@pytest.mark.broken(
@pytest.mark.notimpl(
["pandas"], raises=ValueError, reason="reindex on duplicate values"
)
@pytest.mark.notimpl(
["dask"], raises=AssertionError, reason="DataFrame.index are different"
)
def test_unnest_range(con):
Expand Down Expand Up @@ -1367,7 +1371,7 @@ def test_array_literal_with_exprs(con, input, expected):
["datafusion", "postgres", "pandas", "polars", "risingwave", "dask", "flink"],
raises=com.OperationNotDefinedError,
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["trino"],
raises=TrinoUserError,
reason="sqlglot generates code that assumes there's only at most two fields to unpack from a struct",
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/tests/test_asof_join.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_asof_join(con, time_left, time_right, time_df1, time_df2, direction, op
@pytest.mark.parametrize(
("direction", "op"), [("backward", operator.ge), ("forward", operator.le)]
)
@pytest.mark.broken(
@pytest.mark.notimpl(
["clickhouse"], raises=AssertionError, reason="`time` is truncated to seconds"
)
@pytest.mark.notyet(
Expand Down
14 changes: 7 additions & 7 deletions ibis/backends/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_create_table(backend, con, temp_table, func, sch):
reason="Can't rename temp tables",
raises=ValueError,
),
pytest.mark.broken(
pytest.mark.notimpl(
["bigquery"],
reason="tables created with temp=True cause a 404 on retrieval",
),
Expand All @@ -155,8 +155,8 @@ def test_create_table(backend, con, temp_table, func, sch):
["pyspark", "trino", "exasol", "risingwave"],
reason="No support for temp tables",
),
pytest.mark.broken(["mssql"], reason="Incorrect temp table syntax"),
pytest.mark.broken(
pytest.mark.notimpl(["mssql"], reason="Incorrect temp table syntax"),
pytest.mark.notimpl(
["bigquery"],
reason="tables created with temp=True cause a 404 on retrieval",
),
Expand Down Expand Up @@ -293,7 +293,7 @@ def test_create_table_from_schema(con, new_schema, temp_table):
assert result == new_table.schema()


@mark.broken(
@mark.notimpl(
["oracle"],
reason="oracle temp tables aren't cleaned up on reconnect -- they need to "
"be switched from using atexit to weakref.finalize",
Expand Down Expand Up @@ -1315,10 +1315,10 @@ def test_set_backend_url(url, monkeypatch):
@pytest.mark.notimpl(
["snowflake"], reason="scale not implemented in ibis's snowflake backend"
)
@pytest.mark.broken(
@pytest.mark.never(
["oracle"], reason="oracle doesn't allow DESCRIBE outside of its CLI"
)
@pytest.mark.broken(["druid"], reason="dialect is broken")
@pytest.mark.notimpl(["druid"], reason="dialect is broken")
@pytest.mark.notimpl(
["flink"],
raises=com.IbisError,
Expand All @@ -1345,7 +1345,7 @@ def gen_test_name(con: BaseBackend):
con.drop_table(name, force=True)


@mark.broken(
@mark.notimpl(
["druid"], raises=NotImplementedError, reason="generated SQL fails to parse"
)
@mark.notimpl(["impala"], reason="impala doesn't support memtable")
Expand Down
6 changes: 3 additions & 3 deletions ibis/backends/tests/test_conditionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def test_value_cases_scalar(con, inp, exp):
assert result == exp


@pytest.mark.broken(
@pytest.mark.notimpl(
"exasol",
reason="the int64 RBI column is .to_pandas()ed to an object column, which is incomparable to ints",
raises=AssertionError,
Expand Down Expand Up @@ -117,7 +117,7 @@ def test_ibis_cases_scalar():
assert result == "five"


@pytest.mark.broken(
@pytest.mark.notimpl(
["sqlite", "exasol"],
reason="the int64 RBI column is .to_pandas()ed to an object column, which is incomparable to 5",
raises=TypeError,
Expand All @@ -142,7 +142,7 @@ def test_ibis_cases_column(batting):
assert Counter(result) == Counter(expected)


@pytest.mark.broken("clickhouse", reason="special case this and returns 'oops'")
@pytest.mark.notimpl("clickhouse", reason="special case this and returns 'oops'")
def test_value_cases_null(con):
"""CASE x WHEN NULL never gets hit"""
e = ibis.literal(5).nullif(5).case().when(None, "oops").else_("expected").end()
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_column_pyarrow_batch_chunk_size(awards_players):


@pytest.mark.notimpl(["pandas", "dask"])
@pytest.mark.broken(
@pytest.mark.notimpl(
["sqlite"],
raises=pa.ArrowException,
reason="Test data has empty strings in columns typed as int64",
Expand Down
Loading

0 comments on commit 20ceee5

Please sign in to comment.