ronantakizawa/codereview-bench huggingface dataset:
Repository: moby/moby
File: client/image_pull.go (Go)

Code before:
func newImagePullResponse(rc io.ReadCloser) ImagePullResponse {
	return ImagePullResponse{
		rc:    rc,
		close: &sync.Once{},
	}
}

type ImagePullResponse struct {
	rc    io.ReadCloser
	close *sync.Once
}

// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
	return r.rc.Read(p)
}

// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
	if r.close == nil {
		return nil
	}
	var err error
	r.close.Do(func() {
		if r.rc != nil {
			err = r.rc.Close()
		}
	})
	return err
}

// JSONMessages decodes the response stream as a sequence of JSONMessages.
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
	context.AfterFunc(ctx, func() {
		r.Close()
	})
	dec := json.NewDecoder(r)
	return func(yield func(jsonmessage.JSONMessage, error) bool) {
		defer r.Close()
		for {
			var jm jsonmessage.JSONMessage
			err := dec.Decode(&jm)
			if errors.Is(err, io.EOF) {
				break
			}
			if ctx.Err() != nil {
				yield(jm, ctx.Err())
				return
			}
			if !yield(jm, err) {

Review comment (suggestion):
This slightly changes the behavior - previously the error would only be returned on the first `Close` call. Now each `Close` call will still return the same error as the first run.

Probably not a big issue in this case... but worth pointing out just in case

Code after:
func newImagePullResponse(rc io.ReadCloser) ImagePullResponse {
	if rc == nil {
		panic("nil io.ReadCloser")
	}
	return ImagePullResponse{
		rc:    rc,
		close: sync.OnceValue(rc.Close),
	}
}

type ImagePullResponse struct {
	rc    io.ReadCloser
	close func() error
}

// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
	return r.rc.Read(p)
}

// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
	if r.close == nil {
		return nil
	}
	return r.close()
}

// JSONMessages decodes the response stream as a sequence of JSONMessages.
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
	context.AfterFunc(ctx, func() {
		r.Close()
	})
	dec := json.NewDecoder(r)
	return func(yield func(jsonmessage.JSONMessage, error) bool) {
		defer r.Close()
		for {
			var jm jsonmessage.JSONMessage
			err := dec.Decode(&jm)
			if errors.Is(err, io.EOF) {
				break
			}
			if ctx.Err() != nil {
				yield(jm, ctx.Err())
				return
			}
			if !yield(jm, err) {
				return
			}
		}
Repository: moby/moby
File: daemon/server/router/container/inspect.go (Go)

Code before:
					"NetworkConfig": map[string]any{
						"EndpointID":          bridgeNw.EndpointID,
						"Gateway":             bridgeNw.Gateway,
						"GlobalIPv6Address":   bridgeNw.GlobalIPv6Address,
						"GlobalIPv6PrefixLen": bridgeNw.GlobalIPv6PrefixLen,
						"IPAddress":           bridgeNw.IPAddress,
						"IPPrefixLen":         bridgeNw.IPPrefixLen,
						"IPv6Gateway":         bridgeNw.IPv6Gateway,
						"MacAddress":          bridgeNw.MacAddress,
					},
				}),
			)
		}

		// Migrate the container's main / default network's MacAddress to
		// the Config.MacAddress field for older API versions (< 1.44).
		//
		// This was deprecated in API v1.44, but kept in place until
		// API v1.52, which removed this entirely.
		if ctr.HostConfig != nil {
			mainNetwork := "bridge"
			if ctr.HostConfig.NetworkMode.IsUserDefined() {
				mainNetwork = ctr.HostConfig.NetworkMode.NetworkName()
			}
			if v := ctr.NetworkSettings.Networks[mainNetwork]; v != nil && v.MacAddress != "" {
				macAddress = v.MacAddress
			}
		}
		if macAddress != "" {
			wrapOpts = append(wrapOpts, compat.WithExtraFields(map[string]any{
				"Config": map[string]any{
					"MacAddress": macAddress,
				},
			}))
		}
	}

	return httputils.WriteJSON(w, http.StatusOK, compat.Wrap(ctr, wrapOpts...))
}


