ci(integration-cli): split test suites in a matrix
Split `DockerSuite` for `integration-cli` tests so we can create a matrix to reduce build time in our pipeline (windows atm). We could also use `tags` for this purpose. Let me know what sounds better to you. Keeping the working tree like this for visibility but will squash commits if it sgty.

Here is the overall build time for each of them after splitting (still windows):

```
- DockerApiSuite: ~12m06s
- DockerBenchmarkSuite: ~11s
- DockerCliAttachSuite: ~16s
- DockerCliBuildSuite: ~16m33s
- DockerCliCommitSuite: ~33s
- DockerCliCpSuite: ~54s
- DockerCliCreateSuite: ~45s
- DockerCliEventSuite: ~1m31s
- DockerCliExecSuite: ~26s
- DockerCliHealthSuite: ~8s
- DockerCliHistorySuite: ~32s
- DockerCliImagesSuite: ~27s
- DockerCliImportSuite: ~8s
- DockerCliInfoSuite: ~8s
- DockerCliInspectSuite: ~52s
- DockerCliLinksSuite: ~9s
- DockerCliLoginSuite: ~8s
- DockerCliLogsSuite: ~48s
- DockerCliNetmodeSuite: ~8s
- DockerCliNetworkSuite: ~8s
- DockerCliPluginLogDriverSuite: ~8s
- DockerCliPluginsSuite: ~10s
- DockerCliPortSuite: ~11s
- DockerCliProxySuite: ~8s
- DockerCliPruneSuite: ~9s
- DockerCliPsSuite: ~1m19s
- DockerCliPullSuite: ~9s
- DockerCliPushSuite: ~13s
- DockerCliRestartSuite: ~58s
- DockerCliRmiSuite: ~1m52s
- DockerCliRunSuite: ~4m07s
- DockerCliSaveLoadSuite: ~8s
- DockerCliSearchSuite: ~11s
- DockerCliSniSuite: ~10s
- DockerCliStartSuite: ~26s
- DockerCliStatsSuite: ~7s
- DockerCliTopSuite: ~16s
- DockerCliUpdateSuite: ~11s
- DockerNetworkSuite|DockerHubPullSuite|DockerRegistrySuite|DockerSchema1RegistrySuite|DockerRegistryAuthTokenSuite|DockerRegistryAuthHtpasswdSuite: ~11s
- DockerSwarmSuite: ~8s
- DockerDaemonSuite: ~8s
- DockerExternalVolumeSuite: ~7s
```

Before ~1h20m

After ~42m

![image](https://user-images.githubusercontent.com/1951866/171687129-9ee7b1c2-ce0d-413c-a119-6b853f7fb06b.png)

integration-cli/check_test.go
package main

import (
	"context"
	"flag"
	"fmt"
	"net/http/httptest"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/docker/docker/integration-cli/cli"
	"github.com/docker/docker/integration-cli/daemon"
	"github.com/docker/docker/integration-cli/environment"
	"github.com/docker/docker/internal/test/suite"
	"github.com/docker/docker/pkg/reexec"
	testdaemon "github.com/docker/docker/testutil/daemon"
	ienv "github.com/docker/docker/testutil/environment"
	"github.com/docker/docker/testutil/fakestorage"
	"github.com/docker/docker/testutil/fixtures/plugin"
	"github.com/docker/docker/testutil/registry"
	"gotest.tools/v3/assert"
)

const (
	// the private registry to use for tests
	privateRegistryURL = registry.DefaultURL

	// path to containerd's ctr binary
	ctrBinary = "ctr"

	// the docker daemon binary to use
	dockerdBinary = "dockerd"
)

var (
	testEnv *environment.Execution

	// the docker client binary to use
	dockerBinary = ""

	testEnvOnce sync.Once
)

func init() {
	var err error

	reexec.Init() // This is required for external graphdriver tests

	testEnv, err = environment.New()
	if err != nil {
		panic(err)
	}
}

func TestMain(m *testing.M) {
	flag.Parse()

	// Global set up
	dockerBinary = testEnv.DockerBinary()
	err := ienv.EnsureFrozenImagesLinux(&testEnv.Execution)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	testEnv.Print()
	os.Exit(m.Run())
}

func ensureTestEnvSetup(t *testing.T) {
	testEnvOnce.Do(func() {
		cli.SetTestEnvironment(testEnv)
		fakestorage.SetTestEnvironment(&testEnv.Execution)
		ienv.ProtectAll(t, &testEnv.Execution)
	})
}

func TestDockerSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerSuite{})
}

func TestDockerRegistrySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistrySuite{ds: &DockerSuite{}})
}

func TestDockerSchema1RegistrySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerSchema1RegistrySuite{ds: &DockerSuite{}})
}

func TestDockerRegistryAuthHtpasswdSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistryAuthHtpasswdSuite{ds: &DockerSuite{}})
}

func TestDockerRegistryAuthTokenSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistryAuthTokenSuite{ds: &DockerSuite{}})
}

func TestDockerDaemonSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerDaemonSuite{ds: &DockerSuite{}})
}

func TestDockerSwarmSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerSwarmSuite{ds: &DockerSuite{}})
}

func TestDockerPluginSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerPluginSuite{ds: &DockerSuite{}})
}

func TestDockerExternalVolumeSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	testRequires(t, DaemonIsLinux)
	suite.Run(t, &DockerExternalVolumeSuite{ds: &DockerSuite{}})
}

func TestDockerNetworkSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	testRequires(t, DaemonIsLinux)
	suite.Run(t, &DockerNetworkSuite{ds: &DockerSuite{}})
}

func TestDockerHubPullSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	// FIXME. Temporarily turning this off for Windows as GH16039 was breaking
	// Windows to Linux CI @icecrime
	testRequires(t, DaemonIsLinux)
	suite.Run(t, newDockerHubPullSuite())
}

type DockerSuite struct {
}

func (s *DockerSuite) OnTimeout(c *testing.T) {
	if testEnv.IsRemoteDaemon() {
		return
	}
	path := filepath.Join(os.Getenv("DEST"), "docker.pid")
	b, err := os.ReadFile(path)
	if err != nil {
		c.Fatalf("Failed to get daemon PID from %s\n", path)
	}

	rawPid, err := strconv.ParseInt(string(b), 10, 32)
	if err != nil {
		c.Fatalf("Failed to parse pid from %s: %s\n", path, err)
	}

	daemonPid := int(rawPid)
	if daemonPid > 0 {
		testdaemon.SignalDaemonDump(daemonPid)
	}
}

func (s *DockerSuite) TearDownTest(c *testing.T) {
	testEnv.Clean(c)
}

type DockerRegistrySuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistrySuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistrySuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistrySuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerSchema1RegistrySuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerSchema1RegistrySuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerSchema1RegistrySuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, NotArm64, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c, registry.Schema1)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerSchema1RegistrySuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerRegistryAuthHtpasswdSuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistryAuthHtpasswdSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistryAuthHtpasswdSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c, registry.Htpasswd)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistryAuthHtpasswdSuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		out, err := s.d.Cmd("logout", privateRegistryURL)
		assert.NilError(c, err, out)
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerRegistryAuthTokenSuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistryAuthTokenSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistryAuthTokenSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistryAuthTokenSuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		out, err := s.d.Cmd("logout", privateRegistryURL)
		assert.NilError(c, err, out)
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

func (s *DockerRegistryAuthTokenSuite) setupRegistryWithTokenService(c *testing.T, tokenURL string) {
	if s == nil {
		c.Fatal("registry suite isn't initialized")
	}
	s.reg = registry.NewV2(c, registry.Token(tokenURL))
	s.reg.WaitReady(c)
}

type DockerDaemonSuite struct {
	ds *DockerSuite
	d  *daemon.Daemon
}

func (s *DockerDaemonSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerDaemonSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerDaemonSuite) TearDownTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

func (s *DockerDaemonSuite) TearDownSuite(c *testing.T) {
	filepath.Walk(testdaemon.SockRoot, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			// ignore errors here
			// not cleaning up sockets is not really an error
			return nil
		}
		if fi.Mode() == os.ModeSocket {
			syscall.Unlink(path)
		}
		return nil
	})
	os.RemoveAll(testdaemon.SockRoot)
}

const defaultSwarmPort = 2477

type DockerSwarmSuite struct {
	server      *httptest.Server
	ds          *DockerSuite
	daemonsLock sync.Mutex // protect access to daemons and portIndex
	daemons     []*daemon.Daemon
	portIndex   int
}

