Skip to content

Commit

Permalink
refactor: move a number of tests from C to Python
Browse files Browse the repository at this point in the history
Signed-off-by: William Henderson <william.henderson@nutanix.com>
  • Loading branch information
w-henderson committed Aug 16, 2023
1 parent 0a5f9ef commit 5e950e4
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 386 deletions.
17 changes: 15 additions & 2 deletions test/py/libvfio_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,14 @@ class vfio_user_device_feature_dma_logging_report(Structure):
]


class vfio_user_mig_data(Structure):
_pack_ = 1
_fields_ = [
("argsz", c.c_uint32),
("size", c.c_uint32)
]


class dma_sg_t(Structure):
_fields_ = [
("dma_addr", c.c_void_p),
Expand Down Expand Up @@ -711,10 +719,15 @@ def connect_sock():
return sock


def connect_client(ctx):
def connect_client(ctx, max_data_xfer_size=None):
sock = connect_sock()

json = b'{ "capabilities": { "max_msg_fds": 8 } }'
if max_data_xfer_size is None:
json = b'{ "capabilities": { "max_msg_fds": 8 } }'
else:
json = b'{ "capabilities": { "max_msg_fds": 8, "max_data_xfer_size": '\
+ str(max_data_xfer_size).encode("utf-8") + b' } }'

# struct vfio_user_version
payload = struct.pack("HH%dsc" % len(json), LIBVFIO_USER_MAJOR,
LIBVFIO_USER_MINOR, json, b'\0')
Expand Down
175 changes: 126 additions & 49 deletions test/py/test_migration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# Copyright (c) 2021 Nutanix Inc. All rights reserved.
# Copyright (c) 2023 Nutanix Inc. All rights reserved.
#
# Authors: John Levon <john.levon@nutanix.com>
# Authors: William Henderson <william.henderson@nutanix.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
Expand Down Expand Up @@ -29,11 +29,15 @@

from libvfio_user import *
from collections import deque
import ctypes
import errno

ctx = None
sock = None
current_state = None
path = []
read_data = None
write_data = None


UNREACHABLE_STATES = {
Expand Down Expand Up @@ -68,13 +72,31 @@ def migr_trans_cb(_ctx, state):


@read_data_cb_t
def migr_read_data_cb(_ctx, _buf, _count, _offset):
return
def migr_read_data_cb(_ctx, buf, count):
global read_data
length = min(count, len(read_data))
ctypes.memmove(buf, read_data, length)
read_data = None
return length


@write_data_cb_t
def migr_write_data_cb(_ctx, _buf, _count, _offset):
return
def migr_write_data_cb(_ctx, buf, count):
global write_data
write_data = bytes(count)
ctypes.memmove(write_data, buf, count)
return count


def transition_to_state(state, expect=0):
feature = vfio_user_device_feature(
argsz=len(vfio_user_device_feature()) +
len(vfio_user_device_feature_mig_state()),
flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE
)
payload = vfio_user_device_feature_mig_state(device_state=state)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
expect=expect)


def test_migration_setup():
Expand All @@ -97,40 +119,26 @@ def test_migration_setup():
ret = vfu_realize_ctx(ctx)
assert ret == 0

sock = connect_client(ctx)
sock = connect_client(ctx, 4)


def get_server_shortest_path(a, b, expectA=0, expectB=0):
global ctx, sock, path

feature = vfio_user_device_feature(
argsz=len(vfio_user_device_feature()) +
len(vfio_user_device_feature_mig_state()),
flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE
)
global path

if current_state == VFIO_USER_DEVICE_STATE_STOP_COPY and \
a == VFIO_USER_DEVICE_STATE_PRE_COPY:
# The transition STOP_COPY -> PRE_COPY is explicitly blocked so we
# advance one state to get around this in order to set up the test.
payload = vfio_user_device_feature_mig_state(
device_state=VFIO_USER_DEVICE_STATE_STOP
)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE,
bytes(feature) + bytes(payload))
transition_to_state(VFIO_USER_DEVICE_STATE_STOP)

payload = vfio_user_device_feature_mig_state(device_state=a)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
expect=expectA)
transition_to_state(a, expect=expectA)

if expectA != 0:
return None

path = []

payload = vfio_user_device_feature_mig_state(device_state=b)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
expect=expectB)
transition_to_state(b, expect=expectB)

return path.copy()

