# -*- coding: utf-8 -*-
import io
import pickle
import platform
import re
import warnings
from copy import deepcopy
from unittest import mock

import numpy as np
import pytest

from obspy import Stream, Trace, UTCDateTime, read, read_inventory
from obspy.core.inventory import Channel, Inventory, Network, Station
from obspy.core.stream import _is_pickle, _read_pickle, _write_pickle
from obspy.core.util.attribdict import AttribDict
from obspy.core.util.base import NamedTemporaryFile, _get_entry_points
from obspy.core.util.deprecation_helpers import ObsPyDeprecationWarning
from obspy.core.util.obspy_types import ObsPyException
from obspy.core.util.testing import streams_almost_equal
from obspy.io.xseed import Parser


class TestStream:
    """
    Test suite for obspy.core.stream.Stream.
    """
    @pytest.fixture(scope="package")
    def ascii_path(self, root):
        return root / "io" / "ascii" / "tests" / "data"

    @pytest.fixture()
    def mseed_stream(self):
        """Create a stream for testing."""
        rand = np.random.RandomState(815)
        header = {'network': 'BW', 'station': 'BGLD',
                  'starttime': UTCDateTime(2007, 12, 31, 23, 59, 59, 915000),
                  'npts': 412, 'sampling_rate': 200.0,
                  'channel': 'EHE'}
        trace1 = Trace(data=rand.randint(0, 1000, 412).astype(np.float64),
                       header=deepcopy(header))
        header['starttime'] = UTCDateTime(2008, 1, 1, 0, 0, 4, 35000)
        header['npts'] = 824
        trace2 = Trace(data=rand.randint(0, 1000, 824).astype(np.float64),
                       header=deepcopy(header))
        header['starttime'] = UTCDateTime(2008, 1, 1, 0, 0, 10, 215000)
        trace3 = Trace(data=rand.randint(0, 1000, 824).astype(np.float64),
                       header=deepcopy(header))
        header['starttime'] = UTCDateTime(2008, 1, 1, 0, 0, 18, 455000)
        header['npts'] = 50668
        trace4 = Trace(
            data=rand.randint(0, 1000, 50668).astype(np.float64),
            header=deepcopy(header))
        return Stream(traces=[trace1, trace2, trace3, trace4])

    @pytest.fixture()
    def gse2_stream(self):
        """Another example stream."""
        rand = np.random.RandomState(815)
        header = {'network': '', 'station': 'RNON ', 'location': '',
                  'starttime': UTCDateTime(2004, 6, 9, 20, 5, 59, 849998),
                  'sampling_rate': 200.0, 'npts': 12000,
                  'channel': '  Z'}
        trace = Trace(
            data=rand.randint(0, 1000, 12000).astype(np.float64),
            header=header)
        return Stream(traces=[trace])

    @staticmethod
    def __remove_processing(st):
        """
        Helper method removing the processing information from all traces
        within a Stream object.

        Useful for testing.
        """
        for tr in st:
            if "processing" not in tr.stats:
                continue
            del tr.stats.processing

    def test_init(self):
        """
        Tests the __init__ method of the Stream object.
        """
        # empty
        st = Stream()
        assert len(st) == 0
        # single trace
        st = Stream(Trace())
        assert len(st) == 1
        # array of traces
        st = Stream([Trace(), Trace()])
        assert len(st) == 2

    def test_setitem(self, mseed_stream):
        """
        Tests the __setitem__ method of the Stream object.
        """
        stream = mseed_stream
        stream[0] = stream[3]
        assert stream[0] == stream[3]
        st = deepcopy(stream)
        stream[0].data[0:10] = 999
        assert st[0].data[0] != 999
        st[0] = stream[0]
        np.testing.assert_array_equal(stream[0].data[:10],
                                      np.ones(10, dtype=np.int_) * 999)

    def test_getitem(self):
        """
        Tests the __getitem__ method of the Stream object.
        """
        stream = read()
        assert stream[0] == stream.traces[0]
        assert stream[-1] == stream.traces[-1]
        assert stream[2] == stream.traces[2]
        # out of index should fail
        with pytest.raises(IndexError):
            stream.__getitem__(3)
        with pytest.raises(IndexError):
            stream.__getitem__(-99)

    def test_add(self, mseed_stream, gse2_stream):
        """
        Tests the adding of two stream objects.
        """
        stream = mseed_stream
        assert 4 == len(stream)
        # Add the same stream object to itself.
        stream = stream + stream
        assert 8 == len(stream)
        # This will not create copies of Traces and thus the objects should
        # be identical (and the Traces attributes should be identical).
        for _i in range(4):
            assert (stream[_i] == stream[_i + 4])
            assert (stream[_i] is stream[_i + 4])
        # Now add another stream to it.
        other_stream = gse2_stream
        assert 1 == len(other_stream)
        new_stream = stream + other_stream
        assert 9 == len(new_stream)
        # The traces of all streams are copied.
        for _i in range(8):
            assert new_stream[_i] == stream[_i]
            assert new_stream[_i] is stream[_i]
        # Also test for the newly added stream.
        assert new_stream[8] == other_stream[0]
        assert new_stream[8].stats == other_stream[0].stats
        np.testing.assert_array_equal(new_stream[8].data, other_stream[0].data)
        # adding something else than stream or trace results into TypeError
        with pytest.raises(TypeError):
            stream.__add__(1)
        with pytest.raises(TypeError):
            stream.__add__('test')

    def test_iadd(self, mseed_stream, gse2_stream):
        """
        Tests the __iadd__ method of the Stream objects.
        """
        stream = mseed_stream
        assert 4 == len(stream)
        other_stream = gse2_stream
        assert 1 == len(other_stream)
        # Add the other stream to the stream.
        stream += other_stream
        # This will leave the Traces of the new stream and create a deepcopy of
        # the other Stream's Traces
        assert 5 == len(stream)
        assert other_stream[0] == stream[-1]
        assert other_stream[0].stats == stream[-1].stats
        np.testing.assert_array_equal(other_stream[0].data, stream[-1].data)
        # adding something else than stream or trace results into TypeError
        with pytest.raises(TypeError):
            stream.__iadd__(1)
        with pytest.raises(TypeError):
            stream.__iadd__('test')

    def test_mul(self):
        """
        Tests the __mul__ method of the Stream objects.
        """
        st = Stream(Trace())
        assert len(st) == 1
        st = st * 4
        assert len(st) == 4
        # multiplying by something else than an integer results into TypeError
        with pytest.raises(TypeError):
            st.__mul__(1.2345)
        with pytest.raises(TypeError):
            st.__mul__('test')

    def test_add_trace_to_stream(self):
        """
        Tests using a Trace on __add__ and __iadd__ methods of the Stream.
        """
        st0 = read()
        st1 = st0[0:2]
        tr = st0[2]
        # __add__
        assert st1.__add__(tr) == st0
        assert st1 + tr == st0
        # __iadd__
        st1 += tr
        assert st1 == st0

    def test_append(self, mseed_stream):
        """
        Tests the append method of the Stream object.
        """
        stream = mseed_stream
        # Check current count of traces
        assert len(stream) == 4
        # Append first traces to the Stream object.
        stream.append(stream[0])
        assert len(stream) == 5
        # This is supposed to make a deepcopy of the Trace and thus the two
        # Traces are not identical.
        assert stream[0] == stream[-1]
        # But the attributes and data values should be identical.
        assert stream[0].stats == stream[-1].stats
        np.testing.assert_array_equal(stream[0].data, stream[-1].data)
        # Append the same again
        stream.append(stream[0])
        assert len(stream) == 6
        # Now the two objects should be identical.
        assert stream[0] == stream[-1]
        # Using append with a list of Traces, or int, or ... should fail.
        with pytest.raises(TypeError):
            stream.append(stream[:])
        with pytest.raises(TypeError):
            stream.append(1)
        with pytest.raises(TypeError):
            stream.append(stream[0].data)

    def test_count_and_len(self):
        """
        Tests the count and __len__ methods of the Stream object.
        """
        # empty stream without traces
        stream = Stream()
        assert len(stream) == 0
        assert stream.count() == 0
        # stream with traces
        stream = read()
        assert len(stream) == 3
        assert stream.count() == 3

    def test_extend(self, mseed_stream):
        """
        Tests the extend method of the Stream object.
        """
        stream = mseed_stream
        # Check current count of traces
        assert len(stream) == 4
        # Extend the Stream object with the first two traces.
        stream.extend(stream[0:2])
        assert len(stream) == 6
        # This is NOT supposed to make a deepcopy of the Trace and thus the two
        # Traces compare equal and are identical.
        assert stream[0] == stream[-2]
        assert stream[1] == stream[-1]
        assert stream[0] is stream[-2]
        assert stream[1] is stream[-1]
        # Using extend with a single Traces, or a wrong list, or ...
        # should fail.
        with pytest.raises(TypeError):
            stream.extend(stream[0])
        with pytest.raises(TypeError):
            stream.extend(1)
        with pytest.raises(TypeError):
            stream.extend([stream[0], 1])

    def test_insert(self, mseed_stream):
        """
        Tests the insert Method of the Stream object.
        """
        stream = mseed_stream
        assert 4 == len(stream)
        # Insert the last Trace before the second trace.
        stream.insert(1, stream[-1])
        assert len(stream) == 5
        # This is supposed to make a deepcopy of the Trace and thus the two
        # Traces are not identical.
        # self.assertNotEqual(stream[1], stream[-1])
        assert stream[1] == stream[-1]
        # But the attributes and data values should be identical.
        assert stream[1].stats == stream[-1].stats
        np.testing.assert_array_equal(stream[1].data, stream[-1].data)
        # Do the same again
        stream.insert(1, stream[-1])
        assert len(stream) == 6
        # Now the two Traces should be identical
        assert stream[1] == stream[-1]
        # Do the same with a list of traces this time.
        # Insert the last two Trace before the second trace.
        stream.insert(1, stream[-2:])
        assert len(stream) == 8
        # This is supposed to make a deepcopy of the Trace and thus the two
        # Traces are not identical.
        assert stream[1] == stream[-2]
        assert stream[2] == stream[-1]
        # But the attributes and data values should be identical.
        assert stream[1].stats == stream[-2].stats
        np.testing.assert_array_equal(stream[1].data, stream[-2].data)
        assert stream[2].stats == stream[-1].stats
        np.testing.assert_array_equal(stream[2].data, stream[-1].data)
        # Do the same again
        stream.insert(1, stream[-2:])
        assert len(stream) == 10
        # Now the two Traces should be identical
        assert stream[1] == stream[-2]
        assert stream[2] == stream[-1]
        # Using insert without a single Traces or a list of Traces should fail.
        with pytest.raises(TypeError):
            stream.insert(1, 1)
        with pytest.raises(TypeError):
            stream.insert(stream[0], stream[0])
        with pytest.raises(TypeError):
            stream.insert(1, [stream[0], 1])

    def test_get_gaps(self, mseed_stream):
        """
        Tests the get_gaps method of the Stream objects.

        It is compared directly to the obspy.io.mseed method getGapsList which
        is assumed to be correct.
        """
        stream = mseed_stream
        gap_list = stream.get_gaps()
        # Gaps list created with obspy.io.mseed
        mseed_gap_list = [
            ('BW', 'BGLD', '', 'EHE',
             UTCDateTime(2008, 1, 1, 0, 0, 1, 970000),
             UTCDateTime(2008, 1, 1, 0, 0, 4, 35000),
             2.0599999999999999, 412.0),
            ('BW', 'BGLD', '', 'EHE',
             UTCDateTime(2008, 1, 1, 0, 0, 8, 150000),
             UTCDateTime(2008, 1, 1, 0, 0, 10, 215000),
             2.0599999999999999, 412.0),
            ('BW', 'BGLD', '', 'EHE',
             UTCDateTime(2008, 1, 1, 0, 0, 14, 330000),
             UTCDateTime(2008, 1, 1, 0, 0, 18, 455000),
             4.120, 824.0)]
        # Assert the number of gaps.
        assert len(mseed_gap_list) == len(gap_list)
        for _i in range(len(mseed_gap_list)):
            # Compare the string values directly.
            for _j in range(6):
                assert gap_list[_i][_j] == mseed_gap_list[_i][_j]
            # The small differences are probably due to rounding errors.
            gap_6 = mseed_gap_list[_i][6]
            gap_7 = mseed_gap_list[_i][7]
            assert round(abs(float(gap_6)-float(gap_6)), 3) == 0
            assert round(abs(float(gap_7)-float(gap_7)), 3) == 0

    def test_get_gaps_multiplexed_streams(self):
        """
        Tests the get_gaps method of the Stream objects.
        """
        data = np.random.randint(0, 1000, 412)
        # different channels
        st = Stream()
        for channel in ['EHZ', 'EHN', 'EHE']:
            st.append(Trace(data=data, header={'channel': channel}))
        assert len(st.get_gaps()) == 0
        # different locations
        st = Stream()
        for location in ['', '00', '01']:
            st.append(Trace(data=data, header={'location': location}))
        assert len(st.get_gaps()) == 0
        # different stations
        st = Stream()
        for station in ['MANZ', 'ROTZ', 'BLAS']:
            st.append(Trace(data=data, header={'station': station}))
        assert len(st.get_gaps()) == 0
        # different networks
        st = Stream()
        for network in ['BW', 'GE', 'GR']:
            st.append(Trace(data=data, header={'network': network}))
        assert len(st.get_gaps()) == 0

    def test_get_gaps_masked(self):
        """
        Test get_gaps method of the Stream objects (Issue #2299)
        """
        # Create a Stream with a masked array (analogous to a merged Stream)
        st = Stream()
        data = np.ma.array(np.arange(0, 100, step=1))
        data.mask = np.zeros(data.shape)
        data.mask[50:60] = 1
        st.append(Trace(data=data))
        # Expected gap
        gap = ["", "", "", "",
               UTCDateTime(1970, 1, 1, 0, 0, 49),
               UTCDateTime(1970, 1, 1, 0, 1, 0),
               10., 10]
        # Get the gaps
        gaps = st.get_gaps()
        # Assert the number of gaps
        assert len(gaps) == 1
        # Verify the resulting gap list matches what is expected
        for _i in range(6):
            assert gaps[0][_i] == gap[_i]
        assert round(abs(float(gaps[0][6])-float(gap[6])), 3) == 0
        assert round(abs(float(gaps[0][7])-float(gap[7])), 7) == 0
        # Double-check that the initial Stream is unmodified
        assert len(st) == 1

    def test_pop(self, mseed_stream):
        """
        Test the pop method of the Stream object.
        """
        stream = mseed_stream
        # Make a copy of the Traces.
        traces = deepcopy(stream[:])
        # Remove and return the last Trace.
        temp_trace = stream.pop()
        assert 3 == len(stream)
        # Assert attributes. The objects itself are not identical.
        assert temp_trace.stats == traces[-1].stats
        np.testing.assert_array_equal(temp_trace.data, traces[-1].data)
        # Remove the last copied Trace.
        traces.pop()
        # Remove and return the second Trace.
        temp_trace = stream.pop(1)
        # Assert attributes. The objects itself are not identical.
        assert temp_trace.stats == traces[1].stats
        np.testing.assert_array_equal(temp_trace.data, traces[1].data)
        # Remove the second copied Trace.
        traces.pop(1)
        # Compare all remaining Traces.
        assert 2 == len(stream)
        assert 2 == len(traces)
        for _i in range(len(traces)):
            assert traces[_i].stats == stream[_i].stats
            np.testing.assert_array_equal(traces[_i].data, stream[_i].data)

    def test_slicing(self):
        """
        Tests the __getslice__ method of the Stream object.
        """
        stream = read()
        assert stream[0:] == stream[0:]
        assert stream[:2] == stream[:2]
        assert stream[:] == stream[:]
        assert len(stream) == 3
        new_stream = stream[1:3]
        assert isinstance(new_stream, Stream)
        assert len(new_stream) == 2
        assert new_stream[0].stats == stream[1].stats
        assert new_stream[1].stats == stream[2].stats

    def test_slicing_with_steps(self):
        """
        Tests the __getslice__ method of the Stream object with step.
        """
        tr1 = Trace()
        tr2 = Trace()
        tr3 = Trace()
        tr4 = Trace()
        tr5 = Trace()
        st = Stream([tr1, tr2, tr3, tr4, tr5])
        assert st[0:6].traces == [tr1, tr2, tr3, tr4, tr5]
        assert st[0:6:1].traces == [tr1, tr2, tr3, tr4, tr5]
        assert st[0:6:2].traces == [tr1, tr3, tr5]
        assert st[1:6:2].traces == [tr2, tr4]
        assert st[1:6:6].traces == [tr2]

    def test_slice(self):
        """
        Slice method should not loose attributes set on stream object itself.
        """
        st = read()
        st.test = 1
        st.muh = "Muh"
        st2 = st.slice(st[0].stats.starttime, st[0].stats.endtime)
        assert st2.test == 1
        assert st2.muh == "Muh"

    def test_slice_nearest_sample(self):
        """
        Tests that the nearest_sample argument is correctly passed to the
        trace function calls.
        """
        # It defaults to True.
        st = read()
        utc1 = st[0].stats.starttime + 0.1
        utc2 = st[0].stats.endtime - 0.1
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = st[0]
            st.slice(utc1, utc2)
        assert patch.call_count == 3
        for arg in patch.call_args_list:
            assert arg[1]["nearest_sample"]

        # Force True.
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = st[0]
            st.slice(utc1, utc2, nearest_sample=True)
        assert patch.call_count == 3
        for arg in patch.call_args_list:
            assert arg[1]["nearest_sample"]

        # Set to False.
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = st[0]
            st.slice(1, 2, nearest_sample=False)
        assert patch.call_count == 3
        for arg in patch.call_args_list:
            assert not arg[1]["nearest_sample"]

    def test_cutout(self):
        """
        Test cutout method of the Stream object. Compare against equivalent
        trimming operations.
        """
        t1 = UTCDateTime("2009-06-24")
        t2 = UTCDateTime("2009-08-24T00:20:06.007Z")
        t3 = UTCDateTime("2009-08-24T00:20:16.008Z")
        t4 = UTCDateTime("2011-09-11")
        st = read()
        st_cut = read()
        # 1
        st_cut.cutout(t4, t4 + 10)
        self.__remove_processing(st_cut)
        assert st == st_cut
        # 2
        st_cut.cutout(t1 - 10, t1)
        self.__remove_processing(st_cut)
        assert st == st_cut
        # 3
        st_cut.cutout(t1, t2)
        st.trim(starttime=t2, nearest_sample=True)
        self.__remove_processing(st_cut)
        self.__remove_processing(st)
        assert st == st_cut
        # 4
        st = read()
        st_cut = read()
        st_cut.cutout(t3, t4)
        st.trim(endtime=t3, nearest_sample=True)
        self.__remove_processing(st_cut)
        self.__remove_processing(st)
        assert st == st_cut
        # 5
        st = read()
        st.trim(endtime=t2, nearest_sample=True)
        tmp = read()
        tmp.trim(starttime=t3, nearest_sample=True)
        st += tmp
        st_cut = read()
        st_cut.cutout(t2, t3)
        self.__remove_processing(st_cut)
        self.__remove_processing(st)
        assert st == st_cut

    def test_pop2(self):
        """
        Test the pop method of the Stream object.
        """
        trace = Trace(data=np.arange(0, 1000))
        st = Stream([trace])
        st = st + st + st + st
        assert len(st) == 4
        st.pop()
        assert len(st) == 3
        st[1].stats.station = 'MUH'
        st.pop(0)
        assert len(st) == 2
        assert st[0].stats.station == 'MUH'

    def test_remove(self, mseed_stream):
        """
        Tests the remove method of the Stream object.
        """
        stream = mseed_stream
        # Make a copy of the Traces.
        stream2 = deepcopy(stream)
        # Use the remove method of the Stream object and of the list of Traces.
        stream.remove(stream[1])
        del stream2[1]
        stream.remove(stream[-1])
        del stream2[-1]
        # Compare remaining Streams.
        assert stream == stream2

    def test_reverse(self, mseed_stream):
        """
        Tests the reverse method of the Stream object.
        """
        stream = mseed_stream
        # Make a copy of the Traces.
        traces = deepcopy(stream[:])
        # Use reversing of the Stream object and of the list.
        stream.reverse()
        traces.reverse()
        # Compare all Traces.
        assert 4 == len(stream)
        assert 4 == len(traces)
        for _i in range(len(traces)):
            assert traces[_i].stats == stream[_i].stats
            np.testing.assert_array_equal(traces[_i].data, stream[_i].data)

    def test_select(self):
        """
        Tests the select method of the Stream object.
        """
        # Create a list of header dictionaries.
        headers = [
            {'starttime': UTCDateTime(1990, 1, 1), 'network': 'AA',
             'station': 'ZZZZ', 'channel': 'EHZ', 'sampling_rate': 200.0,
             'npts': 100},
            {'starttime': UTCDateTime(1990, 1, 1), 'network': 'BB',
             'station': 'YYYY', 'channel': 'EHN', 'sampling_rate': 200.0,
             'npts': 100},
            {'starttime': UTCDateTime(2000, 1, 1), 'network': 'AA',
             'station': 'ZZZZ', 'channel': 'BHZ', 'sampling_rate': 20.0,
             'npts': 100},
            {'starttime': UTCDateTime(1989, 1, 1), 'network': 'BB',
             'station': 'XXXX', 'channel': 'BHN', 'sampling_rate': 20.0,
             'npts': 100},
            {'starttime': UTCDateTime(2010, 1, 1), 'network': 'AA',
             'station': 'XXXX', 'channel': 'EHZ', 'sampling_rate': 200.0,
             'npts': 100, 'location': '00'}]
        # Make stream object for test case
        traces = []
        for header in headers:
            traces.append(Trace(data=np.random.randint(0, 1000, 100),
                                header=header))
        stream = Stream(traces=traces)
        # Test cases:
        stream2 = stream.select()
        assert stream == stream2
        with pytest.raises(Exception):
            stream.select(channel="EHZ",
                          component="N")
        stream2 = stream.select(channel='EHE')
        assert len(stream2) == 0
        stream2 = stream.select(channel='EHZ')
        assert len(stream2) == 2
        assert stream[0] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(component='Z')
        assert len(stream2) == 3
        assert stream[0] in stream2
        assert stream[2] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(component='n')
        assert len(stream2) == 2
        assert stream[1] in stream2
        assert stream[3] in stream2
        stream2 = stream.select(channel='BHZ', npts=100, sampling_rate='20.0',
                                network='AA', component='Z', station='ZZZZ')
        assert len(stream2) == 1
        assert stream[2] in stream2
        stream2 = stream.select(channel='EHZ', station="XXXX")
        assert len(stream2) == 1
        assert stream[4] in stream2
        stream2 = stream.select(network='AA')
        assert len(stream2) == 3
        assert stream[0] in stream2
        assert stream[2] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(id='AA.ZZZZ..EHZ')
        assert len(stream2) == 1
        assert stream[0] in stream2
        stream2 = stream.select(sampling_rate=20.0)
        assert len(stream2) == 2
        assert stream[2] in stream2
        assert stream[3] in stream2
        # tests for wildcarded channel:
        stream2 = stream.select(channel='B*')
        assert len(stream2) == 2
        assert stream[2] in stream2
        assert stream[3] in stream2
        stream2 = stream.select(channel='EH*')
        assert len(stream2) == 3
        assert stream[0] in stream2
        assert stream[1] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(channel='*Z')
        assert len(stream2) == 3
        assert stream[0] in stream2
        assert stream[2] in stream2
        assert stream[4] in stream2
        # tests for other wildcard operations:
        stream2 = stream.select(station='[XY]*')
        assert len(stream2) == 3
        assert stream[1] in stream2
        assert stream[3] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(station='[A-Y]*')
        assert len(stream2) == 3
        assert stream[1] in stream2
        assert stream[3] in stream2
        assert stream[4] in stream2
        stream2 = stream.select(station='[A-Y]??*', network='A?')
        assert len(stream2) == 1
        assert stream[4] in stream2
        # test case insensitivity
        stream2 = stream.select(channel='BhZ', npts=100, sampling_rate='20.0',
                                network='aA', station='ZzZz',)
        assert len(stream2) == 1
        assert stream[2] in stream2
        stream2 = stream.select(channel='e?z', network='aa', station='x?X*',
                                location='00', component='z')
        assert len(stream2) == 1
        assert stream[4] in stream2

    def test_select_on_single_letter_channels(self):
        st = read()
        st[0].stats.channel = "Z"
        st[1].stats.channel = "N"
        st[2].stats.channel = "E"

        assert [tr.stats.channel for tr in st] == ["Z", "N", "E"]

        assert st.select(component="Z")[0] == st[0]
        assert st.select(component="N")[0] == st[1]
        assert st.select(component="E")[0] == st[2]

        assert len(st.select(component="Z")) == 1
        assert len(st.select(component="N")) == 1
        assert len(st.select(component="E")) == 1

    def test_select_from_inventory(self):
        # Create a test stream
        headers = [
            {'network': 'AA', 'station': 'ST01', 'channel': 'EHZ',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(1990, 1, 1)},
            {'network': 'AA', 'station': 'ST01', 'channel': 'EHZ',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(1998, 1, 1)},
            {'network': 'AA', 'station': 'ST01', 'channel': 'EHZ',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(2000, 1, 1)},
            {'network': 'AA', 'station': 'ST01', 'channel': 'EHN',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(2000, 1, 1)},
            {'network': 'AA', 'station': 'ST01', 'channel': 'EHE',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(2000, 1, 1)},
            {'network': 'AA', 'station': 'ST02', 'channel': 'EHZ',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(2000, 1, 1)},
            {'network': 'AB', 'station': 'ST01', 'channel': 'EHZ',
             'location': '00', 'sampling_rate': 200.0, 'npts': 100,
             'starttime': UTCDateTime(2000, 1, 1)}
        ]
        traces = []
        for header in headers:
            traces.append(Trace(data=np.random.randint(0, 1000, 100),
                                header=header))
        st = Stream(traces=traces)
        # Create a test inventory
        channels = [
            Channel(code='EHZ', location_code='00',
                    latitude=0.0, longitude=0.0, elevation=0.0, depth=0.0,
                    start_date=UTCDateTime(1990, 1, 1),
                    end_date=UTCDateTime(1998, 1, 1)),
            Channel(code='EHN', location_code='00',
                    latitude=0.0, longitude=0.0, elevation=0.0, depth=0.0,
                    start_date=UTCDateTime(1999, 1, 1))
        ]
        stations = [Station(code='ST01',
                            latitude=0.0,
                            longitude=0.0,
                            elevation=0.0,
                            channels=channels)]
        networks = [Network('AA', stations=stations)]
        inv = Inventory(networks=networks, source='TEST')
        # tests
        assert len(st.select(inventory=inv)) == 2
        st_sel = st.select(network='AB')
        assert len(st_sel.select(inventory=inv)) == 0
        inv_sel = inv.select(starttime=UTCDateTime(2000, 1, 1))
        assert len(st.select(inventory=inv_sel)) == 1

    def test_sort(self):
        """
        Tests the sort method of the Stream object.
        """
        # Create new Stream
        stream = Stream()
        # Create a list of header dictionaries. The sampling rate serves as a
        # unique identifier for each Trace.
        headers = [
            {'starttime': UTCDateTime(1990, 1, 1), 'network': 'AAA',
             'station': 'ZZZ', 'channel': 'XXX', 'sampling_rate': 100.0},
            {'starttime': UTCDateTime(1990, 1, 1), 'network': 'AAA',
             'station': 'YYY', 'channel': 'CCC', 'sampling_rate': 200.0},
            {'starttime': UTCDateTime(2000, 1, 1), 'network': 'AAA',
             'station': 'EEE', 'channel': 'GGG', 'sampling_rate': 300.0},
            {'starttime': UTCDateTime(1989, 1, 1), 'network': 'AAA',
             'station': 'XXX', 'channel': 'GGG', 'sampling_rate': 400.0},
            {'starttime': UTCDateTime(2010, 1, 1), 'network': 'AAA',
             'station': 'XXX', 'channel': 'FFF', 'sampling_rate': 500.0}]
        # Create a Trace object of it and append it to the Stream object.
        for _i in headers:
            new_trace = Trace(header=_i)
            stream.append(new_trace)
        # Use normal sorting.
        stream.sort()
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [300.0, 500.0, 400.0, 200.0, 100.0]
        # Sort after sampling_rate.
        stream.sort(keys=['sampling_rate'])
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [100.0, 200.0, 300.0, 400.0, 500.0]
        # Sort after channel and sampling rate.
        stream.sort(keys=['channel', 'sampling_rate'])
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [200.0, 500.0, 300.0, 400.0, 100.0]
        # Sort after npts and sampling_rate and endtime.
        stream.sort(keys=['npts', 'sampling_rate', 'endtime'])
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [100.0, 200.0, 300.0, 400.0, 500.0]
        # The same with reverted sorting
        # Use normal sorting.
        stream.sort(reverse=True)
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [100.0, 200.0, 400.0, 500.0, 300.0]
        # Sort after sampling_rate.
        stream.sort(keys=['sampling_rate'], reverse=True)
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [500.0, 400.0, 300.0, 200.0, 100.0]
        # Sort after channel and sampling rate.
        stream.sort(keys=['channel', 'sampling_rate'], reverse=True)
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [100.0, 400.0, 300.0, 500.0, 200.0]
        # Sort after npts and sampling_rate and endtime.
        stream.sort(keys=['npts', 'sampling_rate', 'endtime'], reverse=True)
        assert [i.stats.sampling_rate for i in stream.traces] == \
               [500.0, 400.0, 300.0, 200.0, 100.0]
        # Sorting without a list or a wrong item string should fail.
        with pytest.raises(TypeError):
            stream.sort(keys=1)
        with pytest.raises(TypeError):
            stream.sort(keys='sampling_rate')
        with pytest.raises(KeyError):
            stream.sort(keys=['npts', 'wrong_value'])

    def test_sorting_twice(self):
        """
        Sorting twice should not change order.
        """
        stream = Stream()
        headers = [
            {'starttime': UTCDateTime(1990, 1, 1),
             'endtime': UTCDateTime(1990, 1, 2), 'network': 'AAA',
             'station': 'ZZZ', 'channel': 'XXX', 'npts': 10000,
             'sampling_rate': 100.0},
            {'starttime': UTCDateTime(1990, 1, 1),
             'endtime': UTCDateTime(1990, 1, 3), 'network': 'AAA',
             'station': 'YYY', 'channel': 'CCC', 'npts': 10000,
             'sampling_rate': 200.0},
            {'starttime': UTCDateTime(2000, 1, 1),
             'endtime': UTCDateTime(2001, 1, 2), 'network': 'AAA',
             'station': 'EEE', 'channel': 'GGG', 'npts': 1000,
             'sampling_rate': 300.0},
            {'starttime': UTCDateTime(1989, 1, 1),
             'endtime': UTCDateTime(2010, 1, 2), 'network': 'AAA',
             'station': 'XXX', 'channel': 'GGG', 'npts': 10000,
             'sampling_rate': 400.0},
            {'starttime': UTCDateTime(2010, 1, 1),
             'endtime': UTCDateTime(2011, 1, 2), 'network': 'AAA',
             'station': 'XXX', 'channel': 'FFF', 'npts': 1000,
             'sampling_rate': 500.0}]
        # Create a Trace object of it and append it to the Stream object.
        for _i in headers:
            new_trace = Trace(header=_i)
            stream.append(new_trace)
        stream.sort()
        a = [i.stats.sampling_rate for i in stream.traces]
        stream.sort()
        b = [i.stats.sampling_rate for i in stream.traces]
        # should be equal
        assert a == b

    def test_merge_with_different_calibration_factors(self):
        """
        Test the merge method of the Stream object.
        """
        # 1 - different calibration factors for the same channel should fail
        tr1 = Trace(data=np.zeros(5))
        tr1.stats.calib = 1.0
        tr2 = Trace(data=np.zeros(5))
        tr2.stats.calib = 2.0
        st = Stream([tr1, tr2])
        # this also emits an UserWarning
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', UserWarning)
            with pytest.raises(Exception):
                st.merge()
        # 2 - different calibration factors for the different channels is ok
        tr1 = Trace(data=np.zeros(5))
        tr1.stats.calib = 2.00
        tr1.stats.channel = 'EHE'
        tr2 = Trace(data=np.zeros(5))
        tr2.stats.calib = 5.0
        tr2.stats.channel = 'EHZ'
        tr3 = Trace(data=np.zeros(5))
        tr3.stats.calib = 2.00
        tr3.stats.channel = 'EHE'
        tr4 = Trace(data=np.zeros(5))
        tr4.stats.calib = 5.0
        tr4.stats.channel = 'EHZ'
        st = Stream([tr1, tr2, tr3, tr4])
        st.merge()

    def test_merge_with_different_sampling_rates(self):
        """
        Test the merge method of the Stream object.
        """
        # 1 - different sampling rates for the same channel should fail
        tr1 = Trace(data=np.zeros(5))
        tr1.stats.sampling_rate = 200
        tr2 = Trace(data=np.zeros(5))
        tr2.stats.sampling_rate = 50
        st = Stream([tr1, tr2])
        # this also emits an UserWarning
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', UserWarning)
            with pytest.raises(Exception):
                st.merge()
        # 2 - different sampling rates for the different channels is ok
        tr1 = Trace(data=np.zeros(5))
        tr1.stats.sampling_rate = 200
        tr1.stats.channel = 'EHE'
        tr2 = Trace(data=np.zeros(5))
        tr2.stats.sampling_rate = 50
        tr2.stats.channel = 'EHZ'
        tr3 = Trace(data=np.zeros(5))
        tr3.stats.sampling_rate = 200
        tr3.stats.channel = 'EHE'
        tr4 = Trace(data=np.zeros(5))
        tr4.stats.sampling_rate = 50
        tr4.stats.channel = 'EHZ'
        st = Stream([tr1, tr2, tr3, tr4])
        st.merge()

    def test_merge_with_different_data_types(self):
        """
        Test the merge method of the Stream object.
        """
        # 1 - different dtype for the same channel should fail
        tr1 = Trace(data=np.zeros(5, dtype=np.int32))
        tr2 = Trace(data=np.zeros(5, dtype=np.float32))
        st = Stream([tr1, tr2])
        # this also emits an UserWarning
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', UserWarning)
            with pytest.raises(Exception):
                st.merge()
        # 2 - different sampling rates for the different channels is ok
        tr1 = Trace(data=np.zeros(5, dtype=np.int32))
        tr1.stats.channel = 'EHE'
        tr2 = Trace(data=np.zeros(5, dtype=np.float32))
        tr2.stats.channel = 'EHZ'
        tr3 = Trace(data=np.zeros(5, dtype=np.int32))
        tr3.stats.channel = 'EHE'
        tr4 = Trace(data=np.zeros(5, dtype=np.float32))
        tr4.stats.channel = 'EHZ'
        st = Stream([tr1, tr2, tr3, tr4])
        st.merge()

    def test_merge_gaps(self, mseed_stream):
        """
        Test the merge method of the Stream object.
        """
        stream = mseed_stream
        start = UTCDateTime("2007-12-31T23:59:59.915000")
        end = UTCDateTime("2008-01-01T00:04:31.790000")
        assert len(stream) == 4
        assert len(stream[0]) == 412
        assert len(stream[1]) == 824
        assert len(stream[2]) == 824
        assert len(stream[3]) == 50668
        assert stream[0].stats.starttime == start
        assert stream[3].stats.endtime == end
        for i in range(4):
            assert stream[i].stats.sampling_rate == 200
            assert stream[i].get_id() == 'BW.BGLD..EHE'
        stream.verify()
        # merge it
        stream.merge()
        stream.verify()
        assert len(stream) == 1
        assert len(stream[0]) == stream[0].data.size
        assert stream[0].stats.starttime == start
        assert stream[0].stats.endtime == end
        assert stream[0].stats.sampling_rate == 200
        assert stream[0].get_id() == 'BW.BGLD..EHE'

    def test_merge_gaps_2(self):
        """
        Test the merge method of the Stream object on two traces with a gap in
        between.
        """
        tr1 = Trace(data=np.ones(4, dtype=np.int32) * 1)
        tr2 = Trace(data=np.ones(3, dtype=np.int32) * 5)
        tr2.stats.starttime = tr1.stats.starttime + 9
        stream = Stream([tr1, tr2])
        # 1 - masked array
        # Trace 1: 1111
        # Trace 2:          555
        # 1 + 2  : 1111-----555
        st = stream.copy()
        st.merge()
        assert len(st) == 1
        assert isinstance(st[0].data, np.ma.masked_array)
        assert st[0].data.tolist() == \
               [1, 1, 1, 1, None, None, None, None, None, 5, 5, 5]
        # 2 - fill in zeros
        # Trace 1: 1111
        # Trace 2:          555
        # 1 + 2  : 111100000555
        st = stream.copy()
        st.merge(fill_value=0)
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        assert st[0].data.tolist() == [1, 1, 1, 1, 0, 0, 0, 0, 0, 5, 5, 5]
        # 2b - fill in some other user-defined value
        # Trace 1: 1111
        # Trace 2:          555
        # 1 + 2  : 111199999555
        st = stream.copy()
        st.merge(fill_value=9)
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        assert st[0].data.tolist() == [1, 1, 1, 1, 9, 9, 9, 9, 9, 5, 5, 5]
        # 3 - use last value of first trace
        # Trace 1: 1111
        # Trace 2:          555
        # 1 + 2  : 111111111555
        st = stream.copy()
        st.merge(fill_value='latest')
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        assert st[0].data.tolist() == [1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5]
        # 4 - interpolate
        # Trace 1: 1111
        # Trace 2:          555
        # 1 + 2  : 111112334555
        st = stream.copy()
        st.merge(fill_value='interpolate')
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        assert st[0].data.tolist() == [1, 1, 1, 1, 1, 2, 3, 3, 4, 5, 5, 5]

    def test_split(self, mseed_stream):
        """
        Testing splitting of streams containing masked arrays.
        """
        # 1 - create a Stream with gaps
        tr1 = Trace(data=np.ones(4, dtype=np.int32) * 1)
        tr2 = Trace(data=np.ones(3, dtype=np.int32) * 5)
        tr2.stats.starttime = tr1.stats.starttime + 9
        st = Stream([tr1, tr2])
        st.merge()
        assert isinstance(st[0].data, np.ma.masked_array)
        # now we split again
        st2 = st.split()
        assert len(st2) == 2
        assert isinstance(st2[0].data, np.ndarray)
        assert isinstance(st2[1].data, np.ndarray)
        assert st2[0].data.tolist() == [1, 1, 1, 1]
        assert st2[1].data.tolist() == [5, 5, 5]
        # 2 - use default example
        st = mseed_stream
        st.merge()
        assert isinstance(st[0].data, np.ma.masked_array)
        # now we split again
        st2 = st.split()
        assert len(st2) == 4
        assert len(st2[0]) == 412
        assert len(st2[1]) == 824
        assert len(st2[2]) == 824
        assert len(st2[3]) == 50668
        assert st2[0].stats.starttime == \
               UTCDateTime("2007-12-31T23:59:59.915000")
        assert st2[3].stats.endtime == \
               UTCDateTime("2008-01-01T00:04:31.790000")
        for i in range(4):
            assert st2[i].stats.sampling_rate == 200
            assert st2[i].get_id() == 'BW.BGLD..EHE'

    def test_merge_overlaps_default_method(self):
        """
        Test the merge method of the Stream object.
        """
        # 1 - overlapping trace with differing data
        # Trace 1: 0000000
        # Trace 2:      1111111
        # 1 + 2  : 00000--11111
        tr1 = Trace(data=np.zeros(7))
        tr2 = Trace(data=np.ones(7))
        tr2.stats.starttime = tr1.stats.starttime + 5
        st = Stream([tr1, tr2])
        st.merge()
        assert len(st) == 1
        assert isinstance(st[0].data, np.ma.masked_array)
        data_list = st[0].data.tolist()
        assert data_list == [0, 0, 0, 0, 0, None, None, 1, 1, 1, 1, 1]
        # 2 - overlapping trace with same data
        # Trace 1: 0123456
        # Trace 2:      56789
        # 1 + 2  : 0123456789
        tr1 = Trace(data=np.arange(7))
        tr2 = Trace(data=np.arange(5, 10))
        tr2.stats.starttime = tr1.stats.starttime + 5
        st = Stream([tr1, tr2])
        st.merge()
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        np.testing.assert_array_equal(st[0].data, np.arange(10))
        #
        # 3 - contained overlap with same data
        # Trace 1: 0123456789
        # Trace 2:      56
        # 1 + 2  : 0123456789
        tr1 = Trace(data=np.arange(10))
        tr2 = Trace(data=np.arange(5, 7))
        tr2.stats.starttime = tr1.stats.starttime + 5
        st = Stream([tr1, tr2])
        st.merge()
        assert len(st) == 1
        assert isinstance(st[0].data, np.ndarray)
        np.testing.assert_array_equal(st[0].data, np.arange(10))
        #
        # 4 - contained overlap with differing data
        # Trace 1: 0000000000
        # Trace 2:      11
        # 1 + 2  : 00000--000
        tr1 = Trace(data=np.zeros(10))
        tr2 = Trace(data=np.ones(2))
        tr2.stats.starttime = tr1.stats.starttime + 5
        st = Stream([tr1, tr2])
        st.merge()
        assert len(st) == 1
        assert isinstance(st[0].data, np.ma.masked_array)
        assert st[0].data.tolist() == [0, 0, 0, 0, 0, None, None, 0, 0, 0]

    def test_tab_completion_trace(self):
        """
        Test tab completion of Trace object.
        """
        tr = Trace()
        assert 'sampling_rate' in dir(tr.stats)
        assert 'npts' in dir(tr.stats)
        assert 'station' in dir(tr.stats)
        assert 'starttime' in dir(tr.stats)
        assert 'endtime' in dir(tr.stats)
        assert 'calib' in dir(tr.stats)
        assert 'delta' in dir(tr.stats)

    def test_bugfix_merge_drop_trace_if_already_contained(self):
        """
        Trace data already existing in another trace and ending on the same
        end time was not correctly merged until now.
        """
        trace1 = Trace(data=np.empty(10))
        trace2 = Trace(data=np.empty(2))
        trace2.stats.starttime = trace1.stats.endtime - trace1.stats.delta
        st = Stream([trace1, trace2])
        st.merge()

    def test_bugfix_merge_multiple_traces(self):
        """
        Bugfix for merging multiple traces in a row.
        """
        # create a stream with multiple traces overlapping
        trace1 = Trace(data=np.empty(10))
        traces = [trace1]
        for _ in range(10):
            trace = Trace(data=np.empty(10))
            trace.stats.starttime = \
                traces[-1].stats.endtime - trace1.stats.delta
            traces.append(trace)
        st = Stream(traces)
        st.merge()

    def test_bugfix_merge_multiple_traces_2(self):
        """
        Bugfix for merging multiple traces in a row.
        """
        trace1 = Trace(data=np.empty(4190864))
        trace1.stats.sampling_rate = 200
        trace1.stats.starttime = UTCDateTime("2010-01-21T00:00:00.015000Z")
        trace2 = Trace(data=np.empty(603992))
        trace2.stats.sampling_rate = 200
        trace2.stats.starttime = UTCDateTime("2010-01-21T05:49:14.330000Z")
        trace3 = Trace(data=np.empty(222892))
        trace3.stats.sampling_rate = 200
        trace3.stats.starttime = UTCDateTime("2010-01-21T06:39:33.280000Z")
        st = Stream([trace1, trace2, trace3])
        st.merge()

    def test_merge_with_small_sampling_rate(self):
        """
        Bugfix for merging multiple traces with very small sampling rate.
        """
        # create traces
        np.random.seed(815)
        trace1 = Trace(data=np.random.randn(1441))
        trace1.stats.delta = 60.0
        trace1.stats.starttime = UTCDateTime("2009-02-01T00:00:02.995000Z")
        trace2 = Trace(data=np.random.randn(1441))
        trace2.stats.delta = 60.0
        trace2.stats.starttime = UTCDateTime("2009-02-02T00:00:12.095000Z")
        trace3 = Trace(data=np.random.randn(1440))
        trace3.stats.delta = 60.0
        trace3.stats.starttime = UTCDateTime("2009-02-03T00:00:16.395000Z")
        trace4 = Trace(data=np.random.randn(1440))
        trace4.stats.delta = 60.0
        trace4.stats.starttime = UTCDateTime("2009-02-04T00:00:11.095000Z")
        # create stream
        st = Stream([trace1, trace2, trace3, trace4])
        # merge
        st.merge()
        # compare results
        assert len(st) == 1
        assert st[0].stats.delta == 60.0
        assert st[0].stats.starttime == trace1.stats.starttime
        # end time of last trace
        endtime = trace1.stats.starttime + \
            (4 * 1440 - 1) * trace1.stats.delta
        assert st[0].stats.endtime == endtime

    def test_merge_overlaps_method_1(self):
        """
        Test merging with method = 1.
        """
        # Test merging three traces.
        trace1 = Trace(data=np.ones(10))
        trace2 = Trace(data=10 * np.ones(11))
        trace3 = Trace(data=2 * np.ones(20))
        st = Stream([trace1, trace2, trace3])
        st.merge(method=1)
        np.testing.assert_array_equal(st[0].data, 2 * np.ones(20))
        # Any contained traces with different data will be discarded::
        #
        #    Trace 1: 111111111111 (contained trace)
        #    Trace 2:     55
        #    1 + 2  : 111111111111
        trace1 = Trace(data=np.ones(12))
        trace2 = Trace(data=5 * np.ones(2))
        trace2.stats.starttime += 4
        st = Stream([trace1, trace2])
        st.merge(method=1)
        np.testing.assert_array_equal(st[0].data, np.ones(12))
        # No interpolation (``interpolation_samples=0``)::
        #
        #    Trace 1: 11111111
        #    Trace 2:     55555555
        #    1 + 2  : 111155555555
        trace1 = Trace(data=np.ones(8))
        trace2 = Trace(data=5 * np.ones(8))
        trace2.stats.starttime += 4
        st = Stream([trace1, trace2])
        st.merge(method=1)
        np.testing.assert_array_equal(st[0].data, np.array([1] * 4 + [5] * 8))
        # Interpolate first two samples (``interpolation_samples=2``)::
        #
        #     Trace 1: 00000000
        #     Trace 2:     66666666
        #     1 + 2  : 000024666666 (interpolation_samples=2)
        trace1 = Trace(data=np.zeros(8, dtype=np.int32))
        trace2 = Trace(data=6 * np.ones(8, dtype=np.int32))
        trace2.stats.starttime += 4
        st = Stream([trace1, trace2])
        st.merge(method=1, interpolation_samples=2)
        np.testing.assert_array_equal(st[0].data,
                                      np.array([0] * 4 + [2] + [4] + [6] * 6))
        # Interpolate all samples (``interpolation_samples=-1``)::
        #
        #     Trace 1: 00000000
        #     Trace 2:     55555555
        #     1 + 2  : 000012345555
        trace1 = Trace(data=np.zeros(8, dtype=np.int32))
        trace2 = Trace(data=5 * np.ones(8, dtype=np.int32))
        trace2.stats.starttime += 4
        st = Stream([trace1, trace2])
        st.merge(method=1, interpolation_samples=(-1))
        np.testing.assert_array_equal(
            st[0].data, np.array([0] * 4 + [1] + [2] + [3] + [4] + [5] * 4))
        # Interpolate all samples (``interpolation_samples=5``)::
        # Given number of samples is bigger than the actual overlap - should
        # interpolate all samples
        #
        #     Trace 1: 00000000
        #     Trace 2:     55555555
        #     1 + 2  : 000012345555
        trace1 = Trace(data=np.zeros(8, dtype=np.int32))
        trace2 = Trace(data=5 * np.ones(8, dtype=np.int32))
        trace2.stats.starttime += 4
        st = Stream([trace1, trace2])
        st.merge(method=1, interpolation_samples=5)
        np.testing.assert_array_equal(
            st[0].data, np.array([0] * 4 + [1] + [2] + [3] + [4] + [5] * 4))

    def test_trim_removing_empty_traces(self):
        """
        A stream containing several empty traces after trimming should throw
        away the empty traces.
        """
        # create Stream.
        trace1 = Trace(data=np.zeros(10))
        trace1.stats.delta = 1.0
        trace2 = Trace(data=np.ones(10))
        trace2.stats.delta = 1.0
        trace2.stats.starttime = UTCDateTime(1000)
        trace3 = Trace(data=np.arange(10))
        trace3.stats.delta = 1.0
        trace3.stats.starttime = UTCDateTime(2000)
        stream = Stream([trace1, trace2, trace3])
        stream.trim(UTCDateTime(900), UTCDateTime(1100))
        # Check if only trace2 is still in the Stream object.
        assert len(stream) == 1
        np.testing.assert_array_equal(np.ones(10), stream[0].data)
        assert stream[0].stats.starttime == UTCDateTime(1000)
        assert stream[0].stats.npts == 10

    def test_trim_with_small_sampling_rate(self):
        """
        Bugfix for cutting multiple traces with very small sampling rate.
        """
        # create traces
        trace1 = Trace(data=np.empty(1441))
        trace1.stats.delta = 60.0
        trace1.stats.starttime = UTCDateTime("2009-02-01T00:00:02.995000Z")
        trace2 = Trace(data=np.empty(1441))
        trace2.stats.delta = 60.0
        trace2.stats.starttime = UTCDateTime("2009-02-02T00:00:12.095000Z")
        trace3 = Trace(data=np.empty(1440))
        trace3.stats.delta = 60.0
        trace3.stats.starttime = UTCDateTime("2009-02-03T00:00:16.395000Z")
        trace4 = Trace(data=np.empty(1440))
        trace4.stats.delta = 60.0
        trace4.stats.starttime = UTCDateTime("2009-02-04T00:00:11.095000Z")
        # create stream
        st = Stream([trace1, trace2, trace3, trace4])
        # trim
        st.trim(trace1.stats.starttime, trace4.stats.endtime)
        # compare results
        assert len(st) == 4
        assert st[0].stats.delta == 60.0
        assert st[0].stats.starttime == trace1.stats.starttime
        assert st[3].stats.endtime == trace4.stats.endtime

    def test_writing_masked_array(self):
        """
        Writing a masked array should raise an exception.
        """
        # np.ma.masked_array with masked values
        tr = Trace(data=np.ma.masked_all(10))
        st = Stream([tr])
        with pytest.raises(NotImplementedError):
            st.write('filename', 'MSEED')
        # np.ma.masked_array without masked values
        tr = Trace(data=np.ma.ones(10))
        st = Stream([tr])
        with pytest.raises(NotImplementedError):
            st.write('filename', 'MSEED')

    def test_pickle(self):
        """
        Testing pickling of Stream objects.
        """
        tr = Trace(data=np.random.randn(1441))
        st = Stream([tr])
        st.verify()
        # protocol 0 (ASCII)
        temp = pickle.dumps(st, protocol=0)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats
        # protocol 1 (old binary)
        temp = pickle.dumps(st, protocol=1)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats
        # protocol 2 (new binary)
        temp = pickle.dumps(st, protocol=2)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats

    def test_cpickle(self):
        """
        Testing pickling of Stream objects.
        """
        tr = Trace(data=np.random.randn(1441))
        st = Stream([tr])
        st.verify()
        # protocol 0 (ASCII)
        temp = pickle.dumps(st, protocol=0)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats
        # protocol 1 (old binary)
        temp = pickle.dumps(st, protocol=1)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats
        # protocol 2 (new binary)
        temp = pickle.dumps(st, protocol=2)
        st2 = pickle.loads(temp)
        np.testing.assert_array_equal(st[0].data, st2[0].data)
        assert st[0].stats == st2[0].stats

    def test_is_pickle(self):
        """
        Testing _is_pickle function.
        """
        # existing file
        st = read()
        with NamedTemporaryFile() as tf:
            st.write(tf.name, format='PICKLE')
            # check using file name
            assert _is_pickle(tf.name)
            # check using file handler
            assert _is_pickle(tf)
        # not existing files
        assert not _is_pickle('/path/to/pickle.file')
        assert not _is_pickle(12345)

    def test_read_write_pickle(self):
        """
        Testing _read_pickle and _write_pickle functions.
        """
        st = read()
        # write
        with NamedTemporaryFile() as tf:
            # write using file name
            _write_pickle(st, tf.name)
            assert _is_pickle(tf.name)
            # write using file handler
            _write_pickle(st, tf)
            tf.seek(0)
            assert _is_pickle(tf)
            # write using stream write method
            st.write(tf.name, format='PICKLE')
            # check and read directly
            st2 = _read_pickle(tf.name)
            assert len(st2) == 3
            np.testing.assert_array_equal(st2[0].data, st[0].data)
            # use read() with given format
            st2 = read(tf.name, format='PICKLE')
            assert len(st2) == 3
            np.testing.assert_array_equal(st2[0].data, st[0].data)
            # use read() and automatically detect format
            st2 = read(tf.name)
            assert len(st2) == 3
            np.testing.assert_array_equal(st2[0].data, st[0].data)

    def test_read_pickle(self):
        """
        Testing _read_pickle function.

        Pickle support is only provided for pickles created with the same
        versions of ObsPy/python.
        """
        def _remove_format(st):
            """Remove '_format' from stats so streams """
            for tr in st:
                tr.stats.pop("_format", None)
            return st

        # Save stream to bio.
        bio = io.BytesIO()
        st = read()
        st.write(bio, "pickle")
        # Read pickle without specifying format.
        bio.seek(0)
        st_no_format = read(bio)
        # Read pickle with specifying format.
        bio.seek(0)
        st_with_format = read(bio, format="pickle")
        # Test equalities.
        assert st == _remove_format(st_no_format)
        assert st_no_format == _remove_format(st_with_format)

    def test_get_gaps_2(self):
        """
        Test case for issue #73.
        """
        tr1 = Trace(data=np.empty(720000))
        tr1.stats.starttime = UTCDateTime("2010-02-09T00:19:19.850000Z")
        tr1.stats.sampling_rate = 200.0
        tr1.verify()
        tr2 = Trace(data=np.empty(720000))
        tr2.stats.starttime = UTCDateTime("2010-02-09T01:19:19.850000Z")
        tr2.stats.sampling_rate = 200.0
        tr2.verify()
        tr3 = Trace(data=np.empty(720000))
        tr3.stats.starttime = UTCDateTime("2010-02-09T02:19:19.850000Z")
        tr3.stats.sampling_rate = 200.0
        tr3.verify()
        st = Stream([tr1, tr2, tr3])
        st.verify()
        # same sampling rate should have no gaps
        gaps = st.get_gaps()
        assert len(gaps) == 0
        # different sampling rate should result in a gap
        tr3.stats.sampling_rate = 50.0
        gaps = st.get_gaps()
        assert len(gaps) == 1
        # but different ids will be skipped (if only one trace)
        tr3.stats.station = 'MANZ'
        gaps = st.get_gaps()
        assert len(gaps) == 0
        # multiple traces with same id will be handled again
        tr2.stats.station = 'MANZ'
        gaps = st.get_gaps()
        assert len(gaps) == 1

    def test_get_gaps_whole_overlap(self):
        """
        Test get_gaps method with a trace completely overlapping another trace.
        """
        tr1 = Trace(data=np.empty(3600))
        tr1.stats.starttime = UTCDateTime("2018-09-25T00:00:00.000000Z")
        tr1.stats.sampling_rate = 1.
        tr2 = Trace(data=np.empty(60))
        tr2.stats.starttime = UTCDateTime("2018-09-25T00:01:00.000000Z")
        tr2.stats.sampling_rate = 1.
        st = Stream([tr1, tr2])
        gaps = st.get_gaps()
        assert len(gaps) == 1
        gap = gaps[0]
        starttime = gap[4]
        assert starttime == UTCDateTime("2018-09-25T00:01:59.000000Z")
        endtime = gap[5]
        assert endtime == tr2.stats.starttime

    def test_get_gaps_overlap(self):
        """
         Tests the get_gaps method of the Stream objects.

         Test for Issue #1403. Tests if wrong overlaps are returned.
        """
        data = [
            ("2016-01-07T00:00:50.388393Z", 6158),
            ("2016-01-07T00:00:57.248393Z", 1370),
            ("2016-01-07T00:01:31.458393Z", 4107)]

        x = np.arange(20000)
        tr = Trace(x)
        tr.stats.starttime = UTCDateTime("2016-01-07T00:00:50.388393Z")
        tr.stats.sampling_rate = 100

        st = Stream()
        for i, (start, numsamp) in enumerate(data):
            tr_ = tr.slice(starttime=UTCDateTime(start))
            tr_.data = tr_.data[:numsamp]
            st.append(tr_)

        # min_gap=1 is used to only show the gaps
        assert len(st.get_gaps(min_gap=1)) == 0

    def test_comparisons(self):
        """
        Tests all rich comparison operators (==, !=, <, <=, >, >=)
        The latter four are not implemented due to ambiguous meaning and bounce
        an error.
        """
        # create test streams
        tr0 = Trace(np.arange(3))
        tr1 = Trace(np.arange(3))
        tr2 = Trace(np.arange(3), {'station': 'X'})
        tr3 = Trace(np.arange(3),
                    {'processing': ["filter:lowpass:{'freq': 10}"]})
        tr4 = Trace(np.arange(5))
        tr5 = Trace(np.arange(5), {'station': 'X'})
        tr6 = Trace(np.arange(5),
                    {'processing': ["filter:lowpass:{'freq': 10}"]})
        tr7 = Trace(np.arange(5),
                    {'processing': ["filter:lowpass:{'freq': 10}"]})
        st0 = Stream([tr0])
        st1 = Stream([tr1])
        st2 = Stream([tr0, tr1])
        st3 = Stream([tr2, tr3])
        st4 = Stream([tr1, tr2, tr3])
        st5 = Stream([tr4, tr5, tr6])
        st6 = Stream([tr0, tr6])
        st7 = Stream([tr1, tr7])
        st8 = Stream([tr7, tr1])
        st9 = Stream()
        st_a = Stream()
        # tests that should raise a NotImplementedError (i.e. <=, <, >=, >)
        with pytest.raises(NotImplementedError):
            st1.__lt__(st1)
        with pytest.raises(NotImplementedError):
            st1.__le__(st1)
        with pytest.raises(NotImplementedError):
            st1.__gt__(st1)
        with pytest.raises(NotImplementedError):
            st1.__ge__(st1)
        with pytest.raises(NotImplementedError):
            st1.__lt__(st2)
        with pytest.raises(NotImplementedError):
            st1.__le__(st2)
        with pytest.raises(NotImplementedError):
            st1.__gt__(st2)
        with pytest.raises(NotImplementedError):
            st1.__ge__(st2)
        # normal tests
        for st in [st1]:
            assert st0 == st
        for st in [st2, st3, st4, st5, st6, st7, st8, st9, st_a]:
            assert st0 != st
        for st in [st0]:
            assert st1 == st
        for st in [st2, st3, st4, st5, st6, st7, st8, st9, st_a]:
            assert st1 != st
        for st in [st0, st1, st3, st4, st5, st6, st7, st8, st9, st_a]:
            assert st2 != st
        for st in [st0, st1, st2, st4, st5, st6, st7, st8, st9, st_a]:
            assert st3 != st
        for st in [st0, st1, st2, st3, st5, st6, st7, st8, st9, st_a]:
            assert st4 != st
        for st in [st0, st1, st2, st3, st4, st6, st7, st8, st9, st_a]:
            assert st5 != st
        for st in [st7, st8]:
            assert st6 == st
        for st in [st0, st1, st2, st3, st4, st5, st9, st_a]:
            assert st6 != st
        for st in [st6, st8]:
            assert st7 == st
        for st in [st0, st1, st2, st3, st4, st5, st9, st_a]:
            assert st7 != st
        for st in [st6, st7]:
            assert st8 == st
        for st in [st0, st1, st2, st3, st4, st5, st9, st_a]:
            assert st8 != st
        for st in [st_a]:
            assert st9 == st
        for st in [st0, st1, st2, st3, st4, st5, st6, st7, st8]:
            assert st9 != st
        for st in [st9]:
            assert st_a == st
        for st in [st0, st1, st2, st3, st4, st5, st6, st7, st8]:
            assert st_a != st
        # some weird tests against non-Stream objects
        for object in [0, 1, 0.0, 1.0, "", "test", True, False, [], [tr0],
                       set(), set(tr0), {}, {"test": "test"}, Trace(), None]:
            assert st0 != object

    def test_trim_nearest_sample(self):
        """
        Tests to trim at nearest sample
        """
        head = {'sampling_rate': 1.0, 'starttime': UTCDateTime(0.0)}
        tr1 = Trace(data=np.random.randint(0, 1000, 120), header=head)
        tr2 = Trace(data=np.random.randint(0, 1000, 120), header=head)
        tr2.stats.starttime += 0.4
        st = Stream(traces=[tr1, tr2])
        # STARTTIME
        # check that trimming first selects the next best sample, and only
        # then selects the following ones
        #    |  S |    |    |
        #      |    |    |    |
        st.trim(UTCDateTime(0.6), endtime=None)
        assert st[0].stats.starttime.timestamp == 1.0
        assert st[1].stats.starttime.timestamp == 1.4
        # ENDTIME
        # check that trimming first selects the next best sample, and only
        # then selects the following ones
        #    |    |    |  E |
        #      |    |    |    |
        st.trim(starttime=None, endtime=UTCDateTime(2.6))
        assert st[0].stats.endtime.timestamp == 3.0
        assert st[1].stats.endtime.timestamp == 3.4

    def test_trim_consistent_start_end_time_nearest_sample(self):
        """
        Test case for #127. It ensures that the sample sizes stay
        consistent after trimming. That is that _ltrim and _rtrim
        round in the same direction.
        """
        data = np.zeros(10)
        t = UTCDateTime(0)
        traces = []
        for delta in (0, 0.25, 0.5, 0.75, 1):
            traces.append(Trace(data.copy()))
            traces[-1].stats.starttime = t + delta
        st = Stream(traces)
        st.trim(t + 3.5, t + 6.5)
        start = [4.0, 4.25, 4.5, 3.75, 4.0]
        end = [6.0, 6.25, 6.50, 5.75, 6.0]
        for i in range(len(st)):
            assert 3 == st[i].stats.npts
            assert st[i].stats.starttime.timestamp == start[i]
            assert st[i].stats.endtime.timestamp == end[i]

    def test_trim_consistent_start_end_time_nearest_sample_padded(self):
        """
        Test case for #127. It ensures that the sample sizes stay
        consistent after trimming. That is that _ltrim and _rtrim
        round in the same direction. Padded version.
        """
        data = np.zeros(10)
        t = UTCDateTime(0)
        traces = []
        for delta in (0, 0.25, 0.5, 0.75, 1):
            traces.append(Trace(data.copy()))
            traces[-1].stats.starttime = t + delta
        st = Stream(traces)
        st.trim(t - 3.5, t + 16.5, pad=True)
        start = [-4.0, -3.75, -3.5, -4.25, -4.0]
        end = [17.0, 17.25, 17.50, 16.75, 17.0]
        for i in range(len(st)):
            assert 22 == st[i].stats.npts
            assert st[i].stats.starttime.timestamp == start[i]
            assert st[i].stats.endtime.timestamp == end[i]

    def test_trim_consistent_start_end_time(self):
        """
        Test case for #127. It ensures that the sample start and end times
        stay consistent after trimming.
        """
        data = np.zeros(10)
        t = UTCDateTime(0)
        traces = []
        for delta in (0, 0.25, 0.5, 0.75, 1):
            traces.append(Trace(data.copy()))
            traces[-1].stats.starttime = t + delta
        st = Stream(traces)
        st.trim(t + 3.5, t + 6.5, nearest_sample=False)
        start = [4.00, 4.25, 3.50, 3.75, 4.00]
        end = [6.00, 6.25, 6.50, 5.75, 6.00]
        npts = [3, 3, 4, 3, 3]
        for i in range(len(st)):
            assert st[i].stats.npts == npts[i]
            assert st[i].stats.starttime.timestamp == start[i]
            assert st[i].stats.endtime.timestamp == end[i]

    def test_trim_consistent_start_and_time_pad(self):
        """
        Test case for #127. It ensures that the sample start and end times
        stay consistent after trimming. Padded version.
        """
        data = np.zeros(10)
        t = UTCDateTime(0)
        traces = []
        for delta in (0, 0.25, 0.5, 0.75, 1):
            traces.append(Trace(data.copy()))
            traces[-1].stats.starttime = t + delta
        st = Stream(traces)
        st.trim(t - 3.5, t + 16.5, nearest_sample=False, pad=True)
        start = [-3.00, -2.75, -3.50, -3.25, -3.00]
        end = [16.00, 16.25, 16.50, 15.75, 16.00]
        npts = [20, 20, 21, 20, 20]
        for i in range(len(st)):
            assert st[i].stats.npts == npts[i]
            assert st[i].stats.starttime.timestamp == start[i]
            assert st[i].stats.endtime.timestamp == end[i]

    def test_str(self):
        """
        Test case for issue #162 - print streams in a more consistent way.
        """
        tr1 = Trace()
        tr1.stats.station = "1"
        tr2 = Trace()
        tr2.stats.station = "12345"
        st = Stream([tr1, tr2])
        result = st.__str__()
        expected = "2 Trace(s) in Stream:\n" + \
                   ".1..     | 1970-01-01T00:00:00.000000Z - 1970-01-01" + \
                   "T00:00:00.000000Z | 1.0 Hz, 0 samples\n" + \
                   ".12345.. | 1970-01-01T00:00:00.000000Z - 1970-01-01" + \
                   "T00:00:00.000000Z | 1.0 Hz, 0 samples"
        assert result == expected
        # streams containing more than 20 lines will be compressed
        st2 = Stream([tr1]) * 40
        result = st2.__str__()
        assert '40 Trace(s) in Stream:' in result
        assert 'other traces' in result

    def test_cleanup(self, mseed_stream):
        """
        Test case for merging traces in the stream with method=-1. This only
        should merge traces that are exactly the same or contained and exactly
        the same or directly adjacent.
        """
        tr1 = mseed_stream[0]
        start = tr1.stats.starttime
        end = tr1.stats.endtime
        dt = end - start
        delta = tr1.stats.delta
        # test traces that should be merged:
        # contained traces with compatible data
        tr2 = tr1.slice(start, start + dt / 3)
        tr3 = tr1.copy()
        tr4 = tr1.slice(start + dt / 4, end - dt / 4)
        # adjacent traces
        tr5 = tr1.copy()
        tr5.stats.starttime = end + delta
        tr6 = tr1.copy()
        tr6.stats.starttime = start - dt - delta
        # create overlapping traces with compatible data
        tr_01 = tr1.copy()
        tr_01.trim(starttime=start + 2 * delta)
        tr_01.data = np.concatenate([tr_01.data, np.arange(5)])
        tr_02 = tr1.copy()
        tr_02.trim(endtime=end - 2 * delta)
        tr_02.data = np.concatenate([np.arange(5), tr_02.data])
        tr_02.stats.starttime -= 5 * delta

        for _i in [tr1, tr2, tr3, tr4, tr5, tr6, tr_01, tr_02]:
            if "processing" in _i.stats:
                del _i.stats.processing
        # test mergeable traces (contained ones)
        for tr_b in [tr2, tr3, tr4]:
            tr_a = tr1.copy()
            st = Stream([tr_a, tr_b])
            st._cleanup()
            assert st == Stream([tr1])
            assert isinstance(st[0].data, np.ndarray)
        # test mergeable traces (adjacent ones)
        for tr_b in [tr5, tr6]:
            tr_a = tr1.copy()
            st = Stream([tr_a, tr_b])
            st._cleanup()
            assert len(st) == 1
            assert isinstance(st[0].data, np.ndarray)
            st_result = Stream([tr1, tr_b])
            st_result.merge()
            assert st == st_result
        # test mergeable traces (overlapping ones)
        for tr_b in [tr_01, tr_02]:
            tr_a = tr1.copy()
            st = Stream([tr_a, tr_b])
            st._cleanup()
            assert len(st) == 1
            assert isinstance(st[0].data, np.ndarray)
            st_result = Stream([tr1, tr_b])
            st_result.merge()
            assert st == st_result

        # test traces that should not be merged
        tr7 = tr1.copy()
        tr7.stats.sampling_rate *= 2
        tr8 = tr1.copy()
        tr8.stats.station = "AA"
        tr9 = tr1.copy()
        tr9.stats.starttime = end + 10 * delta
        # test some weird gaps near to one sample:
        tr10 = tr1.copy()
        tr10.stats.starttime = end + 0.5 * delta
        tr11 = tr1.copy()
        tr11.stats.starttime = end + 0.1 * delta
        tr12 = tr1.copy()
        tr12.stats.starttime = end + 0.8 * delta
        tr13 = tr1.copy()
        tr13.stats.starttime = end + 1.2 * delta
        # test non-mergeable traces
        for tr_b in [tr7, tr8, tr9, tr10, tr11, tr12, tr13]:
            tr_a = tr1.copy()
            st = Stream([tr_a, tr_b])
            # ignore UserWarnings
            with warnings.catch_warnings(record=True):
                warnings.simplefilter('ignore', UserWarning)
                st._cleanup()
            assert st == Stream([tr_a, tr_b])

    def test_integrate_and_differentiate(self):
        """
        Test integration and differentiation methods of stream
        """
        st1 = read()
        st2 = read()

        st1.filter('lowpass', freq=1.0)
        st2.filter('lowpass', freq=1.0)

        st1.differentiate()
        st1.integrate()
        st2.integrate()
        st2.differentiate()

        np.testing.assert_array_almost_equal(
            st1[0].data[:-1], st2[0].data[:-1], decimal=5)

    def test_read(self, ascii_path):
        """
        Testing read function.
        """
        # 1 - default example
        # dtype
        tr = read(dtype=np.int64)[0]
        assert tr.data.dtype == np.int64
        # dtype is string
        tr2 = read(dtype='i8')[0]
        assert tr2.data.dtype == np.int64
        assert tr == tr2
        # start/end time
        tr2 = read(starttime=tr.stats.starttime + 1,
                   endtime=tr.stats.endtime - 2)[0]
        assert tr2.stats.starttime == tr.stats.starttime + 1
        assert tr2.stats.endtime == tr.stats.endtime - 2
        # headonly
        tr = read(headonly=True)[0]
        assert len(tr.data) == 0

        # 2 - via http
        # now in separate test case "test_read_url_via_network"

        # 3 - some example within obspy
        # dtype
        tr = read('/path/to/slist_float.ascii', dtype=np.int32)[0]
        assert tr.data.dtype == np.int32
        # start/end time
        tr2 = read('/path/to/slist_float.ascii',
                   starttime=tr.stats.starttime + 0.025,
                   endtime=tr.stats.endtime - 0.05)[0]
        assert tr2.stats.starttime == tr.stats.starttime + 0.025
        assert tr2.stats.endtime == tr.stats.endtime - 0.05
        # headonly
        tr = read('/path/to/slist_float.ascii', headonly=True)[0]
        assert len(tr.data) == 0
        # not existing
        with pytest.raises(OSError):
            read('/path/to/UNKNOWN')

        # 4 - file patterns
        filename = ascii_path / 'slist.*'
        st = read(filename)
        assert len(st) == 2
        # exception if no file matches file pattern
        filename = ascii_path / 'NOTEXISTING.*'
        with pytest.raises(Exception):
            read(filename)

        # argument headonly should not be used with start or end time or dtype
        with warnings.catch_warnings(record=True):
            # will usually warn only but here we force to raise an exception
            warnings.simplefilter('error', UserWarning)
            with pytest.raises(UserWarning):
                read('/path/to/slist_float.ascii',
                     headonly=True, starttime=0, endtime=1)

    @pytest.mark.network
    def test_read_url_via_network(self):
        """
        Testing read function with an URL fetching data via network connection
        """
        # 2 - via http
        # dtype
        tr = read('https://examples.obspy.org/test.sac', dtype=np.int32)[0]
        assert tr.data.dtype == np.int32
        # start/end time
        tr2 = read('https://examples.obspy.org/test.sac',
                   starttime=tr.stats.starttime + 1,
                   endtime=tr.stats.endtime - 2)[0]
        assert tr2.stats.starttime == tr.stats.starttime + 1
        assert tr2.stats.endtime == tr.stats.endtime - 2
        # headonly
        tr = read('https://examples.obspy.org/test.sac', headonly=True)[0]
        assert tr.data.size == 0

    def test_read_path(self, datapath):
        """
        Test for reading a pathlib object.
        """
        data_path = datapath / 'IU_ULN_00_LH1_2015-07-18T02.mseed'
        assert data_path.exists()
        st = read(data_path)
        assert isinstance(st, Stream)

    def test_copy(self):
        """
        Testing the copy method of the Stream object.
        """
        st = read()
        st2 = st.copy()
        assert st == st2
        assert st2 == st
        assert not (st is st2)
        assert not (st2 is st)
        assert st.traces[0] == st2.traces[0]
        assert not (st.traces[0] is st2.traces[0])

    def test_merge_with_empty_trace(self):
        """
        Merging a stream containing a empty trace with a differing sampling
        rate should not fail.
        """
        # preparing a dataset
        tr = read()[0]
        st = tr / 3
        # empty and change sampling rate of second trace
        st[1].stats.sampling_rate = 0
        st[1].data = np.array([])
        # merge
        st.merge(fill_value='interpolate')
        assert len(st) == 1

    def test_rotate(self):
        """
        Testing the rotate method.
        """
        st = read()
        # test for #3373
        # add some weird dots in station/location code
        for tr in st:
            tr.stats.station = 'RJ.OB'
            tr.stats.location = '0.'
        st += st.copy()
        st[3:].normalize()
        st2 = st.copy()
        # rotate to RT and back with 6 traces
        st.rotate(method='NE->RT', back_azimuth=30)
        assert (st[0].stats.channel[-1] + st[1].stats.channel[-1] +
                st[2].stats.channel[-1]) == 'ZRT'
        assert (st[3].stats.channel[-1] + st[4].stats.channel[-1] +
                st[5].stats.channel[-1]) == 'ZRT'
        st.rotate(method='RT->NE', back_azimuth=30)
        assert (st[0].stats.channel[-1] + st[1].stats.channel[-1] +
                st[2].stats.channel[-1]) == 'ZNE'
        assert (st[3].stats.channel[-1] + st[4].stats.channel[-1] +
                st[5].stats.channel[-1]) == 'ZNE'
        assert np.allclose(st[0].data, st2[0].data)
        assert np.allclose(st[1].data, st2[1].data)
        assert np.allclose(st[2].data, st2[2].data)
        assert np.allclose(st[3].data, st2[3].data)
        assert np.allclose(st[4].data, st2[4].data)
        assert np.allclose(st[5].data, st2[5].data)
        # again, with angles given in stats and just 2 components
        st = st2.copy()
        st = st[1:3] + st[4:]
        for tr in st[:2]:
            tr.stats.back_azimuth = 190
        for tr in st[2:]:
            tr.stats.back_azimuth = 200
            tr.stats.starttime = tr.stats.starttime + 1000
        st.rotate(method='NE->RT')
        st.rotate(method='RT->NE')
        assert np.allclose(st[0].data, st2[1].data)
        assert np.allclose(st[1].data, st2[2].data)
        assert st[2].stats.back_azimuth == 200
        # rotate to LQT and back with 6 traces
        st = st2.copy()
        st.rotate(method='ZNE->LQT', back_azimuth=100, inclination=30)
        assert (st[0].stats.channel[-1] + st[1].stats.channel[-1] +
                st[2].stats.channel[-1]) == 'LQT'
        st.rotate(method='LQT->ZNE', back_azimuth=100, inclination=30)
        assert st[0].stats.channel[-1] + st[1].stats.channel[-1] + \
               st[2].stats.channel[-1] == 'ZNE'
        assert np.allclose(st[0].data, st2[0].data)
        assert np.allclose(st[1].data, st2[1].data)
        assert np.allclose(st[2].data, st2[2].data)
        assert np.allclose(st[3].data, st2[3].data)
        assert np.allclose(st[4].data, st2[4].data)
        assert np.allclose(st[5].data, st2[5].data)

        # unknown rotate method will raise ValueError
        with pytest.raises(ValueError):
            st.rotate(method='UNKNOWN')
        # rotating without back_azimuth raises TypeError
        st = read()
        for tr in st:
            del tr.stats.back_azimuth
            del tr.stats.inclination
        with pytest.raises(TypeError):
            st.rotate(method='NE->RT')
        # rotating without inclination raises TypeError for LQT-> or ZNE->
        with pytest.raises(TypeError):
            st.rotate(method='ZNE->LQT', back_azimuth=30)
        # having traces with different timespans or sampling rates will fail
        st = read()
        st[1].stats.sampling_rate = 2.0
        with pytest.raises(ValueError):
            st.rotate(method='NE->RT')
        st = read()
        st[1].stats.starttime += 1
        with pytest.raises(ValueError):
            st.rotate(method='NE->RT')
        st = read()
        st[1].stats.sampling_rate = 2.0
        with pytest.raises(ValueError):
            st.rotate(method='ZNE->LQT')
        st = read()
        st[1].stats.starttime += 1
        with pytest.raises(ValueError):
            st.rotate(method='ZNE->LQT')

    def test_plot(self, mseed_stream):
        """
        Tests plot method if matplotlib is installed
        """
        mseed_stream.plot(show=False)

    def test_spectrogram(self, mseed_stream):
        """
        Tests spectrogram method if matplotlib is installed
        """
        mseed_stream.spectrogram(show=False)

    def test_deepcopy(self):
        """
        Tests __deepcopy__ method.

        http://lists.obspy.org/pipermail/obspy-users/2013-April/000451.html
        """
        # example stream
        st = read()
        # set a common header
        st[0].stats.network = 'AA'
        # set format specific header
        st[0].stats.mseed = AttribDict(dataquality='A')
        ct = deepcopy(st)
        # common header
        st[0].stats.network = 'XX'
        assert st[0].stats.network == 'XX'
        assert ct[0].stats.network == 'AA'
        # format specific headers
        st[0].stats.mseed.dataquality = 'X'
        assert st[0].stats.mseed.dataquality == 'X'
        assert ct[0].stats.mseed.dataquality == 'A'

    def test_write(self):
        # writing in unknown format raises ValueError
        st = read()
        with pytest.raises(ValueError):
            st.write('file.ext', format="UNKNOWN")

    def test_detrend(self):
        """
        Test detrend method of stream
        """
        t = np.arange(10)
        data = 0.1 * t + 1.

        tr = Trace(data=data.copy())
        st = Stream([tr, tr])
        st.detrend(type='simple')
        np.testing.assert_array_almost_equal(st[0].data, np.zeros(10))
        np.testing.assert_array_almost_equal(st[1].data, np.zeros(10))

        tr = Trace(data=data.copy())
        st = Stream([tr, tr])
        st.detrend(type='linear')
        np.testing.assert_array_almost_equal(st[0].data, np.zeros(10))
        np.testing.assert_array_almost_equal(st[1].data, np.zeros(10))

        data = np.zeros(10)
        data[3:7] = 1.

        tr = Trace(data=data.copy())
        st = Stream([tr, tr])
        st.detrend(type='simple')
        np.testing.assert_almost_equal(st[0].data[0], 0.)
        np.testing.assert_almost_equal(st[0].data[-1], 0.)
        np.testing.assert_almost_equal(st[1].data[0], 0.)
        np.testing.assert_almost_equal(st[1].data[-1], 0.)

        tr = Trace(data=data.copy())
        st = Stream([tr, tr])
        st.detrend(type='linear')
        np.testing.assert_almost_equal(st[0].data[0], -0.4)
        np.testing.assert_almost_equal(st[0].data[-1], -0.4)
        np.testing.assert_almost_equal(st[1].data[0], -0.4)
        np.testing.assert_almost_equal(st[1].data[-1], -0.4)

    def test_taper(self):
        """
        Test taper method of stream
        """
        data = np.ones(10)
        tr = Trace(data=data.copy())
        st = Stream([tr, tr])
        st.taper(max_percentage=0.05, type='cosine')
        for i in range(len(data)):
            assert st[0].data[i] <= 1.
            assert st[0].data[i] >= 0.
            assert st[1].data[i] <= 1.
            assert st[1].data[i] >= 0.

    def test_issue_540(self):
        """
        Trim with pad=True and given fill value should not return a masked
        NumPy array.
        """
        # fill_value = None
        st = read()
        assert len(st[0]) == 3000
        st.trim(starttime=st[0].stats.starttime - 0.01,
                endtime=st[0].stats.endtime + 0.01, pad=True, fill_value=None)
        assert len(st[0]) == 3002
        assert isinstance(st[0].data, np.ma.masked_array)
        assert st[0].data[0] is np.ma.masked
        assert st[0].data[1] is not np.ma.masked
        assert st[0].data[-2] is not np.ma.masked
        assert st[0].data[-1] is np.ma.masked
        # fill_value = 999
        st = read()
        assert len(st[1]) == 3000
        st.trim(starttime=st[1].stats.starttime - 0.01,
                endtime=st[1].stats.endtime + 0.01, pad=True, fill_value=999)
        assert len(st[1]) == 3002
        assert not isinstance(st[1].data, np.ma.masked_array)
        assert st[1].data[0] == 999
        assert st[1].data[-1] == 999
        # given fill_value but actually no padding at all
        st = read()
        assert len(st[2]) == 3000
        st.trim(starttime=st[2].stats.starttime, endtime=st[2].stats.endtime,
                pad=True, fill_value=-999)
        assert len(st[2]) == 3000
        assert not isinstance(st[2].data, np.ma.masked_array)

    def test_method_chaining(self):
        """
        Tests that method chaining works for all methods on the Stream object
        where it is sensible.
        """
        st1 = read()[0:1]
        st2 = read()

        assert len(st1) == 1
        assert len(st2) == 3

        # Test some list like methods.
        temp_st = st1.append(st1[0].copy())\
            .extend(st2)\
            .insert(0, st1[0].copy())\
            .remove(st1[0])
        assert temp_st is st1
        assert len(st1) == 5
        assert st1[0] == st1[1]
        assert st1[2] == st2[0]
        assert st1[3] == st2[1]
        assert st1[4] == st2[2]

        # Sort and reverse methods.
        st = st2.copy()
        st[0].stats.channel = "B"
        st[1].stats.channel = "C"
        st[2].stats.channel = "A"
        temp_st = st.sort(keys=["channel"]).reverse()
        assert temp_st is st
        assert [tr.stats.channel for tr in st] == ["C", "B", "A"]

        # The others are pretty hard to properly test and probably not worth
        # the effort. A simple demonstrating that they can be chained should be
        # enough.
        temp = st.trim(st[0].stats.starttime + 1, st[0].stats.starttime + 10)\
            .decimate(factor=2, no_filter=True)\
            .resample(st[0].stats.sampling_rate / 2)\
            .simulate(paz_remove={'poles': [-0.037004 + 0.037016j,
                                            -0.037004 - 0.037016j,
                                            -251.33 + 0j],
                                  'zeros': [0j, 0j],
                                  'gain': 60077000.0,
                                  'sensitivity': 2516778400.0})\
            .filter("lowpass", freq=2.0)\
            .differentiate()\
            .integrate()\
            .merge()\
            .cutout(st[0].stats.starttime + 2, st[0].stats.starttime + 2)\
            .detrend()\
            .taper(max_percentage=0.05, type="cosine")\
            .normalize()\
            .verify()\
            .trigger(type="zdetect", nsta=20)\
            .rotate(method="NE->RT", back_azimuth=40)

        # Use the processing chain to check the results. The trim(), merge(),
        # cutout(), verify(), and rotate() methods do not have an entry in the
        # processing chain.
        pr = st[0].stats.processing

        assert "decimate" in pr[1]
        assert "resample" in pr[2]
        assert "simulate" in pr[3]
        assert "filter" in pr[4] and "lowpass" in pr[4]
        assert "differentiate" in pr[5]
        assert "integrate" in pr[6]
        assert "trim" in pr[7]
        assert "detrend" in pr[8]
        assert "taper" in pr[9]
        assert "normalize" in pr[10]
        assert "trigger" in pr[11]

        assert temp is st
        # Cutout duplicates the number of traces.
        assert len(st), 6
        # Clearing also works for method chaining.
        assert len(st.clear()) == 0

    def test_simulate_seedresp_parser(self):
        """
        Test simulate() with giving a Parser object to use for RESP information
        in evalresp.
        Also tests usage without specifying a date for response lookup
        explicitely.
        """
        st = read()
        p = Parser("/path/to/dataless.seed.BW_RJOB")
        kwargs = dict(seedresp={'filename': p, 'units': "DIS"},
                      pre_filt=(1, 2, 50, 60), water_level=60)
        st.simulate(**kwargs)
        for tr in st:
            tr.stats.processing.pop()

        for resp_string, stringio in p.get_resp():
            stringio.seek(0, 0)
            component = resp_string[-1]
            with NamedTemporaryFile() as tf:
                with open(tf.name, "wb") as fh:
                    fh.write(stringio.read())
                tr1 = read().select(component=component)[0]
                tr1.simulate(**kwargs)
                tr1.stats.processing.pop()
            tr2 = st.select(component=component)[0]
            # There is some strange issue on Win32bit (see #2188) and Win64bit
            # (see #2330). Thus we just use assert_allclose() here instead of
            # testing for full equality.
            if platform.system() == "Windows":  # pragma: no cover
                assert tr1.stats == tr2.stats
                # as of 2020-01-21 we see some new Win fails with one sample
                # failing with:
                #    Mismatched elements: 1 / 3000 (0.0333%)
                #    Max absolute difference: 6.617444900424222e-24
                #    Max relative difference: 2.0
                # Maximum amplitudes in the trace are around 1e-8, so we can
                # live with an atol of 1e-22
                np.testing.assert_allclose(
                    tr1.data, tr2.data, rtol=1e-6, atol=1e-22)
            else:
                assert tr1 == tr2

    def test_select_empty_strings(self, mseed_stream):
        """
        Test that select works with values that evaluate True when testing with
        if (e.g. "", 0).
        """
        st = mseed_stream
        st[0].stats.location = "00"
        for tr in st[1:]:
            tr.stats.network = ""
            tr.stats.station = ""
            tr.stats.channel = ""
            tr.data = tr.data[0:0]
        st2 = Stream(st[1:])
        assert st.select(network="") == st2
        assert st.select(station="") == st2
        assert st.select(channel="") == st2
        assert st.select(npts=0) == st2

    def test_remove_response(self):
        """
        Tests that the remove_response method is called for all traces of a
        Stream object
        """
        st1 = read()
        st2 = read()
        for tr in st1:
            tr.remove_response(pre_filt=(0.1, 0.5, 30, 50))
        st2.remove_response(pre_filt=(0.1, 0.5, 30, 50))
        # There is some strange issue on Appveyor. Thus we just use
        # assert_allclose() here instead of testing for full equality.
        # https://ci.appveyor.com/project/obspy/obspy/
        #                                 builds/27495567/job/r4m7ely1nkjht20x
        if platform.system() == "Windows":  # pragma: no cover
            assert streams_almost_equal(st1, st2, atol=0, rtol=1e-6)
        else:
            assert st1 == st2

    def test_attach_response_deprecated(self):
        st = read()
        inv = read_inventory()
        with pytest.warns(
                ObsPyDeprecationWarning, match=r"Stream\.attach_response\(\)"):
            st.attach_response(inv)

    def test_remove_sensitivity(self):
        """
        Tests that the remove_sensitivity method is called for all traces of a
        Stream object
        """
        st1 = read()
        st2 = read()
        for tr in st1:
            tr.remove_sensitivity()
        st2.remove_sensitivity()
        # Some Windows Appveyor CI runs have very minor differences..
        # There is some strange issue on Win32bit (see #2188) and Win64bit
        # (see #2330). Thus we just use assert_allclose() here instead of
        # testing for full equality.
        if platform.system() == "Windows":  # pragma: no cover
            for tr1, tr2 in zip(st1, st2):
                assert tr1.stats == tr2.stats
                np.testing.assert_allclose(tr1.data, tr2.data, rtol=1e-6)
        else:
            assert st1 == st2

    def test_interpolate(self):
        """
        Tests that the interpolate command is called for all traces of a
        Stream object.
        """
        st = read()
        with mock.patch("obspy.core.trace.Trace.interpolate") as patch:
            st.interpolate(sampling_rate=1.0, method="weighted_average_slopes")

        assert len(st) == patch.call_count
        expected = {"sampling_rate": 1.0, "method": "weighted_average_slopes"}
        assert expected == patch.call_args[1]

    def test_integrate(self):
        """
        Tests that the integrate command is called for all traces of a Stream
        object.
        """
        st1 = read()
        st2 = read()

        for tr in st1:
            tr.integrate()
        st2.integrate()
        assert st1 == st2

    def test_integrate_args(self):
        """
        Tests that the integrate command is called for all traces of a Stream
        object and options are passed along correctly.
        """
        st1 = read()
        st2 = read()

        for tr in st1:
            tr.integrate(method='cumtrapz')
        st2.integrate(method='cumtrapz')
        assert st1 == st2

    def test_misaligned_traces(self):
        """
        Tests the option to correct misaligned traces in `Stream._cleanup()`,
        which is the first thing done in any `Stream.merge()` operation.

        We create a simple trace and then prepare other traces that are shifted
        on sub-sample scale. With these shifted traces create different traces
        that are a) contained, b) overlapping, c) directly adjacent and d)
        separated by a "real" gap. In two loops we test a wide variety of
        combinations of actual misalignment ratio (percentage of sampling
        interval) and to-be-fixed misalignment threshold in cleanup().
        """
        samp_rate = 1.23
        delta = 1.0 / samp_rate
        tr = Trace(data=np.arange(10), header=dict(sampling_rate=samp_rate))
        t1 = tr.stats.starttime
        t2 = tr.stats.endtime

        def _gets_merged(tr_, ratio):
            st = Stream([tr.copy(), tr_.copy()])
            st._cleanup(misalignment_threshold=ratio)
            if len(st) == 1:
                return True
            elif len(st) == 2:
                return False
            raise Exception()

        for actual_misalign_percentage in np.linspace(0.01, 0.5, 6):
            # prepare two identical traces, misaligned
            tr_early = tr.copy()
            tr_early.stats.starttime -= actual_misalign_percentage * delta
            tr_late = tr.copy()
            tr_late.stats.starttime += actual_misalign_percentage * delta
            # prepare contained traces
            tr_contained1 = tr_early.copy()
            tr_contained1.trim(t1 + 2 * delta, t2 - delta)
            tr_contained2 = tr_late.copy()
            tr_contained2.trim(t1 + delta, t2 - 3 * delta)
            traces_contained = [tr_contained1, tr_contained2]
            # prepare overlapping traces
            tr_overlap1 = tr_early.copy()
            tr_overlap1.trim(t1 + 4 * delta, t2 + 4 * delta,
                             pad=True, fill_value=99)
            tr_overlap2 = tr_late.copy()
            tr_overlap2.trim(t1 - 5 * delta, t2 - 5 * delta,
                             pad=True, fill_value=99)
            traces_overlap = [tr_overlap1, tr_overlap2]
            # prepare directly adjacent traces
            tr_adjacent1 = tr_early.copy()
            tr_adjacent1.stats.starttime -= len(tr_adjacent1) * delta
            tr_adjacent2 = tr_late.copy()
            tr_adjacent2.stats.starttime -= len(tr_adjacent2) * delta
            tr_adjacent3 = tr_early.copy()
            tr_adjacent3.stats.starttime += len(tr) * delta
            tr_adjacent4 = tr_late.copy()
            tr_adjacent4.stats.starttime += len(tr) * delta
            traces_adjacent = [tr_adjacent1, tr_adjacent2, tr_adjacent3,
                               tr_adjacent4]
            # prepare traces with normal gap
            tr_gap1 = tr_adjacent1.copy()
            tr_gap1.stats.starttime -= delta
            tr_gap2 = tr_adjacent2.copy()
            tr_gap2.stats.starttime -= delta
            tr_gap3 = tr_adjacent3.copy()
            tr_gap3.stats.starttime += delta
            tr_gap4 = tr_adjacent4.copy()
            tr_gap4.stats.starttime += delta
            traces_gap = [tr_gap1, tr_gap2, tr_gap3, tr_gap4]
            for to_be_fixed_misalignmnt_ratio in np.linspace(0.011, 0.49, 5):
                # first test traces with normal gap, these should never change
                for trx in traces_gap:
                    assert not _gets_merged(
                        trx, to_be_fixed_misalignmnt_ratio)
                # now lets check the cases for which depending on
                # the combination of actual/to-be-fixed misalignment ratios
                # there should be a change/merge or not
                should_change = (to_be_fixed_misalignmnt_ratio >=
                                 actual_misalign_percentage)
                for trx in traces_contained + traces_overlap + traces_adjacent:
                    assert should_change == _gets_merged(
                        trx, to_be_fixed_misalignmnt_ratio)

    def test_slide(self):
        """
        Tests for sliding a window across a stream object.
        """
        # 0 - 20 seconds
        tr1 = Trace(data=np.linspace(0, 100, 101))
        tr1.stats.starttime = UTCDateTime(0.0)
        tr1.stats.sampling_rate = 5.0

        # 5 - 10 seconds
        tr2 = Trace(data=np.linspace(25, 75, 51))
        tr2.stats.starttime = UTCDateTime(5.0)
        tr2.stats.sampling_rate = 5.0

        # 15 - 20 seconds
        tr3 = Trace(data=np.linspace(75, 100, 26))
        tr3.stats.starttime = UTCDateTime(0.0)
        tr3.stats.sampling_rate = 15.0

        st = Stream(traces=[tr1, tr2, tr3])

        # First slice it in 4 pieces. Window length is in seconds.
        slices = []
        for window_st in st.slide(window_length=5.0, step=5.0):
            slices.append(window_st)

        assert len(slices) == 4
        assert slices[0] == st.slice(UTCDateTime(0), UTCDateTime(5))
        assert slices[1] == st.slice(UTCDateTime(5), UTCDateTime(10))
        assert slices[2] == st.slice(UTCDateTime(10), UTCDateTime(15))
        assert slices[3] == st.slice(UTCDateTime(15), UTCDateTime(20))

        # Different step which is the distance between two windows measured
        # from the start of the first window in seconds.
        slices = []
        for window_tr in st.slide(window_length=5.0, step=10.0):
            slices.append(window_tr)

        assert len(slices) == 2
        assert slices[0] == st.slice(UTCDateTime(0), UTCDateTime(5))
        assert slices[1] == st.slice(UTCDateTime(10), UTCDateTime(15))

        # Offset determines the initial starting point. It defaults to zero.
        slices = []
        for window_tr in st.slide(window_length=5.0, step=6.5, offset=8.5):
            slices.append(window_tr)

        assert len(slices) == 2
        assert slices[0] == st.slice(UTCDateTime(8.5), UTCDateTime(13.5))
        assert slices[1] == st.slice(UTCDateTime(15.0), UTCDateTime(20.0))

        # By default only full length windows will be returned so any
        # remainder that can no longer make up a full window will not be
        # returned.
        slices = []
        for window_tr in st.slide(window_length=15.0, step=15.0):
            slices.append(window_tr)

        assert len(slices) == 1
        assert slices[0] == st.slice(UTCDateTime(0.0), UTCDateTime(15.0))

        # But it can optionally be returned.
        slices = []
        for window_tr in st.slide(window_length=15.0, step=15.0,
                                  include_partial_windows=True):
            slices.append(window_tr)

        assert len(slices) == 2
        assert slices[0] == st.slice(UTCDateTime(0.0), UTCDateTime(15.0))
        assert slices[1] == st.slice(UTCDateTime(15.0), UTCDateTime(20.0))

        # Negative step lengths work together with an offset.
        slices = []
        for window_tr in st.slide(window_length=5.0, step=-5.0, offset=20.0):
            slices.append(window_tr)

        assert len(slices) == 4
        assert slices[0] == st.slice(UTCDateTime(15), UTCDateTime(20))
        assert slices[1] == st.slice(UTCDateTime(10), UTCDateTime(15))
        assert slices[2] == st.slice(UTCDateTime(5), UTCDateTime(10))
        assert slices[3] == st.slice(UTCDateTime(0), UTCDateTime(5))

    def test_slide_nearest_sample(self):
        """
        Tests that the nearest_sample argument is correctly passed to the
        slice function calls.
        """
        tr = Trace(data=np.linspace(0, 100, 101))
        tr.stats.starttime = UTCDateTime(0.0)
        tr.stats.sampling_rate = 5.0
        st = Stream(traces=[tr, tr.copy()])

        # It defaults to True.
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = tr
            list(st.slide(5, 5))

        # Twice per window as two traces.
        assert patch.call_count == 8
        for arg in patch.call_args_list:
            assert arg[1]["nearest_sample"]

        # Force True.
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = tr
            list(st.slide(5, 5, nearest_sample=True))

        # Twice per window as two traces.
        assert patch.call_count == 8
        for arg in patch.call_args_list:
            assert arg[1]["nearest_sample"]

        # Set to False.
        with mock.patch("obspy.core.trace.Trace.slice") as patch:
            patch.return_value = tr
            list(st.slide(5, 5, nearest_sample=False))

        # Twice per window as two traces.
        assert patch.call_count == 8
        for arg in patch.call_args_list:
            assert not arg[1]["nearest_sample"]

    def test_passing_kwargs_to_trace_detrend(self):
        """
        Simple regression test making sure kwargs are passed to the Trace's
        detrend method.
        """
        st = read()

        with mock.patch("obspy.core.trace.Trace.detrend") as patch:
            st.detrend("polynomial", order=2, plot=True)

        # 3 Traces.
        assert patch.call_count == 3

        for arg in patch.call_args_list:
            assert arg[1]["order"] == 2
            assert arg[1]["plot"]

    def test_read_check_compression(self):
        """
        Test to ensure calling read with check_compression=False does not
        call expensive tar or zip functions.
        """
        with mock.patch("tarfile.is_tarfile") as tar_p:
            with mock.patch("zipfile.is_zipfile") as zip_p:
                read('/path/to/slist.ascii', format='SLIST',
                     check_compression=False)

        # assert neither compression check function was called.
        assert tar_p.call_count == 0
        assert zip_p.call_count == 0

        # ensure compression checks get called when check_compression is True
        with mock.patch("tarfile.is_tarfile", return_value=0) as tar_p:
            with mock.patch("zipfile.is_zipfile", return_value=0) as zip_p:
                read('/path/to/slist.ascii', format='SLIST',
                     check_compression=True)
        assert tar_p.call_count == 1
        assert zip_p.call_count >= 1

    def test_rotate_to_zne(self):
        """
        Tests rotating all traces in stream to ZNE given an inventory object.
        """
        inv = read_inventory("/path/to/ffbx.stationxml", format="STATIONXML")
        parser = Parser("/path/to/ffbx.dataless")
        st_expected = read('/path/to/ffbx_rotated.slist', format='SLIST')
        st_unrotated = read("/path/to/ffbx_unrotated_gaps.mseed",
                            format="MSEED")
        for tr in st_expected:
            # ignore format specific keys and processing which also holds
            # version number
            tr.stats.pop('ascii')
            tr.stats.pop('_format')
        # check rotation using both Inventory and Parser as metadata input
        for metadata in (inv, parser):
            st = st_unrotated.copy()
            st.rotate("->ZNE", inventory=metadata)
            # do some checks on results
            assert len(st) == 30
            # compare data
            st.sort()
            st_expected.sort()
            for tr_got, tr_expected in zip(st, st_expected):
                np.testing.assert_allclose(tr_got.data, tr_expected.data,
                                           rtol=1e-7)
            # compare stats
            for tr_expected, tr_got in zip(st_expected, st):
                # ignore format specific keys and processing which also holds
                # version number
                tr_got.stats.pop('mseed')
                tr_got.stats.pop('_format')
                tr_got.stats.pop('processing')
                assert tr_got.stats == tr_expected.stats

        # check that using something like `components="Z12"` also works,
        st = st_unrotated.copy()
        result = st.rotate("->ZNE", inventory=inv,
                           components='Z12')
        # check that rotation to ZNE worked..
        assert set(tr.stats.channel[-1] for tr in result) == set('ZNE')

    def test_write_empty_stream(self):
        """
        Tests error message when trying to write an empty stream
        """
        st = Stream()
        bio = io.BytesIO()
        msg = 'Can not write empty stream to file.'
        for format_ in _get_entry_points('obspy.plugin.waveform',
                                         'writeFormat').keys():
            with pytest.raises(ObsPyException, match=msg):
                st.write(bio, format=format_)

    def test_stack(self):
        """
        Tests stack method
        """
        # check number of traces and headers
        # stack with default options
        st = read()
        st2 = st.copy().stack()
        assert len(st2) == 1
        assert 'stack' in st2[0].stats
        assert st2[0].stats.stack.group == 'all'
        assert st2[0].stats.stack.count == 3
        assert st2[0].stats.stack.type == 'linear'
        # stack by SEED id
        st += st.copy()
        st2 = st.copy().stack('id')
        assert len(st2) == 3
        assert {tr.stats.stack.group for tr in st2} == {tr.id for tr in st}
        assert {tr.id for tr in st2} == {tr.id for tr in st}
        assert st2[0].stats.stack.count == 2
        # stack by other metadata
        for tr in st[3:]:
            tr.stats.station = 'OTH'
        st2 = st.copy().stack('{network}.{station}')
        assert len(st2) == 2
        assert {tr.stats.stack.group for tr in st2} == \
            {'.'.join((tr.stats.network, tr.stats.station)) for tr in st}
        assert st2[0].stats.stack.count == 3

        # check npts_tol option and correct npts of stack
        st = read()
        st[2].data = st[2].data[:-1]
        npts = len(st[0])
        # self.assertRaisesRegex(ValueError, 'number of points', st.stack)
        with pytest.raises(ValueError):
            st.stack()
        st2 = st.copy().stack(npts_tol=1)
        assert len(st2) == 1
        assert len(st2[0]) == npts-1
        assert len(st[0]) == npts

        # check correct setting of metadata
        st = read()
        st[0].stats.back_azimuth -= 10
        st[0].stats.array1 = np.array([1, 2])
        st[1].stats.array1 = np.array([1, 2, 3])
        st[2].stats.array1 = 'no array here'
        for tr in st:
            tr.stats.array2 = np.array([3, 4])
        st[0].stats.sub1 = {'a': 1}
        st[1].stats.sub1 = {'b': np.array([1, 2])}
        st[2].stats.sub1 = 5
        sub2_dict = {'a': 'b', 'b': 'c', 'nd': np.array([1, 2])}
        for tr in st:
            tr.stats.sub2 = sub2_dict
        st2 = st.copy().stack()
        assert st2[0].stats.station == st[0].stats.station
        assert st2[0].stats.inclination == st[0].stats.inclination
        assert st2[0].stats.starttime == st[0].stats.starttime
        assert st2[0].stats.channel == ''
        assert 'back_azimuth' not in st2[0].stats
        assert 'array1' not in st2[0].stats
        assert 'array2' in st2[0].stats
        assert 'sub1' not in st2[0].stats
        assert 'sub2' in st2[0].stats
        assert st2[0].stats.sub2.keys() == sub2_dict.keys()
        st[1].stats.starttime += 10
        st2 = st.copy().stack()
        assert st2[0].stats.starttime == UTCDateTime(0)
        st2 = st.copy().stack(time_tol=11)
        assert st2[0].stats.starttime == st[0].stats.starttime
        st[0].stats.sampling_rate /= 10
        # self.assertRaisesRegex(ValueError, 'Sampling rate', st.stack)
        with pytest.raises(ValueError):
            st.stack()

        # Check pw and root stacking types, these must result in the linear
        # stack for order 0, resp. 1.
        # For larger order pw stack is always of smaller magnitude than linear
        # stack. For a root stack this is not always the case, but its
        # magnitude is definitely smaller if all stacked samples have the
        # same sign.
        st = read()
        st2 = st.copy().stack()
        st3 = st.copy().stack(stack_type=('pw', 0))
        st4 = st.copy().stack(stack_type=('root', 1))
        assert len(st2) == 1
        assert len(st3) == 1
        assert len(st4) == 1
        np.testing.assert_allclose(st3[0].data, st2[0].data)
        np.testing.assert_allclose(st4[0].data, st2[0].data)
        st3 = st.copy().stack(stack_type=('pw', 2))
        st4 = st.copy().stack(stack_type=('root', 2))
        assert np.sum(np.abs(st3[0].data) <= np.abs(st2[0].data)) == npts
        all_data = np.array([tr.data for tr in st])
        same_sign = np.logical_or(np.all(all_data < 0, axis=0),
                                  np.all(all_data > 0, axis=0))
        npts = np.sum(same_sign)
        assert np.sum(np.abs(st4[0].data[same_sign]) <=
                      np.abs(st2[0].data[same_sign])) == npts

    def test_stream_trim_slice_same_length(self):
        """
        See issue #2608

        End time 00:20:04.045 is in the middle of two samples
        00:20:04.04 versus 00:20:04.05
        """
        st = read()
        utc = st[0].stats.starttime + 1.045
        n1 = len(st.slice(None, utc)[0])
        n2 = len(st.trim(None, utc)[0])
        assert n1 == n2

    def test_filter_ftype(self):
        st = read()
        ftypes = ['butter', 'cheby1', 'cheby2', 'ellip', 'bessel']
        streams = [st.copy().filter('bandpass', 5, 10,
                                    ftype=ftype, rp=10, rs=100)
                   for ftype in ftypes]
        for st in streams[1:]:
            assert not streams_almost_equal(st, streams[0])

    def test_filter_freq_args(self):
        st = read()
        for filtert, freq in [('lowpass', 5,),
                              ('highpass', 5)]:
            stf1 = st.copy().filter(filtert, freq=freq)
            stf2 = st.copy().filter(filtert, freq)
            assert streams_almost_equal(stf2, stf1)
        for filtert, freqmin, freqmax in [('bandpass', 1, 5),
                                          ('bandstop', 1, 5)]:
            stf1 = st.copy().filter(filtert, freqmin=freqmin, freqmax=freqmax)
            stf2 = st.copy().filter(filtert, freqmin, freqmax)
            assert streams_almost_equal(stf2, stf1)

    def test_merge_checks_messages(self, mseed_stream):
        """
        Test improved error messages
        """
        # dtype
        st = mseed_stream.copy()
        st[0].data = np.require(
            st[0].data, dtype=st[0].data.dtype.newbyteorder('<'))
        st[1].data = np.require(
            st[1].data, dtype=st[1].data.dtype.newbyteorder('>'))
        msg = ('Can not merge traces with same ids (BW.BGLD..EHE) but '
               'differing data types (float64, >f8)!')
        with pytest.raises(Exception, match=re.escape(msg)):
            st.merge()
        # sampling rate
        st = mseed_stream.copy()
        st[0].stats.sampling_rate = 200
        st[1].stats.sampling_rate = 20
        msg = ('Can not merge traces with same ids (BW.BGLD..EHE) but '
               'differing sampling rates (200.0, 20.0)!')
        with pytest.raises(Exception, match=re.escape(msg)):
            st.merge()
        # calib
        st = mseed_stream.copy()
        st[0].stats.calib = 12345
        st[1].stats.calib = 100
        msg = ('Can not merge traces with same ids (BW.BGLD..EHE) but '
               'differing calibration factors (12345, 100)!')
        with pytest.raises(Exception, match=re.escape(msg)):
            st.merge()

    def test_newbyteorder(self, mseed_stream):
        """
        Test switching byteorder on trace/stream, for simplicity both things
        tested here
        """
        msg = ('Can not merge traces with same ids (BW.BGLD..EHE) but '
               'differing data types (float64, >f8)!')
        # set different byteorders
        st = mseed_stream
        st[0].data = np.require(
            st[0].data, dtype=st[0].data.dtype.newbyteorder('<'))
        st[1].data = np.require(
            st[1].data, dtype=st[1].data.dtype.newbyteorder('>'))
        # check that we set up correctly this should fail now
        assert st[0].data.dtype == np.dtype('float64')
        assert st[1].data.dtype == np.dtype('>f8')
        with pytest.raises(Exception, match=re.escape(msg)):
            st.merge()
        # check working on trace
        st = mseed_stream.copy()
        assert st[0].data.dtype == np.dtype('float64')
        assert st[1].data.dtype == np.dtype('>f8')
        st[1].newbyteorder('<')
        assert st[1].data.dtype == np.dtype('float64')
        st.merge()
        # check working on stream
        st = mseed_stream.copy()
        assert st[0].data.dtype == np.dtype('float64')
        assert st[1].data.dtype == np.dtype('>f8')
        st.newbyteorder('=')
        for tr in st:
            assert tr.data.dtype == np.dtype('float64')
        st.merge()
