@@ -781,6 +781,46 @@ def test_stale_handle_write_after_chunk_grid_change_raises(
781781 assert {p .name : p .read_bytes () for p in path .iterdir ()} == documents
782782
783783
784+ def _set_attribute (array : AnyArray ) -> None :
785+ array .attrs ["x" ] = 1
786+
787+
788+ def _grow (array : AnyArray ) -> None :
789+ array .resize ((9 ,))
790+
791+
792+ @pytest .mark .parametrize ("operation" , [_set_attribute , _grow ], ids = ["attrs" , "resize" ])
793+ @pytest .mark .parametrize ("zarr_format" , [2 , 3 ])
794+ def test_failed_metadata_save_keeps_stored_document (
795+ tmp_path : Path ,
796+ monkeypatch : pytest .MonkeyPatch ,
797+ zarr_format : Literal [2 , 3 ],
798+ operation : Callable [[AnyArray ], None ],
799+ ) -> None :
800+ """If storing an array's metadata fails, the array still stands for the document
801+ the store holds, which still needs its upgrade."""
802+ path = tmp_path / "legacy.zarr"
803+ _legacy_array (path , zarr_format )
804+ doc_name = ".zarray" if zarr_format == 2 else "zarr.json"
805+ with pytest .warns (ZarrUserWarning , match = "is read as" ):
806+ arr = zarr .open_array (store = path , mode = "r+" )
807+ stored = (path / doc_name ).read_bytes ()
808+ original_set = LocalStore .set
809+
810+ async def failing_set (self : LocalStore , key : str , * args : Any , ** kwargs : Any ) -> None :
811+ if key == doc_name :
812+ raise OSError (f"cannot store { key } " )
813+ await original_set (self , key , * args , ** kwargs )
814+
815+ monkeypatch .setattr (LocalStore , "set" , failing_set )
816+
817+ with pytest .raises (OSError , match = f"cannot store { re .escape (doc_name )} " ):
818+ operation (arr )
819+
820+ assert arr .metadata ._stored_document is not None
821+ assert (path / doc_name ).read_bytes () == stored
822+
823+
784824@pytest .mark .parametrize ("zarr_format" , [2 , 3 ])
785825def test_write_without_stored_document (zarr_format : Literal [2 , 3 ]) -> None :
786826 """An array read from an upgraded document that no store holds (as
0 commit comments