Repository: pymeasure/pymeasure

Issue:
Instrument.read_bytes(-1) does not behave as advertised
The docstring of read_bytes says:

https://github.com/pymeasure/pymeasure/blob/c04f21157d96e5c73e18b025b4b82c77ff2431e7/pymeasure/instruments/instrument.py#L145-L149

Upon testing (see below) it, however, turns out that it only returns the data until a termination character is contained. (The termination character is not stripped). If no termination character is received, `read_bytes(-1)` raises a `pyvisa.errors.VisaIOError`.

I tested this with pyvisa (pyvisa-py) with the code below:

```
from pymeasure.instruments.mksinst import MKS937B
g = MKS937B("TCPIP::10.0.131.254::10001::SOCKET")  # termination_char here is ";"
g.write("SN?")
g.write("SN?")

g.read_bytes(-1) # returns b'@253ACK022869254 ;'
g.read_bytes(-1) # returns b'FF@253ACK022869254 ;'
g.read_bytes(-1) # raises the error below (but the remaining "FF" which is still in the read buffer can not be obtained.
```

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\AJAlab\Documents\GitHub\pymeasure\pymeasure\instruments\instrument.py", line 153, in read_bytes
    return self.adapter.read_bytes(count, **kwargs)
  File "C:\Users\AJAlab\Documents\GitHub\pymeasure\pymeasure\adapters\adapter.py", line 121, in read_bytes
    read = self._read_bytes(count, **kwargs)
  File "C:\Users\AJAlab\Documents\GitHub\pymeasure\pymeasure\adapters\visa.py", line 161, in _read_bytes
    return self.connection.read_raw(**kwargs)
  File "C:\Users\AJAlab\AppData\Roaming\Python\Python310\site-packages\pyvisa\resources\messagebased.py", line 405, in read_raw
    return bytes(self._read_raw(size))
  File "C:\Users\AJAlab\AppData\Roaming\Python\Python310\site-packages\pyvisa\resources\messagebased.py", line 442, in _read_raw
    chunk, status = self.visalib.read(self.session, size)
  File "C:\Users\AJAlab\AppData\Roaming\Python\Python310\site-packages\pyvisa_py\highlevel.py", line 519, in read
    return data, self.handle_return_value(session, status_code)
  File "C:\Users\AJAlab\AppData\Roaming\Python\Python310\site-packages\pyvisa\highlevel.py", line 251, in handle_return_value
    raise errors.VisaIOError(rv)
pyvisa.errors.VisaIOError: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 9600d6bb..d568fbe7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,7 @@ New adapter and instrument mechanics
 ------------------------------------
 - Channel class added. Instrument.channels and Instrument.ch_X (X is any channel name) are reserved for channel implementations.
 - All instruments are required to accept a :code:`name` argument.
+- Changed: :code:`read_bytes` of all Adapters by default does not stop reading on a termination character, unless the new argument :code:`break_on_termchar` is set to `True`.
 
 Deprecated features
 -------------------
diff --git a/pymeasure/adapters/adapter.py b/pymeasure/adapters/adapter.py
index 4f347cd4..48ae5dcd 100644
--- a/pymeasure/adapters/adapter.py
+++ b/pymeasure/adapters/adapter.py
@@ -45,7 +45,7 @@ class Adapter:
             Implement it in the instrument's `read` method instead.
 
     :param log: Parent logger of the 'Adapter' logger.
-    :param kwargs: Keyword arguments just to be cooperative.
+    :param \\**kwargs: Keyword arguments just to be cooperative.
     """
 
     def __init__(self, preprocess_reply=None, log=None, **kwargs):
@@ -63,7 +63,7 @@ class Adapter:
                  FutureWarning)
 
     def __del__(self):
-        """Close connection upon garbage collection of the device"""
+        """Close connection upon garbage collection of the device."""
         self.close()
 
     def close(self):
@@ -81,7 +81,7 @@ class Adapter:
 
         :param str command: Command string to be sent to the instrument
             (without termination).
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         self.log.debug("WRITE:%s", command)
         self._write(command, **kwargs)
@@ -92,7 +92,7 @@ class Adapter:
         Do not override in a subclass!
 
         :param bytes content: The bytes to write to the instrument.
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         self.log.debug("WRITE:%s", content)
         self._write_bytes(content, **kwargs)
@@ -102,24 +102,25 @@ class Adapter:
 
         Do not override in a subclass!
 
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns str: ASCII response of the instrument (excluding read_termination).
         """
         read = self._read(**kwargs)
         self.log.debug("READ:%s", read)
         return read
 
