ronantakizawa/codereview-bench huggingface dataset:
Repository: pandas-dev/pandas
File: pandas/tests/io/parser/test_upcast.py (Python)

Code before:
    # GH#36712
    dtype = np.int64
    na_value = na_values[dtype]
    arr = np.array([na_value, na_value], dtype=dtype)
    result = _maybe_upcast(arr, use_dtype_backend=True)

    expected_mask = np.array([True, True])
    expected = IntegerArray(arr, mask=expected_mask)
    tm.assert_extension_array_equal(result, expected)


@pytest.mark.parametrize("val", [na_values[np.object_], "c"])
def test_maybe_upcast_object(val, string_storage):
    # GH#36712
    pa = pytest.importorskip("pyarrow")

    with pd.option_context("mode.string_storage", string_storage):
        arr = np.array(["a", "b", val], dtype=np.object_)
        result = _maybe_upcast(arr, use_dtype_backend=True)

        if string_storage == "python":
            exp_val = "c" if val == "c" else NA
            dtype = pd.StringDtype()
            expected = StringArray(
                np.array(["a", "b", exp_val], dtype=np.object_), dtype=dtype
            )
        else:
            exp_val = "c" if val == "c" else None
            expected = ArrowStringArray(pa.array(["a", "b", exp_val]))
        tm.assert_extension_array_equal(result, expected)


Review comment (suggestion):
```suggestion
            expected = pd.array(["a", "b", exp_val], dtype=pd.StringDtype())
```

