Repository: pydata/xarray

Issue:
Append to zarr redundant with mode='a'
When appending to a zarr store, we need to set `append_dim=dim_name` but also `mode='a'`. Any reason to also specify the writing mode? I think it should automatically be set to `'a'` if `append_dim is not None`.

Discussion:

Patch:
diff --git a/.codecov.yml b/.codecov.yml
index 59111c89..f3a055c0 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,3 +1,9 @@
+codecov:
+  ci:
+    # by default, codecov doesn't recognize azure as a CI provider
+    - dev.azure.com
+  require_ci_to_pass: yes
+
 coverage:
   status:
     project:
@@ -7,7 +13,4 @@ coverage:
     patch: false
     changes: false
 
-comment:
-  layout: "header, diff"
-  behavior: default
-  require_changes: no
+comment: off
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 105a9b02..f24884c6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,6 +1,6 @@
 ---
 name: Bug report / Feature request
-about: ''
+about: 'Post a problem or idea'
 title: ''
 labels: ''
 assignees: ''
diff --git a/ci/requirements/py36.yml b/ci/requirements/py36.yml
index ffdd33fc..a8481623 100644
--- a/ci/requirements/py36.yml
+++ b/ci/requirements/py36.yml
@@ -8,9 +8,7 @@ dependencies:
   - distributed
   - h5py
   - h5netcdf
-  # pin matplotlib for now, to ensure that we have at least one CI job
-  # testing pseudonetcdf: https://github.com/barronh/pseudonetcdf/issues/69
-  - matplotlib=3.0
+  - matplotlib
   - netcdf4
   - pytest
   - pytest-cov
@@ -29,7 +27,9 @@ dependencies:
   - pseudonetcdf>=3.0.1
   - cfgrib>=0.9.2
   - cdms2
-  - pynio
+  # Disabled due to conflicting dependencies:
+  # https://github.com/pydata/xarray/issues/3154
+  # - pynio
   - iris>=1.10
   - pydap
   - lxml
diff --git a/doc/api.rst b/doc/api.rst
index 1050211c..fd127c5f 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -212,6 +212,7 @@ Reshaping and reorganizing
    Dataset.shift
    Dataset.roll
    Dataset.sortby
+   Dataset.broadcast_like
 
 DataArray
 =========
@@ -386,6 +387,7 @@ Reshaping and reorganizing
    DataArray.shift
    DataArray.roll
    DataArray.sortby
+   DataArray.broadcast_like
 
 .. _api.ufuncs:
 
diff --git a/doc/io.rst b/doc/io.rst
index c85f5663..1039e8bc 100644
--- a/doc/io.rst
+++ b/doc/io.rst
@@ -609,8 +609,9 @@ store is already present at that path, an error will be raised, preventing it
 from being overwritten. To override this behavior and overwrite an existing
 store, add ``mode='w'`` when invoking ``to_zarr``.
 
-It is also possible to append to an existing store. For that, add ``mode='a'``
-and set ``append_dim`` to the name of the dimension along which to append.
+It is also possible to append to an existing store. For that, set
+``append_dim`` to the name of the dimension along which to append. ``mode``
+can be omitted as it will internally be set to ``'a'``.
 
 .. ipython:: python
    :suppress:
@@ -628,7 +629,7 @@ and set ``append_dim`` to the name of the dimension along which to append.
                      coords={'x': [10, 20, 30, 40],
                              'y': [1,2,3,4,5],
                              't': pd.date_range('2001-01-03', periods=2)})
-    ds2.to_zarr('path/to/directory.zarr', mode='a', append_dim='t')
+    ds2.to_zarr('path/to/directory.zarr', append_dim='t')
 
 To store variable length strings use ``dtype=object``.
 
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index f41a9d78..f2f71d14 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -24,12 +24,28 @@ This release increases the minimum required Python version from 3.5.0 to 3.5.3
 New functions/methods
 ~~~~~~~~~~~~~~~~~~~~~
 
+- Added :py:meth:`DataArray.broadcast_like` and :py:meth:`Dataset.broadcast_like`.
+  By `Deepak Cherian <https://github.com/dcherian>`_ and `David Mertz 
+  <http://github.com/DavidMertz>`_.
+
 Enhancements
 ~~~~~~~~~~~~
 
+- In :py:meth:`~xarray.Dataset.to_zarr`, passing ``mode`` is not mandatory if
+  ``append_dim`` is set, as it will automatically be set to ``'a'`` internally.
+  By `David Brochart <https://github.com/davidbrochart>`_.
+
 Bug fixes
 ~~~~~~~~~
 