-    def read_bytes(self, count=-1, **kwargs):
+    def read_bytes(self, count=-1, break_on_termchar=False, **kwargs):
         """Read a certain number of bytes from the instrument.
 
         Do not override in a subclass!
 
         :param int count: Number of bytes to read. A value of -1 indicates to
-            read the whole read buffer.
-        :param kwargs: Keyword arguments for the connection itself.
+            read from the whole read buffer.
+        :param bool break_on_termchar: Stop reading at a termination character.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns bytes: Bytes response of the instrument (including termination).
         """
-        read = self._read_bytes(count, **kwargs)
+        read = self._read_bytes(count, break_on_termchar, **kwargs)
         self.log.debug("READ:%s", read)
         return read
 
@@ -136,7 +137,7 @@ class Adapter:
         """Read string from the instrument. Implement in subclass."""
         raise NotImplementedError("Adapter class has not implemented reading.")
 
-    def _read_bytes(self, count, **kwargs):
+    def _read_bytes(self, count, break_on_termchar, **kwargs):
         """Read bytes from the instrument. Implement in subclass."""
         raise NotImplementedError("Adapter class has not implemented reading bytes.")
 
@@ -218,7 +219,7 @@ class Adapter:
         :param int header_bytes: Number of bytes to ignore in header.
         :param int termination_bytes: Number of bytes to strip at end of message or None.
         :param dtype: The NumPy data type to format the values with.
-        :param kwargs: Further arguments for the NumPy fromstring method.
+        :param \\**kwargs: Further arguments for the NumPy fromstring method.
         :returns: NumPy array of values
         """
         binary = self.read_bytes(-1)
@@ -252,7 +253,7 @@ class Adapter:
         :param command: command string to be sent to the instrument
         :param values: iterable representing the binary values
         :param termination: String added afterwards to terminate the message.
-        :param kwargs: Key-word arguments to pass onto :meth:`Adapter._format_binary_values`
+        :param \\**kwargs: Key-word arguments to pass onto :meth:`Adapter._format_binary_values`
         :returns: number of bytes written
         """
         block = self._format_binary_values(values, **kwargs)
@@ -287,7 +288,7 @@ class FakeAdapter(Adapter):
         self._buffer = ""
         return result
 
-    def _read_bytes(self, count):
+    def _read_bytes(self, count, break_on_termchar):
         """ Return the last commands given after the
         last read call.
         """
diff --git a/pymeasure/adapters/prologix.py b/pymeasure/adapters/prologix.py
index 0b59c9d0..37714f83 100644
--- a/pymeasure/adapters/prologix.py
+++ b/pymeasure/adapters/prologix.py
@@ -46,7 +46,7 @@ class PrologixAdapter(VISAAdapter):
         slow to respond instruments.
 
         .. deprecated:: 0.11
-            Implement it in the instrument's `wait_until_read` method instead.
+            Implement it in the instrument's `wait_for` method instead.
 
     :param preprocess_reply: optional callable used to preprocess
         strings received from the instrument. The callable returns the
@@ -91,7 +91,7 @@ class PrologixAdapter(VISAAdapter):
         # for legacy rw_delay: prefer new style over old one.
         if rw_delay:
             warn(("Parameter `rw_delay` is deprecated. "
-                  "Implement in Instrument's `wait_until_read` instead."),
+                  "Implement in Instrument's `wait_for` instead."),
                  FutureWarning)
             kwargs['query_delay'] = rw_delay
         if serial_timeout:
diff --git a/pymeasure/adapters/protocol.py b/pymeasure/adapters/protocol.py
index eec07846..cb668d2a 100644
--- a/pymeasure/adapters/protocol.py
+++ b/pymeasure/adapters/protocol.py
@@ -24,6 +24,7 @@
 
 import logging
 from unittest.mock import MagicMock
+from warnings import warn
 
 from .adapter import Adapter
 
@@ -129,11 +130,15 @@ class ProtocolAdapter(Adapter):
         """Return an already present or freshly fetched read buffer as a string."""
         return self._read_bytes(-1).decode("utf-8")
 
