def test_get_fields_type_ext_out():
    """Test for field type == ext_out, ensuring we add .CAPTURE to the end of the
    *ENUMS command"""
    conn = ControlConnection()
    cmd = GetFieldInfo("PCAP")
    assert conn.send(cmd) == b"PCAP.*?\n"

    # First yield, the response to "PCAP.*?"
    assert (conn.receive_bytes(b"!SAMPLES 9 ext_out samples\n.\n") ==
            b"*DESC.PCAP.SAMPLES?\n*ENUMS.PCAP.SAMPLES.CAPTURE?\n")

    # Responses to the *DESC and *ENUM commands
    responses = [
        b"OK =Number of gated samples in the current capture\n",
        b"!No\n!Value\n.\n",
    ]
    for response in responses:
        assert (
            conn.receive_bytes(response) == b""
        )  # Expect no bytes back as none of these trigger further commands

    assert get_responses(conn) == [(
        cmd,
        {
            "SAMPLES":
            FieldInfo(
                type="ext_out",
                subtype="samples",
                description="Number of gated samples in the current capture",
                labels=["No", "Value"],
            )
        },
    )]
def test_expected_exception_when_receive_without_send():
    """Test that calling receive_bytes() without first calling send() raises the
    expected exception"""

    conn = ControlConnection()
    with pytest.raises(NoContextAvailable):
        conn.receive_bytes(b"abc\n")
def test_get_block_info():
    conn = ControlConnection()
    cmd = GetBlockInfo()
    assert conn.send(cmd) == b"*BLOCKS?\n"

    # Respond to first yield, the return from the BLOCKS? command
    assert conn.receive_bytes(
        b"!PCAP 1\n!LUT 8\n.\n") == b"*DESC.PCAP?\n*DESC.LUT?\n"

    # First of the *DESC.{block}? yields
    assert (conn.receive_bytes(b"OK =Description for PCAP field\n") == b""
            )  # No data returned as there's still one outstanding request

    # Create an OrderedDict of the output to test key order - that won't happen
    # with a regular dict
    ordered_dict = OrderedDict([
        ("LUT", BlockInfo(number=8, description="Description for LUT field")),
        ("PCAP", BlockInfo(number=1,
                           description="Description for PCAP field")),
    ])

    # Second and last of the *DESC.{block}? yields - as this is the last response we
    # can call get_responses to also get the overall result
    assert not get_responses(conn)
    assert get_responses(conn, b"OK =Description for LUT field\n") == [
        (
            cmd,
            ordered_dict,
        ),
    ]
def test_get_pcap_bits_labels():
    """Simple working testcase for GetPcapBitsLabels"""

    # PandA's return data when it receives "PCAP.*?"
    PCAP_RETURN = [
        "!BITS2 12 ext_out bits",
        "!SHIFT_SUM 4 param uint",
        "!BITS0 10 ext_out bits",
        ".",
    ]

    # PandA's return data when it receives "PCAP.BITS2.BITS?"
    BITS2_RETURN = [
        "!PCOMP2.OUT", "!PGEN1.ACTIVE", "!PGEN2.ACTIVE", "!PULSE1.OUT", "."
    ]

    # PandA's return data when it receives "PCAP.BITS0.BITS?"
    BITS0_RETURN = [
        "!SFP3_SYNC_IN.BIT8",
        "!SFP3_SYNC_IN.BIT9",
        "!SFP3_SYNC_IN.BIT10",
        ".",
    ]

    conn = ControlConnection()
    cmd = GetPcapBitsLabels()
    assert conn.send(cmd) == b"PCAP.*?\n"

    # First yield, requesting response for PCAP.*?
    response_bytes = "\n".join(PCAP_RETURN).encode() + b"\n"
    assert conn.receive_bytes(
        response_bytes) == b"PCAP.BITS2.BITS?\nPCAP.BITS0.BITS?\n"

    # First of the .BITS? yields
    response_bytes = "\n".join(BITS2_RETURN).encode() + b"\n"
    assert (conn.receive_bytes(response_bytes) == b""
            )  # No data returned as there's still one outstanding request

    # Second of the .BITS? yields - as this is the last response we can call
    # get_responses to also get the overall result
    response_bytes = "\n".join(BITS0_RETURN).encode() + b"\n"

    assert not get_responses(conn)
    assert get_responses(conn, response_bytes) == [(
        cmd,
        {
            "PCAP.BITS0": [
                "SFP3_SYNC_IN.BIT8",
                "SFP3_SYNC_IN.BIT9",
                "SFP3_SYNC_IN.BIT10",
            ],
            "PCAP.BITS2": [
                "PCOMP2.OUT",
                "PGEN1.ACTIVE",
                "PGEN2.ACTIVE",
                "PULSE1.OUT",
            ],
        },
    )]