func (s *DockerSwarmSuite) OnTimeout(c *testing.T) {
	s.daemonsLock.Lock()
	defer s.daemonsLock.Unlock()
	for _, d := range s.daemons {
		d.DumpStackAndQuit()
	}
}

func (s *DockerSwarmSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
}

func (s *DockerSwarmSuite) AddDaemon(c *testing.T, joinSwarm, manager bool) *daemon.Daemon {
	c.Helper()
	d := daemon.New(c, dockerBinary, dockerdBinary,
		testdaemon.WithEnvironment(testEnv.Execution),
		testdaemon.WithSwarmPort(defaultSwarmPort+s.portIndex),
	)
	if joinSwarm {
		if len(s.daemons) > 0 {
			d.StartAndSwarmJoin(c, s.daemons[0].Daemon, manager)
		} else {
			d.StartAndSwarmInit(c)
		}
	} else {
		d.StartNodeWithBusybox(c)
	}

	s.daemonsLock.Lock()
	s.portIndex++
	s.daemons = append(s.daemons, d)
	s.daemonsLock.Unlock()

	return d
}

func (s *DockerSwarmSuite) TearDownTest(c *testing.T) {
	testRequires(c, DaemonIsLinux)
	s.daemonsLock.Lock()
	for _, d := range s.daemons {
		if d != nil {
			d.Stop(c)
			d.Cleanup(c)
		}
	}
	s.daemons = nil
	s.portIndex = 0
	s.daemonsLock.Unlock()
	s.ds.TearDownTest(c)
}

type DockerPluginSuite struct {
	ds       *DockerSuite
	registry *registry.V2
}

func (ps *DockerPluginSuite) registryHost() string {
	return privateRegistryURL
}

func (ps *DockerPluginSuite) getPluginRepo() string {
	return path.Join(ps.registryHost(), "plugin", "basic")
}
func (ps *DockerPluginSuite) getPluginRepoWithTag() string {
	return ps.getPluginRepo() + ":" + "latest"
}

func (ps *DockerPluginSuite) SetUpSuite(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting)
	ps.registry = registry.NewV2(c)
	ps.registry.WaitReady(c)

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	err := plugin.CreateInRegistry(ctx, ps.getPluginRepo(), nil)
	assert.NilError(c, err, "failed to create plugin")
}

func (ps *DockerPluginSuite) TearDownSuite(c *testing.T) {
	if ps.registry != nil {
		ps.registry.Close()
	}
}

func (ps *DockerPluginSuite) TearDownTest(c *testing.T) {
	ps.ds.TearDownTest(c)
}

func (ps *DockerPluginSuite) OnTimeout(c *testing.T) {
	ps.ds.OnTimeout(c)
}

package main

import (
	"context"
	"flag"
	"fmt"
	"net/http/httptest"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"testing"
	"time"

	"github.com/docker/docker/integration-cli/cli"
	"github.com/docker/docker/integration-cli/daemon"
	"github.com/docker/docker/integration-cli/environment"
	"github.com/docker/docker/internal/test/suite"
	"github.com/docker/docker/pkg/reexec"
	testdaemon "github.com/docker/docker/testutil/daemon"
	ienv "github.com/docker/docker/testutil/environment"
	"github.com/docker/docker/testutil/fakestorage"
	"github.com/docker/docker/testutil/fixtures/plugin"
	"github.com/docker/docker/testutil/registry"
	"gotest.tools/v3/assert"
)

const (
	// the private registry to use for tests
	privateRegistryURL = registry.DefaultURL

	// path to containerd's ctr binary
	ctrBinary = "ctr"

	// the docker daemon binary to use
	dockerdBinary = "dockerd"
)

var (
	testEnv *environment.Execution

	// the docker client binary to use
	dockerBinary = ""

	testEnvOnce sync.Once
)

func init() {
	var err error

	reexec.Init() // This is required for external graphdriver tests

	testEnv, err = environment.New()
	if err != nil {
		panic(err)
	}
}

func TestMain(m *testing.M) {
	flag.Parse()

	// Global set up
	dockerBinary = testEnv.DockerBinary()
	err := ienv.EnsureFrozenImagesLinux(&testEnv.Execution)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	testEnv.Print()
	os.Exit(m.Run())
}