+- Improved error handling and documentation for `.expand_dims()` 
+  read-only view.
+- Fix tests for big-endian systems (:issue:`3125`).
+  By `Graham Inggs <https://github.com/ginggs>`_.
+- XFAIL several tests which are expected to fail on ARM systems
+  due to a ``datetime`` issue in NumPy (:issue:`2334`).
+  By `Graham Inggs <https://github.com/ginggs>`_.
+
 .. _whats-new.0.12.3:
 
 v0.12.3 (10 July 2019)
@@ -51,6 +67,7 @@ New functions/methods
   (:issue:`3026`).
   By `Julia Kent <https://github.com/jukent>`_.
 
+
 Enhancements
 ~~~~~~~~~~~~
 
@@ -70,6 +87,8 @@ Bug fixes
 - Fix HDF5 error that could arise when reading multiple groups from a file at
   once (:issue:`2954`).
   By `Stephan Hoyer <https://github.com/shoyer>`_.
+- Better error message when using groupby on an empty DataArray (:issue:`3037`).
+  By `Hasan Ahmad <https://github.com/HasanAhmadQ7>`_.
 
 .. _whats-new.0.12.2:
 
diff --git a/xarray/backends/api.py b/xarray/backends/api.py
index e0f269eb..21d91a88 100644
--- a/xarray/backends/api.py
+++ b/xarray/backends/api.py
@@ -1102,7 +1102,7 @@ def _validate_append_dim_and_encoding(ds_to_append, store, append_dim,
                 )
 
 
