On 08/31/22 16:39, Eric Blake wrote:
 As promised in the previous patch, also test the new
 nbd_opt_set_meta_context() API in Python, OCaml, and Golang.
 
 The fact that Go slams all unit tests into the same namespace is
 somewhat annoying; it required munging test 240 now that 250 wants to
 use similar global variables.
 ---
  python/t/250-opt-set-meta.py            | 126 ++++++++++++
  ocaml/tests/Makefile.am                 |   5 +-
  ocaml/tests/test_250_opt_set_meta.ml    | 150 ++++++++++++++
  tests/opt-set-meta.c                    |   1 +
  golang/Makefile.am                      |   3 +-
  golang/libnbd_240_opt_list_meta_test.go |  54 +++---
  golang/libnbd_250_opt_set_meta_test.go  | 248 ++++++++++++++++++++++++
  7 files changed, 558 insertions(+), 29 deletions(-)
  create mode 100644 python/t/250-opt-set-meta.py
  create mode 100644 ocaml/tests/test_250_opt_set_meta.ml
  create mode 100644 golang/libnbd_250_opt_set_meta_test.go
 
 diff --git a/python/t/250-opt-set-meta.py b/python/t/250-opt-set-meta.py
 new file mode 100644
 index 0000000..eb27998
 --- /dev/null
 +++ b/python/t/250-opt-set-meta.py
 @@ -0,0 +1,126 @@
 +# libnbd Python bindings
 +# Copyright (C) 2010-2022 Red Hat Inc.
 +#
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 2 of the License, or
 +# (at your option) any later version.
 +#
 +# This program 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 General Public License for more details.
 +#
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 +
 +import nbd
 +
 +
 +count = 0
 +seen = False
 +
 +
 +def f(user_data, name):
 +    global count
 +    global seen
 +    assert user_data == 42
 +    count = count + 1
 +    if name == nbd.CONTEXT_BASE_ALLOCATION:
 +        seen = True
 +
 +
 +def must_fail(f, *args, **kwds):
 +    try:
 +        f(*args, **kwds)
 +        assert False
 +    except nbd.Error:
 +        pass
 +
 +
 +# First process, with structured replies. Get into negotiating state.
 +h = nbd.NBD()
 +h.set_opt_mode(True)
 +h.connect_command(["nbdkit", "-s", "--exit-with-parent",
"-v",
 +                   "memory", "size=1M"])
 +
 +# No contexts negotiated yet; can_meta should be error if any requested */ 
I think the "*/" at the end of the line may be a remnant from the C code.
 +assert h.get_structured_replies_negotiated() is True
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is False
 +h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
 +must_fail(h.can_meta_context, nbd.CONTEXT_BASE_ALLOCATION) 
(I had to re-read the docs on can_meta_context.) Non-intuitive, but
valid, as far as I can tell.
 +
 +# FIXME: Once nbd_opt_structured_reply exists, check that set before
 +# SR fails server-side, then enable SR for rest of process.
 +
 +# nbdkit does not match wildcard for SET, even though it does for LIST
 +count = 0
 +seen = False
 +h.clear_meta_contexts()
 +h.add_meta_context("base:")
 +r = h.opt_set_meta_context(lambda *args: f(42, *args))
 +assert r == count
 +assert r == 0
 +assert not seen
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is False
 +
 +# Negotiating with no contexts is not an error, but selects nothing
 +count = 0
 +seen = False
 +h.clear_meta_contexts()
 +r = h.opt_set_meta_context(lambda *args: f(42, *args))
 +assert r == 0
 +assert r == count
 +assert not seen
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is False
 +
 +# Request 2 with expectation of 1; with set_request_meta_context off
 +count = 0
 +seen = False
 +h.add_meta_context("x-nosuch:context")
 +h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
 +h.set_request_meta_context(False)
 +r = h.opt_set_meta_context(lambda *args: f(42, *args))
 +assert r == 1
 +assert count == 1
 +assert seen
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is True
 +
 +# Transition to transmission phase; our last set should remain active
 +h.clear_meta_contexts()
 +h.add_meta_context("x-nosuch:context")
 +h.opt_go()
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is True
 +
 +# Now too late to set; but should not lose earlier state
 +count = 0
 +seen = False
 +must_fail(h.opt_set_meta_context, lambda *args: f(42, *args))
 +assert count == 0
 +assert not seen
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is True
 +
 +h.shutdown()
 +
 +# Second process, this time without structured replies server-side.
 +h = nbd.NBD()
 +h.set_opt_mode(True)
 +h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
 +h.connect_command(["nbdkit", "-s", "--exit-with-parent",
"-v",
 +                   "memory", "size=1M", "--no-sr"])
 +assert h.get_structured_replies_negotiated() is False
 +
 +# Expect server-side failure here
 +count = 0
 +seen = False
 +must_fail(h.opt_set_meta_context, lambda *args: f(42, *args))
 +assert count == 0
 +assert not seen
 +must_fail(h.can_meta_context, nbd.CONTEXT_BASE_ALLOCATION)
 +
 +# Even though can_meta fails after failed SET, it returns 0 after go
 +h.opt_go()
 +assert h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION) is False
 +
 +h.shutdown() 