-    def _read_bytes(self, count, **kwargs):
+    def _read_bytes(self, count, break_on_termchar=False, **kwargs):
         """Read `count` number of bytes from the buffer.
 
         :param int count: Number of bytes to read. If -1, return the buffer.
         """
+        if break_on_termchar:
+            warn(("Breaking on termination character in `read_bytes` cannot be tested. "
+                  "You have to separate the message parts in the com_pairs."),
+                 UserWarning)
         if self._read_buffer is not None:
             if count == -1 or count >= len(self._read_buffer):
                 read = self._read_buffer
diff --git a/pymeasure/adapters/serial.py b/pymeasure/adapters/serial.py
index 9a3c5e37..51d091b3 100644
--- a/pymeasure/adapters/serial.py
+++ b/pymeasure/adapters/serial.py
@@ -44,7 +44,7 @@ class SerialAdapter(Adapter):
 
     :param write_termination: String appended to messages before writing them.
     :param read_termination: String expected at end of read message and removed.
-    :param kwargs: Any valid key-word argument for serial.Serial
+    :param \\**kwargs: Any valid key-word argument for serial.Serial
     """
 
     def __init__(self, port, preprocess_reply=None,
@@ -63,7 +63,7 @@ class SerialAdapter(Adapter):
 
         :param str command: Command string to be sent to the instrument
             (without termination).
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         command += self.write_termination
         self._write_bytes(command.encode(), **kwargs)
@@ -72,17 +72,17 @@ class SerialAdapter(Adapter):
         """Write the bytes `content` to the instrument.
 
         :param bytes content: The bytes to write to the instrument.
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         self.connection.write(content, **kwargs)
 
     def _read(self, **kwargs):
         """Read up to (excluding) `read_termination` or the whole read buffer.
 
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns str: ASCII response of the instrument (read_termination is removed first).
         """
-        read = self._read_bytes(-1, **kwargs).decode()
+        read = self._read_bytes(-1, break_on_termchar=True, **kwargs).decode()
         # Python>3.8 this shorter form is possible:
         # self._read_bytes(-1).decode().removesuffix(self.read_termination)
         if self.read_termination:
@@ -90,21 +90,22 @@ class SerialAdapter(Adapter):
         else:
             return read
 
-    def _read_bytes(self, count, **kwargs):
+    def _read_bytes(self, count, break_on_termchar, **kwargs):
         """Read a certain number of bytes from the instrument.
 
         :param int count: Number of bytes to read. A value of -1 indicates to
-            read the whole read buffer or until encountering the read_termination.
-        :param kwargs: Keyword arguments for the connection itself.
+            read from the whole read buffer.
+        :param bool break_on_termchar: Stop reading at a termination character.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns bytes: Bytes response of the instrument (including termination).
         """
-        if count == -1:
-            if self.read_termination:
-                return self.connection.read_until(self.read_termination, **kwargs)
-            else:
-                return b"\n".join(self.connection.readlines(**kwargs))
+        if break_on_termchar and self.read_termination:
+            return self.connection.read_until(self.read_termination.encode(),
+                                              count if count > 0 else None,
+                                              **kwargs)
         else:
-            return self.connection.read(count, **kwargs)
+            # At -1 we read a very large number of bytes, which can be considered the whole buffer.
+            return self.connection.read(1e99 if count == -1 else count, **kwargs)
 
     def __repr__(self):
         return "<SerialAdapter(port='%s')>" % self.connection.port
diff --git a/pymeasure/adapters/visa.py b/pymeasure/adapters/visa.py
index 8cb3858c..58c1a712 100644
--- a/pymeasure/adapters/visa.py
+++ b/pymeasure/adapters/visa.py
@@ -55,7 +55,7 @@ class VISAAdapter(Adapter):
     :param float query_delay: Time in s to wait after writing and before reading.
 
         .. deprecated:: 0.11
-            Implement it in the instrument's `wait_until_read` method instead.
+            Implement it in the instrument's `wait_for` method instead.
 
     :param log: Parent logger of the 'Adapter' logger.
     :param \\**kwargs: Keyword arguments for configuring the PyVISA connection.
@@ -88,7 +88,7 @@ class VISAAdapter(Adapter):
         super().__init__(preprocess_reply=preprocess_reply, log=log)
         if query_delay:
             warn(("Parameter `query_delay` is deprecated. "
-                  "Implement in Instrument's `wait_until_read` instead."),
+                  "Implement in Instrument's `wait_for` instead."),
                  FutureWarning)
             kwargs.setdefault("query_delay", query_delay)
         self.query_delay = query_delay
