Repository: pydata/xarray

Issue:
⚠️ Nightly upstream-dev CI failed ⚠️
[Workflow Run URL](https://github.com/pydata/xarray/actions/runs/11944030848)
<details><summary>Python 3.12 Test Summary</summary>

```
xarray/tests/test_backends.py::TestInstrumentedZarrStore::test_append: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestInstrumentedZarrStore::test_region_write: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_zero_dimensional_variable[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_store[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_test_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_load[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dataset_compute[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_pickle[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_pickle_dataarray[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_None_variable[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_object_dtype[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_encoded_characters[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_numpy_datetime_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_cftime_datetime_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_timedelta_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_float64_data[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_example_1_netcdf[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_coordinates[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_global_coordinates[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_coordinates_with_space[2]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_boolean_dtype[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_orthogonal_indexing[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_vectorized_indexing[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_vectorized_indexing_negative_step[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_outer_indexing_reversed[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_isel_dataarray[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_array_type_after_indexing[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dropna[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_ondisk_after_print[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_bytes_with_fill_value[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_with_fill_value_nchar[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_empty_vlen_string_array[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[2-dtype0-create_unsigned_masked_scaled_data-create_encoded_unsigned_masked_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[2-dtype0-create_signed_masked_scaled_data-create_encoded_signed_masked_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[2-dtype0-create_masked_and_scaled_data-create_encoded_masked_and_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[2-fill_value0-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[2-fill_value1-True]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[2--1-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[2-255-True]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_coordinate_variables_after_dataset_roundtrip[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_grid_mapping_and_bounds_are_coordinates_after_dataarray_roundtrip[2]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_coordinates_encoding[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_endian[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_invalid_dataarray_names_raise[2]: AssertionError: Regex pattern did not match.
 Regex: 'string or None'
 Input: "MemoryStore.__init__() got an unexpected keyword argument 'mode'"
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_kwarg[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_kwarg_dates[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_default_fill_value[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_via_encoding_kwarg[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_in_coord[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_in_coord_via_encoding_kwarg[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_same_dtype[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_multiindex_not_implemented[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[2-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[2-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[2-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_read_non_consolidated_warning[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_non_existent_store[2]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_2_0/ca4396e8-f602-4b95-892c-f886072a0d21'
xarray/tests/test_backends.py::TestZarrDictStore::test_auto_chunk[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_manual_chunk[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_warning_on_bad_chunks[2]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_write_uneven_dask_chunks[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_dask[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_drop_encoding[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_hidden_zarr_keys[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_persistence_modes[2-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_persistence_modes[2-group1]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_compressor_encoding[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_group[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_zarr_mode_w_overwrites_encoding[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dataset_caching[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_rplus_success[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_rplus_fails[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_invalid_dim_raises[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_no_dims_raises[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_append_dim_not_set_raises[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_not_a_raises[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_existing_encoding_raises[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_string_length_mismatch_raises[2-U]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_string_length_mismatch_raises[2-S]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_check_encoding_is_consistent_after_append[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_new_variable[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_append_dim_no_overwrite[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_to_zarr_compute_false_roundtrip[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_to_zarr_append_compute_false_roundtrip[2]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_save_emptydim[2-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_save_emptydim[2-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_no_warning_from_open_emptydim_with_chunks[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-False-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-True-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[2-None-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[2-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[2-r+]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[2-a]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_preexisting_override_metadata[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_errors[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_chunksizes[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_partial_dask_chunks[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_larger_dask_chunks[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_open_zarr_use_cftime[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_read_select_write[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_attributes[2-obj0]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_attributes[2-obj1]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_datetime64_or_timedelta64[2-datetime64[ns]]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_datetime64_or_timedelta64[2-timedelta64[ns]]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_cftime_datetime[2]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_non_existent_store[2]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_2_1/ca800d35-ff58-46d4-a670-d887b3d458a7'
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_manual_chunk[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_warning_on_bad_chunks[2]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_write_uneven_dask_chunks[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_encoding_chunksizes[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_non_existent_store[2]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_2_2/07b52aff-ff7d-48a0-9754-dce50afa69db'
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_manual_chunk[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_warning_on_bad_chunks[2]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_write_uneven_dask_chunks[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_encoding_chunksizes[2]: ValueError: ndarray is not C-contiguous
xarray/tests/test_backends.py::TestZarrDictStore::test_zero_dimensional_variable[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_store[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_test_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_load[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dataset_compute[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_pickle[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_pickle_dataarray[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_None_variable[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_object_dtype[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_encoded_characters[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_numpy_datetime_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_cftime_datetime_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_timedelta_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_float64_data[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_example_1_netcdf[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_coordinates[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_global_coordinates[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_coordinates_with_space[3]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_boolean_dtype[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_orthogonal_indexing[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_vectorized_indexing[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_vectorized_indexing_negative_step[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_outer_indexing_reversed[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_isel_dataarray[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_array_type_after_indexing[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dropna[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_ondisk_after_print[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_bytes_with_fill_value[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_string_with_fill_value_nchar[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_empty_vlen_string_array[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[3-dtype0-create_unsigned_masked_scaled_data-create_encoded_unsigned_masked_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[3-dtype0-create_signed_masked_scaled_data-create_encoded_signed_masked_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_mask_and_scale[3-dtype0-create_masked_and_scaled_data-create_encoded_masked_and_scaled_data]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[3-fill_value0-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[3-fill_value1-True]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[3--1-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_unsigned[3-255-True]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_coordinate_variables_after_dataset_roundtrip[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_grid_mapping_and_bounds_are_coordinates_after_dataarray_roundtrip[3]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_coordinates_encoding[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_invalid_dataarray_names_raise[3]: AssertionError: Regex pattern did not match.
 Regex: 'string or None'
 Input: "MemoryStore.__init__() got an unexpected keyword argument 'mode'"
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_kwarg[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_kwarg_dates[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_default_fill_value[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_via_encoding_kwarg[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_in_coord[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_explicitly_omit_fill_value_in_coord_via_encoding_kwarg[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_same_dtype[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_multiindex_not_implemented[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[3-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[3-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_roundtrip_consolidated[3-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_read_non_consolidated_warning[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_non_existent_store[3]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_3_0/76d6a872-2f54-497e-bca2-12bdaaa83697'
xarray/tests/test_backends.py::TestZarrDictStore::test_auto_chunk[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_manual_chunk[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_warning_on_bad_chunks[3]: Failed: DID NOT WARN. No warnings of type (<class 'UserWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_write_uneven_dask_chunks[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_dask[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_drop_encoding[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dimension_names[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_persistence_modes[3-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_persistence_modes[3-group1]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_compressor_encoding[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_group[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_zarr_mode_w_overwrites_encoding[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_dataset_caching[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_rplus_success[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_rplus_fails[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_invalid_dim_raises[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_no_dims_raises[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_append_dim_not_set_raises[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_mode_not_a_raises[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_existing_encoding_raises[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_string_length_mismatch_works[3-U]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_string_length_mismatch_works[3-S]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_check_encoding_is_consistent_after_append[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_new_variable[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_append_with_append_dim_no_overwrite[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_to_zarr_compute_false_roundtrip[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_to_zarr_append_compute_false_roundtrip[3]: Failed: DID NOT WARN. No warnings of type (<class 'xarray.coding.variables.SerializationWarning'>,) were emitted.
 Emitted warnings: [].
xarray/tests/test_backends.py::TestZarrDictStore::test_save_emptydim[3-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_save_emptydim[3-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_no_warning_from_open_emptydim_with_chunks[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-False-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-True-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-False-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-False-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-False-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-False-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-True-False]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-True-True]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region[3-None-True-True-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[3-None]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[3-r+]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_mode[3-a]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_preexisting_override_metadata[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_region_errors[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_encoding_chunksizes[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_partial_dask_chunks[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunk_encoding_with_larger_dask_chunks[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_open_zarr_use_cftime[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_write_read_select_write[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_attributes[3-obj0]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_attributes[3-obj1]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_datetime64_or_timedelta64[3-datetime64[ns]]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_datetime64_or_timedelta64[3-timedelta64[ns]]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDictStore::test_chunked_cftime_datetime[3]: TypeError: MemoryStore.__init__() got an unexpected keyword argument 'mode'
xarray/tests/test_backends.py::TestZarrDirectoryStore::test_non_existent_store[3]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_3_1/417a58b9-514b-4a71-a6dd-816600e4aba7'
xarray/tests/test_backends.py::TestZarrWriteEmpty::test_non_existent_store[3]: AssertionError: Regex pattern did not match.
 Regex: 'No such file or directory'
 Input: 'Unable to find group: file:///tmp/pytest-of-runner/pytest-0/test_non_existent_store_3_2/3c505bf3-9c7e-4c22-9de2-2f5dc965fbca'
xarray/tests/test_cftimeindex.py::test_multiindex: KeyError: '2001-01'
xarray/tests/test_dask.py::TestToDaskDataFrame::test_to_dask_dataframe: AssertionError: DataFrame.index are different

Attribute "dtype" are different
[left]:  object
[right]: StringDtype(storage=pyarrow, na_value=<NA>)
xarray/tests/test_distributed.py::test_async: IndexError: tuple index out of range
xarray/tests/test_formatting.py::test_display_nbytes: AssertionError: assert '<xarray.Data...197 1198 1199' == '<xarray.Data...197 1198 1199'
  
  Skipping 86 identical leading characters in diff, use -v to show
  - 8, 1199], dtype=int16)
  + 8, 1199], shape=(1200,), dtype=int16)
  ?          +++++++++++++++
    Coordinates:
      * foo      (foo) int16 2kB 0 1 2 3 4 5 6 ... 1194 1195 1196 1197 1198 1199
xarray/tests/test_plot.py::TestContour::test_colors_np_levels: assert False
 +  where False = isinstance(array([[0., 0., 0., 1.],\n       [1., 0., 0., 1.],\n       [1., 1., 1., 1.]]), list)
xarray/tests/test_variable.py::TestVariableWithDask::test_datetime64_conversion: assert True == False
xarray/tests/test_variable.py::TestVariableWithDask::test_timedelta64_conversion: assert True == False
xarray/tests/test_variable.py::TestVariableWithDask::test_multiindex: ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```

</details>


Discussion:
dcherian: Remaining upstream failures are https://github.com/zarr-developers/zarr-python/issues/2501 and that we seem to now have 4 new `list_prefix` calls (I've bumped the count, we can optimize later). :/


Patch:
diff --git a/doc/api.rst b/doc/api.rst
index 0c30ddc4..85ef46ca 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -1210,6 +1210,7 @@ Dataset
    DatasetGroupBy.var
    DatasetGroupBy.dims
    DatasetGroupBy.groups
+   DatasetGroupBy.shuffle_to_chunks
 
 DataArray
 ---------
@@ -1241,6 +1242,7 @@ DataArray
    DataArrayGroupBy.var
    DataArrayGroupBy.dims
    DataArrayGroupBy.groups
+   DataArrayGroupBy.shuffle_to_chunks
 
 Grouper Objects
 ---------------
diff --git a/doc/user-guide/groupby.rst b/doc/user-guide/groupby.rst
index f7d76eda..7cb4e883 100644
--- a/doc/user-guide/groupby.rst
+++ b/doc/user-guide/groupby.rst
@@ -330,3 +330,24 @@ Different groupers can be combined to construct sophisticated GroupBy operations
     from xarray.groupers import BinGrouper
 
     ds.groupby(x=BinGrouper(bins=[5, 15, 25]), letters=UniqueGrouper()).sum()
+
+
+Shuffling
+~~~~~~~~~
+
+Shuffling is a generalization of sorting a DataArray or Dataset by another DataArray, named ``label`` for example, that follows from the idea of grouping by ``label``.
+Shuffling reorders the DataArray or the DataArrays in a Dataset such that all members of a group occur sequentially. For example,
+Shuffle the object using either :py:class:`DatasetGroupBy` or :py:class:`DataArrayGroupBy` as appropriate.
+
+.. ipython:: python
+
+    da = xr.DataArray(
+        dims="x",
+        data=[1, 2, 3, 4, 5, 6],
+        coords={"label": ("x", "a b c a b c".split(" "))},
+    )
+    da.groupby("label").shuffle_to_chunks()
+
+
+For chunked array types (e.g. dask or cubed), shuffle may result in a more optimized communication pattern when compared to direct indexing by the appropriate indexer.
+Shuffling also makes GroupBy operations on chunked arrays an embarrassingly parallel problem, and may significantly improve workloads that use :py:meth:`DatasetGroupBy.map` or :py:meth:`DataArrayGroupBy.map`.
diff --git a/xarray/backends/plugins.py b/xarray/backends/plugins.py
index e321aece..555538c2 100644
--- a/xarray/backends/plugins.py
+++ b/xarray/backends/plugins.py
@@ -200,7 +200,7 @@ def get_backend(engine: str | type[BackendEntrypoint]) -> BackendEntrypoint:
         engines = list_engines()
         if engine not in engines:
             raise ValueError(
-                f"unrecognized engine {engine} must be one of your download engines: {list(engines)}"
+                f"unrecognized engine '{engine}' must be one of your download engines: {list(engines)}. "
                 "To install additional dependencies, see:\n"
                 "https://docs.xarray.dev/en/stable/user-guide/io.html \n"
                 "https://docs.xarray.dev/en/stable/getting-started-guide/installing.html"
diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py
index b2ceb41e..1acc0a50 100644
--- a/xarray/backends/zarr.py
+++ b/xarray/backends/zarr.py
@@ -37,6 +37,7 @@ from xarray.namedarray.pycompat import integer_types
 from xarray.namedarray.utils import module_available
 
 if TYPE_CHECKING:
+    from zarr import Array as ZarrArray
     from zarr import Group as ZarrGroup
 
     from xarray.backends.common import AbstractDataStore
@@ -443,7 +444,7 @@ def extract_zarr_variable_encoding(
     shape = shape if shape else variable.shape
     encoding = variable.encoding.copy()
 
-    safe_to_drop = {"source", "original_shape"}
+    safe_to_drop = {"source", "original_shape", "preferred_chunks"}
     valid_encodings = {
         "codecs",
         "chunks",
@@ -871,16 +872,27 @@ class ZarrStore(AbstractWritableDataStore):
         else:
             zarr = attempt_import("zarr")
 
-        existing_keys = tuple(self.zarr_group.array_keys())
+        if self._mode == "w":
+            # always overwrite, so we don't care about existing names,
+            # and consistency of encoding
+            new_variable_names = set(variables)
+            existing_keys = {}
+            existing_variable_names = {}
+        else:
+            existing_keys = tuple(self.zarr_group.array_keys())
+            existing_variable_names = {
+                vn for vn in variables if _encode_variable_name(vn) in existing_keys
+            }
+            new_variable_names = set(variables) - existing_variable_names
 
-        if self._mode == "r+":
-            new_names = [k for k in variables if k not in existing_keys]
-            if new_names:
-                raise ValueError(
-                    f"dataset contains non-pre-existing variables {new_names}, "
-                    "which is not allowed in ``xarray.Dataset.to_zarr()`` with "
-                    "``mode='r+'``. To allow writing new variables, set ``mode='a'``."
-                )
+        if self._mode == "r+" and (
+            new_names := [k for k in variables if k not in existing_keys]
+        ):
+            raise ValueError(
+                f"dataset contains non-pre-existing variables {new_names!r}, "
+                "which is not allowed in ``xarray.Dataset.to_zarr()`` with "
+                "``mode='r+'``. To allow writing new variables, set ``mode='a'``."
+            )
 
         if self._append_dim is not None and self._append_dim not in existing_keys:
             # For dimensions without coordinate values, we must parse
@@ -895,10 +907,6 @@ class ZarrStore(AbstractWritableDataStore):
                     f"dataset dimensions {existing_dims}"
                 )
 
-        existing_variable_names = {
-            vn for vn in variables if _encode_variable_name(vn) in existing_keys
-        }
-        new_variable_names = set(variables) - existing_variable_names
         variables_encoded, attributes = self.encode(
             {vn: variables[vn] for vn in new_variable_names}, attributes
         )
@@ -920,10 +928,9 @@ class ZarrStore(AbstractWritableDataStore):
             # Modified variables must use the same encoding as the store.
             vars_with_encoding = {}
             for vn in existing_variable_names:
-                if self._mode in ["a", "a-", "r+"]:
-                    _validate_datatypes_for_zarr_append(
-                        vn, existing_vars[vn], variables[vn]
-                    )
+                _validate_datatypes_for_zarr_append(
+                    vn, existing_vars[vn], variables[vn]
+                )
                 vars_with_encoding[vn] = variables[vn].copy(deep=False)
                 vars_with_encoding[vn].encoding = existing_vars[vn].encoding
             vars_with_encoding, _ = self.encode(vars_with_encoding, {})
@@ -968,6 +975,69 @@ class ZarrStore(AbstractWritableDataStore):
     def sync(self):
         pass
 
+    def _open_existing_array(self, *, name) -> ZarrArray:
+        import zarr
+
+        # TODO: if mode="a", consider overriding the existing variable
+        # metadata. This would need some case work properly with region
+        # and append_dim.
+        if self._write_empty is not None:
+            # Write to zarr_group.chunk_store instead of zarr_group.store
+            # See https://github.com/pydata/xarray/pull/8326#discussion_r1365311316 for a longer explanation
+            #    The open_consolidated() enforces a mode of r or r+
+            #    (and to_zarr with region provided enforces a read mode of r+),
+            #    and this function makes sure the resulting Group has a store of type ConsolidatedMetadataStore
+            #    and a 'normal Store subtype for chunk_store.
+            #    The exact type depends on if a local path was used, or a URL of some sort,
+            #    but the point is that it's not a read-only ConsolidatedMetadataStore.
+            #    It is safe to write chunk data to the chunk_store because no metadata would be changed by
+            #    to_zarr with the region parameter:
+            #     - Because the write mode is enforced to be r+, no new variables can be added to the store
+            #       (this is also checked and enforced in xarray.backends.api.py::to_zarr()).
+            #     - Existing variables already have their attrs included in the consolidated metadata file.
+            #     - The size of dimensions can not be expanded, that would require a call using `append_dim`
+            #        which is mutually exclusive with `region`
+            zarr_array = zarr.open(
+                store=(
+                    self.zarr_group.store if _zarr_v3() else self.zarr_group.chunk_store
+                ),
+                # TODO: see if zarr should normalize these strings.
+                path="/".join([self.zarr_group.name.rstrip("/"), name]).lstrip("/"),
+                write_empty_chunks=self._write_empty,
+            )
+        else:
+            zarr_array = self.zarr_group[name]
+
+        return zarr_array
+
+    def _create_new_array(
+        self, *, name, shape, dtype, fill_value, encoding, attrs
+    ) -> ZarrArray:
+        if coding.strings.check_vlen_dtype(dtype) is str:
+            dtype = str
+
+        if self._write_empty is not None:
+            if (
+                "write_empty_chunks" in encoding
+                and encoding["write_empty_chunks"] != self._write_empty
+            ):
+                raise ValueError(
+                    'Differing "write_empty_chunks" values in encoding and parameters'
+                    f'Got {encoding["write_empty_chunks"] = } and {self._write_empty = }'
+                )
+            else:
+                encoding["write_empty_chunks"] = self._write_empty
+
+        zarr_array = self.zarr_group.create(
+            name,
+            shape=shape,
+            dtype=dtype,
+            fill_value=fill_value,
+            **encoding,
+        )
+        zarr_array = _put_attrs(zarr_array, attrs)
+        return zarr_array
+
     def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=None):
         """
         This provides a centralized method to set the variables on the data
@@ -986,8 +1056,6 @@ class ZarrStore(AbstractWritableDataStore):
             dimensions.
         """
 
-        import zarr
-
         existing_keys = tuple(self.zarr_group.array_keys())
         is_zarr_v3_format = _zarr_v3() and self.zarr_group.metadata.zarr_format == 3
 
@@ -1016,47 +1084,13 @@ class ZarrStore(AbstractWritableDataStore):
                 else:
                     del v.encoding["_FillValue"]
 
-            zarr_array = None
             zarr_shape = None
             write_region = self._write_region if self._write_region is not None else {}
             write_region = {dim: write_region.get(dim, slice(None)) for dim in dims}
 
-            if name in existing_keys:
+            if self._mode != "w" and name in existing_keys:
                 # existing variable
-                # TODO: if mode="a", consider overriding the existing variable
-                # metadata. This would need some case work properly with region
-                # and append_dim.
-                if self._write_empty is not None:
-                    # Write to zarr_group.chunk_store instead of zarr_group.store
-                    # See https://github.com/pydata/xarray/pull/8326#discussion_r1365311316 for a longer explanation
-                    #    The open_consolidated() enforces a mode of r or r+
-                    #    (and to_zarr with region provided enforces a read mode of r+),
-                    #    and this function makes sure the resulting Group has a store of type ConsolidatedMetadataStore
-                    #    and a 'normal Store subtype for chunk_store.
-                    #    The exact type depends on if a local path was used, or a URL of some sort,
-                    #    but the point is that it's not a read-only ConsolidatedMetadataStore.
-                    #    It is safe to write chunk data to the chunk_store because no metadata would be changed by
-                    #    to_zarr with the region parameter:
-                    #     - Because the write mode is enforced to be r+, no new variables can be added to the store
-                    #       (this is also checked and enforced in xarray.backends.api.py::to_zarr()).
-                    #     - Existing variables already have their attrs included in the consolidated metadata file.
-                    #     - The size of dimensions can not be expanded, that would require a call using `append_dim`
-                    #        which is mutually exclusive with `region`
-                    zarr_array = zarr.open(
-                        store=(
-                            self.zarr_group.store
-                            if _zarr_v3()
-                            else self.zarr_group.chunk_store
-                        ),
-                        # TODO: see if zarr should normalize these strings.
-                        path="/".join([self.zarr_group.name.rstrip("/"), name]).lstrip(
-                            "/"
-                        ),
-                        write_empty_chunks=self._write_empty,
-                    )
-                else:
-                    zarr_array = self.zarr_group[name]
-
+                zarr_array = self._open_existing_array(name=name)
                 if self._append_dim is not None and self._append_dim in dims:
                     # resize existing variable
                     append_axis = dims.index(self._append_dim)
@@ -1089,40 +1123,27 @@ class ZarrStore(AbstractWritableDataStore):
                 shape=zarr_shape,
             )
 
-            if name not in existing_keys:
+            if self._mode == "w" or name not in existing_keys:
                 # new variable
-                encoded_attrs = {}
+                encoded_attrs = {k: self.encode_attribute(v) for k, v in attrs.items()}
                 # the magic for storing the hidden dimension data
                 if is_zarr_v3_format:
                     encoding["dimension_names"] = dims
                 else:
                     encoded_attrs[DIMENSION_KEY] = dims
-                for k2, v2 in attrs.items():
-                    encoded_attrs[k2] = self.encode_attribute(v2)
-
-                if coding.strings.check_vlen_dtype(dtype) is str:
-                    dtype = str
-
-                if self._write_empty is not None:
-                    if (
-                        "write_empty_chunks" in encoding
-                        and encoding["write_empty_chunks"] != self._write_empty
-                    ):
-                        raise ValueError(
-                            'Differing "write_empty_chunks" values in encoding and parameters'
-                            f'Got {encoding["write_empty_chunks"] = } and {self._write_empty = }'
-                        )
-                    else:
-                        encoding["write_empty_chunks"] = self._write_empty
-
-                zarr_array = self.zarr_group.create(
-                    name,
-                    shape=shape,
+
+                encoding["exists_ok" if _zarr_v3() else "overwrite"] = (
+                    True if self._mode == "w" else False
+                )
+
+                zarr_array = self._create_new_array(
+                    name=name,
                     dtype=dtype,
+                    shape=shape,
                     fill_value=fill_value,
-                    **encoding,
+                    encoding=encoding,
+                    attrs=encoded_attrs,
                 )
-                zarr_array = _put_attrs(zarr_array, encoded_attrs)
 
             writer.add(v.data, zarr_array, region)
 
diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py
index 14481451..eae11c0c 100644
--- a/xarray/core/dataarray.py
+++ b/xarray/core/dataarray.py
@@ -63,6 +63,7 @@ from xarray.core.types import (
     Bins,
     DaCompatible,
     NetcdfWriteModes,
+    T_Chunks,
     T_DataArray,
     T_DataArrayOrSet,
     ZarrWriteModes,
@@ -105,6 +106,7 @@ if TYPE_CHECKING:
         Dims,
         ErrorOptions,
         ErrorOptionsWithWarn,
+        GroupIndices,
         GroupInput,
         InterpOptions,
         PadModeOptions,
@@ -1687,6 +1689,12 @@ class DataArray(
         )
         return self._from_temp_dataset(ds)
 
+    def _shuffle(
+        self, dim: Hashable, *, indices: GroupIndices, chunks: T_Chunks
+    ) -> Self:
+        ds = self._to_temp_dataset()._shuffle(dim=dim, indices=indices, chunks=chunks)
+        return self._from_temp_dataset(ds)
+
     def head(
         self,
         indexers: Mapping[Any, int] | int | None = None,
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index cc34a8cc..5056a80a 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -155,6 +155,7 @@ if TYPE_CHECKING:
         DsCompatible,
         ErrorOptions,
         ErrorOptionsWithWarn,
+        GroupIndices,
         GroupInput,
         InterpOptions,
         JoinOptions,
@@ -166,6 +167,7 @@ if TYPE_CHECKING:
         ResampleCompatible,
         SideOptions,
         T_ChunkDimFreq,
+        T_Chunks,
         T_DatasetPadConstantValues,
         T_Xarray,
     )
@@ -3237,6 +3239,38 @@ class Dataset(
         result = self.isel(indexers=query_results.dim_indexers, drop=drop)
         return result._overwrite_indexes(*query_results.as_tuple()[1:])
 
+    def _shuffle(self, dim, *, indices: GroupIndices, chunks: T_Chunks) -> Self:
+        # Shuffling is only different from `isel` for chunked arrays.
+        # Extract them out, and treat them specially. The rest, we route through isel.
+        # This makes it easy to ensure correct handling of indexes.
+        is_chunked = {
+            name: var
+            for name, var in self._variables.items()
+            if is_chunked_array(var._data)
+        }
+        subset = self[[name for name in self._variables if name not in is_chunked]]
+
+        no_slices: list[list[int]] = [
+            list(range(*idx.indices(self.sizes[dim])))
+            if isinstance(idx, slice)
+            else idx
+            for idx in indices
+        ]
+        no_slices = [idx for idx in no_slices if idx]
+
+        shuffled = (
+            subset
+            if dim not in subset.dims
+            else subset.isel({dim: np.concatenate(no_slices)})
+        )
+        for name, var in is_chunked.items():
+            shuffled[name] = var._shuffle(
+                indices=no_slices,
+                dim=dim,
+                chunks=chunks,
+            )
+        return shuffled
+
     def head(
         self,
         indexers: Mapping[Any, int] | int | None = None,
diff --git a/xarray/core/groupby.py b/xarray/core/groupby.py
index 7a32cd7b..9596d19e 100644
--- a/xarray/core/groupby.py
+++ b/xarray/core/groupby.py
@@ -57,7 +57,13 @@ if TYPE_CHECKING:
 
     from xarray.core.dataarray import DataArray
     from xarray.core.dataset import Dataset
-    from xarray.core.types import GroupIndex, GroupIndices, GroupInput, GroupKey
+    from xarray.core.types import (
+        GroupIndex,
+        GroupIndices,
+        GroupInput,
+        GroupKey,
+        T_Chunks,
+    )
     from xarray.core.utils import Frozen
     from xarray.groupers import EncodedGroups, Grouper
 
@@ -676,6 +682,76 @@ class GroupBy(Generic[T_Xarray]):
             self._sizes = self._obj.isel({self._group_dim: index}).sizes
         return self._sizes
 
+    def shuffle_to_chunks(self, chunks: T_Chunks = None) -> T_Xarray:
+        """
+        Sort or "shuffle" the underlying object.
+
+        "Shuffle" means the object is sorted so that all group members occur sequentially,
+        in the same chunk. Multiple groups may occur in the same chunk.
+        This method is particularly useful for chunked arrays (e.g. dask, cubed).
+        particularly when you need to map a function that requires all members of a group
+        to be present in a single chunk. For chunked array types, the order of appearance
+        is not guaranteed, but will depend on the input chunking.
+
+        Parameters
+        ----------
+        chunks : int, tuple of int, "auto" or mapping of hashable to int or tuple of int, optional
+            How to adjust chunks along dimensions not present in the array being grouped by.
+
+        Returns
+        -------
+        DataArrayGroupBy or DatasetGroupBy
+
+        Examples
+        --------
+        >>> import dask.array
+        >>> da = xr.DataArray(
+        ...     dims="x",
+        ...     data=dask.array.arange(10, chunks=3),
+        ...     coords={"x": [1, 2, 3, 1, 2, 3, 1, 2, 3, 0]},
+        ...     name="a",
+        ... )
+        >>> shuffled = da.groupby("x").shuffle_to_chunks()
+        >>> shuffled
+        <xarray.DataArray 'a' (x: 10)> Size: 80B
+        dask.array<shuffle, shape=(10,), dtype=int64, chunksize=(3,), chunktype=numpy.ndarray>
+        Coordinates:
+          * x        (x) int64 80B 0 1 1 1 2 2 2 3 3 3
+
+        >>> shuffled.groupby("x").quantile(q=0.5).compute()
+        <xarray.DataArray 'a' (x: 4)> Size: 32B
+        array([9., 3., 4., 5.])
+        Coordinates:
+            quantile  float64 8B 0.5
+          * x         (x) int64 32B 0 1 2 3
+
+        See Also
+        --------
+        dask.dataframe.DataFrame.shuffle
+        dask.array.shuffle
+        """
+        self._raise_if_by_is_chunked()
+        return self._shuffle_obj(chunks)
+
+    def _shuffle_obj(self, chunks: T_Chunks) -> T_Xarray:
+        from xarray.core.dataarray import DataArray
+
+        was_array = isinstance(self._obj, DataArray)
+        as_dataset = self._obj._to_temp_dataset() if was_array else self._obj
+
+        for grouper in self.groupers:
+            if grouper.name not in as_dataset._variables:
+                as_dataset.coords[grouper.name] = grouper.group
+
+        shuffled = as_dataset._shuffle(
+            dim=self._group_dim, indices=self.encoded.group_indices, chunks=chunks
+        )
+        unstacked: Dataset = self._maybe_unstack(shuffled)
+        if was_array:
+            return self._obj._from_temp_dataset(unstacked)
+        else:
+            return unstacked  # type: ignore[return-value]
+
     def map(
         self,
         func: Callable,
@@ -896,7 +972,9 @@ class GroupBy(Generic[T_Xarray]):
             #       and `inserted_dims`
             # if multiple groupers all share the same single dimension, then
             # we don't stack/unstack. Do that manually now.
-            obj = obj.unstack(*self.encoded.unique_coord.dims)
+            dims_to_unstack = self.encoded.unique_coord.dims
+            if all(dim in obj.dims for dim in dims_to_unstack):
+                obj = obj.unstack(*dims_to_unstack)
             to_drop = [
                 grouper.name
                 for grouper in self.groupers
diff --git a/xarray/core/resample.py b/xarray/core/resample.py
index 9dd91d86..5cc98c96 100644
--- a/xarray/core/resample.py
+++ b/xarray/core/resample.py
@@ -14,6 +14,7 @@ from xarray.core.types import Dims, InterpOptions, T_Xarray
 if TYPE_CHECKING:
     from xarray.core.dataarray import DataArray
     from xarray.core.dataset import Dataset
+    from xarray.core.types import T_Chunks
 
 from xarray.groupers import RESAMPLE_DIM
 
@@ -58,6 +59,50 @@ class Resample(GroupBy[T_Xarray]):
         result = result.rename({RESAMPLE_DIM: self._group_dim})
         return result
 
+    def shuffle_to_chunks(self, chunks: T_Chunks = None):
+        """
+        Sort or "shuffle" the underlying object.
+
+        "Shuffle" means the object is sorted so that all group members occur sequentially,
+        in the same chunk. Multiple groups may occur in the same chunk.
+        This method is particularly useful for chunked arrays (e.g. dask, cubed).
+        particularly when you need to map a function that requires all members of a group
+        to be present in a single chunk. For chunked array types, the order of appearance
+        is not guaranteed, but will depend on the input chunking.
+
+        Parameters
+        ----------
+        chunks : int, tuple of int, "auto" or mapping of hashable to int or tuple of int, optional
+            How to adjust chunks along dimensions not present in the array being grouped by.
+
+        Returns
+        -------
+        DataArrayGroupBy or DatasetGroupBy
+
+        Examples
+        --------
+        >>> import dask.array
+        >>> da = xr.DataArray(
+        ...     dims="time",
+        ...     data=dask.array.arange(10, chunks=1),
+        ...     coords={"time": xr.date_range("2001-01-01", freq="12h", periods=10)},
+        ...     name="a",
+        ... )
+        >>> shuffled = da.resample(time="2D").shuffle_to_chunks()
+        >>> shuffled
+        <xarray.DataArray 'a' (time: 10)> Size: 80B
+        dask.array<shuffle, shape=(10,), dtype=int64, chunksize=(4,), chunktype=numpy.ndarray>
+        Coordinates:
+          * time     (time) datetime64[ns] 80B 2001-01-01 ... 2001-01-05T12:00:00
+
+        See Also
+        --------
+        dask.dataframe.DataFrame.shuffle
+        dask.array.shuffle
+        """
+        (grouper,) = self.groupers
+        return self._shuffle_obj(chunks).drop_vars(RESAMPLE_DIM)
+
     def _drop_coords(self) -> T_Xarray:
         """Drop non-dimension coordinates along the resampled dimension."""
         obj = self._obj
diff --git a/xarray/core/types.py b/xarray/core/types.py
index 56d45ddf..11b26da0 100644
--- a/xarray/core/types.py
+++ b/xarray/core/types.py
@@ -362,7 +362,7 @@ NetcdfWriteModes = Literal["w", "a"]
 ZarrWriteModes = Literal["w", "w-", "a", "a-", "r+", "r"]
 
 GroupKey = Any
-GroupIndex = Union[int, slice, list[int]]
+GroupIndex = Union[slice, list[int]]
 GroupIndices = tuple[GroupIndex, ...]
 Bins = Union[
     int, Sequence[int], Sequence[float], Sequence[pd.Timestamp], np.ndarray, pd.Index
diff --git a/xarray/core/variable.py b/xarray/core/variable.py
index 3b41b786..9f660d08 100644
--- a/xarray/core/variable.py
+++ b/xarray/core/variable.py
@@ -45,7 +45,13 @@ from xarray.core.utils import (
     maybe_coerce_to_str,
 )
 from xarray.namedarray.core import NamedArray, _raise_if_any_duplicate_dimensions
-from xarray.namedarray.pycompat import integer_types, is_0d_dask_array, to_duck_array
+from xarray.namedarray.parallelcompat import get_chunked_array_type
+from xarray.namedarray.pycompat import (
+    integer_types,
+    is_0d_dask_array,
+    is_chunked_array,
+    to_duck_array,
+)
 from xarray.namedarray.utils import module_available
 from xarray.util.deprecation_helpers import _deprecate_positional_args, deprecate_dims
 
@@ -1019,6 +1025,24 @@ class Variable(NamedArray, AbstractArray, VariableArithmetic):
         new = self.copy(deep=False)
         return new.load(**kwargs)
 
+    def _shuffle(
+        self, indices: list[list[int]], dim: Hashable, chunks: T_Chunks
+    ) -> Self:
+        # TODO (dcherian): consider making this public API
+        array = self._data
+        if is_chunked_array(array):
+            chunkmanager = get_chunked_array_type(array)
+            return self._replace(
+                data=chunkmanager.shuffle(
+                    array,
+                    indexer=indices,
+                    axis=self.get_axis_num(dim),
+                    chunks=chunks,
+                )
+            )
+        else:
+            return self.isel({dim: np.concatenate(indices)})
+
     def isel(
         self,
         indexers: Mapping[Any, Any] | None = None,
diff --git a/xarray/groupers.py b/xarray/groupers.py
index 89b189e5..dac4c430 100644
--- a/xarray/groupers.py
+++ b/xarray/groupers.py
@@ -30,6 +30,7 @@ from xarray.core.types import (
     DatetimeLike,
     GroupIndices,
     ResampleCompatible,
+    Self,
     SideOptions,
 )
 from xarray.core.variable import Variable
@@ -139,6 +140,13 @@ class Grouper(ABC):
         """
         pass
 
+    @abstractmethod
+    def reset(self) -> Self:
+        """
+        Creates a new version of this Grouper clearing any caches.
+        """
+        pass
+
 
 class Resampler(Grouper):
     """
@@ -177,6 +185,9 @@ class UniqueGrouper(Grouper):
                 self._group_as_index = pd.Index(np.array(self.group).ravel())
         return self._group_as_index
 
+    def reset(self) -> Self:
+        return type(self)()
+
     def factorize(self, group: T_Group) -> EncodedGroups:
         self.group = group
 
@@ -325,6 +336,16 @@ class BinGrouper(Grouper):
     include_lowest: bool = False
     duplicates: Literal["raise", "drop"] = "raise"
 
+    def reset(self) -> Self:
+        return type(self)(
+            bins=self.bins,
+            right=self.right,
+            labels=self.labels,
+            precision=self.precision,
+            include_lowest=self.include_lowest,
+            duplicates=self.duplicates,
+        )
+
     def __post_init__(self) -> None:
         if duck_array_ops.isnull(self.bins).all():
             raise ValueError("All bin edges are NaN.")
@@ -427,6 +448,15 @@ class TimeResampler(Resampler):
     index_grouper: CFTimeGrouper | pd.Grouper = field(init=False, repr=False)
     group_as_index: pd.Index = field(init=False, repr=False)
 
+    def reset(self) -> Self:
+        return type(self)(
+            freq=self.freq,
+            closed=self.closed,
+            label=self.label,
+            origin=self.origin,
+            offset=self.offset,
+        )
+
     def _init_properties(self, group: T_Group) -> None:
         from xarray import CFTimeIndex
 
diff --git a/xarray/namedarray/_typing.py b/xarray/namedarray/_typing.py
index 90c442d2..95e7d7ad 100644
--- a/xarray/namedarray/_typing.py
+++ b/xarray/namedarray/_typing.py
@@ -78,7 +78,8 @@ _AxisLike = Union[_Axis, _Axes]
 _Chunks = tuple[_Shape, ...]
 _NormalizedChunks = tuple[tuple[int, ...], ...]
 # FYI in some cases we don't allow `None`, which this doesn't take account of.
-T_ChunkDim: TypeAlias = int | Literal["auto"] | None | tuple[int, ...]
+# # FYI the `str` is for a size string, e.g. "16MB", supported by dask.
+T_ChunkDim: TypeAlias = str | int | Literal["auto"] | None | tuple[int, ...]
 # We allow the tuple form of this (though arguably we could transition to named dims only)
 T_Chunks: TypeAlias = T_ChunkDim | Mapping[Any, T_ChunkDim]
 
diff --git a/xarray/namedarray/daskmanager.py b/xarray/namedarray/daskmanager.py
index 9725e341..6485ba37 100644
--- a/xarray/namedarray/daskmanager.py
+++ b/xarray/namedarray/daskmanager.py
@@ -249,3 +249,18 @@ class DaskManager(ChunkManagerEntrypoint["DaskArray"]):
             targets=targets,
             **kwargs,
         )
+
+    def shuffle(
+        self, x: DaskArray, indexer: list[list[int]], axis: int, chunks: T_Chunks
+    ) -> DaskArray:
+        import dask.array
+
+        if not module_available("dask", minversion="2024.08.1"):
+            raise ValueError(
+                "This method is very inefficient on dask<2024.08.1. Please upgrade."
+            )
+        if chunks is None:
+            chunks = "auto"
+        if chunks != "auto":
+            raise NotImplementedError("Only chunks='auto' is supported at present.")
+        return dask.array.shuffle(x, indexer, axis, chunks="auto")
diff --git a/xarray/namedarray/parallelcompat.py b/xarray/namedarray/parallelcompat.py
index 6ecc9ae9..69dd4ab5 100644
--- a/xarray/namedarray/parallelcompat.py
+++ b/xarray/namedarray/parallelcompat.py
@@ -20,6 +20,7 @@ from xarray.namedarray.pycompat import is_chunked_array
 
 if TYPE_CHECKING:
     from xarray.namedarray._typing import (
+        T_Chunks,
         _Chunks,
         _DType,
         _DType_co,
@@ -356,6 +357,11 @@ class ChunkManagerEntrypoint(ABC, Generic[T_ChunkedArray]):
         """
         raise NotImplementedError()
 
+    def shuffle(
+        self, x: T_ChunkedArray, indexer: list[list[int]], axis: int, chunks: T_Chunks
+    ) -> T_ChunkedArray:
+        raise NotImplementedError()
+
     def persist(
         self, *data: T_ChunkedArray | Any, **kwargs: Any
     ) -> tuple[T_ChunkedArray | Any, ...]:


Test patch:
diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py
index f086e3ca..442a0e51 100644
--- a/xarray/tests/__init__.py
+++ b/xarray/tests/__init__.py
@@ -107,6 +107,9 @@ with warnings.catch_warnings():
     has_h5netcdf, requires_h5netcdf = _importorskip("h5netcdf")
 has_cftime, requires_cftime = _importorskip("cftime")
 has_dask, requires_dask = _importorskip("dask")
+has_dask_ge_2024_08_1, requires_dask_ge_2024_08_1 = _importorskip(
+    "dask", minversion="2024.08.1"
+)
 has_dask_ge_2024_11_0, requires_dask_ge_2024_11_0 = _importorskip("dask", "2024.11.0")
 with warnings.catch_warnings():
     warnings.filterwarnings(
@@ -138,6 +141,7 @@ has_cartopy, requires_cartopy = _importorskip("cartopy")
 has_pint, requires_pint = _importorskip("pint")
 has_numexpr, requires_numexpr = _importorskip("numexpr")
 has_flox, requires_flox = _importorskip("flox")
+has_netcdf, requires_netcdf = _importorskip("netcdf")
 has_pandas_ge_2_2, requires_pandas_ge_2_2 = _importorskip("pandas", "2.2")
 has_pandas_3, requires_pandas_3 = _importorskip("pandas", "3.0.0.dev0")
 
@@ -152,7 +156,7 @@ requires_numbagg_or_bottleneck = pytest.mark.skipif(
     not has_numbagg_or_bottleneck, reason="requires numbagg or bottleneck"
 )
 has_numpy_2, requires_numpy_2 = _importorskip("numpy", "2.0.0")
-_, requires_flox_0_9_12 = _importorskip("flox", "0.9.12")
+has_flox_0_9_12, requires_flox_0_9_12 = _importorskip("flox", "0.9.12")
 
 has_array_api_strict, requires_array_api_strict = _importorskip("array_api_strict")
 
diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py
index 9e150507..3ca7c08e 100644
--- a/xarray/tests/test_backends.py
+++ b/xarray/tests/test_backends.py
@@ -78,6 +78,7 @@ from xarray.tests import (
     requires_h5netcdf_1_4_0_or_above,
     requires_h5netcdf_ros3,
     requires_iris,
+    requires_netcdf,
     requires_netCDF4,
     requires_netCDF4_1_6_2_or_above,
     requires_netCDF4_1_7_0_or_above,
@@ -634,6 +635,7 @@ class DatasetIOBase:
         with self.roundtrip(expected) as actual:
             assert_identical(expected, actual)
 
+    @requires_netcdf
     def test_roundtrip_example_1_netcdf(self) -> None:
         with open_example_dataset("example_1.nc") as expected:
             with self.roundtrip(expected) as actual:
@@ -1126,13 +1128,11 @@ class CFEncodedBase(DatasetIOBase):
 
     def test_grid_mapping_and_bounds_are_not_coordinates_in_file(self) -> None:
         original = self._create_cf_dataset()
-        with create_tmp_file() as tmp_file:
-            original.to_netcdf(tmp_file)
-            with open_dataset(tmp_file, decode_coords=False) as ds:
-                assert ds.coords["latitude"].attrs["bounds"] == "latitude_bnds"
-                assert ds.coords["longitude"].attrs["bounds"] == "longitude_bnds"
-                assert "coordinates" not in ds["variable"].attrs
-                assert "coordinates" not in ds.attrs
+        with self.roundtrip(original, open_kwargs={"decode_coords": False}) as ds:
+            assert ds.coords["latitude"].attrs["bounds"] == "latitude_bnds"
+            assert ds.coords["longitude"].attrs["bounds"] == "longitude_bnds"
+            assert "coordinates" not in ds["variable"].attrs
+            assert "coordinates" not in ds.attrs
 
     def test_coordinate_variables_after_dataset_roundtrip(self) -> None:
         original = self._create_cf_dataset()
@@ -1174,6 +1174,7 @@ class CFEncodedBase(DatasetIOBase):
                 assert_identical(actual, original["variable"].to_dataset())
 
     @requires_iris
+    @requires_netcdf
     def test_coordinate_variables_after_iris_roundtrip(self) -> None:
         original = self._create_cf_dataset()
         iris_cube = original["variable"].to_iris()
@@ -1194,36 +1195,30 @@ class CFEncodedBase(DatasetIOBase):
         )
         with self.roundtrip(original) as actual:
             assert_identical(actual, original)
-        with create_tmp_file() as tmp_file:
-            original.to_netcdf(tmp_file)
-            with open_dataset(tmp_file, decode_coords=False) as ds:
-                assert equals_latlon(ds["temp"].attrs["coordinates"])
-                assert equals_latlon(ds["precip"].attrs["coordinates"])
-                assert "coordinates" not in ds.attrs
-                assert "coordinates" not in ds["lat"].attrs
-                assert "coordinates" not in ds["lon"].attrs
+        with self.roundtrip(original, open_kwargs=dict(decode_coords=False)) as ds:
+            assert equals_latlon(ds["temp"].attrs["coordinates"])
+            assert equals_latlon(ds["precip"].attrs["coordinates"])
+            assert "coordinates" not in ds.attrs
+            assert "coordinates" not in ds["lat"].attrs
+            assert "coordinates" not in ds["lon"].attrs
 
         modified = original.drop_vars(["temp", "precip"])
         with self.roundtrip(modified) as actual:
             assert_identical(actual, modified)
-        with create_tmp_file() as tmp_file:
-            modified.to_netcdf(tmp_file)
-            with open_dataset(tmp_file, decode_coords=False) as ds:
-                assert equals_latlon(ds.attrs["coordinates"])
-                assert "coordinates" not in ds["lat"].attrs
-                assert "coordinates" not in ds["lon"].attrs
+        with self.roundtrip(modified, open_kwargs=dict(decode_coords=False)) as ds:
+            assert equals_latlon(ds.attrs["coordinates"])
+            assert "coordinates" not in ds["lat"].attrs
+            assert "coordinates" not in ds["lon"].attrs
 
         original["temp"].encoding["coordinates"] = "lat"
         with self.roundtrip(original) as actual:
             assert_identical(actual, original)
         original["precip"].encoding["coordinates"] = "lat"
-        with create_tmp_file() as tmp_file:
-            original.to_netcdf(tmp_file)
-            with open_dataset(tmp_file, decode_coords=True) as ds:
-                assert "lon" not in ds["temp"].encoding["coordinates"]
-                assert "lon" not in ds["precip"].encoding["coordinates"]
-                assert "coordinates" not in ds["lat"].encoding
-                assert "coordinates" not in ds["lon"].encoding
+        with self.roundtrip(original, open_kwargs=dict(decode_coords=True)) as ds:
+            assert "lon" not in ds["temp"].encoding["coordinates"]
+            assert "lon" not in ds["precip"].encoding["coordinates"]
+            assert "coordinates" not in ds["lat"].encoding
+            assert "coordinates" not in ds["lon"].encoding
 
     def test_roundtrip_endian(self) -> None:
         skip_if_zarr_format_3("zarr v3 has not implemented endian support yet")
@@ -2322,7 +2317,9 @@ class ZarrBase(CFEncodedBase):
                     assert_identical(ds, expected)
 
     def test_non_existent_store(self) -> None:
-        with pytest.raises(FileNotFoundError, match="No such file or directory"):
+        with pytest.raises(
+            FileNotFoundError, match="(No such file or directory|Unable to find group)"
+        ):
             xr.open_zarr(f"{uuid.uuid4()}")
 
     @pytest.mark.skipif(has_zarr_v3, reason="chunk_store not implemented in zarr v3")
@@ -2552,6 +2549,7 @@ class ZarrBase(CFEncodedBase):
             # don't actually check equality because the data could be corrupted
             pass
 
+    @requires_netcdf
     def test_drop_encoding(self):
         with open_example_dataset("example_1.nc") as ds:
             encodings = {v: {**ds[v].encoding} for v in ds.data_vars}
@@ -3274,7 +3272,7 @@ class TestInstrumentedZarrStore:
             pytest.skip("Instrumented tests only work on latest Zarr.")
 
         if has_zarr_v3:
-            kwargs = {"mode": "a"}
+            kwargs = {"read_only": False}
         else:
             kwargs = {}  # type: ignore[arg-type,unused-ignore]
 
@@ -3316,10 +3314,10 @@ class TestInstrumentedZarrStore:
             if has_zarr_v3:
                 # TODO: verify these
                 expected = {
-                    "set": 17,
-                    "get": 12,
+                    "set": 5,
+                    "get": 7,
                     "list_dir": 3,
-                    "list_prefix": 0,
+                    "list_prefix": 1,
                 }
             else:
                 expected = {
@@ -3341,7 +3339,7 @@ class TestInstrumentedZarrStore:
             # 6057128b: {'iter': 5, 'contains': 2, 'setitem': 5, 'getitem': 10, "listdir": 5, "list_prefix": 0}
             if has_zarr_v3:
                 expected = {
-                    "set": 10,
+                    "set": 4,
                     "get": 16,  # TODO: fixme upstream (should be 8)
                     "list_dir": 3,  # TODO: fixme upstream (should be 2)
                     "list_prefix": 0,
@@ -3364,7 +3362,7 @@ class TestInstrumentedZarrStore:
 
             if has_zarr_v3:
                 expected = {
-                    "set": 10,
+                    "set": 4,
                     "get": 16,  # TODO: fixme upstream (should be 8)
                     "list_dir": 3,  # TODO: fixme upstream (should be 2)
                     "list_prefix": 0,
@@ -3397,7 +3395,7 @@ class TestInstrumentedZarrStore:
                     "set": 5,
                     "get": 10,
                     "list_dir": 3,
-                    "list_prefix": 0,
+                    "list_prefix": 4,
                 }
             else:
                 expected = {
@@ -3491,7 +3489,7 @@ class TestZarrDictStore(ZarrBase):
     @contextlib.contextmanager
     def create_zarr_target(self):
         if has_zarr_v3:
-            yield zarr.storage.MemoryStore({}, mode="a")
+            yield zarr.storage.MemoryStore({}, read_only=False)
         else:
             yield {}
 
diff --git a/xarray/tests/test_groupby.py b/xarray/tests/test_groupby.py
index 3fc7fcac..2d5d6c4c 100644
--- a/xarray/tests/test_groupby.py
+++ b/xarray/tests/test_groupby.py
@@ -4,6 +4,7 @@ import datetime
 import operator
 import warnings
 from itertools import pairwise
+from typing import Literal
 from unittest import mock
 
 import numpy as np
@@ -32,11 +33,13 @@ from xarray.tests import (
     create_test_data,
     has_cftime,
     has_dask,
+    has_dask_ge_2024_08_1,
     has_flox,
     has_pandas_ge_2_2,
     raise_if_dask_computes,
     requires_cftime,
     requires_dask,
+    requires_dask_ge_2024_08_1,
     requires_flox,
     requires_flox_0_9_12,
     requires_pandas_ge_2_2,
@@ -632,10 +635,25 @@ def test_groupby_repr_datetime(obj) -> None:
     assert actual == expected
 
 
+@pytest.mark.filterwarnings("ignore:No index created for dimension id:UserWarning")
 @pytest.mark.filterwarnings("ignore:Converting non-nanosecond")
 @pytest.mark.filterwarnings("ignore:invalid value encountered in divide:RuntimeWarning")
-@pytest.mark.filterwarnings("ignore:No index created for dimension id:UserWarning")
-def test_groupby_drops_nans() -> None:
+@pytest.mark.parametrize("shuffle", [True, False])
+@pytest.mark.parametrize(
+    "chunk",
+    [
+        pytest.param(
+            dict(lat=1), marks=pytest.mark.skipif(not has_dask, reason="no dask")
+        ),
+        pytest.param(
+            dict(lat=2, lon=2), marks=pytest.mark.skipif(not has_dask, reason="no dask")
+        ),
+        False,
+    ],
+)
+def test_groupby_drops_nans(shuffle: bool, chunk: Literal[False] | dict) -> None:
+    if shuffle and chunk and not has_dask_ge_2024_08_1:
+        pytest.skip()
     # GH2383
     # nan in 2D data variable (requires stacking)
     ds = xr.Dataset(
@@ -650,13 +668,17 @@ def test_groupby_drops_nans() -> None:
     ds["id"].values[3, 0] = np.nan
     ds["id"].values[-1, -1] = np.nan
 
+    if chunk:
+        ds["variable"] = ds["variable"].chunk(chunk)
     grouped = ds.groupby(ds.id)
+    if shuffle:
+        grouped = grouped.shuffle_to_chunks().groupby(ds.id)
 
     # non reduction operation
     expected1 = ds.copy()
-    expected1.variable.values[0, 0, :] = np.nan
-    expected1.variable.values[-1, -1, :] = np.nan
-    expected1.variable.values[3, 0, :] = np.nan
+    expected1.variable.data[0, 0, :] = np.nan
+    expected1.variable.data[-1, -1, :] = np.nan
+    expected1.variable.data[3, 0, :] = np.nan
     actual1 = grouped.map(lambda x: x).transpose(*ds.variable.dims)
     assert_identical(actual1, expected1)
 
@@ -1355,11 +1377,27 @@ class TestDataArrayGroupBy:
         assert_allclose(expected_sum_axis1, grouped.reduce(np.sum, "y"))
         assert_allclose(expected_sum_axis1, grouped.sum("y"))
 
+    @pytest.mark.parametrize("use_flox", [True, False])
+    @pytest.mark.parametrize("shuffle", [True, False])
+    @pytest.mark.parametrize(
+        "chunk",
+        [
+            pytest.param(
+                True, marks=pytest.mark.skipif(not has_dask, reason="no dask")
+            ),
+            False,
+        ],
+    )
     @pytest.mark.parametrize("method", ["sum", "mean", "median"])
-    def test_groupby_reductions(self, method) -> None:
-        array = self.da
-        grouped = array.groupby("abc")
+    def test_groupby_reductions(
+        self, use_flox: bool, method: str, shuffle: bool, chunk: bool
+    ) -> None:
+        if shuffle and chunk and not has_dask_ge_2024_08_1:
+            pytest.skip()
 
+        array = self.da
+        if chunk:
+            array.data = array.chunk({"y": 5}).data
         reduction = getattr(np, method)
         expected = Dataset(
             {
@@ -1377,14 +1415,14 @@ class TestDataArrayGroupBy:
             }
         )["foo"]
 
-        with xr.set_options(use_flox=False):
-            actual_legacy = getattr(grouped, method)(dim="y")
-
-        with xr.set_options(use_flox=True):
-            actual_npg = getattr(grouped, method)(dim="y")
+        with raise_if_dask_computes():
+            grouped = array.groupby("abc")
+            if shuffle:
+                grouped = grouped.shuffle_to_chunks().groupby("abc")
 
-        assert_allclose(expected, actual_legacy)
-        assert_allclose(expected, actual_npg)
+            with xr.set_options(use_flox=use_flox):
+                actual = getattr(grouped, method)(dim="y")
+        assert_allclose(expected, actual)
 
     def test_groupby_count(self) -> None:
         array = DataArray(
@@ -1648,13 +1686,17 @@ class TestDataArrayGroupBy:
         )
 
         with xr.set_options(use_flox=use_flox):
-            actual = array.groupby_bins("dim_0", bins=bins, **cut_kwargs).sum()
+            gb = array.groupby_bins("dim_0", bins=bins, **cut_kwargs)
+            shuffled = gb.shuffle_to_chunks().groupby_bins(
+                "dim_0", bins=bins, **cut_kwargs
+            )
+            actual = gb.sum()
             assert_identical(expected, actual)
+            assert_identical(expected, shuffled.sum())
 
-            actual = array.groupby_bins("dim_0", bins=bins, **cut_kwargs).map(
-                lambda x: x.sum()
-            )
+            actual = gb.map(lambda x: x.sum())
             assert_identical(expected, actual)
+            assert_identical(expected, shuffled.map(lambda x: x.sum()))
 
             # make sure original array dims are unchanged
             assert len(array.dim_0) == 4
@@ -1799,6 +1841,7 @@ class TestDataArrayGroupBy:
 
 
 class TestDataArrayResample:
+    @pytest.mark.parametrize("shuffle", [True, False])
     @pytest.mark.parametrize("use_cftime", [True, False])
     @pytest.mark.parametrize(
         "resample_freq",
@@ -1813,7 +1856,7 @@ class TestDataArrayResample:
         ],
     )
     def test_resample(
-        self, use_cftime: bool, resample_freq: ResampleCompatible
+        self, use_cftime: bool, shuffle: bool, resample_freq: ResampleCompatible
     ) -> None:
         if use_cftime and not has_cftime:
             pytest.skip()
@@ -1836,16 +1879,22 @@ class TestDataArrayResample:
 
         array = DataArray(np.arange(10), [("time", times)])
 
-        actual = array.resample(time=resample_freq).mean()
+        rs = array.resample(time=resample_freq)
+        shuffled = rs.shuffle_to_chunks().resample(time=resample_freq)
+        actual = rs.mean()
         expected = resample_as_pandas(array, resample_freq)
         assert_identical(expected, actual)
+        assert_identical(expected, shuffled.mean())
 
-        actual = array.resample(time=resample_freq).reduce(np.mean)
-        assert_identical(expected, actual)
+        assert_identical(expected, rs.reduce(np.mean))
+        assert_identical(expected, shuffled.reduce(np.mean))
 
-        actual = array.resample(time=resample_freq, closed="right").mean()
-        expected = resample_as_pandas(array, resample_freq, closed="right")
+        rs = array.resample(time="24h", closed="right")
+        actual = rs.mean()
+        shuffled = rs.shuffle_to_chunks().resample(time="24h", closed="right")
+        expected = resample_as_pandas(array, "24h", closed="right")
         assert_identical(expected, actual)
+        assert_identical(expected, shuffled.mean())
 
         with pytest.raises(ValueError, match=r"Index must be monotonic"):
             array[[2, 0, 1]].resample(time=resample_freq)
@@ -2673,6 +2722,9 @@ def test_custom_grouper() -> None:
             codes = group.copy(data=codes_).rename("year")
             return EncodedGroups(codes=codes, full_index=pd.Index(uniques))
 
+        def reset(self):
+            return type(self)()
+
     da = xr.DataArray(
         dims="time",
         data=np.arange(20),
@@ -2769,8 +2821,9 @@ def test_multiple_groupers_string(as_dataset) -> None:
         obj.groupby("labels1", foo=UniqueGrouper())
 
 
+@pytest.mark.parametrize("shuffle", [True, False])
 @pytest.mark.parametrize("use_flox", [True, False])
-def test_multiple_groupers(use_flox) -> None:
+def test_multiple_groupers(use_flox: bool, shuffle: bool) -> None:
     da = DataArray(
         np.array([1, 2, 3, 0, 2, np.nan]),
         dims="d",
@@ -2781,7 +2834,11 @@ def test_multiple_groupers(use_flox) -> None:
         name="foo",
     )
 
-    gb = da.groupby(labels1=UniqueGrouper(), labels2=UniqueGrouper())
+    groupers: dict[str, Grouper]
+    groupers = dict(labels1=UniqueGrouper(), labels2=UniqueGrouper())
+    gb = da.groupby(groupers)
+    if shuffle:
+        gb = gb.shuffle_to_chunks().groupby(groupers)
     repr(gb)
 
     expected = DataArray(
@@ -2800,7 +2857,10 @@ def test_multiple_groupers(use_flox) -> None:
     # -------
     coords = {"a": ("x", [0, 0, 1, 1]), "b": ("y", [0, 0, 1, 1])}
     square = DataArray(np.arange(16).reshape(4, 4), coords=coords, dims=["x", "y"])
-    gb = square.groupby(a=UniqueGrouper(), b=UniqueGrouper())
+    groupers = dict(a=UniqueGrouper(), b=UniqueGrouper())
+    gb = square.groupby(groupers)
+    if shuffle:
+        gb = gb.shuffle_to_chunks().groupby(groupers)
     repr(gb)
     with xr.set_options(use_flox=use_flox):
         actual = gb.mean()
@@ -2823,12 +2883,18 @@ def test_multiple_groupers(use_flox) -> None:
         coords={"xy": (("x", "y"), [["a", "b", "c"], ["b", "c", "c"]], {"foo": "bar"})},
         dims=["x", "y", "z"],
     )
-    gb = b.groupby(x=UniqueGrouper(), y=UniqueGrouper())
+    groupers = dict(x=UniqueGrouper(), y=UniqueGrouper())
+    gb = b.groupby(groupers)
+    if shuffle:
+        gb = gb.shuffle_to_chunks().groupby(groupers)
     repr(gb)
     with xr.set_options(use_flox=use_flox):
         assert_identical(gb.mean("z"), b.mean("z"))
 
-    gb = b.groupby(x=UniqueGrouper(), xy=UniqueGrouper())
+    groupers = dict(x=UniqueGrouper(), xy=UniqueGrouper())
+    gb = b.groupby(groupers)
+    if shuffle:
+        gb = gb.shuffle_to_chunks().groupby(groupers)
     repr(gb)
     with xr.set_options(use_flox=use_flox):
         actual = gb.mean()
@@ -2873,13 +2939,19 @@ def test_multiple_groupers(use_flox) -> None:
 
 
 @pytest.mark.parametrize("use_flox", [True, False])
-def test_multiple_groupers_mixed(use_flox) -> None:
+@pytest.mark.parametrize("shuffle", [True, False])
+def test_multiple_groupers_mixed(use_flox: bool, shuffle: bool) -> None:
     # This groupby has missing groups
     ds = xr.Dataset(
         {"foo": (("x", "y"), np.arange(12).reshape((4, 3)))},
         coords={"x": [10, 20, 30, 40], "letters": ("x", list("abba"))},
     )
-    gb = ds.groupby(x=BinGrouper(bins=[5, 15, 25]), letters=UniqueGrouper())
+    groupers: dict[str, Grouper] = dict(
+        x=BinGrouper(bins=[5, 15, 25]), letters=UniqueGrouper()
+    )
+    gb = ds.groupby(groupers)
+    if shuffle:
+        gb = gb.shuffle_to_chunks().groupby(groupers)
     expected_data = np.array(
         [
             [[0.0, np.nan], [np.nan, 3.0]],
@@ -3107,6 +3179,50 @@ def test_groupby_multiple_bin_grouper_missing_groups():
     assert_identical(actual, expected)
 
 
+@requires_dask_ge_2024_08_1
+def test_shuffle_simple() -> None:
+    import dask
+
+    da = xr.DataArray(
+        dims="x",
+        data=dask.array.from_array([1, 2, 3, 4, 5, 6], chunks=2),
+        coords={"label": ("x", "a b c a b c".split(" "))},
+    )
+    actual = da.groupby(label=UniqueGrouper()).shuffle_to_chunks()
+    expected = da.isel(x=[0, 3, 1, 4, 2, 5])
+    assert_identical(actual, expected)
+
+    with pytest.raises(ValueError):
+        da.chunk(x=2, eagerly_load_group=False).groupby("label").shuffle_to_chunks()
+
+
+@requires_dask_ge_2024_08_1
+@pytest.mark.parametrize(
+    "chunks, expected_chunks",
+    [
+        ((1,), (1, 3, 3, 3)),
+        ((10,), (10,)),
+    ],
+)
+def test_shuffle_by(chunks, expected_chunks):
+    import dask.array
+
+    from xarray.groupers import UniqueGrouper
+
+    da = xr.DataArray(
+        dims="x",
+        data=dask.array.arange(10, chunks=chunks),
+        coords={"x": [1, 2, 3, 1, 2, 3, 1, 2, 3, 0]},
+        name="a",
+    )
+    ds = da.to_dataset()
+
+    for obj in [ds, da]:
+        actual = obj.groupby(x=UniqueGrouper()).shuffle_to_chunks()
+        assert_identical(actual, obj.sortby("x"))
+        assert actual.chunksizes["x"] == expected_chunks
+
+
 @requires_dask
 def test_groupby_dask_eager_load_warnings():
     ds = xr.Dataset(