Acked-by: Laszlo Ersek <lersek(a)redhat.com>
Laszlo
 diff --git a/ocaml/tests/Makefile.am b/ocaml/tests/Makefile.am
 index 24f626d..f8ce810 100644
 --- a/ocaml/tests/Makefile.am
 +++ b/ocaml/tests/Makefile.am
 @@ -1,5 +1,5 @@
  # nbd client library in userspace
 -# Copyright (C) 2013-2020 Red Hat Inc.
 +# Copyright (C) 2013-2022 Red Hat Inc.
  #
  # This library is free software; you can redistribute it and/or
  # modify it under the terms of the GNU Lesser General Public
 @@ -31,6 +31,7 @@ EXTRA_DIST = \
  	test_220_opt_list.ml \
  	test_230_opt_info.ml \
  	test_240_opt_list_meta.ml \
 +	test_250_opt_set_meta.ml \
  	test_300_get_size.ml \
  	test_400_pread.ml \
  	test_405_pread_structured.ml \
 @@ -59,6 +60,7 @@ tests_bc = \
  	test_220_opt_list.bc \
  	test_230_opt_info.bc \
  	test_240_opt_list_meta.bc \
 +	test_250_opt_set_meta.bc \
  	test_300_get_size.bc \
  	test_400_pread.bc \
  	test_405_pread_structured.bc \
 @@ -84,6 +86,7 @@ tests_opt = \
  	test_220_opt_list.opt \
  	test_230_opt_info.opt \
  	test_240_opt_list_meta.opt \
 +	test_250_opt_set_meta.opt \
  	test_300_get_size.opt \
  	test_400_pread.opt \
  	test_405_pread_structured.opt \
 diff --git a/ocaml/tests/test_250_opt_set_meta.ml b/ocaml/tests/test_250_opt_set_meta.ml
 new file mode 100644
 index 0000000..d74e9e0
 --- /dev/null
 +++ b/ocaml/tests/test_250_opt_set_meta.ml
 @@ -0,0 +1,150 @@
 +(* hey emacs, this is OCaml code: -*- tuareg -*- *)
 +(* libnbd OCaml test case
 + * Copyright (C) 2013-2022 Red Hat Inc.
 + *
 + * 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
 + *)
 +
 +let count = ref 0
 +let seen = ref false
 +let f user_data name =
 +  assert (user_data = 42);
 +  count := !count + 1;
 +  if name = NBD.context_base_allocation then
 +    seen := true;
 +  0
 +
 +let () =
 +  let nbd = NBD.create () in
 +  NBD.set_opt_mode nbd true;
 +  NBD.connect_command nbd
 +                      ["nbdkit"; "-s";
"--exit-with-parent"; "-v";
 +                       "memory"; "size=1M"];
 +
 +  (* No contexts negotiated yet; can_meta should be error if any requested *)
 +  let sr = NBD.get_structured_replies_negotiated nbd in
 +  assert sr;
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert (not m);
 +  NBD.add_meta_context nbd NBD.context_base_allocation;
 +  (try
 +     let _ = NBD.can_meta_context nbd NBD.context_base_allocation in
 +     assert false
 +   with
 +     NBD.Error (errstr, errno) -> ()
 +  );
 +
 +  (* FIXME: Once nbd_opt_structured_reply exists, check that set before
 +   * SR fails server-side, then enable SR for rest of process.
 +   *)
 +
 +  (* nbdkit does not match wildcard for SET, even though it does for LIST *)
 +  count := 0;
 +  seen := false;
 +  NBD.clear_meta_contexts nbd;
 +  NBD.add_meta_context nbd "base:";
 +  let r = NBD.opt_set_meta_context nbd (f 42) in
 +  assert (r = !count);
 +  assert (r = 0);
 +  assert (not !seen);
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert (not m);
 +
 +  (* Negotiating with no contexts is not an error, but selects nothing *)
 +  count := 0;
 +  seen := false;
 +  NBD.clear_meta_contexts nbd;
 +  let r = NBD.opt_set_meta_context nbd (f 42) in
 +  assert (r = 0);
 +  assert (r = !count);
 +  assert (not !seen);
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert (not m);
 +
 +  (* Request 2 with expectation of 1; with set_request_meta_context off *)
 +  count := 0;
 +  seen := false;
 +  NBD.add_meta_context nbd "x-nosuch:context";
 +  NBD.add_meta_context nbd NBD.context_base_allocation;
 +  NBD.set_request_meta_context nbd false;
 +  let r = NBD.opt_set_meta_context nbd (f 42) in
 +  assert (r = 1);
 +  assert (r = !count);
 +  assert !seen;
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert m;
 +
 +  (* Transition to transmission phase; our last set should remain active *)
 +  NBD.clear_meta_contexts nbd;
 +  NBD.add_meta_context nbd "x-nosuch:context";
 +  NBD.opt_go nbd;
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert m;
 +
 +  (* Now too late to set; but should not lose earlier state *)
 +  count := 0;
 +  seen := false;
 +  (try
 +     let _ = NBD.opt_set_meta_context nbd (f 42) in
 +     assert false
 +   with
 +     NBD.Error (errstr, errno) -> ()
 +  );
 +  assert (0 = !count);
 +  assert (not !seen);
 +  let s = NBD.get_size nbd in
 +  assert (s = 1048576_L);
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert m;
 +
 +  NBD.shutdown nbd;
 +
 +  (* Second process, this time without structured replies server-side. *)
 +  let nbd = NBD.create () in
 +  NBD.set_opt_mode nbd true;
 +  NBD.add_meta_context nbd NBD.context_base_allocation;
 +  NBD.connect_command nbd
 +                      ["nbdkit"; "-s";
"--exit-with-parent"; "-v";
 +                       "memory"; "size=1M"; "--no-sr"];
 +  let sr = NBD.get_structured_replies_negotiated nbd in
 +  assert (not sr);
 +
 +  (* Expect server-side failure here *)
 +  count := 0;
 +  seen := false;
 +  NBD.add_meta_context nbd "base:";
 +  (try
 +     let _ = NBD.opt_set_meta_context nbd (f 42) in
 +     assert false
 +   with
 +     NBD.Error (errstr, errno) -> ()
 +  );
 +  assert (0 = !count);
 +  assert (not !seen);
 +  (try
 +     let _ = NBD.can_meta_context nbd NBD.context_base_allocation in
 +     assert false
 +   with
 +     NBD.Error (errstr, errno) -> ()
 +  );
 +
 +  (* Even though can_meta fails after failed SET, it returns 0 after go *)
 +  NBD.opt_go nbd;
 +  let m = NBD.can_meta_context nbd NBD.context_base_allocation in
 +  assert (not m);
 +
 +  NBD.shutdown nbd
 +
 +let () = Gc.compact ()
 diff --git a/tests/opt-set-meta.c b/tests/opt-set-meta.c
 index 01c9604..bd00276 100644
 --- a/tests/opt-set-meta.c
 +++ b/tests/opt-set-meta.c
 @@ -17,6 +17,7 @@
   */
 
  /* Test behavior of nbd_opt_set_meta_context. */
 +/* See also unit test 250 in the various language ports. */
 
  #include <config.h>
 
 diff --git a/golang/Makefile.am b/golang/Makefile.am
 index f170cbc..f6e6f91 100644
 --- a/golang/Makefile.am
 +++ b/golang/Makefile.am
 @@ -1,5 +1,5 @@
  # nbd client library in userspace
 -# Copyright (C) 2013-2020 Red Hat Inc.
 +# Copyright (C) 2013-2022 Red Hat Inc.
  #
  # This library is free software; you can redistribute it and/or
  # modify it under the terms of the GNU Lesser General Public
 @@ -35,6 +35,7 @@ source_files = \
  	libnbd_220_opt_list_test.go \
  	libnbd_230_opt_info_test.go \
  	libnbd_240_opt_list_meta_test.go \
 +	libnbd_250_opt_set_meta_test.go \
  	libnbd_300_get_size_test.go \
  	libnbd_400_pread_test.go \
  	libnbd_405_pread_structured_test.go \
 diff --git a/golang/libnbd_240_opt_list_meta_test.go