@@ -101,10 +101,12 @@ class VISAAdapter(Adapter):
             # Allow to reuse the connection.
             self.resource_name = getattr(resource_name, "resource_name", None)
             self.connection = resource_name.connection
+            self.manager = resource_name.manager
             self.query_delay = resource_name.query_delay
             return
         elif isinstance(resource_name, int):
             resource_name = "GPIB0::%d::INSTR" % resource_name
+
         self.resource_name = resource_name
         self.manager = pyvisa.ResourceManager(visa_library)
 
@@ -126,12 +128,26 @@ class VISAAdapter(Adapter):
             **kwargs
         )
 
+    def close(self):
+        """Close the connection.
+
+        .. note::
+
+            This closes the connection to the resource for all adapters using
+            it currently (e.g. different adapters using the same GPIB line).
+        """
+        super().close()
+        try:
+            self.manager.close()
+        except AttributeError:
+            pass  # Closed from another adapter using the same connection.
+
     def _write(self, command, **kwargs):
         """Write a string command to the instrument appending `write_termination`.
 
         :param str command: Command string to be sent to the instrument
             (without termination).
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         self.connection.write(command, **kwargs)
 
@@ -139,30 +155,39 @@ class VISAAdapter(Adapter):
         """Write the bytes `content` to the instrument.
 
         :param bytes content: The bytes to write to the instrument.
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         """
         self.connection.write_raw(content, **kwargs)
 
     def _read(self, **kwargs):
         """Read up to (excluding) `read_termination` or the whole read buffer.
 
-        :param kwargs: Keyword arguments for the connection itself.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns str: ASCII response of the instrument (excluding read_termination).
         """
         return self.connection.read(**kwargs)
 
-    def _read_bytes(self, count, **kwargs):
+    def _read_bytes(self, count, break_on_termchar=False, **kwargs):
         """Read a certain number of bytes from the instrument.
 
         :param int count: Number of bytes to read. A value of -1 indicates to
-            read the whole read buffer.
-        :param kwargs: Keyword arguments for the connection itself.
+            read from the whole read buffer.
+        :param bool break_on_termchar: Stop reading at a termination character.
+        :param \\**kwargs: Keyword arguments for the connection itself.
         :returns bytes: Bytes response of the instrument (including termination).
         """
-        if count == -1:
-            return self.connection.read_raw(**kwargs)
+        if count >= 0:
+            return self.connection.read_bytes(count, break_on_termchar=break_on_termchar, **kwargs)
+        elif break_on_termchar:
+            return self.connection.read_raw(None, **kwargs)
         else:
-            return self.connection.read_bytes(count, **kwargs)
+            read_termination = self.connection.read_termination
+            self.connection.read_termination = None
+            # Try except allows to set the read_termination even after an error.
+            try:
+                return self.connection.read_raw(**kwargs)
+            finally:
+                self.connection.read_termination = read_termination
 
     def ask(self, command):
         """ Writes the command to the instrument and returns the resulting
@@ -186,7 +211,7 @@ class VISAAdapter(Adapter):
             Call `Instrument.values` instead.
 
         :param command: SCPI command to be sent to the instrument
-        :param kwargs: Key-word arguments to pass onto `query_ascii_values`
+        :param \\**kwargs: Key-word arguments to pass onto `query_ascii_values`
         :returns: Formatted response of the instrument.
         """
         warn("`Adapter.ask_values` is deprecated, call `Instrument.values` instead.",
diff --git a/pymeasure/adapters/vxi11.py b/pymeasure/adapters/vxi11.py
index 55b7bb4b..f11ce6af 100644
--- a/pymeasure/adapters/vxi11.py
+++ b/pymeasure/adapters/vxi11.py
@@ -125,15 +125,24 @@ class VXI11Adapter(Adapter):
         warn("Use `read_bytes` instead.", FutureWarning)
         return self.read_bytes(-1)
 
-    def _read_bytes(self, count, **kwargs):
+    def _read_bytes(self, count, break_on_termchar=False, **kwargs):
         """Read a certain number of bytes from the instrument.
 
         :param int count: Number of bytes to read. A value of -1 indicates to
             read the whole read buffer.
