On Wed, Aug 02, 2023 at 12:40:50PM +0000, Tage Johansson wrote:
> diff --git a/rust/run-tests.sh b/rust/run-tests.sh
> index 7a0bc85..005000e 100755
> --- a/rust/run-tests.sh
> +++ b/rust/run-tests.sh
> @@ -1,6 +1,6 @@
>  #!/bin/bash -
>  # nbd client library in userspace
> -# Copyright Red Hat
> +# Copyright Tage Johansson
>  #
>  # This library is free software; you can redistribute it and/or
>  # modify it under the terms of the GNU Lesser General Public
> @@ -21,4 +21,6 @@
>  set -e
>  set -x
>  
> -cargo test
> +requires nbdkit --version
> +
> +$CARGO test -- --nocapture

The change to this file should be lifted into patch 1.

I'm a bit surprised that $CARGO works (see also my comment on patch 1
about using @CARGO@ here).  Is $CARGO set in the Makefile.am?

> diff --git a/rust/tests/nbdkit_pattern/mod.rs 
> b/rust/tests/nbdkit_pattern/mod.rs
> new file mode 100644
> index 0000000..5f4069e
> --- /dev/null
> +++ b/rust/tests/nbdkit_pattern/mod.rs
> @@ -0,0 +1,28 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +use once_cell::sync::Lazy;
> +
> +/// The byte pattern as described in nbdkit-PATTERN-plugin(1).
> +pub static PATTERN: Lazy<Vec<u8>> = Lazy::new(|| {
> +    let mut pattern = Vec::with_capacity(512);
> +    for i in 0u64..64 {
> +        pattern.extend_from_slice((i * 8).to_be_bytes().as_slice());
> +    }
> +    assert_eq!(pattern.len(), 512);
> +    pattern
> +});
> diff --git a/rust/tests/test_100_handle.rs b/rust/tests/test_100_handle.rs
> new file mode 100644
> index 0000000..85e18aa
> --- /dev/null
> +++ b/rust/tests/test_100_handle.rs
> @@ -0,0 +1,25 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +//! Just check that we can link with libnbd and create a handle.
> +
> +#![deny(warnings)]
> +
> +#[test]
> +fn test_nbd_handle_new() {
> +    let _ = libnbd::Handle::new().unwrap();
> +}
> diff --git a/rust/tests/test_110_defaults.rs b/rust/tests/test_110_defaults.rs
> new file mode 100644
> index 0000000..ac1e29c
> --- /dev/null
> +++ b/rust/tests/test_110_defaults.rs
> @@ -0,0 +1,33 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +#[test]
> +fn test_defaults() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +
> +    assert!(nbd.get_export_name().unwrap().is_empty());
> +    assert!(!nbd.get_full_info().unwrap());
> +    assert_eq!(nbd.get_tls(), libnbd::Tls::Disable);
> +    assert!(nbd.get_request_structured_replies());
> +    assert!(nbd.get_request_meta_context().unwrap());
> +    assert!(nbd.get_request_block_size().unwrap());
> +    assert!(nbd.get_pread_initialize());
> +    assert!(nbd.get_handshake_flags().is_all());
> +    assert!(!nbd.get_opt_mode());
> +}
> diff --git a/rust/tests/test_120_set_non_defaults.rs 
> b/rust/tests/test_120_set_non_defaults.rs
> new file mode 100644
> index 0000000..7f1edab
> --- /dev/null
> +++ b/rust/tests/test_120_set_non_defaults.rs
> @@ -0,0 +1,53 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +#[test]
> +fn test_set_non_defaults() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +
> +    nbd.set_export_name("name").unwrap();
> +    assert_eq!(nbd.get_export_name().unwrap(), b"name");
> +
> +    nbd.set_full_info(true).unwrap();
> +    assert!(nbd.get_full_info().unwrap());
> +
> +    if nbd.supports_tls() {
> +        nbd.set_tls(libnbd::Tls::Allow).unwrap();
> +        assert_eq!(nbd.get_tls(), libnbd::Tls::Allow);
> +    }
> +
> +    nbd.set_request_structured_replies(false).unwrap();
> +    assert!(!nbd.get_request_structured_replies());
> +
> +    nbd.set_request_meta_context(false).unwrap();
> +    assert!(!nbd.get_request_meta_context().unwrap());
> +
> +    nbd.set_request_block_size(false).unwrap();
> +    assert!(!nbd.get_request_block_size().unwrap());
> +
> +    nbd.set_pread_initialize(false).unwrap();
> +    assert!(!nbd.get_pread_initialize());
> +
> +    nbd.set_handshake_flags(libnbd::HandshakeFlag::empty())
> +        .unwrap();
> +    assert!(nbd.get_handshake_flags().is_empty());
> +
> +    nbd.set_opt_mode(true).unwrap();
> +    assert!(nbd.get_opt_mode());
> +}
> diff --git a/rust/tests/test_130_private_data.rs 
> b/rust/tests/test_130_private_data.rs
> new file mode 100644
> index 0000000..bb507fb
> --- /dev/null
> +++ b/rust/tests/test_130_private_data.rs
> @@ -0,0 +1,28 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +#[test]
> +fn test_private_data() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +
> +    assert_eq!(nbd.get_private_data(), 0);
> +    assert_eq!(nbd.set_private_data(42), 0);
> +    assert_eq!(nbd.set_private_data(314), 42);
> +    assert_eq!(nbd.get_private_data(), 314);
> +}
> diff --git a/rust/tests/test_140_explicit_close.rs 
> b/rust/tests/test_140_explicit_close.rs
> new file mode 100644
> index 0000000..59ab382
> --- /dev/null
> +++ b/rust/tests/test_140_explicit_close.rs
> @@ -0,0 +1,31 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +mod test_log;
> +
> +use test_log::DEBUG_LOGGER;
> +
> +#[test]
> +fn test_private_data() {
> +    DEBUG_LOGGER.init();
> +
> +    let nbd = libnbd::Handle::new().unwrap();
> +    drop(nbd);
> +    assert!(DEBUG_LOGGER.contains("closing handle"));
> +}
> diff --git a/rust/tests/test_200_connect_command.rs 
> b/rust/tests/test_200_connect_command.rs
> new file mode 100644
> index 0000000..8338650
> --- /dev/null
> +++ b/rust/tests/test_200_connect_command.rs
> @@ -0,0 +1,32 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +
> +#[test]
> +fn test_connect_command() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "null",
> +    ])
> +    .unwrap();
> +}
> diff --git a/rust/tests/test_210_opt_abort.rs 
> b/rust/tests/test_210_opt_abort.rs
> new file mode 100644
> index 0000000..c59805f
> --- /dev/null
> +++ b/rust/tests/test_210_opt_abort.rs
> @@ -0,0 +1,31 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +#[test]
> +fn test_opt_abort() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&["nbdkit", "-s", "--exit-with-parent", "-v", 
> "null"])
> +        .unwrap();
> +    assert_eq!(nbd.get_protocol().unwrap(), b"newstyle-fixed");
> +    assert!(nbd.get_structured_replies_negotiated().unwrap());
> +
> +    nbd.opt_abort().unwrap();
> +    assert!(nbd.aio_is_closed());
> +}
> diff --git a/rust/tests/test_220_opt_list.rs b/rust/tests/test_220_opt_list.rs
> new file mode 100644
> index 0000000..180a95b
> --- /dev/null
> +++ b/rust/tests/test_220_opt_list.rs
> @@ -0,0 +1,86 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use std::env;
> +use std::os::unix::ffi::OsStringExt as _;
> +use std::path::Path;
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +/// Test different types of connections.
> +struct ConnTester {
> +    script_path: String,
> +}
> +
> +impl ConnTester {
> +    fn new() -> Self {
> +        let srcdir = env::var("srcdir").unwrap();
> +        let srcdir = Path::new(&srcdir);
> +        let script_path = srcdir.join("../tests/opt-list.sh");
> +        let script_path =
> +            
> String::from_utf8(script_path.into_os_string().into_vec()).unwrap();
> +        Self { script_path }
> +    }
> +
> +    fn connect(
> +        &self,
> +        mode: u8,
> +        expected_exports: &[&str],
> +    ) -> libnbd::Result<()> {
> +        let nbd = libnbd::Handle::new().unwrap();
> +        nbd.set_opt_mode(true).unwrap();
> +        nbd.connect_command(&[
> +            "nbdkit",
> +            "-s",
> +            "--exit-with-parent",
> +            "-v",
> +            "sh",
> +            &self.script_path,
> +            &format!("mode={mode}"),
> +        ])
> +        .unwrap();
> +
> +        // Collect all exports in this list.
> +        let exports = Arc::new(Mutex::new(Vec::new()));
> +        let exports_clone = exports.clone();
> +        let count = nbd.opt_list(move |name, _| {
> +            exports_clone
> +                .lock()
> +                .unwrap()
> +                .push(String::from_utf8(name.to_owned()).unwrap());
> +            0
> +        })?;
> +        let exports = 
> Arc::into_inner(exports).unwrap().into_inner().unwrap();
> +        assert_eq!(exports.len(), count as usize);
> +        assert_eq!(exports.len(), expected_exports.len());
> +        for (export, &expected) in exports.iter().zip(expected_exports) {
> +            assert_eq!(export, expected);
> +        }
> +        Ok(())
> +    }
> +}
> +
> +#[test]
> +fn test_opt_list() {
> +    let conn_tester = ConnTester::new();
> +    assert!(conn_tester.connect(0, &[]).is_err());
> +    assert!(conn_tester.connect(1, &["a", "b"]).is_ok());
> +    assert!(conn_tester.connect(2, &[]).is_ok());
> +    assert!(conn_tester.connect(3, &["a"]).is_ok());
> +}
> diff --git a/rust/tests/test_230_opt_info.rs b/rust/tests/test_230_opt_info.rs
> new file mode 100644
> index 0000000..00e5fb0
> --- /dev/null
> +++ b/rust/tests/test_230_opt_info.rs
> @@ -0,0 +1,120 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use libnbd::CONTEXT_BASE_ALLOCATION;
> +use std::env;
> +use std::path::Path;
> +
> +#[test]
> +fn test_opt_info() {
> +    let srcdir = env::var("srcdir").unwrap();
> +    let srcdir = Path::new(&srcdir);
> +    let script_path = srcdir.join("../tests/opt-info.sh");
> +    let script_path = script_path.to_str().unwrap();
> +
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "sh",
> +        script_path,
> +    ])
> +    .unwrap();
> +    nbd.add_meta_context(CONTEXT_BASE_ALLOCATION).unwrap();
> +
> +    // No size, flags, or meta-contexts yet
> +    assert!(nbd.get_size().is_err());
> +    assert!(nbd.is_read_only().is_err());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +
> +    // info with no prior name gets info on ""
> +    assert!(nbd.opt_info().is_ok());
> +    assert_eq!(nbd.get_size().unwrap(), 0);
> +    assert!(nbd.is_read_only().unwrap());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // changing export wipes out prior info
> +    nbd.set_export_name("b").unwrap();
> +    assert!(nbd.get_size().is_err());
> +    assert!(nbd.is_read_only().is_err());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +
> +    // info on something not present fails
> +    nbd.set_export_name("a").unwrap();
> +    assert!(nbd.opt_info().is_err());
> +
> +    // info for a different export, with automatic meta_context disabled
> +    nbd.set_export_name("b").unwrap();
> +    nbd.set_request_meta_context(false).unwrap();
> +    nbd.opt_info().unwrap();
> +    // idempotent name change is no-op
> +    nbd.set_export_name("b").unwrap();
> +    assert_eq!(nbd.get_size().unwrap(), 1);
> +    assert!(!nbd.is_read_only().unwrap());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +    nbd.set_request_meta_context(true).unwrap();
> +
> +    // go on something not present
> +    nbd.set_export_name("a").unwrap();
> +    assert!(nbd.opt_go().is_err());
> +    assert!(nbd.get_size().is_err());
> +    assert!(nbd.is_read_only().is_err());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +
> +    // go on a valid export
> +    nbd.set_export_name("good").unwrap();
> +    nbd.opt_go().unwrap();
> +    assert_eq!(nbd.get_size().unwrap(), 4);
> +    assert!(nbd.is_read_only().unwrap());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // now info is no longer valid, but does not wipe data
> +    assert!(nbd.set_export_name("a").is_err());
> +    assert_eq!(nbd.get_export_name().unwrap(), b"good");
> +    assert!(nbd.opt_info().is_err());
> +    assert_eq!(nbd.get_size().unwrap(), 4);
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +    nbd.shutdown(None).unwrap();
> +
> +    // Another connection. This time, check that SET_META triggered by 
> opt_info
> +    // persists through nbd_opt_go with set_request_meta_context disabled.
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "sh",
> +        &script_path,
> +    ])
> +    .unwrap();
> +    nbd.add_meta_context("x-unexpected:bogus").unwrap();
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +    nbd.opt_info().unwrap();
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +    nbd.set_request_meta_context(false).unwrap();
> +    // Adding to the request list now won't matter
> +    nbd.add_meta_context(CONTEXT_BASE_ALLOCATION).unwrap();
> +    nbd.opt_go().unwrap();
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +}
> diff --git a/rust/tests/test_240_opt_list_meta.rs 
> b/rust/tests/test_240_opt_list_meta.rs
> new file mode 100644
> index 0000000..5598458
> --- /dev/null
> +++ b/rust/tests/test_240_opt_list_meta.rs
> @@ -0,0 +1,147 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +/// A struct with information about listed meta contexts.
> +#[derive(Debug, Clone, PartialEq, Eq)]
> +struct CtxInfo {
> +    /// Whether the meta context "base:alloc" is listed.
> +    has_alloc: bool,
> +    /// The number of listed meta contexts.
> +    count: u32,
> +}
> +
> +fn list_meta_ctxs(nbd: &libnbd::Handle) -> libnbd::Result<CtxInfo> {
> +    let info = Arc::new(Mutex::new(CtxInfo {
> +        has_alloc: false,
> +        count: 0,
> +    }));
> +    let info_clone = info.clone();
> +    let replies = nbd.opt_list_meta_context(move |ctx| {
> +        let mut info = info_clone.lock().unwrap();
> +        info.count += 1;
> +        if ctx == libnbd::CONTEXT_BASE_ALLOCATION {
> +            info.has_alloc = true;
> +        }
> +        0
> +    })?;
> +    let info = Arc::into_inner(info).unwrap().into_inner().unwrap();
> +    assert_eq!(info.count, replies);
> +    Ok(info)
> +}
> +
> +#[test]
> +fn test_opt_list_meta() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "memory",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    // First pass: empty query should give at least "base:allocation".
> +    let info = list_meta_ctxs(&nbd).unwrap();
> +    assert!(info.count >= 1);
> +    assert!(info.has_alloc);
> +    let max = info.count;
> +
> +    // Second pass: bogus query has no response.
> +    nbd.add_meta_context("x-nosuch:").unwrap();
> +    assert_eq!(
> +        list_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +
> +    // Third pass: specific query should have one match.
> +    nbd.add_meta_context("base:allocation").unwrap();
> +    assert_eq!(nbd.get_nr_meta_contexts().unwrap(), 2);
> +    assert_eq!(nbd.get_meta_context(1).unwrap(), b"base:allocation");
> +    assert_eq!(
> +        list_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 1,
> +            has_alloc: true
> +        }
> +    );
> +
> +    // Fourth pass: opt_list_meta_context is stateless, so it should
> +    // not wipe status learned during opt_info
> +    assert!(nbd.can_meta_context("base:allocation").is_err());
> +    assert!(nbd.get_size().is_err());
> +    nbd.opt_info().unwrap();
> +    assert_eq!(nbd.get_size().unwrap(), 1048576);
> +    assert!(nbd.can_meta_context("base:allocation").unwrap());
> +    nbd.clear_meta_contexts().unwrap();
> +    nbd.add_meta_context("x-nosuch:").unwrap();
> +    assert_eq!(
> +        list_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +    assert_eq!(nbd.get_size().unwrap(), 1048576);
> +    assert!(nbd.can_meta_context("base:allocation").unwrap());
> +
> +    // Final pass: "base:" query should get at least "base:allocation"
> +    nbd.add_meta_context("base:").unwrap();
> +    let info = list_meta_ctxs(&nbd).unwrap();
> +    assert!(info.count >= 1);
> +    assert!(info.count <= max);
> +    assert!(info.has_alloc);
> +
> +    // Repeat but this time without structured replies. Deal gracefully
> +    // with older servers that don't allow the attempt.
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.set_request_structured_replies(false).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "memory",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +    let bytes = nbd.stats_bytes_sent();
> +    if let Ok(info) = list_meta_ctxs(&nbd) {
> +        assert!(info.count >= 1);
> +        assert!(info.has_alloc)
> +    } else {
> +        assert!(nbd.stats_bytes_sent() > bytes);
> +        // ignoring failure from old server
> +    }
> +
> +    // Now enable structured replies, and a retry should pass.
> +    assert!(nbd.opt_structured_reply().unwrap());
> +    let info = list_meta_ctxs(&nbd).unwrap();
> +    assert!(info.count >= 1);
> +    assert!(info.has_alloc);
> +}
> diff --git a/rust/tests/test_245_opt_list_meta_queries.rs 
> b/rust/tests/test_245_opt_list_meta_queries.rs
> new file mode 100644
> index 0000000..da5c674
> --- /dev/null
> +++ b/rust/tests/test_245_opt_list_meta_queries.rs
> @@ -0,0 +1,93 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +/// A struct with information about listed meta contexts.
> +#[derive(Debug, Clone, PartialEq, Eq)]
> +struct CtxInfo {
> +    /// Whether the meta context "base:allocation" is listed.
> +    has_alloc: bool,
> +    /// The number of listed meta contexts.
> +    count: u32,
> +}
> +
> +fn list_meta_ctxs(
> +    nbd: &libnbd::Handle,
> +    queries: &[&[u8]],
> +) -> libnbd::Result<CtxInfo> {
> +    let info = Arc::new(Mutex::new(CtxInfo {
> +        has_alloc: false,
> +        count: 0,
> +    }));
> +    let info_clone = info.clone();
> +    let replies = nbd.opt_list_meta_context_queries(queries, move |ctx| {
> +        let mut info = info_clone.lock().unwrap();
> +        info.count += 1;
> +        if ctx == libnbd::CONTEXT_BASE_ALLOCATION {
> +            info.has_alloc = true;
> +        }
> +        0
> +    })?;
> +    let info = Arc::into_inner(info).unwrap().into_inner().unwrap();
> +    assert_eq!(info.count, replies);
> +    Ok(info)
> +}
> +
> +#[test]
> +fn test_opt_list_meta_queries() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "memory",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    // First pass: empty query should give at least "base:allocation".
> +    nbd.add_meta_context("x-nosuch:").unwrap();
> +    let info = list_meta_ctxs(&nbd, &[]).unwrap();
> +    assert!(info.count >= 1);
> +    assert!(info.has_alloc);
> +
> +    // Second pass: bogus query has no response.
> +    nbd.clear_meta_contexts().unwrap();
> +    assert_eq!(
> +        list_meta_ctxs(&nbd, &[b"x-nosuch:"]).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +
> +    // Third pass: specific query should have one match.
> +    assert_eq!(
> +        list_meta_ctxs(&nbd, &[b"x-nosuch:", 
> libnbd::CONTEXT_BASE_ALLOCATION])
> +            .unwrap(),
> +        CtxInfo {
> +            count: 1,
> +            has_alloc: true
> +        }
> +    );
> +}
> diff --git a/rust/tests/test_250_opt_set_meta.rs 
> b/rust/tests/test_250_opt_set_meta.rs
> new file mode 100644
> index 0000000..c7a8144
> --- /dev/null
> +++ b/rust/tests/test_250_opt_set_meta.rs
> @@ -0,0 +1,123 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use libnbd::CONTEXT_BASE_ALLOCATION;
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +/// A struct with information about set meta contexts.
> +#[derive(Debug, Clone, PartialEq, Eq)]
> +struct CtxInfo {
> +    /// Whether the meta context "base:allocation" is set.
> +    has_alloc: bool,
> +    /// The number of set meta contexts.
> +    count: u32,
> +}
> +
> +fn set_meta_ctxs(nbd: &libnbd::Handle) -> libnbd::Result<CtxInfo> {
> +    let info = Arc::new(Mutex::new(CtxInfo {
> +        has_alloc: false,
> +        count: 0,
> +    }));
> +    let info_clone = info.clone();
> +    let replies = nbd.opt_set_meta_context(move |ctx| {
> +        let mut info = info_clone.lock().unwrap();
> +        info.count += 1;
> +        if ctx == CONTEXT_BASE_ALLOCATION {
> +            info.has_alloc = true;
> +        }
> +        0
> +    })?;
> +    let info = Arc::into_inner(info).unwrap().into_inner().unwrap();
> +    assert_eq!(info.count, replies);
> +    Ok(info)
> +}
> +
> +#[test]
> +fn test_opt_set_meta() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.set_request_structured_replies(false).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "memory",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    // No contexts negotiated yet; can_meta should be error if any requested
> +    assert!(!nbd.get_structured_replies_negotiated().unwrap());
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +    nbd.add_meta_context(CONTEXT_BASE_ALLOCATION).unwrap();
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +
> +    // SET cannot succeed until SR is negotiated.
> +    assert!(nbd.opt_structured_reply().unwrap());
> +    assert!(nbd.get_structured_replies_negotiated().unwrap());
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
> +
> +    // nbdkit does not match wildcard for SET, even though it does for LIST
> +    nbd.clear_meta_contexts().unwrap();
> +    nbd.add_meta_context("base:").unwrap();
> +    assert_eq!(
> +        set_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Negotiating with no contexts is not an error, but selects nothing
> +    nbd.clear_meta_contexts().unwrap();
> +    assert_eq!(
> +        set_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +
> +    // Request 2 with expectation of 1; with set_request_meta_context off
> +    nbd.add_meta_context("x-nosuch:context").unwrap();
> +    nbd.add_meta_context(CONTEXT_BASE_ALLOCATION).unwrap();
> +    nbd.set_request_meta_context(false).unwrap();
> +    assert_eq!(
> +        set_meta_ctxs(&nbd).unwrap(),
> +        CtxInfo {
> +            count: 1,
> +            has_alloc: true
> +        }
> +    );
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Transition to transmission phase; our last set should remain active
> +    nbd.clear_meta_contexts().unwrap();
> +    nbd.add_meta_context("x-nosuch:context").unwrap();
> +    nbd.opt_go().unwrap();
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Now too late to set; but should not lose earlier state
> +    assert!(set_meta_ctxs(&nbd).is_err());
> +    assert_eq!(nbd.get_size().unwrap(), 1048576);
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +}
> diff --git a/rust/tests/test_255_opt_set_meta_queries.rs 
> b/rust/tests/test_255_opt_set_meta_queries.rs
> new file mode 100644
> index 0000000..143a2f1
> --- /dev/null
> +++ b/rust/tests/test_255_opt_set_meta_queries.rs
> @@ -0,0 +1,109 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use libnbd::CONTEXT_BASE_ALLOCATION;
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +/// A struct with information about set meta contexts.
> +#[derive(Debug, Clone, PartialEq, Eq)]
> +struct CtxInfo {
> +    /// Whether the meta context "base:allocation" is set.
> +    has_alloc: bool,
> +    /// The number of set meta contexts.
> +    count: u32,
> +}
> +
> +fn set_meta_ctxs_queries(
> +    nbd: &libnbd::Handle,
> +    queries: &[impl AsRef<[u8]>],
> +) -> libnbd::Result<CtxInfo> {
> +    let info = Arc::new(Mutex::new(CtxInfo {
> +        has_alloc: false,
> +        count: 0,
> +    }));
> +    let info_clone = info.clone();
> +    let replies = nbd.opt_set_meta_context_queries(queries, move |ctx| {
> +        let mut info = info_clone.lock().unwrap();
> +        info.count += 1;
> +        if ctx == CONTEXT_BASE_ALLOCATION {
> +            info.has_alloc = true;
> +        }
> +        0
> +    })?;
> +    let info = Arc::into_inner(info).unwrap().into_inner().unwrap();
> +    assert_eq!(info.count, replies);
> +    Ok(info)
> +}
> +
> +#[test]
> +fn test_opt_set_meta_queries() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.set_opt_mode(true).unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "memory",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    // nbdkit does not match wildcard for SET, even though it does for LIST
> +    assert_eq!(
> +        set_meta_ctxs_queries(&nbd, &["base:"]).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Negotiating with no contexts is not an error, but selects nothing
> +    // An explicit empty list overrides a non-empty implicit list.
> +    nbd.add_meta_context(CONTEXT_BASE_ALLOCATION).unwrap();
> +    assert_eq!(
> +        set_meta_ctxs_queries(&nbd, &[] as &[&str]).unwrap(),
> +        CtxInfo {
> +            count: 0,
> +            has_alloc: false
> +        }
> +    );
> +    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Request 2 with expectation of 1.
> +    assert_eq!(
> +        set_meta_ctxs_queries(
> +            &nbd,
> +            &[b"x-nosuch:context".as_slice(), CONTEXT_BASE_ALLOCATION]
> +        )
> +        .unwrap(),
> +        CtxInfo {
> +            count: 1,
> +            has_alloc: true
> +        }
> +    );
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +
> +    // Transition to transmission phase; our last set should remain active
> +    nbd.set_request_meta_context(false).unwrap();
> +    nbd.opt_go().unwrap();
> +    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
> +}
> diff --git a/rust/tests/test_300_get_size.rs b/rust/tests/test_300_get_size.rs
> new file mode 100644
> index 0000000..c830164
> --- /dev/null
> +++ b/rust/tests/test_300_get_size.rs
> @@ -0,0 +1,35 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +
> +#[test]
> +fn test_get_size() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "null",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    assert_eq!(nbd.get_size().unwrap(), 1048576);
> +}
> diff --git a/rust/tests/test_400_pread.rs b/rust/tests/test_400_pread.rs
> new file mode 100644
> index 0000000..93b826c
> --- /dev/null
> +++ b/rust/tests/test_400_pread.rs
> @@ -0,0 +1,39 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +mod nbdkit_pattern;
> +use nbdkit_pattern::PATTERN;
> +
> +#[test]
> +fn test_pread() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "pattern",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    let mut buf = [0; 512];
> +    nbd.pread(&mut buf, 0, None).unwrap();
> +    assert_eq!(buf.as_slice(), PATTERN.as_slice());
> +}
> diff --git a/rust/tests/test_405_pread_structured.rs 
> b/rust/tests/test_405_pread_structured.rs
> new file mode 100644
> index 0000000..39a7fd5
> --- /dev/null
> +++ b/rust/tests/test_405_pread_structured.rs
> @@ -0,0 +1,79 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +mod nbdkit_pattern;
> +use nbdkit_pattern::PATTERN;
> +
> +#[test]
> +fn test_pread_structured() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "pattern",
> +        "size=1M",
> +    ])
> +    .unwrap();
> +
> +    fn f(buf: &[u8], offset: u64, s: u32, err: &mut i32) {
> +        assert_eq!(*err, 0);
> +        *err = 42;
> +        assert_eq!(buf, PATTERN.as_slice());
> +        assert_eq!(offset, 0);
> +        assert_eq!(s, libnbd::READ_DATA);
> +    }
> +
> +    let mut buf = [0; 512];
> +    nbd.pread_structured(
> +        &mut buf,
> +        0,
> +        |b, o, s, e| {
> +            f(b, o, s, e);
> +            0
> +        },
> +        None,
> +    )
> +    .unwrap();
> +    assert_eq!(buf.as_slice(), PATTERN.as_slice());
> +
> +    nbd.pread_structured(
> +        &mut buf,
> +        0,
> +        |b, o, s, e| {
> +            f(b, o, s, e);
> +            0
> +        },
> +        Some(libnbd::CmdFlag::DF),
> +    )
> +    .unwrap();
> +    assert_eq!(buf.as_slice(), PATTERN.as_slice());
> +
> +    let res = nbd.pread_structured(
> +        &mut buf,
> +        0,
> +        |b, o, s, e| {
> +            f(b, o, s, e);
> +            -1
> +        },
> +        Some(libnbd::CmdFlag::DF),
> +    );
> +    assert_eq!(res.unwrap_err().errno(), Some(42));
> +}
> diff --git a/rust/tests/test_410_pwrite.rs b/rust/tests/test_410_pwrite.rs
> new file mode 100644
> index 0000000..d4d6b99
> --- /dev/null
> +++ b/rust/tests/test_410_pwrite.rs
> @@ -0,0 +1,58 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use std::fs::{self, File};
> +
> +#[test]
> +fn test_pwrite() {
> +    let tmp_dir = tempfile::tempdir().unwrap();
> +    let data_file_path = tmp_dir.path().join("pwrite_test.data");
> +    let data_file = File::create(&data_file_path).unwrap();
> +    data_file.set_len(512).unwrap();
> +    drop(data_file);
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "file",
> +        data_file_path.to_str().unwrap(),
> +    ])
> +    .unwrap();
> +
> +    let mut buf_1 = [0; 512];
> +    buf_1[10] = 0x01;
> +    buf_1[510] = 0x55;
> +    buf_1[511] = 0xAA;
> +
> +    let flags = Some(libnbd::CmdFlag::FUA);
> +    nbd.pwrite(&buf_1, 0, flags).unwrap();
> +
> +    let mut buf_2 = [0; 512];
> +    nbd.pread(&mut buf_2, 0, None).unwrap();
> +
> +    assert_eq!(buf_1, buf_2);
> +
> +    // Drop nbd before tmp_dir is dropped.
> +    drop(nbd);
> +
> +    let data_file_content = fs::read(&data_file_path).unwrap();
> +    assert_eq!(buf_1.as_slice(), data_file_content.as_slice());
> +}
> diff --git a/rust/tests/test_460_block_status.rs 
> b/rust/tests/test_460_block_status.rs
> new file mode 100644
> index 0000000..7cdcb34
> --- /dev/null
> +++ b/rust/tests/test_460_block_status.rs
> @@ -0,0 +1,92 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +use std::env;
> +use std::path::Path;
> +use std::sync::Arc;
> +use std::sync::Mutex;
> +
> +fn block_status_get_entries(
> +    nbd: &libnbd::Handle,
> +    count: u64,
> +    offset: u64,
> +    flags: Option<libnbd::CmdFlag>,
> +) -> Vec<u32> {
> +    let entries = Arc::new(Mutex::new(None));
> +    let entries_clone = entries.clone();
> +    nbd.block_status(
> +        count,
> +        offset,
> +        move |metacontext, _, entries, err| {
> +            assert_eq!(*err, 0);
> +            if metacontext == libnbd::CONTEXT_BASE_ALLOCATION {
> +                *entries_clone.lock().unwrap() = Some(entries.to_vec());
> +            }
> +            0
> +        },
> +        flags,
> +    )
> +    .unwrap();
> +    Arc::into_inner(entries)
> +        .unwrap()
> +        .into_inner()
> +        .unwrap()
> +        .unwrap()
> +}
> +
> +#[test]
> +fn test_block_status() {
> +    let srcdir = env::var("srcdir").unwrap();
> +    let srcdir = Path::new(&srcdir);
> +    let script_path = srcdir.join("../tests/meta-base-allocation.sh");
> +    let script_path = script_path.to_str().unwrap();
> +    let nbd = libnbd::Handle::new().unwrap();
> +    nbd.add_meta_context(libnbd::CONTEXT_BASE_ALLOCATION)
> +        .unwrap();
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "sh",
> +        script_path,
> +    ])
> +    .unwrap();
> +
> +    assert_eq!(
> +        block_status_get_entries(&nbd, 65536, 0, None).as_slice(),
> +        &[8192, 0, 8192, 1, 16384, 3, 16384, 2, 16384, 0,]
> +    );
> +
> +    assert_eq!(
> +        block_status_get_entries(&nbd, 1024, 32256, None).as_slice(),
> +        &[512, 3, 16384, 2]
> +    );
> +
> +    assert_eq!(
> +        block_status_get_entries(
> +            &nbd,
> +            1024,
> +            32256,
> +            Some(libnbd::CmdFlag::REQ_ONE)
> +        )
> +        .as_slice(),
> +        &[512, 3]
> +    );
> +}
> diff --git a/rust/tests/test_620_stats.rs b/rust/tests/test_620_stats.rs
> new file mode 100644
> index 0000000..134d59a
> --- /dev/null
> +++ b/rust/tests/test_620_stats.rs
> @@ -0,0 +1,75 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +#![deny(warnings)]
> +
> +
> +#[test]
> +fn test_stats() {
> +    let nbd = libnbd::Handle::new().unwrap();
> +
> +    // Pre-connection, stats start out at 0
> +    assert_eq!(nbd.stats_bytes_sent(), 0);
> +    assert_eq!(nbd.stats_chunks_sent(), 0);
> +    assert_eq!(nbd.stats_bytes_received(), 0);
> +    assert_eq!(nbd.stats_chunks_received(), 0);
> +
> +    // Connection performs handshaking, which increments stats.
> +    // The number of bytes/chunks here may grow over time as more features 
> get
> +    // automatically negotiated, so merely check that they are non-zero.
> +    nbd.connect_command(&[
> +        "nbdkit",
> +        "-s",
> +        "--exit-with-parent",
> +        "-v",
> +        "null",
> +    ])
> +    .unwrap();
> +
> +    let bs1 = nbd.stats_bytes_sent();
> +    let cs1 = nbd.stats_chunks_sent();
> +    let br1 = nbd.stats_bytes_received();
> +    let cr1 = nbd.stats_chunks_received();
> +    assert!(cs1 > 0);
> +    assert!(bs1 > cs1);
> +    assert!(cr1 > 0);
> +    assert!(br1 > cr1);
> +
> +    // A flush command should be one chunk out, one chunk back (even if
> +    // structured replies are in use)
> +    nbd.flush(None).unwrap();
> +    let bs2 = nbd.stats_bytes_sent();
> +    let cs2 = nbd.stats_chunks_sent();
> +    let br2 = nbd.stats_bytes_received();
> +    let cr2 = nbd.stats_chunks_received();
> +    assert_eq!(bs2, bs1 + 28);
> +    assert_eq!(cs2, cs1 + 1);
> +    assert_eq!(br2, br1 + 16); // assumes nbdkit uses simple reply
> +    assert_eq!(cr2, cr1 + 1);
> +
> +    // Stats are still readable after the connection closes; we don't know if
> +    // the server sent reply bytes to our NBD_CMD_DISC, so don't insist on 
> it.
> +    nbd.shutdown(None).unwrap();
> +    let bs3 = nbd.stats_bytes_sent();
> +    let cs3 = nbd.stats_chunks_sent();
> +    let br3 = nbd.stats_bytes_received();
> +    let cr3 = nbd.stats_chunks_received();
> +    assert!(bs3 > bs2);
> +    assert_eq!(cs3, cs2 + 1);
> +    assert!(br3 >= br2);
> +    assert!(cr3 == cr2 || cr3 == cr2 + 1);
> +}
> diff --git a/rust/tests/test_log/mod.rs b/rust/tests/test_log/mod.rs
> new file mode 100644
> index 0000000..8dbcd79
> --- /dev/null
> +++ b/rust/tests/test_log/mod.rs
> @@ -0,0 +1,86 @@
> +// libnbd Rust test case
> +// Copyright Tage Johansson
> +//
> +// This library is free software; you can redistribute it and/or
> +// modify it under the terms of the GNU Lesser General Public
> +// License as published by the Free Software Foundation; either
> +// version 2 of the License, or (at your option) any later version.
> +//
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +// Lesser General Public License for more details.
> +//
> +// You should have received a copy of the GNU Lesser General Public
> +// License along with this library; if not, write to the Free Software
> +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 
> USA
> +
> +//! This module provides facilities for capturing log output and asserting 
> that
> +//! it does or does not contain certain messages. The primary use of this 
> module
> +//! is to assert that certain libnbd operations are or are not performed.
> +
> +#![allow(unused)]
> +
> +use std::sync::Mutex;
> +
> +/// Logger that stores all debug messages in a list.
> +pub struct DebugLogger {
> +    /// All targets and messages logged. Wrapped in a mutex so that it can be
> +    /// updated with an imutable reference to self.
> +    entries: Mutex<Vec<(String, String)>>,
> +    is_initialized: Mutex<bool>,
> +}
> +
> +impl DebugLogger {
> +    const fn new() -> Self {
> +        Self {
> +            entries: Mutex::new(Vec::new()),
> +            is_initialized: Mutex::new(false),
> +        }
> +    }
> +
> +    /// Set this logger as the global logger.
> +    pub fn init(&'static self) {
> +        let mut is_initialized = self.is_initialized.lock().unwrap();
> +        if !*is_initialized {
> +            log::set_logger(self).unwrap();
> +            log::set_max_level(log::LevelFilter::Debug);
> +            *is_initialized = true;
> +        }
> +    }
> +
> +    /// Check wether a specific message has been logged.
> +    pub fn contains(&self, msg: &str) -> bool {
> +        self.entries.lock().unwrap().iter().any(|(_, x)| x == msg)
> +    }
> +
> +    /// Print all logged messages, in no particular order.
> +    ///
> +    /// Only for debug purposes. Remember to run cargo test with the `--
> +    /// --nocapture` arguments. That is, from the rust directory run:
> +    /// `./../run cargo test -- --nocapture`
> +    pub fn print_messages(&self) {
> +        for (target, msg) in self.entries.lock().unwrap().iter() {
> +            eprintln!("{target}: {msg}");
> +        }
> +    }
> +}
> +
> +/// A static global `DebugLogger`. Just call `.init()` on this to set it as 
> the
> +/// global logger.
> +pub static DEBUG_LOGGER: DebugLogger = DebugLogger::new();
> +
> +impl log::Log for DebugLogger {
> +    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
> +        metadata.level() == log::Level::Debug
> +    }
> +
> +    fn log(&self, record: &log::Record<'_>) {
> +        self.entries
> +            .lock()
> +            .unwrap()
> +            .push((record.target().to_string(), record.args().to_string()));
> +    }
> +
> +    fn flush(&self) {}
> +}

For the rest of this change:

Acked-by: Richard W.M. Jones <rjo...@redhat.com>

Can patch 5 be moved so it is the second in the series?  As far as I
can tell it doesn't seem to depend on either the asynch handle or
FnOnce callbacks, therefore shouldn't depend on patches 2, 3 & 4 at
all.

The advantage of moving it earlier in the series is that we will then
be able to fix up the remaining problems with patches 1 & 5 (which
will be 1 & 2) and send them upstream sooner than the rest of the
change.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-builder quickly builds VMs from scratch
http://libguestfs.org/virt-builder.1.html
_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to