rust/vmstate: Add unit test for vmstate_of macro

The vmstate has too many combinations of VMStateFlags and VMStateField.
Currently, the best way to test is to ensure that the Rust vmstate
definition is consistent with the (possibly corresponding) C version.

Add a unit test to cover some patterns accepted by vmstate_of macro,
which correspond to the following C version macros:
 * VMSTATE_U16
 * VMSTATE_UNUSED
 * VMSTATE_VARRAY_UINT16_UNSAFE
 * VMSTATE_VARRAY_MULTIPLY

Note: Because vmstate_info_* are defined in vmstate-types.c, it's
necessary to link libmigration to rust unit tests. In the future,
maybe it's possible to spilt libmigration from rust_qemu_api_objs.

Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
Link: https://lore.kernel.org/r/20250318130219.1799170-12-zhao1.liu@intel.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Zhao Liu 2025-03-18 21:02:15 +08:00 committed by Paolo Bonzini
parent b131003721
commit 1998502196
3 changed files with 139 additions and 2 deletions

View file

@ -58,7 +58,8 @@ rust_qemu_api_objs = static_library(
libchardev.extract_all_objects(recursive: false),
libcrypto.extract_all_objects(recursive: false),
libauthz.extract_all_objects(recursive: false),
libio.extract_all_objects(recursive: false)])
libio.extract_all_objects(recursive: false),
libmigration.extract_all_objects(recursive: false)])
rust_qemu_api_deps = declare_dependency(
dependencies: [
qom_ss.dependencies(),
@ -71,7 +72,7 @@ rust_qemu_api_deps = declare_dependency(
test('rust-qemu-api-integration',
executable(
'rust-qemu-api-integration',
'tests/tests.rs',
files('tests/tests.rs', 'tests/vmstate_tests.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: ['--test'],
install: false,

View file

@ -17,6 +17,8 @@ use qemu_api::{
zeroable::Zeroable,
};
mod vmstate_tests;
// Test that macros can compile.
pub static VMSTATE: VMStateDescription = VMStateDescription {
name: c_str!("name").as_ptr(),

View file

@ -0,0 +1,134 @@
// Copyright (C) 2025 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, mem::size_of, slice};
use qemu_api::{
bindings::{vmstate_info_int8, vmstate_info_uint8, vmstate_info_unused_buffer, VMStateFlags},
c_str,
vmstate::{VMStateDescription, VMStateField},
vmstate_fields, vmstate_of, vmstate_unused,
zeroable::Zeroable,
};
const FOO_ARRAY_MAX: usize = 3;
// =========================== Test VMSTATE_FOOA ===========================
// Test the use cases of the vmstate macro, corresponding to the following C
// macro variants:
// * VMSTATE_FOOA:
// - VMSTATE_U16
// - VMSTATE_UNUSED
// - VMSTATE_VARRAY_UINT16_UNSAFE
// - VMSTATE_VARRAY_MULTIPLY
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
struct FooA {
arr: [u8; FOO_ARRAY_MAX],
num: u16,
arr_mul: [i8; FOO_ARRAY_MAX],
num_mul: u32,
elem: i8,
}
static VMSTATE_FOOA: VMStateDescription = VMStateDescription {
name: c_str!("foo_a").as_ptr(),
version_id: 1,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_of!(FooA, elem),
vmstate_unused!(size_of::<i64>()),
vmstate_of!(FooA, arr[0 .. num]).with_version_id(0),
vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]),
},
..Zeroable::ZERO
};
#[test]
fn test_vmstate_uint16() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 1st VMStateField ("elem") in VMSTATE_FOOA (corresponding to VMSTATE_UINT16)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(),
b"elem\0"
);
assert_eq!(foo_fields[0].offset, 16);
assert_eq!(foo_fields[0].num_offset, 0);
assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int8 });
assert_eq!(foo_fields[0].version_id, 0);
assert_eq!(foo_fields[0].size, 1);
assert_eq!(foo_fields[0].num, 0);
assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE);
assert!(foo_fields[0].vmsd.is_null());
assert!(foo_fields[0].field_exists.is_none());
}
#[test]
fn test_vmstate_unused() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 2nd VMStateField ("unused") in VMSTATE_FOOA (corresponding to VMSTATE_UNUSED)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(),
b"unused\0"
);
assert_eq!(foo_fields[1].offset, 0);
assert_eq!(foo_fields[1].num_offset, 0);
assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_unused_buffer });
assert_eq!(foo_fields[1].version_id, 0);
assert_eq!(foo_fields[1].size, 8);
assert_eq!(foo_fields[1].num, 0);
assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_BUFFER);
assert!(foo_fields[1].vmsd.is_null());
assert!(foo_fields[1].field_exists.is_none());
}
#[test]
fn test_vmstate_varray_uint16_unsafe() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 3rd VMStateField ("arr") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_UINT16_UNSAFE)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(),
b"arr\0"
);
assert_eq!(foo_fields[2].offset, 0);
assert_eq!(foo_fields[2].num_offset, 4);
assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 });
assert_eq!(foo_fields[2].version_id, 0);
assert_eq!(foo_fields[2].size, 1);
assert_eq!(foo_fields[2].num, 0);
assert_eq!(foo_fields[2].flags, VMStateFlags::VMS_VARRAY_UINT16);
assert!(foo_fields[2].vmsd.is_null());
assert!(foo_fields[2].field_exists.is_none());
}
#[test]
fn test_vmstate_varray_multiply() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 4th VMStateField ("arr_mul") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_MULTIPLY)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(),
b"arr_mul\0"
);
assert_eq!(foo_fields[3].offset, 6);
assert_eq!(foo_fields[3].num_offset, 12);
assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_int8 });
assert_eq!(foo_fields[3].version_id, 0);
assert_eq!(foo_fields[3].size, 1);
assert_eq!(foo_fields[3].num, 16);
assert_eq!(
foo_fields[3].flags.0,
VMStateFlags::VMS_VARRAY_UINT32.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0
);
assert!(foo_fields[3].vmsd.is_null());
assert!(foo_fields[3].field_exists.is_none());
// The last VMStateField in VMSTATE_FOOA.
assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END);
}