+        :param bool break_on_termchar: Stop reading at a termination character.
         :param kwargs: Keyword arguments for the connection itself.
         :returns bytes: Bytes response of the instrument (including termination).
         """
-        return self.connection.read_raw(count, **kwargs)
+        if self.connection.term_char and not break_on_termchar:
+            read_termination = self.connection.term_char
+            self.connection.term_char = None
+            try:
+                return self.connection.read_raw(count, **kwargs)
+            finally:
+                self.connection.term_char = read_termination
+        else:
+            return self.connection.read_raw(count, **kwargs)
 
     def ask_raw(self, command):
         """ Wrapper function for the ask_raw command using the


Test patch:
diff --git a/tests/adapters/test_adapter.py b/tests/adapters/test_adapter.py
index 8c964207..785da241 100644
--- a/tests/adapters/test_adapter.py
+++ b/tests/adapters/test_adapter.py
@@ -85,7 +85,7 @@ def test_read_bytes(fake):
 @pytest.mark.parametrize("method, args", (['_write', ['5']],
                                           ['_read', []],
                                           ['_write_bytes', ['8']],
-                                          ['_read_bytes', ['5']],
+                                          ['_read_bytes', ['5', False]],
                                           ))
 def test_not_implemented_methods(adapter, method, args):
     with pytest.raises(NotImplementedError):
diff --git a/tests/adapters/test_protocol.py b/tests/adapters/test_protocol.py
index 6e806a52..ea3bdf4c 100644
--- a/tests/adapters/test_protocol.py
+++ b/tests/adapters/test_protocol.py
@@ -27,7 +27,7 @@ import pytest
 
 from pymeasure.adapters.protocol import to_bytes, ProtocolAdapter
 
-from pytest import mark, raises, fixture
+from pytest import mark, raises, fixture, warns
 
 
 @pytest.fixture
@@ -213,6 +213,11 @@ class Test_read_bytes:
         with raises(AssertionError, match="None, None"):
             a.read_bytes(1)
 
+    def test_break_on_termchar_raises_warning(self):
+        a = ProtocolAdapter([(None, b"Response")])
+        with warns(UserWarning, match="cannot be tested"):
+            assert a.read_bytes(10, break_on_termchar=True) == b"Response"
+
 
 def test_read_write_sequence():
     """Test several consecutive writes and reads, including ask."""
diff --git a/tests/adapters/test_serial.py b/tests/adapters/test_serial.py
index bc2aa0ed..d08970bd 100644
--- a/tests/adapters/test_serial.py
+++ b/tests/adapters/test_serial.py
@@ -60,8 +60,24 @@ def test_write_bytes(adapter, msg):
 
 
 def test_read_bytes(adapter):