func ensureTestEnvSetup(t *testing.T) {
	testEnvOnce.Do(func() {
		cli.SetTestEnvironment(testEnv)
		fakestorage.SetTestEnvironment(&testEnv.Execution)
		ienv.ProtectAll(t, &testEnv.Execution)
	})
}

func TestDockerSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerAPISuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerBenchmarkSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIAttachSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIBuildSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLICommitSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLICpSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLICreateSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIEventSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIExecSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIHealthSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIHistorySuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIImagesSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIImportSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIInfoSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIInspectSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLILinksSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLILoginSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLILogsSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLINetmodeSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLINetworkSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPluginLogDriverSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPluginsSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPortSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIProxySuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPruneSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPsSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPullSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIPushSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIRestartSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIRmiSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIRunSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLISaveLoadSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLISearchSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLISNISuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIStartSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIStatsSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLITopSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIUpdateSuite{ds: &DockerSuite{}})
	suite.Run(t, &DockerCLIVolumeSuite{ds: &DockerSuite{}})
}

func TestDockerAPISuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerAPISuite{ds: &DockerSuite{}})
}

func TestDockerBenchmarkSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerBenchmarkSuite{ds: &DockerSuite{}})
}

func TestDockerCLIAttachSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIAttachSuite{ds: &DockerSuite{}})
}

func TestDockerCLIBuildSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIBuildSuite{ds: &DockerSuite{}})
}

func TestDockerCLICommitSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLICommitSuite{ds: &DockerSuite{}})
}

func TestDockerCLICpSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLICpSuite{ds: &DockerSuite{}})
}

func TestDockerCLICreateSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLICreateSuite{ds: &DockerSuite{}})
}

func TestDockerCLIEventSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIEventSuite{ds: &DockerSuite{}})
}

func TestDockerCLIExecSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIExecSuite{ds: &DockerSuite{}})
}

func TestDockerCLIHealthSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIHealthSuite{ds: &DockerSuite{}})
}

func TestDockerCLIHistorySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIHistorySuite{ds: &DockerSuite{}})
}

func TestDockerCLIImagesSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIImagesSuite{ds: &DockerSuite{}})
}

func TestDockerCLIImportSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIImportSuite{ds: &DockerSuite{}})
}

func TestDockerCLIInfoSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIInfoSuite{ds: &DockerSuite{}})
}

func TestDockerCLIInspectSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIInspectSuite{ds: &DockerSuite{}})
}

func TestDockerCLILinksSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLILinksSuite{ds: &DockerSuite{}})
}

func TestDockerCLILoginSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLILoginSuite{ds: &DockerSuite{}})
}

func TestDockerCLILogsSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLILogsSuite{ds: &DockerSuite{}})
}

func TestDockerCLINetmodeSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLINetmodeSuite{ds: &DockerSuite{}})
}

func TestDockerCLINetworkSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLINetworkSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPluginLogDriverSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPluginLogDriverSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPluginsSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPluginsSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPortSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPortSuite{ds: &DockerSuite{}})
}

func TestDockerCLIProxySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIProxySuite{ds: &DockerSuite{}})
}

func TestDockerCLIPruneSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPruneSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPsSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPsSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPullSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPullSuite{ds: &DockerSuite{}})
}

func TestDockerCLIPushSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIPushSuite{ds: &DockerSuite{}})
}

func TestDockerCLIRestartSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIRestartSuite{ds: &DockerSuite{}})
}

func TestDockerCLIRmiSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIRmiSuite{ds: &DockerSuite{}})
}

func TestDockerCLIRunSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIRunSuite{ds: &DockerSuite{}})
}

func TestDockerCLISaveLoadSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLISaveLoadSuite{ds: &DockerSuite{}})
}

func TestDockerCLISearchSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLISearchSuite{ds: &DockerSuite{}})
}

func TestDockerCLISNISuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLISNISuite{ds: &DockerSuite{}})
}

func TestDockerCLIStartSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIStartSuite{ds: &DockerSuite{}})
}

func TestDockerCLIStatsSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIStatsSuite{ds: &DockerSuite{}})
}

func TestDockerCLITopSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLITopSuite{ds: &DockerSuite{}})
}

func TestDockerCLIUpdateSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIUpdateSuite{ds: &DockerSuite{}})
}

func TestDockerCLIVolumeSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerCLIVolumeSuite{ds: &DockerSuite{}})
}

func TestDockerRegistrySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistrySuite{ds: &DockerSuite{}})
}

func TestDockerSchema1RegistrySuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerSchema1RegistrySuite{ds: &DockerSuite{}})
}

func TestDockerRegistryAuthHtpasswdSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistryAuthHtpasswdSuite{ds: &DockerSuite{}})
}

func TestDockerRegistryAuthTokenSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerRegistryAuthTokenSuite{ds: &DockerSuite{}})
}

func TestDockerDaemonSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerDaemonSuite{ds: &DockerSuite{}})
}

func TestDockerSwarmSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerSwarmSuite{ds: &DockerSuite{}})
}

func TestDockerPluginSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	suite.Run(t, &DockerPluginSuite{ds: &DockerSuite{}})
}

func TestDockerExternalVolumeSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	testRequires(t, DaemonIsLinux)
	suite.Run(t, &DockerExternalVolumeSuite{ds: &DockerSuite{}})
}

func TestDockerNetworkSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	testRequires(t, DaemonIsLinux)
	suite.Run(t, &DockerNetworkSuite{ds: &DockerSuite{}})
}

func TestDockerHubPullSuite(t *testing.T) {
	ensureTestEnvSetup(t)
	// FIXME. Temporarily turning this off for Windows as GH16039 was breaking
	// Windows to Linux CI @icecrime
	testRequires(t, DaemonIsLinux)
	suite.Run(t, newDockerHubPullSuite())
}

type DockerSuite struct {
}

func (s *DockerSuite) OnTimeout(c *testing.T) {
	if testEnv.IsRemoteDaemon() {
		return
	}
	path := filepath.Join(os.Getenv("DEST"), "docker.pid")
	b, err := os.ReadFile(path)
	if err != nil {
		c.Fatalf("Failed to get daemon PID from %s\n", path)
	}

	rawPid, err := strconv.ParseInt(string(b), 10, 32)
	if err != nil {
		c.Fatalf("Failed to parse pid from %s: %s\n", path, err)
	}

	daemonPid := int(rawPid)
	if daemonPid > 0 {
		testdaemon.SignalDaemonDump(daemonPid)
	}
}

func (s *DockerSuite) TearDownTest(c *testing.T) {
	testEnv.Clean(c)
}

type DockerRegistrySuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistrySuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistrySuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistrySuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerSchema1RegistrySuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerSchema1RegistrySuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerSchema1RegistrySuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, NotArm64, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c, registry.Schema1)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerSchema1RegistrySuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerRegistryAuthHtpasswdSuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistryAuthHtpasswdSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistryAuthHtpasswdSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.reg = registry.NewV2(c, registry.Htpasswd)
	s.reg.WaitReady(c)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistryAuthHtpasswdSuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		out, err := s.d.Cmd("logout", privateRegistryURL)
		assert.NilError(c, err, out)
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

type DockerRegistryAuthTokenSuite struct {
	ds  *DockerSuite
	reg *registry.V2
	d   *daemon.Daemon
}

func (s *DockerRegistryAuthTokenSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerRegistryAuthTokenSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting, testEnv.IsLocalDaemon)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerRegistryAuthTokenSuite) TearDownTest(c *testing.T) {
	if s.reg != nil {
		out, err := s.d.Cmd("logout", privateRegistryURL)
		assert.NilError(c, err, out)
		s.reg.Close()
	}
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

func (s *DockerRegistryAuthTokenSuite) setupRegistryWithTokenService(c *testing.T, tokenURL string) {
	if s == nil {
		c.Fatal("registry suite isn't initialized")
	}
	s.reg = registry.NewV2(c, registry.Token(tokenURL))
	s.reg.WaitReady(c)
}

type DockerDaemonSuite struct {
	ds *DockerSuite
	d  *daemon.Daemon
}

func (s *DockerDaemonSuite) OnTimeout(c *testing.T) {
	s.d.DumpStackAndQuit()
}

func (s *DockerDaemonSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
	s.d = daemon.New(c, dockerBinary, dockerdBinary, testdaemon.WithEnvironment(testEnv.Execution))
}

func (s *DockerDaemonSuite) TearDownTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
	if s.d != nil {
		s.d.Stop(c)
	}
	s.ds.TearDownTest(c)
}