def test_connection_gets_muliline():
    conn = ControlConnection()
    cmd = Get("SEQ1.TABLE")
    assert conn.send(cmd) == b"SEQ1.TABLE?\n"
    assert not get_responses(conn, b"!1048576\n!0\n!10")
    assert get_responses(conn, b"00\n!1000\n.\n") == [
        (cmd, ["1048576", "0", "1000", "1000"])
    ]
def test_put_fails_with_single_line_exception():
    conn = ControlConnection()
    cmd = Put("PCAP.blah", "something")
    assert conn.send(cmd) == b"PCAP.blah=something\n"
    assert get_responses(conn, b"ERR No such field\n") == [(
        cmd,
        ACommandException(
            "Put(field='PCAP.blah', value='something') -> ERR No such field"),
    )]
def test_save():
    conn = ControlConnection()
    cmd = GetState()
    assert (
        conn.send(cmd) ==
        b"*CHANGES.ATTR?\n*CHANGES.CONFIG?\n*CHANGES.TABLE?\n*CHANGES.METADATA?\n"
    )
    response_bytes = "\n".join(STATE_RESPONSES).encode() + b"\n"
    assert (conn.receive_bytes(response_bytes[:107]) ==
            b"Table.B?\nMultiLineMeta1?\nMultiLineMeta2?\n")
    assert not get_responses(conn)
    assert get_responses(conn, response_bytes[107:]) == [(cmd, STATE_SAVEFILE)]
def test_get_block_info_skip_description():
    """Test that the skip_description flag causes GetBlockInfo to not retrieve
    descriptions"""
    conn = ControlConnection()
    cmd = GetBlockInfo(skip_description=True)
    assert conn.send(cmd) == b"*BLOCKS?\n"

    ordered_dict = OrderedDict([
        ("PCAP", BlockInfo(number=1, description=None)),
    ])
    # Only a yield for the BLOCKS.* request, not for description as well
    assert get_responses(conn, b"!PCAP 1\n.\n") == [(cmd, ordered_dict)]
def test_put_fails_with_multiline_exception():
    conn = ControlConnection()
    cmd = Put("PCAP.blah", "something")
    assert conn.send(cmd) == b"PCAP.blah=something\n"
    assert get_responses(conn,
                         b"!This is bad\n!Very bad\n!Don't do this\n.\n") == [(
                             cmd,
                             ACommandException("""\
Put(field='PCAP.blah', value='something') ->
    This is bad
    Very bad
    Don't do this"""),
                         )]
def test_get_block_info_error():
    """Test that any errors from *BLOCKS command are correctly reported"""
    conn = ControlConnection()
    cmd = GetBlockInfo()
    assert conn.send(cmd) == b"*BLOCKS?\n"

    # Provide error from PandA server
    assert conn.receive_bytes(b"ERR Cannot read blocks\n") == b""

    assert get_responses(conn) == [(
        cmd,
        ACommandException(
            "GetBlockInfo(skip_description=False) -> ERR Cannot read blocks"),
    )]
def test_get_fields_non_existant_field():
    """Test that querying for an unknown field returns a sensible error"""
    conn = ControlConnection()
    cmd = GetFieldInfo("FOO")
    assert conn.send(cmd) == b"FOO.*?\n"

    # Provide the error string the PandA would provide
    assert conn.receive_bytes(b"ERR No such block\n") == b""

    assert get_responses(conn) == [(
        cmd,
        ACommandException(
            "GetFieldInfo(block='FOO', skip_description=False) -> ERR No such block"
        ),
    )]
def test_get_pcap_bits_labels_no_bits_fields():
    """Test we get no response when no BITS fields are returned by the PandA"""

    # PandA's return data when it receives "PCAP.*?"
    PCAP_RETURN = [
        "!SHIFT_SUM 4 param uint",
        "!ACTIVE 5 bit_out",
        "!ENABLE 0 bit_mux",
        ".",
    ]
    conn = ControlConnection()
    cmd = GetPcapBitsLabels()
    assert conn.send(cmd) == b"PCAP.*?\n"

    # As there are no BITS fields in the PCAP return, expect no response
    response_bytes = "\n".join(PCAP_RETURN).encode() + b"\n"
    assert conn.receive_bytes(response_bytes) == b""
def test_get_block_info_desc_err():
    """Test when the DESC command returns an error"""
    conn = ControlConnection()
    cmd = GetBlockInfo()
    assert conn.send(cmd) == b"*BLOCKS?\n"

    # Respond to first yield, the return from the BLOCKS? command
    assert conn.receive_bytes(b"!PCAP 1\n.\n") == b"*DESC.PCAP?\n"

    # First of the *DESC.{block}? yields
    assert (conn.receive_bytes(b"ERR could not get description\n") == b""
            )  # No data returned as there's still one outstanding request

    assert get_responses(conn) == [(
        cmd,
        ACommandException(
            "GetBlockInfo(skip_description=False) -> ERR could not get description"
        ),
    )]
