@@ -748,6 +748,46 @@ def test_stale_handle_write_after_chunk_grid_change_raises(
748748 assert {p .name : p .read_bytes () for p in path .iterdir ()} == documents
749749
750750
751+ def _set_attribute (array : AnyArray ) -> None :
752+ array .attrs ["x" ] = 1
753+
754+
755+ def _grow (array : AnyArray ) -> None :
756+ array .resize ((9 ,))
757+
758+
759+ @pytest .mark .parametrize ("operation" , [_set_attribute , _grow ], ids = ["attrs" , "resize" ])
760+ @pytest .mark .parametrize ("zarr_format" , [2 , 3 ])
761+ def test_failed_metadata_save_keeps_stored_document (
762+ tmp_path : Path ,
763+ monkeypatch : pytest .MonkeyPatch ,
764+ zarr_format : Literal [2 , 3 ],
765+ operation : Callable [[AnyArray ], None ],
766+ ) -> None :
767+ """If storing an array's metadata fails, the array still stands for the document
768+ the store holds, which still needs its upgrade."""
769+ path = tmp_path / "legacy.zarr"
770+ _legacy_array (path , zarr_format )
771+ doc_name = ".zarray" if zarr_format == 2 else "zarr.json"
772+ with pytest .warns (ZarrUserWarning , match = "is read as" ):
773+ arr = zarr .open_array (store = path , mode = "r+" )
774+ stored = (path / doc_name ).read_bytes ()
775+ original_set = LocalStore .set
776+
777+ async def failing_set (self : LocalStore , key : str , * args : Any , ** kwargs : Any ) -> None :
778+ if key == doc_name :
779+ raise OSError (f"cannot store { key } " )
780+ await original_set (self , key , * args , ** kwargs )
781+
782+ monkeypatch .setattr (LocalStore , "set" , failing_set )
783+
784+ with pytest .raises (OSError , match = f"cannot store { re .escape (doc_name )} " ):
785+ operation (arr )
786+
787+ assert arr .metadata ._stored_document is not None
788+ assert (path / doc_name ).read_bytes () == stored
789+
790+
751791@pytest .mark .parametrize ("zarr_format" , [2 , 3 ])
752792def test_write_without_stored_document (zarr_format : Literal [2 , 3 ]) -> None :
753793 """An array read from an upgraded document that no store holds (as
0 commit comments