Add a couple of integration tests as rust/tests/test_async_*.rs. They
are very similar to the tests for the synchronous API.
---
 rust/Cargo.toml                               |   1 +
 rust/tests/test_async_100_handle.rs           |  25 +++
 rust/tests/test_async_200_connect_command.rs  |  26 +++
 rust/tests/test_async_210_opt_abort.rs        |  32 ++++
 rust/tests/test_async_220_opt_list.rs         |  86 ++++++++++
 rust/tests/test_async_230_opt_info.rs         | 122 ++++++++++++++
 rust/tests/test_async_240_opt_list_meta.rs    | 150 ++++++++++++++++++
 .../test_async_245_opt_list_meta_queries.rs   |  94 +++++++++++
 rust/tests/test_async_250_opt_set_meta.rs     | 125 +++++++++++++++
 .../test_async_255_opt_set_meta_queries.rs    | 110 +++++++++++++
 rust/tests/test_async_400_pread.rs            |  40 +++++
 rust/tests/test_async_405_pread_structured.rs |  84 ++++++++++
 rust/tests/test_async_410_pwrite.rs           |  59 +++++++
 rust/tests/test_async_460_block_status.rs     |  98 ++++++++++++
 rust/tests/test_async_620_stats.rs            |  69 ++++++++
 15 files changed, 1121 insertions(+)
 create mode 100644 rust/tests/test_async_100_handle.rs
 create mode 100644 rust/tests/test_async_200_connect_command.rs
 create mode 100644 rust/tests/test_async_210_opt_abort.rs
 create mode 100644 rust/tests/test_async_220_opt_list.rs
 create mode 100644 rust/tests/test_async_230_opt_info.rs
 create mode 100644 rust/tests/test_async_240_opt_list_meta.rs
 create mode 100644 rust/tests/test_async_245_opt_list_meta_queries.rs
 create mode 100644 rust/tests/test_async_250_opt_set_meta.rs
 create mode 100644 rust/tests/test_async_255_opt_set_meta_queries.rs
 create mode 100644 rust/tests/test_async_400_pread.rs
 create mode 100644 rust/tests/test_async_405_pread_structured.rs
 create mode 100644 rust/tests/test_async_410_pwrite.rs
 create mode 100644 rust/tests/test_async_460_block_status.rs
 create mode 100644 rust/tests/test_async_620_stats.rs

diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index a9b5988..c49f9f2 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -59,3 +59,4 @@ anyhow = "1.0.72"
 once_cell = "1.18.0"
 pretty-hex = "0.3.0"
 tempfile = "3.6.0"