b/golang/libnbd_240_opt_list_meta_test.go
 index 47df787..4af1008 100644
 --- a/golang/libnbd_240_opt_list_meta_test.go
 +++ b/golang/libnbd_240_opt_list_meta_test.go
 @@ -22,16 +22,16 @@ import (
  	"testing"
  )
 
 -var count uint
 -var seen bool
 +var list_count uint
 +var list_seen bool
 
  func listmetaf(user_data int, name string) int {
  	if user_data != 42 {
  		panic("expected user_data == 42")
  	}
 -	count++
 +	list_count++
  	if (name == context_base_allocation) {
 -		seen = true
 +		list_seen = true
  	}
  	return 0
  }
 @@ -57,22 +57,22 @@ func Test240OptListMeta(t *testing.T) {
  	}
 
  	/* First pass: empty query should give at least "base:allocation". */
 -	count = 0
 -	seen = false
 +	list_count = 0
 +	list_seen = false
  	r, err := h.OptListMetaContext(func(name string) int {
  	        return listmetaf(42, name)
  	})
  	if err != nil {
  		t.Fatalf("could not request opt_list_meta_context: %s", err)
  	}
 -	if r != count || r < 1 || !seen {
 -		t.Fatalf("unexpected count after opt_list_meta_context")
 +	if r != list_count || r < 1 || !list_seen {
 +		t.Fatalf("unexpected list_count after opt_list_meta_context")
  	}
 -	max := count
 +	max := list_count
 
  	/* Second pass: bogus query has no response. */
 -	count = 0
 -	seen = false
 +	list_count = 0
 +	list_seen = false
  	err = h.AddMetaContext("x-nosuch:")
  	if err != nil {
  		t.Fatalf("could not request add_meta_context: %s", err)
 @@ -83,13 +83,13 @@ func Test240OptListMeta(t *testing.T) {
  	if err != nil {
  		t.Fatalf("could not request opt_list_meta_context: %s", err)
  	}
 -	if r != 0 || r != count || seen {
 -		t.Fatalf("unexpected count after opt_list_meta_context")
 +	if r != 0 || r != list_count || list_seen {
 +		t.Fatalf("unexpected list_count after opt_list_meta_context")
  	}
 
  	/* Third pass: specific query should have one match. */
 -	count = 0
 -	seen = false
 +	list_count = 0
 +	list_seen = false
  	err = h.AddMetaContext(context_base_allocation)
  	if err != nil {
  		t.Fatalf("could not request add_meta_context: %s", err)
 @@ -114,15 +114,15 @@ func Test240OptListMeta(t *testing.T) {
  	if err != nil {
  		t.Fatalf("could not request opt_list_meta_context: %s", err)
  	}
 -	if r != 1 || r != count || !seen {
 -		t.Fatalf("unexpected count after opt_list_meta_context")
 +	if r != 1 || r != list_count || !list_seen {
 +		t.Fatalf("unexpected list_count after opt_list_meta_context")
  	}
 
  	/* Fourth pass: opt_list_meta_context is stateless, so it should
       * not wipe status learned during opt_info
  	 */
 -	count = 0
 -	seen = false
 +	list_count = 0
 +	list_seen = false
          _, err = h.GetSize()
  	if err == nil {
  		t.Fatalf("expected error")
 @@ -147,7 +147,7 @@ func Test240OptListMeta(t *testing.T) {
  		t.Fatalf("can_meta_context failed unexpectedly: %s", err)
  	}
  	if !meta {
 -		t.Fatalf("unexpected count after can_meta_context")
 +		t.Fatalf("unexpected list_count after can_meta_context")
  	}
  	err = h.ClearMetaContexts()
  	if err != nil {
 @@ -163,8 +163,8 @@ func Test240OptListMeta(t *testing.T) {
  	if err != nil {
  		t.Fatalf("could not request opt_list_meta_context: %s", err)
  	}
 -	if r != 0 || r != count || seen {
 -		t.Fatalf("unexpected count after opt_list_meta_context")
 +	if r != 0 || r != list_count || list_seen {
 +		t.Fatalf("unexpected list_count after opt_list_meta_context")
  	}
          size, err = h.GetSize()
  	if err != nil {
 @@ -178,13 +178,13 @@ func Test240OptListMeta(t *testing.T) {
  		t.Fatalf("can_meta_context failed unexpectedly: %s", err)
  	}
  	if !meta {
 -		t.Fatalf("unexpected count after can_meta_context")
 +		t.Fatalf("unexpected list_count after can_meta_context")
  	}
  	err = h.ClearMetaContexts()
 
  	/* Final pass: "base:" query should get at least "base:allocation"
*/
 -	count = 0
 -	seen = false
 +	list_count = 0
 +	list_seen = false
  	err = h.AddMetaContext("base:")
  	if err != nil {
  		t.Fatalf("could not request add_meta_context: %s", err)
 @@ -195,8 +195,8 @@ func Test240OptListMeta(t *testing.T) {
  	if err != nil {
  		t.Fatalf("could not request opt_list_meta_context: %s", err)
  	}
 -	if r < 1 || r > max || r != count || !seen {
 -		t.Fatalf("unexpected count after opt_list_meta_context")
 +	if r < 1 || r > max || r != list_count || !list_seen {
 +		t.Fatalf("unexpected list_count after opt_list_meta_context")
  	}
 
  	err = h.OptAbort()
 diff --git a/golang/libnbd_250_opt_set_meta_test.go
b/golang/libnbd_250_opt_set_meta_test.go
 new file mode 100644
 index 0000000..c29b7f4
 --- /dev/null
 +++ b/golang/libnbd_250_opt_set_meta_test.go
 @@ -0,0 +1,248 @@
 +/* libnbd golang tests
 + * Copyright (C) 2013-2022 Red Hat Inc.
 + *
 + * 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
 + */
 +
 +package libnbd
 +
 +import (
 +	"testing"
 +)
 +
 +var set_count uint
 +var set_seen bool
 +
 +func setmetaf(user_data int, name string) int {
 +	if user_data != 42 {
 +		panic("expected user_data == 42")
 +	}
 +	set_count++
 +	if (name == context_base_allocation) {
 +		set_seen = true
 +	}
 +	return 0
 +}
 +
 +func Test250OptSetMeta(t *testing.T) {
 +	h, err := Create()
 +	if err != nil {
 +		t.Fatalf("could not create handle: %s", err)
 +	}
 +	defer h.Close()
 +
 +	err = h.SetOptMode(true)
 +	if err != nil {
 +		t.Fatalf("could not set opt mode: %s", err)
 +	}
 +
 +	err = h.ConnectCommand([]string{
 +		"nbdkit", "-s", "--exit-with-parent", "-v",
 +		"memory", "size=1M",
 +	})
 +	if err != nil {
 +		t.Fatalf("could not connect: %s", err)
 +	}
 +
 +        /* No contexts negotiated yet; CanMeta should be error if any requested */
 +        sr, err := h.GetStructuredRepliesNegotiated()
 +	if err != nil {
 +		t.Fatalf("could not check structured replies negotiated: %s", err)
 +	}
 +        if !sr {
 +        	t.Fatalf("unexpected structured replies state")
 +        }
 +        meta, err := h.CanMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not check can meta context: %s", err)
 +	}
 +        if meta {
 +        	t.Fatalf("unexpected can meta context state")
 +        }
 +	err = h.AddMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +        _, err = h.CanMetaContext(context_base_allocation)
 +	if err == nil {
 +		t.Fatalf("expected error")
 +	}
 +
 +        /* FIXME: Once OptStructuredReply exists, check that set before
 +         * SR fails server-side, then enable SR for rest of process.
 +         */
 +
 +	/* nbdkit does not match wildcard for SET, even though it does for LIST */
 +	set_count = 0
 +	set_seen = false
 +        err = h.ClearMetaContexts()
 +	if err != nil {
 +		t.Fatalf("could not request clear_meta_contexts: %s", err)
 +	}
 +	err = h.AddMetaContext("base:")
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +	r, err := h.OptSetMetaContext(func(name string) int {
 +	        return setmetaf(42, name)
 +	})
 +	if err != nil {
 +		t.Fatalf("could not request opt_set_meta_context: %s", err)
 +	}
 +	if r != set_count || r != 0 || set_seen {
 +		t.Fatalf("unexpected set_count after opt_set_meta_context")
 +	}
 +
 +	/* Request 2 with expectation of 1; with SetRequestMetaContext off */
 +	set_count = 0
 +	set_seen = false
 +	err = h.AddMetaContext("x-nosuch:context")
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +	err = h.AddMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +        err = h.SetRequestMetaContext(false)
 +	if err != nil {
 +		t.Fatalf("could not set_request_meta_context: %s", err)
 +	}
 +	r, err = h.OptSetMetaContext(func(name string) int {
 +	        return setmetaf(42, name)
 +	})
 +	if err != nil {
 +		t.Fatalf("could not request opt_set_meta_context: %s", err)
 +	}
 +	if r != 1 || r != set_count || !set_seen {
 +		t.Fatalf("unexpected set_count after opt_set_meta_context")
 +	}
 +        meta, err = h.CanMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not check can meta context: %s", err)
 +	}
 +        if !meta {
 +        	t.Fatalf("unexpected can meta context state")
 +        }
 +
 +	/* Transition to transmission phase; our last set should remain active */
 +        err = h.ClearMetaContexts()
 +	if err != nil {
 +		t.Fatalf("could not request clear_meta_contexts: %s", err)
 +	}
 +	err = h.AddMetaContext("x-nosuch:context")
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +        err = h.OptGo()
 +	if err != nil {
 +		t.Fatalf("could not request opt_go: %s", err)
 +	}
 +        meta, err = h.CanMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not check can meta context: %s", err)
 +	}
 +        if !meta {
 +        	t.Fatalf("unexpected can meta context state")
 +        }
 +
 +        /* Now too late to set; but should not lose earlier state */
 +	set_count = 0
 +	set_seen = false
 +	_, err = h.OptSetMetaContext(func(name string) int {
 +	        return setmetaf(42, name)
 +	})
 +	if err == nil {
 +		t.Fatalf("expected error")
 +	}
 +	if set_count != 0 || set_seen {
 +		t.Fatalf("unexpected set_count after opt_set_meta_context")
 +	}
 +        meta, err = h.CanMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not check can meta context: %s", err)
 +	}
 +        if !meta {
 +        	t.Fatalf("unexpected can meta context state")
 +        }
 +
 +	err = h.Shutdown(nil)
 +	if err != nil {
 +		t.Fatalf("could not request shutdown: %s", err)
 +	}
 +
 +        /* Second process, this time without structured replies server-side. */
 +	h, err = Create()
 +	if err != nil {
 +		t.Fatalf("could not create handle: %s", err)
 +	}
 +	defer h.Close()
 +
 +	err = h.SetOptMode(true)
 +	if err != nil {
 +		t.Fatalf("could not set opt mode: %s", err)
 +	}
 +
 +	err = h.AddMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not request add_meta_context: %s", err)
 +	}
 +
 +        err = h.ConnectCommand([]string{
 +		"nbdkit", "-s", "--exit-with-parent", "-v",
 +		"memory", "size=1M", "--no-sr",
 +	})
 +	if err != nil {
 +		t.Fatalf("could not connect: %s", err)
 +	}
 +
 +        sr, err = h.GetStructuredRepliesNegotiated()
 +	if err != nil {
 +		t.Fatalf("could not check structured replies negotiated: %s", err)
 +	}
 +        if sr {
 +        	t.Fatalf("unexpected structured replies state")
 +        }
 +
 +        /* Expect server-side failure here */
 +	set_count = 0
 +	set_seen = false
 +	_, err = h.OptSetMetaContext(func(name string) int {
 +	        return setmetaf(42, name)
 +	})
 +	if err == nil {
 +		t.Fatalf("expected error")
 +	}
 +	if set_count != 0 || set_seen {
 +		t.Fatalf("unexpected set_count after opt_set_meta_context")
 +	}
 +        _, err = h.CanMetaContext(context_base_allocation)
 +	if err == nil {
 +		t.Fatalf("expected error")
 +	}
 +
 +        /* Even though CanMeta fails after failed SET, it returns 0 after go */
 +        err = h.OptGo()
 +	if err != nil {
 +		t.Fatalf("could not request opt_go: %s", err)
 +	}
 +        meta, err = h.CanMetaContext(context_base_allocation)
 +	if err != nil {
 +		t.Fatalf("could not check can meta context: %s", err)
 +	}
 +        if meta {
 +        	t.Fatalf("unexpected can meta context state")
 +        }
 +}