Review comment (suggestion):
Since commit 8c64b85fb90c9e518aee6da3e8adfd0bfeabf255, this has only been filled in if the MAC address is explicitly configured - by copying [DesiredMacAddress](https://github.com/moby/moby/blob/c646091d579f41b9d3c5e4fbbccd430b23e958e3/daemon/network/settings.go#L29-L31) instead of `MacAddress` ... but I don't think this code has that struct.

So it's a change in the older API responses, but maybe not an important one.

Code after:
					"Gateway":             bridgeNw.Gateway,
					"GlobalIPv6Address":   bridgeNw.GlobalIPv6Address,
					"GlobalIPv6PrefixLen": bridgeNw.GlobalIPv6PrefixLen,
					"IPAddress":           bridgeNw.IPAddress,
					"IPPrefixLen":         bridgeNw.IPPrefixLen,
					"IPv6Gateway":         bridgeNw.IPv6Gateway,
					"MacAddress":          bridgeNw.MacAddress,
				},
			}))
		}

		// Migrate the container's main / default network's MacAddress to
		// the Config.MacAddress field for older API versions (< 1.44).
		//
		// This was deprecated in API v1.44, but kept in place until
		// API v1.52, which removed this entirely.
		if desiredMACAddress != "" {
			wrapOpts = append(wrapOpts, compat.WithExtraFields(map[string]any{
				"Config": map[string]any{
					"MacAddress": desiredMACAddress,
				},
			}))
		}
	}

	return httputils.WriteJSON(w, http.StatusOK, compat.Wrap(ctr, wrapOpts...))
}
Repository: moby/moby
File: client/plugin_list.go (Go)

Code before:
package client

import (
	"context"
	"encoding/json"
	"net/url"

	"github.com/moby/moby/api/types/plugin"
)

// PluginList returns the installed plugins
func (cli *Client) PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error) {
	var plugins plugin.ListResponse
	query := url.Values{}

	filter.updateURLValues(query)
	resp, err := cli.get(ctx, "/plugins", query, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return plugins, err
	}

	err = json.NewDecoder(resp.Body).Decode(&plugins)
	return plugins, err
}


Review comment (suggestion):
FWIW; I did not yet change the return type here, so this one (and probably other list methods) will still need to go through one iteration once we have a good shape for those.


(This one was mostly to get the `Filters` out of the function signature itself)

Code after:
package client

import (
	"context"
	"encoding/json"
	"net/url"

	"github.com/moby/moby/api/types/plugin"
)

// PluginListOptions holds parameters to list plugins.
type PluginListOptions struct {
	Filters Filters
}

// PluginList returns the installed plugins
func (cli *Client) PluginList(ctx context.Context, opts PluginListOptions) (plugin.ListResponse, error) {
	var plugins plugin.ListResponse
	query := url.Values{}

	opts.Filters.updateURLValues(query)
	resp, err := cli.get(ctx, "/plugins", query, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return plugins, err
	}

	err = json.NewDecoder(resp.Body).Decode(&plugins)
	return plugins, err
}
Repository: moby/moby
File: client/volume_prune.go (Go)

Code before:
// VolumePruneOptions holds parameters to prune networks.
type VolumePruneOptions struct {
	Filters Filters
}

// VolumePruneResult holds the result from the [Client.VolumesPrune] method.
type VolumePruneResult struct {
	Report volume.PruneReport
}

// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error) {
	query := url.Values{}
	opts.Filters.updateURLValues(query)

	resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumePruneResult{}, err
	}

	var report volume.PruneReport
	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
		return VolumePruneResult{}, fmt.Errorf("Error retrieving volume prune report: %v", err)
	}

	return VolumePruneResult{Report: report}, nil
}


Review comment (suggestion):
Ideally, the next step would be to make the API have a `all` field or query-string, instead of using a filter for this.

Code after:

// VolumePruneOptions holds parameters to prune volumes.
type VolumePruneOptions struct {
	// All controls whether named volumes should also be pruned. By
	// default, only anonymous volumes are pruned.
	All bool

	// Filters to apply when pruning.
	Filters Filters
}