Simpler? (for this test I don't think we need to use the class constructor?)

Code after:
    dtype = np.int64
    na_value = na_values[dtype]
    arr = np.array([na_value, na_value], dtype=dtype)
    result = _maybe_upcast(arr, use_dtype_backend=True)

    expected_mask = np.array([True, True])
    expected = IntegerArray(arr, mask=expected_mask)
    tm.assert_extension_array_equal(result, expected)


@pytest.mark.parametrize("val", [na_values[np.object_], "c"])
def test_maybe_upcast_object(val, string_storage):
    # GH#36712
    pa = pytest.importorskip("pyarrow")

    with pd.option_context("mode.string_storage", string_storage):
        arr = np.array(["a", "b", val], dtype=np.object_)
        result = _maybe_upcast(arr, use_dtype_backend=True)

        if string_storage == "python":
            exp_val = "c" if val == "c" else NA
            expected = pd.array(["a", "b", exp_val], dtype=pd.StringDtype())
        else:
            exp_val = "c" if val == "c" else None
            expected = ArrowStringArray(pa.array(["a", "b", exp_val]))
        tm.assert_extension_array_equal(result, expected)
Repository: pandas-dev/pandas
File: scripts/tests/test_validate_min_versions_in_sync.py (Python)

Code before:
import pathlib
import tomllib

import pytest
import yaml

from scripts.validate_min_versions_in_sync import (
    get_toml_map_from,
    get_yaml_map_from,
    pin_min_versions_to_yaml_file,
)

BASE_PATH = pathlib.Path(__file__).parents[2]


@pytest.mark.parametrize(
    "src_toml, src_yaml, expected_yaml",
    [
        (
            BASE_PATH / "scripts/tests/data/deps_minimum.toml",
            BASE_PATH / "scripts/tests/data/deps_unmodified_random.yaml",
            BASE_PATH / "scripts/tests/data/deps_expected_random.yaml",
        ),
        (
            BASE_PATH / "scripts/tests/data/deps_minimum.toml",
            BASE_PATH / "scripts/tests/data/deps_unmodified_same_version.yaml",
            BASE_PATH / "scripts/tests/data/deps_expected_same_version.yaml",
        ),
        (
            BASE_PATH / "scripts/tests/data/deps_minimum.toml",
            BASE_PATH / "scripts/tests/data/deps_unmodified_duplicate_package.yaml",
            BASE_PATH / "scripts/tests/data/deps_expected_duplicate_package.yaml",
        ),
        (
            BASE_PATH / "scripts/tests/data/deps_minimum.toml",
            BASE_PATH / "scripts/tests/data/deps_unmodified_no_version.yaml",
            BASE_PATH / "scripts/tests/data/deps_expected_no_version.yaml",
        ),

Review comment (suggestion):
Might be more robust to define `pathlib.Path("scripts/tests/data/").resolve()` that can be used for the test below

Code after:
import pathlib
import tomllib

import pytest
import yaml

from scripts.validate_min_versions_in_sync import (
    get_toml_map_from,
    get_yaml_map_from,
    pin_min_versions_to_yaml_file,
)

DATA_PATH = pathlib.Path(__file__).parents[2] / "scripts/tests/data/"


@pytest.mark.parametrize(
    "src_toml, src_yaml, expected_yaml",
    [
        (
            DATA_PATH / "deps_minimum.toml",
            DATA_PATH / "deps_unmodified_random.yaml",
            DATA_PATH / "deps_expected_random.yaml",
        ),
        (
            DATA_PATH / "deps_minimum.toml",
            DATA_PATH / "deps_unmodified_same_version.yaml",
            DATA_PATH / "deps_expected_same_version.yaml",
        ),
        (
            DATA_PATH / "deps_minimum.toml",
            DATA_PATH / "deps_unmodified_duplicate_package.yaml",
            DATA_PATH / "deps_expected_duplicate_package.yaml",
        ),
        (
            DATA_PATH / "deps_minimum.toml",
            DATA_PATH / "deps_unmodified_no_version.yaml",
            DATA_PATH / "deps_expected_no_version.yaml",
        ),
Repository: pandas-dev/pandas
File: pandas/_libs/src/vendored/ujson/python/objToJSON.c (C)

Code before:
  }

  GET_TC(tc)->itemValue = PyList_GET_ITEM(obj, GET_TC(tc)->index);
  GET_TC(tc)->index++;
  return 1;
}

static void List_iterEnd(JSOBJ Py_UNUSED(obj), JSONTypeContext *Py_UNUSED(tc)) {
}

static JSOBJ List_iterGetValue(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {
  return GET_TC(tc)->itemValue;
}

static const char *List_iterGetName(JSOBJ Py_UNUSED(obj),
                                    JSONTypeContext *Py_UNUSED(tc),
                                    size_t *Py_UNUSED(outLen)) {
  return NULL;
}

//=============================================================================
// pandas Index iteration functions
//=============================================================================
static void Index_iterBegin(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {
  GET_TC(tc)->index = 0;
  GET_TC(tc)->cStr = PyObject_Malloc(20);
  if (!GET_TC(tc)->cStr) {
    PyErr_NoMemory();
  }
}

static int Index_iterNext(JSOBJ obj, JSONTypeContext *tc) {
  const Py_ssize_t index = GET_TC(tc)->index;
  Py_XDECREF(GET_TC(tc)->itemValue);
  if (!GET_TC(tc)->cStr) {
    return 0;
  }

  if (index == 0) {
    strcpy((char *)GET_TC(tc)->cStr, "name");
    GET_TC(tc)->itemValue = PyObject_GetAttrString(obj, "name");
  } else if (index == 1) {
    strcpy((char *)GET_TC(tc)->cStr, "data");
    GET_TC(tc)->itemValue = get_values(obj);
    if (!GET_TC(tc)->itemValue) {
      return 0;
    }
  } else {
    return 0;
  }


Review comment (suggestion):
Its probably better to create a static const variable for a size within the module, rather than repeating the hard-coded 20 multiple times

Code after:
  if (GET_TC(tc)->index >= GET_TC(tc)->size) {
    return 0;
  }

  GET_TC(tc)->itemValue = PyList_GET_ITEM(obj, GET_TC(tc)->index);
  GET_TC(tc)->index++;
  return 1;
}

static void List_iterEnd(JSOBJ Py_UNUSED(obj), JSONTypeContext *Py_UNUSED(tc)) {
}

static JSOBJ List_iterGetValue(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {
  return GET_TC(tc)->itemValue;
}

static const char *List_iterGetName(JSOBJ Py_UNUSED(obj),
                                    JSONTypeContext *Py_UNUSED(tc),
                                    size_t *Py_UNUSED(outLen)) {
  return NULL;
}

//=============================================================================
// pandas Index iteration functions
//=============================================================================
static void Index_iterBegin(JSOBJ Py_UNUSED(obj), JSONTypeContext *tc) {
  GET_TC(tc)->index = 0;
  GET_TC(tc)->cStr = PyObject_Malloc(CSTR_SIZE);
  if (!GET_TC(tc)->cStr) {
    PyErr_NoMemory();
  }
}

static int Index_iterNext(JSOBJ obj, JSONTypeContext *tc) {
  const Py_ssize_t index = GET_TC(tc)->index;
  Py_XDECREF(GET_TC(tc)->itemValue);
  if (!GET_TC(tc)->cStr) {
    return 0;
  }

  if (index == 0) {
    strcpy(GET_TC(tc)->cStr, "name");
    GET_TC(tc)->itemValue = PyObject_GetAttrString(obj, "name");
  } else if (index == 1) {
    strcpy(GET_TC(tc)->cStr, "data");
    GET_TC(tc)->itemValue = get_values(obj);
    if (!GET_TC(tc)->itemValue) {
      return 0;
    }
  } else {
    return 0;
Repository: pandas-dev/pandas
File: pandas/tests/frame/indexing/test_insert.py (Python)

Code before:
            "Expected a one-dimensional object, got a DataFrame with 2 columns instead."
        )
        with pytest.raises(ValueError, match=msg):
            df.insert(1, "newcol", df)

    def test_insert_int64_loc(self):
        # GH#53193
        df = DataFrame({"a": [1, 2]})
        df.insert(np.int64(0), "b", 0)
        tm.assert_frame_equal(df, DataFrame({"b": [0, 0], "a": [1, 2]}))

    def test_insert_delete_mixed_multiindex_columns(self):
        # GH#56853
        from pandas import (
            DataFrame,
            Timestamp,
        )

        df = DataFrame({("A", Timestamp("2024-01-01")): [0]})

        # Insert scalar label 'B'; alongside MultiIndex columns
        df.insert(1, "B", [1])

        # Verify MultiIndex and that 'B' appears in the top level
        assert isinstance(df.columns, MultiIndex)
        assert "B" in df.columns.get_level_values(0)

        # Should not raise RecursionError (this was the original bug)
        del df["B"]

        # Confirm 'B' no longer present and structure restored
        assert "B" not in df.columns.get_level_values(0)
        expected = DataFrame({("A", Timestamp("2024-01-01")): [0]})
        tm.assert_frame_equal(df, expected)


Review comment (suggestion):
Can you instead check the entire state of `df`. Also the comment repeats the code and can be removed.

```python
expected = ...
tm.assert_frame_equal(df, expected)
```


Code after:

        msg = (
            "Expected a one-dimensional object, got a DataFrame with 2 columns instead."
        )
        with pytest.raises(ValueError, match=msg):
            df.insert(1, "newcol", df)

    def test_insert_int64_loc(self):
        # GH#53193
        df = DataFrame({"a": [1, 2]})
        df.insert(np.int64(0), "b", 0)
        tm.assert_frame_equal(df, DataFrame({"b": [0, 0], "a": [1, 2]}))

    def test_insert_delete_mixed_multiindex_columns(self):
        # GH#56853

        df = DataFrame({("A", Timestamp("2024-01-01")): [0]})
        df.insert(1, "B", [1])

        expected = DataFrame(
            [[0, 1]],
            columns=MultiIndex.from_tuples(
                [("A", Timestamp("2024-01-01")), ("B", NaT)]
            ),
        )
        tm.assert_frame_equal(df, expected)

        # Should not raise RecursionError (this was the original bug)
        del df["B"]

        expected = DataFrame({("A", Timestamp("2024-01-01")): [0]})
        tm.assert_frame_equal(df, expected)
Repository: pandas-dev/pandas
File: pandas/tests/generic/test_finalize.py (Python)

Code before:
"""An exhaustive list of pandas methods exercising NDFrame.__finalize__."""

import operator
import re

import numpy as np
import pytest

from pandas._typing import MergeHow

import pandas as pd

# TODO:
# * Binary methods (mul, div, etc.)
# * Binary outputs (align, etc.)
# * top-level methods (concat, merge, get_dummies, etc.)
# * window
# * cumulative reductions

not_implemented_mark = pytest.mark.xfail(reason="not implemented")

mi = pd.MultiIndex.from_product([["a", "b"], [0, 1]], names=["A", "B"])

frame_data = ({"A": [1]},)
frame_mi_data = ({"A": [1, 2, 3, 4]}, mi)


Review comment (suggestion):
Can you revert this change.

https://pandas.pydata.org/pandas-docs/dev/development/contributing_docstring.html#writing-a-docstring

Code after:
"""
An exhaustive list of pandas methods exercising NDFrame.__finalize__.
"""

from copy import deepcopy
import operator
import re

import numpy as np
import pytest

from pandas._typing import MergeHow

import pandas as pd

# TODO:
# * Binary methods (mul, div, etc.)
# * Binary outputs (align, etc.)
# * top-level methods (concat, merge, get_dummies, etc.)
# * window
# * cumulative reductions

not_implemented_mark = pytest.mark.xfail(reason="not implemented")

mi = pd.MultiIndex.from_product([["a", "b"], [0, 1]], names=["A", "B"])
Repository: pandas-dev/pandas
File: pandas/tests/generic/test_finalize.py (Python)

Code before:
    [
        lambda x: x.agg(["sum", "count"]),
        lambda x: x.agg("std"),
        lambda x: x.agg("var"),
        lambda x: x.agg("sem"),
        lambda x: x.agg("size"),
        lambda x: x.agg("ohlc"),
    ],
)
@not_implemented_mark
def test_groupby_finalize_not_implemented(obj, method):
    obj.attrs = {"a": 1}
    result = method(obj.groupby([0, 0]))
    assert result.attrs == {"a": 1}


def test_finalize_frame_series_name():
    # https://github.com/pandas-dev/pandas/pull/37186/files#r506978889
    # ensure we don't copy the column `name` to the Series.
    df = pd.DataFrame({"name": [1, 2]})
    result = pd.Series([1, 2]).__finalize__(df)
    assert result.name is None


# ----------------------------------------------------------------------------
# Tests for merge


@pytest.mark.parametrize(
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
@pytest.mark.parametrize(
    "how",
    [
        "left",
        "right",
        "inner",
        "outer",
        "left_anti",
        "right_anti",
        "cross",
    ],
)
def test_merge_sets_duplication_allowance_flag(
    how: MergeHow,
    allow_on_left: bool,
    allow_on_right: bool,
):
    """Check that DataFrame.merge correctly sets the allow_duplicate_labels flag
    on its result.

Review comment (suggestion):
I think we should be consistent with the other sections.

```suggestion
# Reshaping
```

Code after:
    "method",
    [
        lambda x: x.agg(["sum", "count"]),
        lambda x: x.agg("std"),
        lambda x: x.agg("var"),
        lambda x: x.agg("sem"),
        lambda x: x.agg("size"),
        lambda x: x.agg("ohlc"),
    ],
)
@not_implemented_mark
def test_groupby_finalize_not_implemented(obj, method):
    obj.attrs = {"a": 1}
    result = method(obj.groupby([0, 0]))
    assert result.attrs == {"a": 1}


def test_finalize_frame_series_name():
    # https://github.com/pandas-dev/pandas/pull/37186/files#r506978889
    # ensure we don't copy the column `name` to the Series.
    df = pd.DataFrame({"name": [1, 2]})
    result = pd.Series([1, 2]).__finalize__(df)
    assert result.name is None


# ----------------------------------------------------------------------------
# Merge


@pytest.mark.parametrize(
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
@pytest.mark.parametrize(
    "how",
    [
        "left",
        "right",
        "inner",
        "outer",
        "left_anti",
        "right_anti",
        "cross",
    ],
)
def test_merge_correctly_sets_duplication_allowance_flag(
    how: MergeHow,
    allow_on_left: bool,
    allow_on_right: bool,
):
    left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
Repository: pandas-dev/pandas
File: pandas/tests/generic/test_finalize.py (Python)

Code before:
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
@pytest.mark.parametrize(
    "how",
    [
        "left",
        "right",
        "inner",
        "outer",
        "left_anti",
        "right_anti",
        "cross",
    ],
)
def test_merge_sets_duplication_allowance_flag(
    how: MergeHow,
    allow_on_left: bool,
    allow_on_right: bool,
):
    """Check that DataFrame.merge correctly sets the allow_duplicate_labels flag
    on its result.

    The flag on the result should be set to true if and only if both arguments
    to merge have their flags set to True.
    """
    # Arrange
    left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
    right = pd.DataFrame({"test": [1]}).set_flags(
        allows_duplicate_labels=allow_on_right,
    )

    # Act
    if not how == "cross":
        result = left.merge(right, how=how, on="test")
    else:
        result = left.merge(right, how=how)

    # Assert
    expected_duplication_allowance = allow_on_left and allow_on_right
    assert result.flags.allows_duplicate_labels == expected_duplication_allowance


@pytest.mark.parametrize(
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
def test_merge_asof_sets_duplication_allowance_flag(
    allow_on_left: bool,
    allow_on_right: bool,
):

Review comment (suggestion):
Can you remove docstrings from tests, we generally do not add these. Comments are fine if they are adding something that is not already in the code itself.

Code after:
@pytest.mark.parametrize(
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
@pytest.mark.parametrize(
    "how",
    [
        "left",
        "right",
        "inner",
        "outer",
        "left_anti",
        "right_anti",
        "cross",
    ],
)
def test_merge_correctly_sets_duplication_allowance_flag(
    how: MergeHow,
    allow_on_left: bool,
    allow_on_right: bool,
):
    left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
    right = pd.DataFrame({"test": [1]}).set_flags(
        allows_duplicate_labels=allow_on_right,
    )

    if not how == "cross":
        result = left.merge(right, how=how, on="test")
    else:
        result = left.merge(right, how=how)

    expected_duplication_allowance = allow_on_left and allow_on_right
    assert result.flags.allows_duplicate_labels == expected_duplication_allowance


@pytest.mark.parametrize(
    ["allow_on_left", "allow_on_right"],
    [(False, False), (False, True), (True, False), (True, True)],
)
def test_merge_asof_correctly_sets_duplication_allowance_flag(
    allow_on_left: bool,
    allow_on_right: bool,
):
    left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
    right = pd.DataFrame({"test": [1]}).set_flags(
        allows_duplicate_labels=allow_on_right,
    )

    result = pd.merge_asof(left, right)

    expected_duplication_allowance = allow_on_left and allow_on_right
Repository: pandas-dev/pandas
File: pandas/tests/generic/test_finalize.py (Python)

Code before:
    # Act
    result = pd.merge_asof(left, right)

    # Assert
    expected_duplication_allowance = allow_on_left and allow_on_right
    assert result.flags.allows_duplicate_labels == expected_duplication_allowance


def test_merge_propagates_metadata_from_equal_input_metadata():
    """Check that pandas.merge sets the metadata of its result to a deep copy of
    the metadata from its left input, if the metadata from both inputs are equal.
    """
    # Arrange
    metadata = {"a": 2}
    left = pd.DataFrame({"test": [1]})
    left.attrs = metadata
    right = pd.DataFrame({"test": [1]})
    right.attrs = metadata.copy()

    # Act
    result = left.merge(right, how="inner", on="test")

    # Assert
    assert result.attrs == metadata
    left.attrs = {"b": 3}
    assert result.attrs == metadata


def test_merge_does_not_propagate_metadata_from_unequal_input_metadata():
    """Check that the metadata for the result of pandas.merge is empty if the
    metadata for both inputs to pandas.merge are not equal.
    """
    # Arrange
    left = pd.DataFrame({"test": [1]})
    left.attrs = {"a": 2}
    right = pd.DataFrame({"test": [1]})
    right.attrs = {"b": 3}

    # Act
    result = left.merge(right, how="inner", on="test")

    # Assert
    assert result.attrs == {}


no_metadata = pd.DataFrame({"test": [1]})

has_metadata = pd.DataFrame({"test": [1]})
has_metadata.attrs = {"a": 2}



Review comment (suggestion):
I don't think this will check whether or not a deep copy is made. Recommend doing

    assert result.attrs is not left.attrs
    assert result.attrs["a"] is not left.attrs["a"]

instead. Also I recommend adding in `right` as well for good measure.


Code after:
    assert result.attrs == metadata

    # Verify that merge deep-copies the attr dictionary.
    assert result.attrs is not left.attrs
    assert result.attrs is not right.attrs
    assert result.attrs["a"] is not left.attrs["a"]
    assert result.attrs["a"] is not right.attrs["a"]


def test_merge_does_not_propagate_metadata_from_unequal_input_metadata():
    left = pd.DataFrame({"test": [1]})
    left.attrs = {"a": 2}
    right = pd.DataFrame({"test": [1]})
    right.attrs = {"b": 3}

    result = left.merge(right, how="inner", on="test")

    assert result.attrs == {}


@pytest.mark.parametrize(
    ["left_has_metadata", "right_has_metadata", "expected"],
    [
        (False, True, {}),
        (True, False, {}),
        (False, False, {}),
    ],
    ids=["left-empty", "right-empty", "both-empty"],
)
def test_merge_does_not_propagate_metadata_if_one_input_has_no_metadata(
    left_has_metadata: bool,
    right_has_metadata: bool,
    expected: dict,
):
    left = pd.DataFrame({"test": [1]})
    right = pd.DataFrame({"test": [1]})

    if left_has_metadata:
        left.attrs = {"a": [1, 2]}
    else:
        left.attrs = {}

    if right_has_metadata:
        right.attrs = {"a": [1, 2]}
    else:
        right.attrs = {}

    result = left.merge(right, how="inner", on="test")

    assert result.attrs == expected
Repository: pandas-dev/pandas
File: pandas/tests/generic/test_finalize.py (Python)

Code before:
    assert result.attrs == metadata
    left.attrs = {"b": 3}
    assert result.attrs == metadata


def test_merge_does_not_propagate_metadata_from_unequal_input_metadata():
    """Check that the metadata for the result of pandas.merge is empty if the
    metadata for both inputs to pandas.merge are not equal.
    """
    # Arrange
    left = pd.DataFrame({"test": [1]})
    left.attrs = {"a": 2}
    right = pd.DataFrame({"test": [1]})
    right.attrs = {"b": 3}

    # Act
    result = left.merge(right, how="inner", on="test")

    # Assert
    assert result.attrs == {}


no_metadata = pd.DataFrame({"test": [1]})

has_metadata = pd.DataFrame({"test": [1]})
has_metadata.attrs = {"a": 2}


@pytest.mark.parametrize(
    ["left", "right", "expected"],
    [
        (no_metadata, has_metadata, {}),
        (has_metadata, no_metadata, {}),
        (no_metadata, no_metadata, {}),
    ],
    ids=["left-empty", "right-empty", "both-empty"],
)
def test_merge_does_not_propagate_metadata_if_one_input_has_no_metadata(
    left: pd.DataFrame,
    right: pd.DataFrame,
    expected: dict,
):
    """Check that if the metadata for one input to pandas.merge is empty, the result
    of merge has the same metadata as the other input.

    (empty)         (A)      (A)         (empty)    (empty)       (empty)
       |             |        |             |          |             |
        --> merge <--          --> merge <--            --> merge <--
              |                      |                        |
           (empty)                (empty)                  (empty)
    """

Review comment (suggestion):
Instead of having module level variables, can you use indicator strings in the parametrization (e.g. "no_metadata", "has_metadata") and then do something like:

```python
if left == "no_metadata":
     left = pd.DataFrame(...)
else:
    ...
```

Code after:
        (False, True, {}),
        (True, False, {}),
        (False, False, {}),
    ],
    ids=["left-empty", "right-empty", "both-empty"],
)
def test_merge_does_not_propagate_metadata_if_one_input_has_no_metadata(
    left_has_metadata: bool,
    right_has_metadata: bool,
    expected: dict,
):
    left = pd.DataFrame({"test": [1]})
    right = pd.DataFrame({"test": [1]})

    if left_has_metadata:
        left.attrs = {"a": [1, 2]}
    else:
        left.attrs = {}

    if right_has_metadata:
        right.attrs = {"a": [1, 2]}
    else:
        right.attrs = {}

    result = left.merge(right, how="inner", on="test")

    assert result.attrs == expected