def test_get_fields_no_enums_no_description():
    """Test that a field that does not have any enum labels does not attempt to
    retrieve them, and also does not retrieve a description."""
    conn = ControlConnection()
    cmd = GetFieldInfo("LVDSIN", skip_description=True)
    assert conn.send(cmd) == b"LVDSIN.*?\n"

    assert get_responses(conn, b"!VAL 0 bit_out\n.\n") == [(
        cmd,
        {
            "VAL":
            FieldInfo(
                type="bit_out",
                subtype=None,
                description=None,
                labels=None,
            ),
        },
    )]
def test_get_fields():
    conn = ControlConnection()
    cmd = GetFieldInfo("LUT")
    assert conn.send(cmd) == b"LUT.*?\n"

    # First yield, the response to "LUT.*?"
    assert (
        conn.receive_bytes(b"!TYPEA 5 param enum\n!INPA 1 bit_mux\n.\n") ==
        b"*DESC.LUT.INPA?\n*ENUMS.LUT.INPA?\n*DESC.LUT.TYPEA?\n*ENUMS.LUT.TYPEA?\n"
    )

    # Responses to the 2 *DESC and 2 *ENUM commands
    responses = [
        b"OK =Input A\n",
        b"!TTLIN1.VAL\n!LVDSIN1.VAL\n.\n"
        b"OK =Source of the value of A for calculation\n",
        b"!Input-Level\n!Pulse-On-Rising-Edge\n.\n",
    ]
    for response in responses:
        assert (
            conn.receive_bytes(response) == b""
        )  # Expect no bytes back as none of these trigger further commands

    assert get_responses(conn) == [(
        cmd,
        {
            "INPA":
            FieldInfo(
                type="bit_mux",
                subtype=None,
                description="Input A",
                labels=["TTLIN1.VAL", "LVDSIN1.VAL"],
            ),
            "TYPEA":
            FieldInfo(
                type="param",
                subtype="enum",
                description="Source of the value of A for calculation",
                labels=["Input-Level", "Pulse-On-Rising-Edge"],
            ),
        },
    )]
def test_get_fields_no_enums():
    """Test that a field that does not have any enum labels does not attempt to
    retrieve them"""
    conn = ControlConnection()
    cmd = GetFieldInfo("LVDSIN")
    assert conn.send(cmd) == b"LVDSIN.*?\n"

    # First yield, the response to "LVDSIN.*?"
    assert conn.receive_bytes(b"!VAL 0 bit_out\n.\n") == b"*DESC.LVDSIN.VAL?\n"

    assert get_responses(conn, b"OK =LVDS input value\n") == [(
        cmd,
        {
            "VAL":
            FieldInfo(
                type="bit_out",
                subtype=None,
                description="LVDS input value",
                labels=None,
            ),
        },
    )]
def test_get_fields_type_skip_description():
    """Test that the skip_description flag causes no description to be retrieved
    for the field"""
    conn = ControlConnection()
    cmd = GetFieldInfo("PCAP", True)
    assert conn.send(cmd) == b"PCAP.*?\n"

    assert (conn.receive_bytes(b"!SAMPLES 9 ext_out samples\n.\n") ==
            b"*ENUMS.PCAP.SAMPLES.CAPTURE?\n")

    assert conn.receive_bytes(b"!No\n!Value\n.\n") == b""

    assert get_responses(conn) == [(
        cmd,
        {
            "SAMPLES":
            FieldInfo(
                type="ext_out",
                subtype="samples",
                description=None,
                labels=["No", "Value"],
            )
        },
    )]
def test_connect_put_multi_line():
    conn = ControlConnection()
    cmd = Put("SEQ1.TABLE", ["1048576", "0", "1000", "1000"])
    assert conn.send(cmd) == b"SEQ1.TABLE<\n1048576\n0\n1000\n1000\n\n"
    assert get_responses(conn, b"OK\n") == [(cmd, None)]
def test_connect_put_single_line():
    conn = ControlConnection()
    cmd = Put("PCAP.TRIG", "PULSE1.OUT")
    assert conn.send(cmd) == b"PCAP.TRIG=PULSE1.OUT\n"
    assert get_responses(conn, b"OK\n") == [(cmd, None)]
def test_load():
    conn = ControlConnection()
    cmd = SetState(STATE_SAVEFILE)
    assert conn.send(cmd) == ("\n".join(STATE_SAVEFILE)).encode() + b"\n"
    response_bytes = "\n".join(["OK"] * 7).encode() + b"\n"
    assert get_responses(conn, response_bytes) == [(cmd, None)]
def test_connection_gets_split_value():
    conn = ControlConnection()
    cmd = Get("PCAP.ACTIVE")
    assert conn.send(cmd) == b"PCAP.ACTIVE?\n"
    assert not get_responses(conn, b"OK =1")
    assert get_responses(conn, b"\nAnySpamWeLike") == [(cmd, "1")]
def get_responses(conn: ControlConnection, received=b""):
    assert not conn.receive_bytes(received)
    return list(conn.responses())