-    adapter.write_bytes(b"basd\x02fasdf\n")
-    assert adapter.read_bytes(11) == b"basd\x02fasdf\n"
+    """Test whether `count` bytes are returned, even though a term char is defined."""
+    adapter.read_termination = "\n"
+    adapter.write_bytes(b"basd\x02\nfasdf\n")
+    assert adapter.read_bytes(9) == b"basd\x02\nfas"
+
+
+def test_read_bytes_unlimited(adapter):
+    """Test whether all bytes are returned, even though a term char is defined."""
+    adapter.read_termination = "\n"
+    adapter.write_bytes(b"basd\x02\nfasdf\n")
+    assert adapter.read_bytes(-1) == b"basd\x02\nfasdf\n"
+
+
+@pytest.mark.parametrize("count", (-1, 8))
+def test_read_bytes_break_on_termchar(adapter, count):
+    adapter.read_termination = "\n"
+    adapter.write_bytes(b"basd\x02\nfasdf\n")
+    assert adapter.read_bytes(count, break_on_termchar=True) == b"basd\x02\n"
 
 
 @pytest.mark.parametrize("test_input,expected", [([1, 2, 3], b'OUTP#13\x01\x02\x03'),
diff --git a/tests/adapters/test_visa.py b/tests/adapters/test_visa.py
index 32fa41a5..4871e174 100644
--- a/tests/adapters/test_visa.py
+++ b/tests/adapters/test_visa.py
@@ -24,6 +24,7 @@
 import importlib.util
 
 import pytest
+import pyvisa
 
 from pymeasure.adapters import VISAAdapter
 from pymeasure.test import expected_protocol
@@ -38,15 +39,37 @@ if not is_pyvisa_sim_installed:
 
 @pytest.fixture
 def adapter():
-    return VISAAdapter(SIM_RESOURCE, visa_library='@sim',
-                       read_termination="\n")
+    adapter = VISAAdapter(SIM_RESOURCE, visa_library='@sim',
+                          read_termination="\n",
+                          timeout=10,
+                          )
+    yield adapter
+    # Empty the read buffer, as something might remain there after a test.
+    # `clear` is not implemented in pyvisa-sim and `flush_read_buffer` seems to do nothing.
+    adapter.timeout = 0
+    try:
+        adapter.read_bytes(-1)
+    except pyvisa.errors.VisaIOError as exc:
+        if not exc.args[0].startswith("VI_ERROR_TMO"):
+            raise
+    # Close the connection
+    adapter.close()
+
+
+def test_nested_adapter():
+    a0 = VISAAdapter(SIM_RESOURCE, visa_library='@sim', read_termination="\n")
+    a = VISAAdapter(a0)
+    assert a.resource_name == SIM_RESOURCE
+    assert a.connection == a0.connection
+    assert a.manager == a0.manager
 
 
-@pytest.mark.parametrize("query_delay", (0, 10))
-def test_nested_adapter(query_delay):
-    a0 = VISAAdapter(SIM_RESOURCE, visa_library='@sim', read_termination="\n",
-                     query_delay=query_delay)
-    a = VISAAdapter(a0)
+def test_nested_adapter_query_delay():
+    query_delay = 10
+    with pytest.warns(FutureWarning, match="query_delay"):
+        a0 = VISAAdapter(SIM_RESOURCE, visa_library='@sim', read_termination="\n",
+                         query_delay=query_delay)
+        a = VISAAdapter(a0)
     assert a.resource_name == SIM_RESOURCE
     assert a.connection == a0.connection
     assert a.query_delay == query_delay
@@ -73,6 +96,26 @@ def test_open_gpib():
     assert a.resource_name == "GPIB0::5::INSTR"
 
 
+class TestClose:
+    @pytest.fixture
+    def adapterC(self):
+        return VISAAdapter(SIM_RESOURCE, visa_library='@sim')
+
+    def test_connection_session_closed(self, adapterC):
+        # Verify first, that it works before closing
+        assert adapterC.connection.session is not None
+        adapterC.close()
+        with pytest.raises(pyvisa.errors.InvalidSession, match="Invalid session"):
+            adapterC.connection.session
+
+    def test_manager_session_closed(self, adapterC):
+        # Verify first, that it works before closing
+        assert adapterC.manager.session is not None
+        adapterC.close()
+        with pytest.raises(pyvisa.errors.InvalidSession, match="Invalid session"):
+            adapterC.manager.session
+
+
 def test_write_read(adapter):
     adapter.write(":VOLT:IMM:AMPL?")
     assert float(adapter.read()) == 1
@@ -88,14 +131,28 @@ def test_write_bytes_read(adapter):
     assert adapter.read() == "SCPI,MOCK,VERSION_1.0"
 
 
-def test_write_read_bytes(adapter):
-    adapter.write("*IDN?")
-    assert adapter.read_bytes(22) == b"SCPI,MOCK,VERSION_1.0\n"
+class TestReadBytes:
+    @pytest.fixture()
+    def adapterR(self, adapter):
+        adapter.write("*IDN?")
+        yield adapter
 
+    def test_read_bytes(self, adapterR):
+        assert adapterR.read_bytes(22) == b"SCPI,MOCK,VERSION_1.0\n"
 
-def test_write_read_all_bytes(adapter):
-    adapter.write("*IDN?")
-    assert adapter.read_bytes(-1) == b"SCPI,MOCK,VERSION_1.0\n"
+    def test_read_all_bytes(self, adapterR):
+        assert adapterR.read_bytes(-1) == b"SCPI,MOCK,VERSION_1.0\n"
+
+    @pytest.mark.parametrize("count", (-1, 7))
+    def test_read_break_on_termchar(self, adapterR, count):
+        """Test read_bytes breaks on termchar."""
+        adapterR.connection.read_termination = ","
+        assert adapterR.read_bytes(count, break_on_termchar=True) == b"SCPI,"
+
+    def test_read_no_break_on_termchar(self, adapterR):
+        adapterR.connection.read_termination = ","
+        # `break_on_termchar=False` is default value
+        assert adapterR.read_bytes(-1) == b"SCPI,MOCK,VERSION_1.0\n"
 
 
 def test_visa_adapter(adapter):