// VolumePruneResult holds the result from the [Client.VolumesPrune] method.
type VolumePruneResult struct {
	Report volume.PruneReport
}

// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error) {
	if opts.All {
		if _, ok := opts.Filters["all"]; ok {
			return VolumePruneResult{}, errdefs.ErrInvalidArgument.WithMessage(`conflicting options: cannot specify both "all" and "all" filter`)
		}
		if opts.Filters == nil {
			opts.Filters = Filters{}
		}
		opts.Filters.Add("all", "true")
	}

	query := url.Values{}
	opts.Filters.updateURLValues(query)

	resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumePruneResult{}, err
	}

	var report volume.PruneReport
	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
		return VolumePruneResult{}, fmt.Errorf("Error retrieving volume prune report: %v", err)
	}

	return VolumePruneResult{Report: report}, nil
}
Repository: moby/moby
File: client/volume_inspect.go (Go)

Code before:
package client

import (
	"bytes"
	"context"
	"encoding/json"
	"io"

	"github.com/moby/moby/api/types/volume"
)

// VolumeInspectResponse holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResponse struct {
	volume.Volume
}

// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResponse, error) {
	vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
	return vol, err
}

// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResponse, []byte, error) {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return VolumeInspectResponse{}, nil, err
	}

	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeInspectResponse{}, nil, err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return VolumeInspectResponse{}, nil, err

Review comment (suggestion):
Let's use `..Result` instead of `..Response`. 

```suggestion
type VolumeInspectResult struct {
```

Code after:
package client

import (
	"bytes"
	"context"
	"encoding/json"
	"io"

	"github.com/moby/moby/api/types/volume"
)

// VolumeInspectResult holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResult struct {
	Volume volume.Volume
}

// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error) {
	vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
	return vol, err
}

// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error) {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return VolumeInspectResult{}, nil, err
Repository: moby/moby
File: client/volume_inspect.go (Go)

Code before:
package client

import (
	"bytes"
	"context"
	"encoding/json"
	"io"

	"github.com/moby/moby/api/types/volume"
)

// VolumeInspectResult holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResult struct {
	volume.Volume
}

// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error) {
	vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
	return vol, err
}

// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error) {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}


Review comment (suggestion):
Saw the other comment, but wondering if it should be a field after all; at least I think embedding it, will mean that "extending" the structure means things get somewhat intertwined with the volume properties; if we would add fields, we're merging those with the volume, and ... that may shadow things inadvertently;

```go
type VolumeInspectResult struct {
	volume.Volume
}

type VolumeInspectResult2 struct {
	volume.Volume
	Name string // Some client-side field we added, but wasn't a in volume when we added it
}

func TestVolumeBlah(t *testing.T) {
	foo := VolumeInspectResult{Volume: volume.Volume{Name: "foo"}}
	fmt.Println(foo.Name)

	foo2 := VolumeInspectResult2{
		Name:   "bar",
		Volume: volume.Volume{Name: "foo"},
	}
	fmt.Println(foo2.Name)
}
```







Code after:
package client

import (
	"bytes"
	"context"
	"encoding/json"
	"io"

	"github.com/moby/moby/api/types/volume"
)

// VolumeInspectResult holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResult struct {
	Volume volume.Volume
}

// VolumeInspect returns the information about a specific volume in the docker host.
func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error) {
	vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
	return vol, err
}

// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error) {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return VolumeInspectResult{}, nil, err
	}
Repository: moby/moby
File: client/volume_remove.go (Go)

Code before:
package client

import (
	"context"
	"net/url"
)

// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return err
	}

	query := url.Values{}
	if force {
		query.Set("force", "1")
	}
	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
	defer ensureReaderClosed(resp)
	return err
}


Review comment (suggestion):
Note to self / reminder; we need to document what exactly is being forced; is this only "ignore non-existing volumes" or more?

If it's only "ignore none-existing volumes" then we should consider to either deprecate this option, or to handle `errdefs.IsNotFound(err)` in the client.

Code after:
package client

import (
	"context"
	"net/url"
)

// VolumeRemoveOptions holds optional parameters for volume removal.
type VolumeRemoveOptions struct {
	// Force the removal of the volume
	Force bool
}

// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) error {
	volumeID, err := trimID("volume", volumeID)
	if err != nil {
		return err
	}

	query := url.Values{}
	if options.Force {
		query.Set("force", "1")
	}
	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
	defer ensureReaderClosed(resp)
	return err
}
Repository: moby/moby
File: client/volume_create.go (Go)

Code before:
	// Example: custom
	Driver string `json:"Driver,omitempty"`

	// A mapping of driver options and values. These options are
	// passed directly to the driver and are driver specific.
	//
	// Example: {"device":"tmpfs","o":"size=100m,uid=1000","type":"tmpfs"}
	DriverOpts map[string]string `json:"DriverOpts,omitempty"`

	// User-defined key/value metadata.
	// Example: {"com.example.some-label":"some-value","com.example.some-other-label":"some-other-value"}
	Labels map[string]string `json:"Labels,omitempty"`

	// The new volume's name. If not specified, Docker generates a name.
	//
	// Example: tardis
	Name string `json:"Name,omitempty"`
}

type VolumeCreateResult struct {
	volume.Volume
}

// VolumeCreate creates a volume in the docker host.
func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) {
	resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeCreateResult{}, err
	}

	var vol volume.Volume
	err = json.NewDecoder(resp.Body).Decode(&vol)
	return VolumeCreateResult{Volume: vol}, err
}


Review comment (suggestion):
Oh! We should probably do this different;

- The Client's `VolumeCreateOptions`  is a distinct type; it _could_ wrap the api-type, or be a separate type
- Then internally in the `VolumeCreate` function, it gets converted to a `api.volume.CreateRequest`

That way;

- The `VolumeCreateOptions` can have a different "shape" (fields, types, etc) if needed
- And those fields get converted to the right fields, types, shape to produce the API struct / request.



Code after:
	ClusterVolumeSpec *volume.ClusterVolumeSpec
}

// VolumeCreateResult is the result of a volume creation.
type VolumeCreateResult struct {
	Volume volume.Volume
}

// VolumeCreate creates a volume in the docker host.
func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) {
	createRequest := volume.CreateRequest{
		Name:              options.Name,
		Driver:            options.Driver,
		DriverOpts:        options.DriverOpts,
		Labels:            options.Labels,
		ClusterVolumeSpec: options.ClusterVolumeSpec,
	}
	resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return VolumeCreateResult{}, err
	}

	var v volume.Volume
	err = json.NewDecoder(resp.Body).Decode(&v)
	return VolumeCreateResult{Volume: v}, err
}
Repository: moby/moby
File: integration/system/api_test.go (Go)

Code before:
package system