func (s *DockerDaemonSuite) TearDownSuite(c *testing.T) {
	filepath.Walk(testdaemon.SockRoot, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			// ignore errors here
			// not cleaning up sockets is not really an error
			return nil
		}
		if fi.Mode() == os.ModeSocket {
			syscall.Unlink(path)
		}
		return nil
	})
	os.RemoveAll(testdaemon.SockRoot)
}

const defaultSwarmPort = 2477

type DockerSwarmSuite struct {
	server      *httptest.Server
	ds          *DockerSuite
	daemonsLock sync.Mutex // protect access to daemons and portIndex
	daemons     []*daemon.Daemon
	portIndex   int
}

func (s *DockerSwarmSuite) OnTimeout(c *testing.T) {
	s.daemonsLock.Lock()
	defer s.daemonsLock.Unlock()
	for _, d := range s.daemons {
		d.DumpStackAndQuit()
	}
}

func (s *DockerSwarmSuite) SetUpTest(c *testing.T) {
	testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
}

func (s *DockerSwarmSuite) AddDaemon(c *testing.T, joinSwarm, manager bool) *daemon.Daemon {
	c.Helper()
	d := daemon.New(c, dockerBinary, dockerdBinary,
		testdaemon.WithEnvironment(testEnv.Execution),
		testdaemon.WithSwarmPort(defaultSwarmPort+s.portIndex),
	)
	if joinSwarm {
		if len(s.daemons) > 0 {
			d.StartAndSwarmJoin(c, s.daemons[0].Daemon, manager)
		} else {
			d.StartAndSwarmInit(c)
		}
	} else {
		d.StartNodeWithBusybox(c)
	}

	s.daemonsLock.Lock()
	s.portIndex++
	s.daemons = append(s.daemons, d)
	s.daemonsLock.Unlock()

	return d
}

func (s *DockerSwarmSuite) TearDownTest(c *testing.T) {
	testRequires(c, DaemonIsLinux)
	s.daemonsLock.Lock()
	for _, d := range s.daemons {
		if d != nil {
			d.Stop(c)
			d.Cleanup(c)
		}
	}
	s.daemons = nil
	s.portIndex = 0
	s.daemonsLock.Unlock()
	s.ds.TearDownTest(c)
}

type DockerPluginSuite struct {
	ds       *DockerSuite
	registry *registry.V2
}

func (ps *DockerPluginSuite) registryHost() string {
	return privateRegistryURL
}

func (ps *DockerPluginSuite) getPluginRepo() string {
	return path.Join(ps.registryHost(), "plugin", "basic")
}
func (ps *DockerPluginSuite) getPluginRepoWithTag() string {
	return ps.getPluginRepo() + ":" + "latest"
}

func (ps *DockerPluginSuite) SetUpSuite(c *testing.T) {
	testRequires(c, DaemonIsLinux, RegistryHosting)
	ps.registry = registry.NewV2(c)
	ps.registry.WaitReady(c)

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	err := plugin.CreateInRegistry(ctx, ps.getPluginRepo(), nil)
	assert.NilError(c, err, "failed to create plugin")
}

func (ps *DockerPluginSuite) TearDownSuite(c *testing.T) {
	if ps.registry != nil {
		ps.registry.Close()
	}
}

func (ps *DockerPluginSuite) TearDownTest(c *testing.T) {
	ps.ds.TearDownTest(c)
}

func (ps *DockerPluginSuite) OnTimeout(c *testing.T) {
	ps.ds.OnTimeout(c)
}

> Nice! I'm still a little worried about all those duplicated tiny "test suite" function entrypoints

If it's about this function, it will be removed in a follow-up when linux tests will be moved to GHA so we can also use the `gotestlist` logic. I created this function temporarily just for backward compat with what Jenkins currently consumes: https://github.com/moby/moby/blob/20d6b5c1bdedffe74f9dd774e3e22ad59360a51c/Jenkinsfile#L328. As I don't have write access to this repo, making changes to the Jenkinsfile will not be reflected unfortunately but I could replace the `DockerSuite` with this list.