+tokio = { version = "1.29.1", default-features = false, features = 
["rt-multi-thread", "macros"] }
diff --git a/rust/tests/test_async_100_handle.rs 
b/rust/tests/test_async_100_handle.rs
new file mode 100644
index 0000000..e50bad9
--- /dev/null
+++ b/rust/tests/test_async_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)]
+
+#[tokio::test]
+async fn test_async_nbd_handle_new() {
+    let _ = libnbd::AsyncHandle::new().unwrap();
+}
diff --git a/rust/tests/test_async_200_connect_command.rs 
b/rust/tests/test_async_200_connect_command.rs
new file mode 100644
index 0000000..2f4e7cc
--- /dev/null
+++ b/rust/tests/test_async_200_connect_command.rs
@@ -0,0 +1,26 @@
+// 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)]
+
+#[tokio::test]
+async fn test_async_connect_command() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.connect_command(&["nbdkit", "-s", "--exit-with-parent", "-v", "null"])
+        .await
+        .unwrap();
+}
diff --git a/rust/tests/test_async_210_opt_abort.rs 
b/rust/tests/test_async_210_opt_abort.rs
new file mode 100644
index 0000000..e85fa0c
--- /dev/null
+++ b/rust/tests/test_async_210_opt_abort.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)]
+
+#[tokio::test]
+async fn test_opt_abort() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&["nbdkit", "-s", "--exit-with-parent", "-v", "null"])
+        .await
+        .unwrap();
+    assert_eq!(nbd.get_protocol().unwrap(), b"newstyle-fixed");
+    assert!(nbd.get_structured_replies_negotiated().unwrap());
+
+    nbd.opt_abort().await.unwrap();
+    assert!(nbd.aio_is_closed());
+}
diff --git a/rust/tests/test_async_220_opt_list.rs 
b/rust/tests/test_async_220_opt_list.rs
new file mode 100644
index 0000000..5d86a1d
--- /dev/null
+++ b/rust/tests/test_async_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, 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 }
+    }
+
+    async fn connect(
+        &self,
+        mode: u8,
+        expected_exports: &[&str],
+    ) -> libnbd::SharedResult<()> {
+        let nbd = libnbd::AsyncHandle::new().unwrap();
+        nbd.set_opt_mode(true).unwrap();
+        nbd.connect_command(&[
+            "nbdkit",
+            "-s",
+            "--exit-with-parent",
+            "-v",
+            "sh",
+            &self.script_path,
+            format!("mode={mode}").as_str(),
+        ])
+        .await
+        .unwrap();
+
+        // Collect all exports in this list.
+        let exports = Arc::new(Mutex::new(Vec::new()));
+        let exports_clone = exports.clone();
+        nbd.opt_list(move |name, _| {
+            exports_clone
+                .lock()
+                .unwrap()
+                .push(String::from_utf8(name.to_owned()).unwrap());
+            0
+        })
+        .await?;
+        let exports = Arc::try_unwrap(exports).unwrap().into_inner().unwrap();
+        assert_eq!(exports.len(), expected_exports.len());
+        for (export, &expected) in exports.iter().zip(expected_exports) {
+            assert_eq!(export, expected);
+        }
+        Ok(())
+    }
+}
+
+#[tokio::test]
+async fn test_opt_list() {
+    let conn_tester = ConnTester::new();
+    assert!(conn_tester.connect(0, &[]).await.is_err());
+    assert!(conn_tester.connect(1, &["a", "b"]).await.is_ok());
+    assert!(conn_tester.connect(2, &[]).await.is_ok());
+    assert!(conn_tester.connect(3, &["a"]).await.is_ok());
+}
diff --git a/rust/tests/test_async_230_opt_info.rs 
b/rust/tests/test_async_230_opt_info.rs
new file mode 100644
index 0000000..174200b
--- /dev/null
+++ b/rust/tests/test_async_230_opt_info.rs
@@ -0,0 +1,122 @@
+// 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;
+
+#[tokio::test]
+async 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::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "sh",
+        script_path,
+    ])
+    .await
+    .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().await.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().await.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().await.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().await.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().await.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().await.is_err());
+    assert_eq!(nbd.get_size().unwrap(), 4);
+    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
+    nbd.disconnect(None).await.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::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "sh",
+        &script_path,
+    ])
+    .await
+    .unwrap();
+    nbd.add_meta_context("x-unexpected:bogus").unwrap();
+    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).is_err());
+    nbd.opt_info().await.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().await.unwrap();
+    assert!(!nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
+}
diff --git a/rust/tests/test_async_240_opt_list_meta.rs 
b/rust/tests/test_async_240_opt_list_meta.rs
new file mode 100644
index 0000000..fade096
--- /dev/null
+++ b/rust/tests/test_async_240_opt_list_meta.rs
@@ -0,0 +1,150 @@
+// 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, 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,
+}
+
+async fn list_meta_ctxs(
+    nbd: &libnbd::AsyncHandle,
+) -> libnbd::SharedResult<CtxInfo> {
+    let info = Arc::new(Mutex::new(CtxInfo {
+        has_alloc: false,
+        count: 0,
+    }));
+    let info_clone = info.clone();
+    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
+    })
+    .await?;
+    let info = Arc::try_unwrap(info).unwrap().into_inner().unwrap();
+    Ok(info)
+}
+
+#[tokio::test]
+async fn test_async_opt_list_meta() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "memory",
+        "size=1M",
+    ])
+    .await
+    .unwrap();
+
+    // First pass: empty query should give at least "base:allocation".
+    let info = list_meta_ctxs(&nbd).await.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).await.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).await.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().await.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).await.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).await.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::AsyncHandle::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",
+    ])
+    .await
+    .unwrap();
+    let bytes = nbd.stats_bytes_sent();
+    if let Ok(info) = list_meta_ctxs(&nbd).await {
+        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.
+    nbd.opt_structured_reply().await.unwrap();
+    let info = list_meta_ctxs(&nbd).await.unwrap();
+    assert!(info.count >= 1);
+    assert!(info.has_alloc);
+}
diff --git a/rust/tests/test_async_245_opt_list_meta_queries.rs 
b/rust/tests/test_async_245_opt_list_meta_queries.rs
new file mode 100644
index 0000000..5e6b01f
--- /dev/null
+++ b/rust/tests/test_async_245_opt_list_meta_queries.rs
@@ -0,0 +1,94 @@
+// 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, 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,
+}
+
+async fn list_meta_ctxs(
+    nbd: &libnbd::AsyncHandle,
+    queries: &[&[u8]],
+) -> libnbd::SharedResult<CtxInfo> {
+    let info = Arc::new(Mutex::new(CtxInfo {
+        has_alloc: false,
+        count: 0,
+    }));
+    let info_clone = info.clone();
+    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
+    })
+    .await?;
+    let info = Arc::try_unwrap(info).unwrap().into_inner().unwrap();
+    Ok(info)
+}
+
+#[tokio::test]
+async fn test_async_opt_list_meta_queries() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "memory",
+        "size=1M",
+    ])
+    .await
+    .unwrap();
+
+    // First pass: empty query should give at least "base:allocation".
+    nbd.add_meta_context("x-nosuch:").unwrap();
+    let info = list_meta_ctxs(&nbd, &[]).await.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:"]).await.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])
+            .await
+            .unwrap(),
+        CtxInfo {
+            count: 1,
+            has_alloc: true
+        }
+    );
+}
diff --git a/rust/tests/test_async_250_opt_set_meta.rs 
b/rust/tests/test_async_250_opt_set_meta.rs
new file mode 100644
index 0000000..7a401bc
--- /dev/null
+++ b/rust/tests/test_async_250_opt_set_meta.rs
@@ -0,0 +1,125 @@
+// 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, 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,
+}
+
+async fn set_meta_ctxs(
+    nbd: &libnbd::AsyncHandle,
+) -> libnbd::SharedResult<CtxInfo> {
+    let info = Arc::new(Mutex::new(CtxInfo {
+        has_alloc: false,
+        count: 0,
+    }));
+    let info_clone = info.clone();
+    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
+    })
+    .await?;
+    let info = Arc::try_unwrap(info).unwrap().into_inner().unwrap();
+    Ok(info)
+}
+
+#[tokio::test]
+async fn test_async_opt_set_meta() {
+    let nbd = libnbd::AsyncHandle::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",
+    ])
+    .await
+    .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.
+    nbd.opt_structured_reply().await.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).await.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).await.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).await.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().await.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).await.is_err());
+    assert_eq!(nbd.get_size().unwrap(), 1048576);
+    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
+}
diff --git a/rust/tests/test_async_255_opt_set_meta_queries.rs 
b/rust/tests/test_async_255_opt_set_meta_queries.rs
new file mode 100644
index 0000000..652e760
--- /dev/null
+++ b/rust/tests/test_async_255_opt_set_meta_queries.rs
@@ -0,0 +1,110 @@
+// 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, 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,
+}
+
+async fn set_meta_ctxs_queries(
+    nbd: &libnbd::AsyncHandle,
+    queries: &[impl AsRef<[u8]>],
+) -> libnbd::SharedResult<CtxInfo> {
+    let info = Arc::new(Mutex::new(CtxInfo {
+        has_alloc: false,
+        count: 0,
+    }));
+    let info_clone = info.clone();
+    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
+    })
+    .await?;
+    let info = Arc::try_unwrap(info).unwrap().into_inner().unwrap();
+    Ok(info)
+}
+
+#[tokio::test]
+async fn test_async_opt_set_meta_queries() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.set_opt_mode(true).unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "memory",
+        "size=1M",
+    ])
+    .await
+    .unwrap();
+
+    // nbdkit does not match wildcard for SET, even though it does for LIST
+    assert_eq!(
+        set_meta_ctxs_queries(&nbd, &["base:"]).await.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]).await.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]
+        )
+        .await
+        .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().await.unwrap();
+    assert!(nbd.can_meta_context(CONTEXT_BASE_ALLOCATION).unwrap());
+}
diff --git a/rust/tests/test_async_400_pread.rs 
b/rust/tests/test_async_400_pread.rs
new file mode 100644
index 0000000..16a6f89
--- /dev/null
+++ b/rust/tests/test_async_400_pread.rs
@@ -0,0 +1,40 @@
+// 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;
+
+#[tokio::test]
+async fn test_async_pread() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "pattern",
+        "size=1M",
+    ])
+    .await
+    .unwrap();
+
+    let mut buf = [0; 512];
+    nbd.pread(&mut buf, 0, None).await.unwrap();
+    assert_eq!(buf.as_slice(), PATTERN.as_slice());
+}
diff --git a/rust/tests/test_async_405_pread_structured.rs 
b/rust/tests/test_async_405_pread_structured.rs
new file mode 100644
index 0000000..cf8fa28
--- /dev/null
+++ b/rust/tests/test_async_405_pread_structured.rs
@@ -0,0 +1,84 @@
+// 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;
+
+#[tokio::test]
+async fn test_async_pread_structured() {
+    let nbd = libnbd::AsyncHandle::new().unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "pattern",
+        "size=1M",
+    ])
+    .await
+    .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,
+    )
+    .await
+    .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),
+    )
+    .await
+    .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),
+        )
+        .await;
+    assert_eq!(res.unwrap_err().errno(), Some(42));
+}
diff --git a/rust/tests/test_async_410_pwrite.rs 
b/rust/tests/test_async_410_pwrite.rs
new file mode 100644
index 0000000..9c4e4b8
--- /dev/null
+++ b/rust/tests/test_async_410_pwrite.rs
@@ -0,0 +1,59 @@
+// 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};
+
+#[tokio::test]
+async fn test_async_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::AsyncHandle::new().unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "file",
+        data_file_path.to_str().unwrap(),
+    ])
+    .await
+    .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).await.unwrap();
+
+    let mut buf_2 = [0; 512];
+    nbd.pread(&mut buf_2, 0, None).await.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_async_460_block_status.rs 
b/rust/tests/test_async_460_block_status.rs
new file mode 100644
index 0000000..c3536e6
--- /dev/null
+++ b/rust/tests/test_async_460_block_status.rs
@@ -0,0 +1,98 @@
+// 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, Mutex};
+
+async fn block_status_get_entries(
+    nbd: &libnbd::AsyncHandle,
+    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,
+    )
+    .await
+    .unwrap();
+    Arc::try_unwrap(entries)
+        .unwrap()
+        .into_inner()
+        .unwrap()
+        .unwrap()
+}
+
+#[tokio::test]
+async fn test_async_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::AsyncHandle::new().unwrap();
+    nbd.add_meta_context(libnbd::CONTEXT_BASE_ALLOCATION)
+        .unwrap();
+    nbd.connect_command(&[
+        "nbdkit",
+        "-s",
+        "--exit-with-parent",
+        "-v",
+        "sh",
+        script_path,
+    ])
+    .await
+    .unwrap();
+
+    assert_eq!(
+        block_status_get_entries(&nbd, 65536, 0, None)
+            .await
+            .as_slice(),
+        &[8192, 0, 8192, 1, 16384, 3, 16384, 2, 16384, 0,]
+    );
+
+    assert_eq!(
+        block_status_get_entries(&nbd, 1024, 32256, None)
+            .await
+            .as_slice(),
+        &[512, 3, 16384, 2]
+    );
+
+    assert_eq!(
+        block_status_get_entries(
+            &nbd,
+            1024,
+            32256,
+            Some(libnbd::CmdFlag::REQ_ONE)
+        )
+        .await
+        .as_slice(),
+        &[512, 3]
+    );
+}
diff --git a/rust/tests/test_async_620_stats.rs 
b/rust/tests/test_async_620_stats.rs
new file mode 100644
index 0000000..fb03232
--- /dev/null
+++ b/rust/tests/test_async_620_stats.rs
@@ -0,0 +1,69 @@
+// 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)]
+
+#[tokio::test]
+async fn test_async_stats() {
+    let nbd = libnbd::AsyncHandle::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"])
+        .await
+        .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).await.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.disconnect(None).await.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);
+}
-- 
2.41.0


_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to