import (
	"net/http"
	"testing"

	"github.com/moby/moby/v2/internal/testutil/request"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

func TestAPIErrorNotFoundJSON(t *testing.T) {
	ctx := setupTest(t)

	// 404 is a different code path to normal errors, so test separately
	httpResp, body, err := request.Get(ctx, "/notfound", request.JSON)
	assert.NilError(t, err)
	assert.Equal(t, httpResp.StatusCode, http.StatusNotFound)
	assert.Assert(t, is.Contains(httpResp.Header.Get("Content-Type"), "application/json"))

	b, err := request.ReadBody(body)
	assert.NilError(t, err)
	assert.Equal(t, getErrorMessage(t, b), "page not found")
}


Review comment (refactor):
I opened a PR to add a utility;

- https://github.com/moby/moby/pull/51220

If that is merged, this could become something like;

```suggestion
	httpResp, _, err := request.Get(testutil.GetContext(c), "/notfound", request.JSON)
	assert.NilError(c, err)
	assert.Equal(c, httpResp.StatusCode, http.StatusNotFound)

	var respErr common.ErrorResponse
	assert.NilError(c, request.ReadJSONResponse(httpResp, &respErr))
	assert.Error(c, respErr, "page not found")
```

Code after:
package system

import (
	"net/http"
	"testing"

	"github.com/moby/moby/api/types/common"
	"github.com/moby/moby/v2/internal/testutil/request"
	"gotest.tools/v3/assert"
)

func TestAPIErrorNotFoundJSON(t *testing.T) {
	ctx := setupTest(t)

	// 404 is a different code path to normal errors, so test separately
	httpResp, _, err := request.Get(ctx, "/notfound", request.JSON)
	assert.NilError(t, err)
	assert.Equal(t, httpResp.StatusCode, http.StatusNotFound)

	var respErr common.ErrorResponse
	assert.NilError(t, request.ReadJSONResponse(httpResp, &respErr))
	assert.Error(t, respErr, "page not found")
}
Repository: moby/moby
File: integration/container/mounts_linux_test.go (Go)

Code before:
	}
	roAsStr := ro.Source + ":" + ro.Target + ":ro,rprivate"
	roVerifier := nonRecursiveVerifier
	if rroSupported {
		roVerifier = forceRecursiveVerifier
	}

	// Non-recursive
	nonRecursive := ro
	nonRecursive.BindOptions = &mounttypes.BindOptions{
		ReadOnlyNonRecursive: true,
		Propagation:          mounttypes.PropagationRPrivate,
	}

	// Force recursive
	forceRecursive := ro
	forceRecursive.BindOptions = &mounttypes.BindOptions{
		ReadOnlyForceRecursive: true,
		Propagation:            mounttypes.PropagationRPrivate,
	}

	apiClient := testEnv.APIClient()

	containers := []string{
		container.Run(ctx, t, apiClient, container.WithMount(ro), container.WithCmd(roVerifier...)),
		container.Run(ctx, t, apiClient, container.WithBindRaw(roAsStr), container.WithCmd(roVerifier...)),

		container.Run(ctx, t, apiClient, container.WithMount(nonRecursive), container.WithCmd(nonRecursiveVerifier...)),
	}

	if rroSupported {
		containers = append(containers,
			container.Run(ctx, t, apiClient, container.WithMount(forceRecursive), container.WithCmd(forceRecursiveVerifier...)),
		)
	}

	for _, c := range containers {
		poll.WaitOn(t, container.IsSuccessful(ctx, apiClient, c))
	}
}

func isRROSupported() bool {
	return kernel.CheckKernelVersion(5, 12, 0)
}


Review comment (refactor):
I haven't checked, but is this for all tests that use `container.WithMount` / `container.WithBindRaw`, or something specific to these tests?


(considering if there's something global to set up and/or for the test-utility to set it automatically

Code after:
	}
	roAsStr := ro.Source + ":" + ro.Target + ":ro,rprivate"
	roVerifier := nonRecursiveVerifier
	if rroSupported {
		roVerifier = forceRecursiveVerifier
	}

	// Non-recursive
	nonRecursive := ro
	nonRecursive.BindOptions = &mounttypes.BindOptions{
		ReadOnlyNonRecursive: true,
		Propagation:          mounttypes.PropagationRPrivate,
	}

	// Force recursive
	forceRecursive := ro
	forceRecursive.BindOptions = &mounttypes.BindOptions{
		ReadOnlyForceRecursive: true,
		Propagation:            mounttypes.PropagationRPrivate,
	}

	apiClient := testEnv.APIClient()

	containers := []string{
		container.Run(ctx, t, apiClient, container.WithMount(ro), container.WithCmd(roVerifier...), container.WithSecurityOpt("label=disable")),
		container.Run(ctx, t, apiClient, container.WithBindRaw(roAsStr), container.WithCmd(roVerifier...), container.WithSecurityOpt("label=disable")),

		container.Run(ctx, t, apiClient, container.WithMount(nonRecursive), container.WithCmd(nonRecursiveVerifier...), container.WithSecurityOpt("label=disable")),
	}

	if rroSupported {
		containers = append(containers,
			container.Run(ctx, t, apiClient, container.WithMount(forceRecursive), container.WithCmd(forceRecursiveVerifier...), container.WithSecurityOpt("label=disable")),
		)
	}

	for _, c := range containers {
		poll.WaitOn(t, container.IsSuccessful(ctx, apiClient, c))
	}
}

func isRROSupported() bool {
	return kernel.CheckKernelVersion(5, 12, 0)
}