-def to_zarr(dataset, store=None, mode='w-', synchronizer=None, group=None,
+def to_zarr(dataset, store=None, mode=None, synchronizer=None, group=None,
             encoding=None, compute=True, consolidated=False, append_dim=None):
     """This function creates an appropriate datastore for writing a dataset to
     a zarr ztore
@@ -1118,7 +1118,7 @@ def to_zarr(dataset, store=None, mode='w-', synchronizer=None, group=None,
     _validate_dataset_names(dataset)
     _validate_attrs(dataset)
 
-    if mode == "a":
+    if mode == 'a':
         _validate_datatypes_for_zarr_append(dataset)
         _validate_append_dim_and_encoding(dataset, store, append_dim,
                                           group=group,
diff --git a/xarray/core/alignment.py b/xarray/core/alignment.py
index 412560d9..6d7fcb98 100644
--- a/xarray/core/alignment.py
+++ b/xarray/core/alignment.py
@@ -391,6 +391,58 @@ def reindex_variables(
     return reindexed, new_indexes
 
 
+def _get_broadcast_dims_map_common_coords(args, exclude):
+
+    common_coords = OrderedDict()
+    dims_map = OrderedDict()
+    for arg in args:
+        for dim in arg.dims:
+            if dim not in common_coords and dim not in exclude:
+                dims_map[dim] = arg.sizes[dim]
+                if dim in arg.coords:
+                    common_coords[dim] = arg.coords[dim].variable
+
+    return dims_map, common_coords
+
+
+def _broadcast_helper(arg, exclude, dims_map, common_coords):
+
+    from .dataarray import DataArray
+    from .dataset import Dataset
+
+    def _set_dims(var):
+        # Add excluded dims to a copy of dims_map
+        var_dims_map = dims_map.copy()
+        for dim in exclude:
+            with suppress(ValueError):
+                # ignore dim not in var.dims
+                var_dims_map[dim] = var.shape[var.dims.index(dim)]
+
+        return var.set_dims(var_dims_map)
+
+    def _broadcast_array(array):
+        data = _set_dims(array.variable)
+        coords = OrderedDict(array.coords)
+        coords.update(common_coords)
+        return DataArray(data, coords, data.dims, name=array.name,
+                         attrs=array.attrs)
+
+    def _broadcast_dataset(ds):
+        data_vars = OrderedDict(
+            (k, _set_dims(ds.variables[k]))
+            for k in ds.data_vars)
+        coords = OrderedDict(ds.coords)
+        coords.update(common_coords)
+        return Dataset(data_vars, coords, ds.attrs)
+
+    if isinstance(arg, DataArray):
+        return _broadcast_array(arg)
+    elif isinstance(arg, Dataset):
+        return _broadcast_dataset(arg)
+    else:
+        raise ValueError('all input must be Dataset or DataArray objects')
+
+
 def broadcast(*args, exclude=None):
     """Explicitly broadcast any number of DataArray or Dataset objects against
     one another.
@@ -463,55 +515,16 @@ def broadcast(*args, exclude=None):
         a        (x, y) int64 1 1 2 2 3 3
         b        (x, y) int64 5 6 5 6 5 6
     """
-    from .dataarray import DataArray
-    from .dataset import Dataset
 
     if exclude is None:
         exclude = set()
     args = align(*args, join='outer', copy=False, exclude=exclude)
 
-    common_coords = OrderedDict()
-    dims_map = OrderedDict()
-    for arg in args:
-        for dim in arg.dims:
-            if dim not in common_coords and dim not in exclude:
-                dims_map[dim] = arg.sizes[dim]
-                if dim in arg.coords:
-                    common_coords[dim] = arg.coords[dim].variable
-
-    def _set_dims(var):
-        # Add excluded dims to a copy of dims_map
-        var_dims_map = dims_map.copy()
-        for dim in exclude:
-            with suppress(ValueError):
-                # ignore dim not in var.dims
-                var_dims_map[dim] = var.shape[var.dims.index(dim)]
-
-        return var.set_dims(var_dims_map)
-
-    def _broadcast_array(array):
-        data = _set_dims(array.variable)
-        coords = OrderedDict(array.coords)
-        coords.update(common_coords)
-        return DataArray(data, coords, data.dims, name=array.name,
-                         attrs=array.attrs)
-
-    def _broadcast_dataset(ds):
-        data_vars = OrderedDict(
-            (k, _set_dims(ds.variables[k]))
-            for k in ds.data_vars)
-        coords = OrderedDict(ds.coords)
-        coords.update(common_coords)
-        return Dataset(data_vars, coords, ds.attrs)
-
+    dims_map, common_coords = _get_broadcast_dims_map_common_coords(
+        args, exclude)
     result = []
     for arg in args:
-        if isinstance(arg, DataArray):
-            result.append(_broadcast_array(arg))
-        elif isinstance(arg, Dataset):
-            result.append(_broadcast_dataset(arg))
-        else:
-            raise ValueError('all input must be Dataset or DataArray objects')
+        result.append(_broadcast_helper(arg, exclude, dims_map, common_coords))
 
     return tuple(result)
 
diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py
index b93e4a5c..99014e9e 100644
--- a/xarray/core/dataarray.py
+++ b/xarray/core/dataarray.py
@@ -15,7 +15,9 @@ from . import (
     utils)
 from .accessor_dt import DatetimeAccessor
 from .accessor_str import StringAccessor
-from .alignment import align, reindex_like_indexers
+from .alignment import (align, _broadcast_helper,
+                        _get_broadcast_dims_map_common_coords,
+                        reindex_like_indexers)
 from .common import AbstractArray, DataWithCoords
 from .coordinates import (
     DataArrayCoordinates, LevelCoordinatesSource, assert_coordinate_consistent,
@@ -993,6 +995,70 @@ class DataArray(AbstractArray, DataWithCoords):
             dim=dim, method=method, tolerance=tolerance, **indexers)
         return self._from_temp_dataset(ds)
 
+    def broadcast_like(self,
+                       other: Union['DataArray', Dataset],
+                       exclude=None) -> 'DataArray':
+        """Broadcast a DataArray to the shape of another DataArray or Dataset
+
+        This is equivalent to xr.broadcast(other, self)[1]
+
+        xarray objects are broadcast against each other in arithmetic
+        operations, so this method is not be necessary for most uses.
+
+        If no change is needed, the input data is returned to the output
+        without being copied.
+
+        If new coords are added by the broadcast, their values are
+        NaN filled.
+
+        Parameters
+        ----------
+        other : Dataset or DataArray
+            Object against which to broadcast this array.
+
+        exclude : sequence of str, optional
+            Dimensions that must not be broadcasted
+
+        Returns
+        -------
+        new_da: xr.DataArray
+
+        Examples
+        --------
+
+        >>> arr1
+        <xarray.DataArray (x: 2, y: 3)>
+        array([[0.840235, 0.215216, 0.77917 ],
+               [0.726351, 0.543824, 0.875115]])
+        Coordinates:
+          * x        (x) <U1 'a' 'b'
+          * y        (y) <U1 'a' 'b' 'c'
+        >>> arr2
+        <xarray.DataArray (x: 3, y: 2)>
+        array([[0.612611, 0.125753],
+               [0.853181, 0.948818],
+               [0.180885, 0.33363 ]])
+        Coordinates:
+          * x        (x) <U1 'a' 'b' 'c'
+          * y        (y) <U1 'a' 'b'
+        >>> arr1.broadcast_like(arr2)
+        <xarray.DataArray (x: 3, y: 3)>
+        array([[0.840235, 0.215216, 0.77917 ],
+               [0.726351, 0.543824, 0.875115],
+               [     nan,      nan,      nan]])
+        Coordinates:
+          * x        (x) object 'a' 'b' 'c'
+          * y        (y) object 'a' 'b' 'c'
+        """
+        if exclude is None:
+            exclude = set()
+        args = align(other, self, join='outer', copy=False, exclude=exclude)
+
+        dims_map, common_coords = _get_broadcast_dims_map_common_coords(
+            args, exclude)
+
+        return _broadcast_helper(args[1], exclude, dims_map, common_coords)
+
     def reindex_like(self, other: Union['DataArray', Dataset],
                      method: Optional[str] = None, tolerance=None,
                      copy: bool = True, fill_value=dtypes.NA) -> 'DataArray':
@@ -1271,7 +1337,9 @@ class DataArray(AbstractArray, DataWithCoords):
                                      Mapping[Hashable, Any]] = None,
                     axis=None, **dim_kwargs: Any) -> 'DataArray':
         """Return a new object with an additional axis (or axes) inserted at
-        the corresponding position in the array shape.
+        the corresponding position in the array shape. The new object is a
+        view into the underlying array, not a copy.
+
 
         If dim is already a scalar coordinate, it will be promoted to a 1D
         coordinate consisting of a single value.
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index ebc23773..f37d87ac 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -17,7 +17,8 @@ import xarray as xr
 from ..coding.cftimeindex import _parse_array_of_cftime_strings
 from . import (alignment, dtypes, duck_array_ops, formatting, groupby,
                indexing, ops, pdcompat, resample, rolling, utils)
-from .alignment import align
+from .alignment import (align, _broadcast_helper,
+                        _get_broadcast_dims_map_common_coords)
 from .common import (ALL_DIMS, DataWithCoords, ImplementsDatasetReduce,
                      _contains_datetime_like_objects)
 from .coordinates import (DatasetCoordinates, LevelCoordinatesSource,
@@ -1389,7 +1390,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
     def to_zarr(
         self,
         store: Union[MutableMapping, str, Path] = None,
-        mode: str = 'w-',
+        mode: str = None,
         synchronizer=None,
         group: str = None,
         encoding: Mapping = None,
@@ -1407,10 +1408,13 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         ----------
         store : MutableMapping, str or Path, optional
             Store or path to directory in file system.
-        mode : {'w', 'w-', 'a'}
+        mode : {'w', 'w-', 'a', None}
             Persistence mode: 'w' means create (overwrite if exists);
             'w-' means create (fail if exists);
             'a' means append (create if does not exist).
+            If ``append_dim`` is set, ``mode`` can be omitted as it is
+            internally set to ``'a'``. Otherwise, ``mode`` will default to
+            `w-` if not set.
         synchronizer : object, optional
             Array synchronizer
         group : str, optional
@@ -1426,7 +1430,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
             If True, apply zarr's `consolidate_metadata` function to the store
             after writing.
         append_dim: hashable, optional
-            If mode='a', the dimension on which the data will be appended.
+            If set, the dimension on which the data will be appended.
 
         References
         ----------
@@ -1434,6 +1438,16 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         """
         if encoding is None:
             encoding = {}
+        if (mode == 'a') or (append_dim is not None):
+            if mode is None:
+                mode = 'a'
+            elif mode != 'a':
+                raise ValueError(
+                    "append_dim was set along with mode='{}', either set "
+                    "mode='a' or don't set it.".format(mode)
+                )
+        elif mode is None:
+            mode = 'w-'
         if mode not in ['w', 'w-', 'a']:
             # TODO: figure out how to handle 'r+'
             raise ValueError("The only supported options for mode are 'w',"
@@ -2016,6 +2030,30 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         )
         return self.isel_points(dim=dim, **pos_indexers)
 
+    def broadcast_like(self,
+                       other: Union['Dataset', 'DataArray'],
+                       exclude=None) -> 'Dataset':
+        """Broadcast this DataArray against another Dataset or DataArray.
+        This is equivalent to xr.broadcast(other, self)[1]
+
+        Parameters
+        ----------
+        other : Dataset or DataArray
+            Object against which to broadcast this array.
+        exclude : sequence of str, optional
+            Dimensions that must not be broadcasted
+
+        """
+
+        if exclude is None:
+            exclude = set()
+        args = align(other, self, join='outer', copy=False, exclude=exclude)
+
+        dims_map, common_coords = _get_broadcast_dims_map_common_coords(
+            args, exclude)
+
+        return _broadcast_helper(args[1], exclude, dims_map, common_coords)
+
     def reindex_like(self, other, method=None, tolerance=None, copy=True,
                      fill_value=dtypes.NA):
         """Conform this object onto the indexes of another object, filling in
@@ -2505,7 +2543,8 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
 
     def expand_dims(self, dim=None, axis=None, **dim_kwargs):
         """Return a new object with an additional axis (or axes) inserted at
-        the corresponding position in the array shape.
+        the corresponding position in the array shape.  The new object is a
+        view into the underlying array, not a copy.
 
         If dim is already a scalar coordinate, it will be promoted to a 1D
         coordinate consisting of a single value.
diff --git a/xarray/core/groupby.py b/xarray/core/groupby.py
index eb3b850c..0649ecab 100644
--- a/xarray/core/groupby.py
+++ b/xarray/core/groupby.py
@@ -232,6 +232,9 @@ class GroupBy(SupportsArithmetic):
                 raise TypeError('`group` must be an xarray.DataArray or the '
                                 'name of an xarray variable or dimension')
             group = obj[group]
+            if len(group) == 0:
+                raise ValueError("{} must not be empty".format(group.name))
+
             if group.name not in obj.coords and group.name in obj.dims:
                 # DummyGroups should not appear on groupby results
                 group = _DummyGroup(obj, group.name, group.coords)
diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py
index 02953b74..e262d9ee 100644
--- a/xarray/core/indexing.py
+++ b/xarray/core/indexing.py
@@ -1177,7 +1177,15 @@ class NumpyIndexingAdapter(ExplicitlyIndexedNDArrayMixin):
 
     def __setitem__(self, key, value):
         array, key = self._indexing_array_and_key(key)
-        array[key] = value
+        try:
+            array[key] = value
+        except ValueError:
+            # More informative exception if read-only view
+            if not array.flags.writeable and not array.flags.owndata:
+                raise ValueError("Assignment destination is a view.  "
+                                 "Do you want to .copy() array first?")
+            else:
+                raise
 
 
 class DaskIndexingAdapter(ExplicitlyIndexedNDArrayMixin):
diff --git a/xarray/util/print_versions.py b/xarray/util/print_versions.py
index 8295259a..d9e8c6a2 100755
--- a/xarray/util/print_versions.py
+++ b/xarray/util/print_versions.py
@@ -68,7 +68,7 @@ def netcdf_and_hdf5_versions():
     except ImportError:
         try:
             import h5py
-            libhdf5_version = h5py.__hdf5libversion__
+            libhdf5_version = h5py.version.hdf5_version
         except ImportError:
             pass
     return [('libhdf5', libhdf5_version), ('libnetcdf', libnetcdf_version)]
@@ -77,7 +77,10 @@ def netcdf_and_hdf5_versions():
 def show_versions(file=sys.stdout):
     sys_info = get_sys_info()
 
-    sys_info.extend(netcdf_and_hdf5_versions())
+    try:
+        sys_info.extend(netcdf_and_hdf5_versions())
+    except Exception as e:
+        print("Error collecting netcdf / hdf5 version: {}".format(e))
 
     deps = [
         # (MODULE_NAME, f(mod) -> mod version)


Test patch:
diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py
index 81bb1a1e..4e50a8bc 100644
--- a/xarray/tests/__init__.py
+++ b/xarray/tests/__init__.py
@@ -31,6 +31,11 @@ try:
 except ImportError:
     pass
 
+import platform
+arm_xfail = pytest.mark.xfail(platform.machine() == 'aarch64' or
+                              'arm' in platform.machine(),
+                              reason='expected failure on ARM')
+
 
 def _importorskip(modname, minversion=None):
     try:
diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py
index f5c27f2f..1a0256af 100644
--- a/xarray/tests/test_backends.py
+++ b/xarray/tests/test_backends.py
@@ -35,7 +35,7 @@ from . import (
     requires_cftime, requires_dask, requires_h5fileobj, requires_h5netcdf,
     requires_netCDF4, requires_pathlib, requires_pseudonetcdf, requires_pydap,
     requires_pynio, requires_rasterio, requires_scipy,
-    requires_scipy_or_netCDF4, requires_zarr)
+    requires_scipy_or_netCDF4, requires_zarr, arm_xfail)
 from .test_coding_times import (
     _ALL_CALENDARS, _NON_STANDARD_CALENDARS, _STANDARD_CALENDARS)
 from .test_dataset import create_test_data, create_append_test_data
@@ -400,6 +400,7 @@ class DatasetIOBase:
             assert_identical(expected, actual)
             assert actual['x'].encoding['_Encoding'] == 'ascii'
 
+    @arm_xfail
     def test_roundtrip_numpy_datetime_data(self):
         times = pd.to_datetime(['2000-01-01', '2000-01-02', 'NaT'])
         expected = Dataset({'t': ('t', times), 't0': times[0]})
@@ -1654,7 +1655,7 @@ class ZarrBase(CFEncodedBase):
         # check append mode for append write
         with self.create_zarr_target() as store_target:
             ds.to_zarr(store_target, mode='w')
-            ds_to_append.to_zarr(store_target, mode='a', append_dim='time')
+            ds_to_append.to_zarr(store_target, append_dim='time')
             original = xr.concat([ds, ds_to_append], dim='time')
             assert_identical(original, xr.open_zarr(store_target))
 
@@ -1690,7 +1691,7 @@ class ZarrBase(CFEncodedBase):
         ds, ds_to_append, _ = create_append_test_data()
         with self.create_zarr_target() as store_target:
             ds.to_zarr(store_target, mode='w')
-            ds_to_append.to_zarr(store_target, mode='a', append_dim='time')
+            ds_to_append.to_zarr(store_target, append_dim='time')
             original = xr.concat([ds, ds_to_append], dim='time')
             assert_identical(original, xr.open_zarr(store_target))
 
@@ -1706,8 +1707,7 @@ class ZarrBase(CFEncodedBase):
         with pytest.raises(ValueError):
             with self.create_zarr_target() as store_target:
                 ds.to_zarr(store_target, mode='w')
-                ds_to_append.to_zarr(store_target, mode='a',
-                                     append_dim='notvalid')
+                ds_to_append.to_zarr(store_target, append_dim='notvalid')
 
     def test_append_with_append_dim_not_set_raises(self):
 
@@ -1719,6 +1719,17 @@ class ZarrBase(CFEncodedBase):
                 ds.to_zarr(store_target, mode='w')
                 ds_to_append.to_zarr(store_target, mode='a')
 
+    def test_append_with_mode_not_a_raises(self):
+
+        ds, ds_to_append, _ = create_append_test_data()
+
+        # check failure when append_dim is set and mode != 'a'
+        with pytest.raises(ValueError):
+            with self.create_zarr_target() as store_target:
+                ds.to_zarr(store_target, mode='w')
+                ds_to_append.to_zarr(store_target, mode='w',
+                                     append_dim='time')
+
     def test_append_with_existing_encoding_raises(self):
 
         ds, ds_to_append, _ = create_append_test_data()
@@ -1727,8 +1738,7 @@ class ZarrBase(CFEncodedBase):
         with pytest.raises(ValueError):
             with self.create_zarr_target() as store_target:
                 ds.to_zarr(store_target, mode='w')
-                ds_to_append.to_zarr(store_target, mode='a',
-                                     append_dim='time',
+                ds_to_append.to_zarr(store_target, append_dim='time',
                                      encoding={'da': {'compressor': None}})
 
     def test_check_encoding_is_consistent_after_append(self):
@@ -1741,7 +1751,7 @@ class ZarrBase(CFEncodedBase):
             compressor = zarr.Blosc()
             encoding = {'da': {'compressor': compressor}}
             ds.to_zarr(store_target, mode='w', encoding=encoding)
-            ds_to_append.to_zarr(store_target, mode='a', append_dim='time')
+            ds_to_append.to_zarr(store_target, append_dim='time')
             actual_ds = xr.open_zarr(store_target)
             actual_encoding = actual_ds['da'].encoding['compressor']
             assert actual_encoding.get_config() == compressor.get_config()
@@ -1802,7 +1812,7 @@ class ZarrBase(CFEncodedBase):
                 assert_identical(ds, actual)
 
             delayed_obj = self.save(ds_to_append, store, compute=False,
-                                    mode='a', append_dim='time')
+                                    append_dim='time')
             assert isinstance(delayed_obj, Delayed)
 
             with pytest.raises(AssertionError):
diff --git a/xarray/tests/test_coding_times.py b/xarray/tests/test_coding_times.py
index 4de0d7f6..dacf68f6 100644
--- a/xarray/tests/test_coding_times.py
+++ b/xarray/tests/test_coding_times.py
@@ -16,7 +16,7 @@ from xarray.testing import assert_equal
 
 from . import (
     assert_array_equal, has_cftime, has_cftime_or_netCDF4, has_dask,
-    requires_cftime, requires_cftime_or_netCDF4)
+    requires_cftime, requires_cftime_or_netCDF4, arm_xfail)
 
 try:
     from pandas.errors import OutOfBoundsDatetime
@@ -470,6 +470,7 @@ def test_decode_360_day_calendar():
         assert_array_equal(actual, expected)
 
 
+@arm_xfail
 @pytest.mark.skipif(not has_cftime_or_netCDF4, reason='cftime not installed')
 @pytest.mark.parametrize(
     ['num_dates', 'units', 'expected_list'],
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index 30385779..5697704b 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -1255,6 +1255,25 @@ class TestDataArray:
         expected = DataArray(2, coords={1: 2}, name=1)
         assert_identical(actual, expected)
 
+    def test_broadcast_like(self):
+        arr1 = DataArray(np.ones((2, 3)), dims=['x', 'y'],
+                         coords={'x': ['a', 'b'], 'y': ['a', 'b', 'c']})
+        arr2 = DataArray(np.ones((3, 2)), dims=['x', 'y'],
+                         coords={'x': ['a', 'b', 'c'], 'y': ['a', 'b']})
+        orig1, orig2 = broadcast(arr1, arr2)
+        new1 = arr1.broadcast_like(arr2)
+        new2 = arr2.broadcast_like(arr1)
+
+        assert orig1.identical(new1)
+        assert orig2.identical(new2)
+
+        orig3 = DataArray(np.random.randn(5), [('x', range(5))])
+        orig4 = DataArray(np.random.randn(6), [('y', range(6))])
+        new3, new4 = broadcast(orig3, orig4)
+
+        assert_identical(orig3.broadcast_like(orig4), new3.transpose('y', 'x'))
+        assert_identical(orig4.broadcast_like(orig3), new4)
+
     def test_reindex_like(self):
         foo = DataArray(np.random.randn(5, 6),
                         [('x', range(5)), ('y', range(6))])
@@ -3125,7 +3144,9 @@ class TestDataArray:
         expected_no_data = expected.copy()
         del expected_no_data['data']
         del expected_no_data['coords']['x']['data']
-        expected_no_data['coords']['x'].update({'dtype': '<U1', 'shape': (2,)})
+        endiantype = '<U1' if sys.byteorder == 'little' else '>U1'
+        expected_no_data['coords']['x'].update({'dtype': endiantype,
+                                                'shape': (2,)})
         expected_no_data.update({'dtype': 'float64', 'shape': (2, 3)})
         actual_no_data = array.to_dict(data=False)
         assert expected_no_data == actual_no_data
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index c4925352..fc6f7f36 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -1560,6 +1560,21 @@ class TestDataset:
         assert_identical(mdata.sel(x={'one': 'a', 'two': 1}),
                          mdata.sel(one='a', two=1))
 
+    def test_broadcast_like(self):
+        original1 = DataArray(np.random.randn(5),
+                              [('x', range(5))], name='a').to_dataset()
+
+        original2 = DataArray(np.random.randn(6),
+                              [('y', range(6))], name='b')
+
+        expected1, expected2 = broadcast(original1, original2)
+
+        assert_identical(original1.broadcast_like(original2),
+                         expected1.transpose('y', 'x'))
+
+        assert_identical(original2.broadcast_like(original1),
+                         expected2)
+
     def test_reindex_like(self):
         data = create_test_data()
         data['letters'] = ('dim3', 10 * ['a'])
@@ -3445,7 +3460,8 @@ class TestDataset:
         del expected_no_data['coords']['t']['data']
         del expected_no_data['data_vars']['a']['data']
         del expected_no_data['data_vars']['b']['data']
-        expected_no_data['coords']['t'].update({'dtype': '<U1',
+        endiantype = '<U1' if sys.byteorder == 'little' else '>U1'
+        expected_no_data['coords']['t'].update({'dtype': endiantype,
                                                 'shape': (10,)})
         expected_no_data['data_vars']['a'].update({'dtype': 'float64',
                                                    'shape': (10,)})
diff --git a/xarray/tests/test_duck_array_ops.py b/xarray/tests/test_duck_array_ops.py
index 87a7a286..b0b7cfbd 100644
--- a/xarray/tests/test_duck_array_ops.py
+++ b/xarray/tests/test_duck_array_ops.py
@@ -17,7 +17,7 @@ from xarray.testing import assert_allclose, assert_equal
 
 from . import (
     assert_array_equal, has_dask, has_np113, raises_regex, requires_cftime,
-    requires_dask)
+    requires_dask, arm_xfail)
 
 
 class TestOps:
@@ -252,6 +252,7 @@ def assert_dask_array(da, dask):
         assert isinstance(da.data, dask_array_type)
 
 
+@arm_xfail
 @pytest.mark.parametrize('dask', [False, True])
 def test_datetime_reduce(dask):
     time = np.array(pd.date_range('15/12/1999', periods=11))
diff --git a/xarray/tests/test_formatting.py b/xarray/tests/test_formatting.py
index 8a3c95a4..be3e368e 100644
--- a/xarray/tests/test_formatting.py
+++ b/xarray/tests/test_formatting.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from textwrap import dedent
+import sys
 
 import numpy as np
 import pandas as pd
@@ -204,6 +205,7 @@ class TestFormatting:
                     'label': ('x', np.array([1, 2], dtype='int64'))},
             attrs={'units': 'kg'})
 
+        byteorder = '<' if sys.byteorder == 'little' else '>'
         expected = dedent("""\
         Left and right DataArray objects are not identical
         Differing dimensions:
@@ -215,8 +217,8 @@ class TestFormatting:
         R
             array([1, 2], dtype=int64)
         Differing coordinates:
-        L * x        (x) <U1 'a' 'b'
-        R * x        (x) <U1 'a' 'c'
+        L * x        (x) %cU1 'a' 'b'
+        R * x        (x) %cU1 'a' 'c'
         Coordinates only on the left object:
           * y        (y) int64 1 2 3
         Coordinates only on the right object:
@@ -225,7 +227,7 @@ class TestFormatting:
         L   units: m
         R   units: kg
         Attributes only on the left object:
-            description: desc""")
+            description: desc""" % (byteorder, byteorder))
 
         actual = formatting.diff_array_repr(da_a, da_b, 'identical')
         try:
@@ -277,13 +279,14 @@ class TestFormatting:
             attrs={'units': 'kg'}
         )
 
+        byteorder = '<' if sys.byteorder == 'little' else '>'
         expected = dedent("""\
         Left and right Dataset objects are not identical
         Differing dimensions:
             (x: 2, y: 3) != (x: 2)
         Differing coordinates:
-        L * x        (x) <U1 'a' 'b'
-        R * x        (x) <U1 'a' 'c'
+        L * x        (x) %cU1 'a' 'b'
+        R * x        (x) %cU1 'a' 'c'
             source: 0
         Coordinates only on the left object:
           * y        (y) int64 1 2 3
@@ -298,7 +301,7 @@ class TestFormatting:
         L   units: m
         R   units: kg
         Attributes only on the left object:
-            description: desc""")
+            description: desc""" % (byteorder, byteorder))
 
         actual = formatting.diff_dataset_repr(ds_a, ds_b, 'identical')
         assert actual == expected
diff --git a/xarray/tests/test_groupby.py b/xarray/tests/test_groupby.py
index 5433bd00..0f1adf3d 100644
--- a/xarray/tests/test_groupby.py
+++ b/xarray/tests/test_groupby.py
@@ -105,6 +105,14 @@ def test_ds_groupby_apply_func_args():
     assert_identical(expected, actual)
 
 
+def test_da_groupby_empty():
+
+    empty_array = xr.DataArray([], dims='dim')
+
+    with pytest.raises(ValueError):
+        empty_array.groupby('dim')
+
+
 def test_da_groupby_quantile():
 
     array = xr.DataArray([1, 2, 3, 4, 5, 6],
diff --git a/xarray/tests/test_indexing.py b/xarray/tests/test_indexing.py
index c044d2ed..64eee80d 100644
--- a/xarray/tests/test_indexing.py
+++ b/xarray/tests/test_indexing.py
@@ -144,6 +144,16 @@ class TestIndexers:
                      [True, True, True, True, False, False, False, False],
                      pd.MultiIndex.from_product([[1, 2], [-1, -2]]))
 
+    def test_read_only_view(self):
+        from collections import OrderedDict
+        arr = DataArray(np.random.rand(3, 3),
+                        coords={'x': np.arange(3), 'y': np.arange(3)},
+                        dims=('x', 'y'))     # Create a 2D DataArray
+        arr = arr.expand_dims(OrderedDict([('z', 3)]), -1)  # New dimension 'z'
+        arr['z'] = np.arange(3)              # New coords to dimension 'z'
+        with pytest.raises(ValueError, match='Do you want to .copy()'):
+            arr.loc[0, 0, 0] = 999
+
 
 class TestLazyArray:
     def test_slice_slice(self):