Expand All @@ -149,8 +157,6 @@ def test_migration_shortest_state_transition_paths():
by the implementation correctly follow these rules.
"""

global ctx, sock

# states (vertices)
V = {VFIO_USER_DEVICE_STATE_ERROR, VFIO_USER_DEVICE_STATE_STOP,
VFIO_USER_DEVICE_STATE_RUNNING, VFIO_USER_DEVICE_STATE_STOP_COPY,
Expand Down Expand Up @@ -218,38 +224,109 @@ def test_migration_shortest_state_transition_paths():


def test_migration_stop_copy_to_pre_copy_blocked():
global ctx, sock
transition_to_state(VFIO_USER_DEVICE_STATE_STOP_COPY)
transition_to_state(VFIO_USER_DEVICE_STATE_PRE_COPY, expect=errno.EINVAL)

feature = vfio_user_device_feature(
argsz=len(vfio_user_device_feature()) +
len(vfio_user_device_feature_mig_state()),
flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE

def test_migration_nonexistent_state():
transition_to_state(0xabcd, expect=errno.EINVAL)


def test_handle_mig_data_read():
global read_data

transition_to_state(VFIO_USER_DEVICE_STATE_RUNNING)

argsz = len(vfio_user_mig_data())

payload = vfio_user_mig_data(
argsz=argsz,
size=4
)
payload = vfio_user_device_feature_mig_state(
device_state=VFIO_USER_DEVICE_STATE_STOP_COPY

data = bytes([0, 1, 2, 3])

transition_to_state(VFIO_USER_DEVICE_STATE_PRE_COPY)
read_data = data
result = msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload)
assert len(result) == argsz + 4
assert result[argsz:argsz + 4] == data

transition_to_state(VFIO_USER_DEVICE_STATE_STOP_COPY)
read_data = data
result = msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload)
assert len(result) == argsz + 4
assert result[argsz:argsz + 4] == data


def test_handle_mig_data_read_too_long():
global read_data

transition_to_state(VFIO_USER_DEVICE_STATE_RUNNING)

payload = vfio_user_mig_data(
argsz=len(vfio_user_mig_data()),
size=8
)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload))

payload = vfio_user_device_feature_mig_state(
device_state=VFIO_USER_DEVICE_STATE_PRE_COPY
# When we set up the tests at the top of this file we specify that the max
# data transfer size is 4 bytes. Here we test to check that a transfer of 8
# bytes fails.

transition_to_state(VFIO_USER_DEVICE_STATE_PRE_COPY)
read_data = bytes([1, 2, 3, 4, 5, 6, 7, 8])
msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, expect=errno.EINVAL)


def test_handle_mig_data_read_invalid_state():
global read_data

payload = vfio_user_mig_data(
argsz=len(vfio_user_mig_data()),
size=4
)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
expect=22)

data = bytes([1, 2, 3, 4])

def test_migration_nonexistent_state():
global ctx, sock
transition_to_state(VFIO_USER_DEVICE_STATE_RUNNING)
read_data = data
msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, expect=errno.EINVAL)

feature = vfio_user_device_feature(
argsz=len(vfio_user_device_feature()) +
len(vfio_user_device_feature_mig_state()),
flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE
transition_to_state(VFIO_USER_DEVICE_STATE_STOP)
read_data = data
msg(ctx, sock, VFIO_USER_MIG_DATA_READ, payload, expect=errno.EINVAL)


def test_handle_mig_data_write():
payload = vfio_user_mig_data(
argsz=len(vfio_user_mig_data()) + 4,
size=4
)
payload = vfio_user_device_feature_mig_state(
device_state=0xabcd

data = bytes([1, 2, 3, 4])

transition_to_state(VFIO_USER_DEVICE_STATE_RESUMING)
msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data)
assert write_data == data


def test_handle_mig_data_write_invalid_state():
payload = vfio_user_mig_data(
argsz=len(vfio_user_mig_data()) + 4,
size=4
)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
expect=22)

data = bytes([1, 2, 3, 4])

transition_to_state(VFIO_USER_DEVICE_STATE_RUNNING)
msg(ctx, sock, VFIO_USER_MIG_DATA_WRITE, bytes(payload) + data,
expect=errno.EINVAL)


def test_device_feature_short_write():
payload = struct.pack("I", 8)

msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, payload, expect=errno.EINVAL)


def test_migration_cleanup():
Expand Down
54 changes: 54 additions & 0 deletions test/py/test_request_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,58 @@ def test_disconnected_socket_quiesce_busy(mock_quiesce):
mock_quiesce.assert_called_once_with(ctx)


@patch('libvfio_user.reset_cb')
@patch('libvfio_user.quiesce_cb', side_effect=fail_with_errno(errno.EBUSY))
@patch('libvfio_user.migr_trans_cb')
def test_reply_fail_quiesce_busy(mock_migr_trans_cb, mock_quiesce,
mock_reset):
"""Tests failing to reply and the quiesce callback returning EBUSY."""

global ctx, sock

def migr_trans_cb_side_effect(ctx, state):
sock.close()
return 0
mock_migr_trans_cb.side_effect = migr_trans_cb_side_effect

# change the state, it should close the socket causing the reply to fail
feature = vfio_user_device_feature(
argsz=len(vfio_user_device_feature()) +
len(vfio_user_device_feature_mig_state()),
flags=VFIO_DEVICE_FEATURE_SET | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE
)
payload = vfio_user_device_feature_mig_state(
device_state=VFIO_USER_DEVICE_STATE_STOP_COPY
)
msg(ctx, sock, VFIO_USER_DEVICE_FEATURE, bytes(feature) + bytes(payload),
rsp=False, busy=True)

# vfu_run_ctx will try to reset the context and to do that it needs to
# quiesce the device first
mock_quiesce.assert_called_once_with(ctx)

# vfu_run_ctx will be returning EBUSY and nothing should have happened
# until the device quiesces
for _ in range(0, 3):
vfu_run_ctx(ctx, errno.EBUSY)
mock_quiesce.assert_called_once_with(ctx)
mock_reset.assert_not_called()

ret = vfu_device_quiesced(ctx, 0)
assert ret == 0

# the device quiesced, reset should should happen now
mock_quiesce.assert_called_once_with(ctx)
mock_reset.assert_called_once_with(ctx, VFU_RESET_LOST_CONN)

try:
get_reply(sock)
except OSError as e:
assert e.errno == errno.EBADF
else:
assert False

vfu_run_ctx(ctx, errno.ENOTCONN)


# ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: #
Loading

0 comments on commit 5e950e4

Please sign in to comment.