Преглед изворни кода

Merge pull request #198 from docker/windows_e2e

Windows e2e tests
Guillaume Tardif пре 5 година
родитељ
комит
3ad1e699a2

+ 2 - 0
.gitattributes

@@ -0,0 +1,2 @@
+core.autocrlf false
+*.golden text eol=lf

+ 32 - 1
.github/workflows/ci.yml

@@ -21,7 +21,7 @@ jobs:
       - name: Run golangci-lint
         run: |
           curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b . v1.27.0
-          ./golangci-lint run
+          ./golangci-lint run --timeout 10m0s
 
   build:
     name: Build
@@ -62,3 +62,34 @@ jobs:
 
       - name: E2E Test
         run: make e2e-local
+
+  windows-build:
+    name: Windows Build
+    runs-on: windows-latest
+    env:
+      GO111MODULE: "on"
+    steps:
+      - name: Set up Go 1.14
+        uses: actions/setup-go@v1
+        with:
+          go-version: 1.14
+        id: go
+
+      - name: Checkout code into the Go module directory
+        uses: actions/checkout@v2
+
+      - uses: actions/cache@v1
+        with:
+          path: ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
+
+      - name: Test
+        run: make -f builder.Makefile test
+
+      - name: Build
+        run: make -f builder.Makefile cli
+
+      - name: E2E Test
+        run: make e2e-win-ci

+ 4 - 1
Makefile

@@ -37,7 +37,10 @@ cli: ## Compile the cli
 	--output ./bin
 
 e2e-local: ## Run End to end local tests
-	go test -v ./tests/e2e ./moby/e2e
+	go test -v ./tests/e2e ./tests/skip-win-ci-e2e ./moby/e2e
+
+e2e-win-ci: ## Run End to end local tests on windows CI, no docker for linux containers available ATM
+	go test -v ./tests/e2e
 
 e2e-aci: ## Run End to end ACI tests (requires azure login)
 	go test -v ./tests/aci-e2e

+ 2 - 2
azure/convert/volume.go

@@ -3,7 +3,6 @@ package convert
 import (
 	"fmt"
 	"net/url"
-	"path/filepath"
 	"strings"
 
 	"github.com/pkg/errors"
@@ -113,7 +112,8 @@ func (v *volumeInput) parse(name string, s string) error {
 	v.name = name
 	v.target = volumeURL.Path
 	if v.target == "" {
-		v.target = filepath.Join("/run/volumes/", v.share)
+		// Do not use filepath.Join, on Windows it will replace / by \
+		v.target = "/run/volumes/" + v.share
 	}
 	return nil
 }

+ 4 - 74
tests/e2e/e2e_test.go

@@ -28,13 +28,8 @@
 package main
 
 import (
-	"fmt"
-	"io/ioutil"
-	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
-	"strings"
 	"testing"
 	"time"
 
@@ -65,7 +60,7 @@ func (s *E2eSuite) TestContextDefault() {
 		output := s.NewDockerCommand("context", "show").ExecOrDie()
 		Expect(output).To(ContainSubstring("default"))
 		output = s.NewCommand("docker", "context", "ls").ExecOrDie()
-		golden.Assert(s.T(), output, "ls-out-default.golden")
+		golden.Assert(s.T(), output, GoldenFile("ls-out-default"))
 	})
 }
 
@@ -106,7 +101,7 @@ func (s *E2eSuite) TestSetupError() {
 	It("should display an error if cannot shell out to docker-classic", func() {
 		err := os.Setenv("PATH", s.BinDir)
 		Expect(err).To(BeNil())
-		err = os.Remove(filepath.Join(s.BinDir, "docker-classic"))
+		err = os.Remove(filepath.Join(s.BinDir, DockerClassicExecutable()))
 		Expect(err).To(BeNil())
 		output, err := s.NewDockerCommand("ps").Exec()
 		Expect(output).To(ContainSubstring("docker-classic"))
@@ -115,37 +110,6 @@ func (s *E2eSuite) TestSetupError() {
 	})
 }
 
-func (s *E2eSuite) TestKillChildOnCancel() {
-	It("should kill docker-classic if parent command is cancelled", func() {
-		out := s.ListProcessesCommand().ExecOrDie()
-		Expect(out).NotTo(ContainSubstring("docker-classic"))
-
-		dir := s.ConfigDir
-		Expect(ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), []byte(`FROM alpine:3.10
-RUN sleep 100`), 0644)).To(Succeed())
-		shutdown := make(chan time.Time)
-		errs := make(chan error)
-		ctx := s.NewDockerCommand("build", "--no-cache", "-t", "test-sleep-image", ".").WithinDirectory(dir).WithTimeout(shutdown)
-		go func() {
-			_, err := ctx.Exec()
-			errs <- err
-		}()
-		err := WaitFor(time.Second, 10*time.Second, errs, func() bool {
-			out := s.ListProcessesCommand().ExecOrDie()
-			return strings.Contains(out, "docker-classic")
-		})
-		Expect(err).NotTo(HaveOccurred())
-		log.Println("Killing docker process")
-
-		close(shutdown)
-		err = WaitFor(time.Second, 12*time.Second, nil, func() bool {
-			out := s.ListProcessesCommand().ExecOrDie()
-			return !strings.Contains(out, "docker-classic")
-		})
-		Expect(err).NotTo(HaveOccurred())
-	})
-}
-
 func (s *E2eSuite) TestLegacy() {
 	It("should list all legacy commands", func() {
 		output := s.NewDockerCommand("--help").ExecOrDie()
@@ -159,7 +123,7 @@ func (s *E2eSuite) TestLegacy() {
 
 	It("should run local container in less than 10 secs", func() {
 		s.NewDockerCommand("pull", "hello-world").ExecOrDie()
-		output := s.NewDockerCommand("run", "--rm", "hello-world").WithTimeout(time.NewTimer(10 * time.Second).C).ExecOrDie()
+		output := s.NewDockerCommand("run", "--rm", "hello-world").WithTimeout(time.NewTimer(20 * time.Second).C).ExecOrDie()
 		Expect(output).To(ContainSubstring("Hello from Docker!"))
 	})
 }
@@ -187,7 +151,7 @@ func (s *E2eSuite) TestMockBackend() {
 		currentContext := s.NewDockerCommand("context", "use", "test-example").ExecOrDie()
 		Expect(currentContext).To(ContainSubstring("test-example"))
 		output := s.NewDockerCommand("context", "ls").ExecOrDie()
-		golden.Assert(s.T(), output, "ls-out-test-example.golden")
+		golden.Assert(s.T(), output, GoldenFile("ls-out-test-example"))
 		output = s.NewDockerCommand("context", "show").ExecOrDie()
 		Expect(output).To(ContainSubstring("test-example"))
 	})
@@ -222,40 +186,6 @@ func (s *E2eSuite) TestMockBackend() {
 	})
 }
 
-func (s *E2eSuite) TestAPIServer() {
-	_, err := exec.LookPath("yarn")
-	if err != nil || os.Getenv("SKIP_NODE") != "" {
-		s.T().Skip("skipping, yarn not installed")
-	}
-	It("can run 'serve' command", func() {
-		cName := "test-example"
-		s.NewDockerCommand("context", "create", cName, "example").ExecOrDie()
-
-		sPath := fmt.Sprintf("unix:///%s/docker.sock", s.ConfigDir)
-		server, err := serveAPI(s.ConfigDir, sPath)
-		Expect(err).To(BeNil())
-		defer killProcess(server)
-
-		s.NewCommand("yarn", "install").WithinDirectory("../node-client").ExecOrDie()
-		output := s.NewCommand("yarn", "run", "start", cName, sPath).WithinDirectory("../node-client").ExecOrDie()
-		Expect(output).To(ContainSubstring("nginx"))
-	})
-}
-
 func TestE2e(t *testing.T) {
 	suite.Run(t, new(E2eSuite))
 }
-
-func killProcess(process *os.Process) {
-	err := process.Kill()
-	Expect(err).To(BeNil())
-}
-
-func serveAPI(configDir string, address string) (*os.Process, error) {
-	cmd := exec.Command("../../bin/docker", "--config", configDir, "serve", "--address", address)
-	err := cmd.Start()
-	if err != nil {
-		return nil, err
-	}
-	return cmd.Process, nil
-}

+ 2 - 0
tests/e2e/testdata/ls-out-default-windows.golden

@@ -0,0 +1,2 @@
+NAME                TYPE                DESCRIPTION                               DOCKER ENPOINT                   KUBERNETES ENDPOINT   ORCHESTRATOR
+default *           docker              Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm

+ 3 - 0
tests/e2e/testdata/ls-out-test-example-windows.golden

@@ -0,0 +1,3 @@
+NAME                TYPE                DESCRIPTION                               DOCKER ENPOINT                   KUBERNETES ENDPOINT   ORCHESTRATOR
+default             docker              Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
+test-example *      example                                                                                                              

+ 14 - 0
tests/framework/helper.go

@@ -28,6 +28,7 @@
 package framework
 
 import (
+	"runtime"
 	"strings"
 
 	"github.com/robpike/filter"
@@ -48,6 +49,19 @@ func Columns(line string) []string {
 	return filter.Choose(strings.Split(line, " "), nonEmptyString).([]string)
 }
 
+// GoldenFile golden file specific to platform
+func GoldenFile(name string) string {
+	if IsWindows() {
+		return name + "-windows.golden"
+	}
+	return name + ".golden"
+}
+
+// IsWindows windows or other GOOS
+func IsWindows() bool {
+	return runtime.GOOS == "windows"
+}
+
 // It runs func
 func It(description string, test func()) {
 	test()

+ 39 - 11
tests/framework/suite.go

@@ -33,7 +33,6 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"runtime"
 	"time"
 
 	"github.com/onsi/gomega"
@@ -56,13 +55,14 @@ func (s *Suite) SetupSuite() {
 		log.Error(message)
 		cp := filepath.Join(s.ConfigDir, "config.json")
 		d, _ := ioutil.ReadFile(cp)
+		fmt.Printf("Bin dir:%s\n", s.BinDir)
 		fmt.Printf("Contents of %s:\n%s\n\nContents of config dir:\n", cp, string(d))
 		for _, p := range dirContents(s.ConfigDir) {
 			fmt.Println(p)
 		}
 		s.T().Fail()
 	})
-	s.linkClassicDocker()
+	s.copyExecutablesInBinDir()
 }
 
 // TearDownSuite is run after all tests
@@ -79,22 +79,42 @@ func dirContents(dir string) []string {
 	return res
 }
 
-func (s *Suite) linkClassicDocker() {
-	p, err := exec.LookPath("docker-classic")
+func (s *Suite) copyExecutablesInBinDir() {
+	p, err := exec.LookPath(DockerClassicExecutable())
 	if err != nil {
-		p, err = exec.LookPath("docker")
+		p, err = exec.LookPath(dockerExecutable())
 	}
 	gomega.Expect(err).To(gomega.BeNil())
-	err = os.Symlink(p, filepath.Join(s.BinDir, "docker-classic"))
+	err = copyFile(p, filepath.Join(s.BinDir, DockerClassicExecutable()))
 	gomega.Expect(err).To(gomega.BeNil())
-	dockerPath, err := filepath.Abs("../../bin/docker")
+	dockerPath, err := filepath.Abs("../../bin/" + dockerExecutable())
 	gomega.Expect(err).To(gomega.BeNil())
-	err = os.Symlink(dockerPath, filepath.Join(s.BinDir, "docker"))
+	err = copyFile(dockerPath, filepath.Join(s.BinDir, dockerExecutable()))
 	gomega.Expect(err).To(gomega.BeNil())
-	err = os.Setenv("PATH", fmt.Sprintf("%s:%s", s.BinDir, os.Getenv("PATH")))
+	err = os.Setenv("PATH", concatenatePath(s.BinDir))
 	gomega.Expect(err).To(gomega.BeNil())
 }
 
+func concatenatePath(path string) string {
+	if IsWindows() {
+		return fmt.Sprintf("%s;%s", path, os.Getenv("PATH"))
+	}
+	return fmt.Sprintf("%s:%s", path, os.Getenv("PATH"))
+}
+
+func copyFile(sourceFile string, destinationFile string) error {
+	input, err := ioutil.ReadFile(sourceFile)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(destinationFile, input, 0777)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 // BeforeTest is run before each test
 func (s *Suite) BeforeTest(suite, test string) {
 	d, _ := ioutil.TempDir("", "")
@@ -109,7 +129,7 @@ func (s *Suite) AfterTest(suite, test string) {
 
 // ListProcessesCommand creates a command to list processes, "tasklist" on windows, "ps" otherwise.
 func (s *Suite) ListProcessesCommand() *CmdContext {
-	if runtime.GOOS == "windows" {
+	if IsWindows() {
 		return s.NewCommand("tasklist")
 	}
 	return s.NewCommand("ps")
@@ -125,12 +145,20 @@ func (s *Suite) NewCommand(command string, args ...string) *CmdContext {
 }
 
 func dockerExecutable() string {
-	if runtime.GOOS == "windows" {
+	if IsWindows() {
 		return "docker.exe"
 	}
 	return "docker"
 }
 
+// DockerClassicExecutable binary name based on platform
+func DockerClassicExecutable() string {
+	if IsWindows() {
+		return "docker-classic.exe"
+	}
+	return "docker-classic"
+}
+
 // NewDockerCommand creates a docker builder.
 func (s *Suite) NewDockerCommand(args ...string) *CmdContext {
 	return s.NewCommand(dockerExecutable(), args...)

+ 127 - 0
tests/skip-win-ci-e2e/skip_win_ci_test.go

@@ -0,0 +1,127 @@
+/*
+	Copyright (c) 2020 Docker Inc.
+
+	Permission is hereby granted, free of charge, to any person
+	obtaining a copy of this software and associated documentation
+	files (the "Software"), to deal in the Software without
+	restriction, including without limitation the rights to use, copy,
+	modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be
+	included in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+	EXPRESS OR IMPLIED,
+	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+	HOLDERS BE LIABLE FOR ANY CLAIM,
+	DAMAGES OR OTHER LIABILITY,
+	WHETHER IN AN ACTION OF CONTRACT,
+	TORT OR OTHERWISE,
+	ARISING FROM, OUT OF OR IN CONNECTION WITH
+	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+
+	. "github.com/onsi/gomega"
+	"github.com/stretchr/testify/suite"
+
+	. "github.com/docker/api/tests/framework"
+)
+
+type NonWinCIE2eSuite struct {
+	Suite
+}
+
+func (s *NonWinCIE2eSuite) TestKillChildOnCancel() {
+	It("should kill docker-classic if parent command is cancelled", func() {
+		out := s.ListProcessesCommand().ExecOrDie()
+		Expect(out).NotTo(ContainSubstring("docker-classic"))
+
+		dir := s.ConfigDir
+		Expect(ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), []byte(`FROM alpine:3.10
+RUN sleep 100`), 0644)).To(Succeed())
+		shutdown := make(chan time.Time)
+		errs := make(chan error)
+		ctx := s.NewDockerCommand("build", "--no-cache", "-t", "test-sleep-image", ".").WithinDirectory(dir).WithTimeout(shutdown)
+		go func() {
+			_, err := ctx.Exec()
+			errs <- err
+		}()
+		err := WaitFor(time.Second, 10*time.Second, errs, func() bool {
+			out := s.ListProcessesCommand().ExecOrDie()
+			return strings.Contains(out, "docker-classic")
+		})
+		Expect(err).NotTo(HaveOccurred())
+		log.Println("Killing docker process")
+
+		close(shutdown)
+		err = WaitFor(time.Second, 12*time.Second, nil, func() bool {
+			out := s.ListProcessesCommand().ExecOrDie()
+			return !strings.Contains(out, "docker-classic")
+		})
+		Expect(err).NotTo(HaveOccurred())
+	})
+}
+
+func (s *NonWinCIE2eSuite) TestAPIServer() {
+	_, err := exec.LookPath("yarn")
+	if err != nil || os.Getenv("SKIP_NODE") != "" {
+		s.T().Skip("skipping, yarn not installed")
+	}
+	It("can run 'serve' command", func() {
+		cName := "test-example"
+		s.NewDockerCommand("context", "create", cName, "example").ExecOrDie()
+
+		//sPath := fmt.Sprintf("unix:///%s/docker.sock", s.ConfigDir)
+		sPath, cliAddress := s.getGrpcServerAndCLientAddress()
+		server, err := serveAPI(s.ConfigDir, sPath)
+		Expect(err).To(BeNil())
+		defer killProcess(server)
+
+		s.NewCommand("yarn", "install").WithinDirectory("../node-client").ExecOrDie()
+		output := s.NewCommand("yarn", "run", "start", cName, cliAddress).WithinDirectory("../node-client").ExecOrDie()
+		Expect(output).To(ContainSubstring("nginx"))
+	})
+}
+
+func (s *NonWinCIE2eSuite) getGrpcServerAndCLientAddress() (string, string) {
+	if IsWindows() {
+		return "npipe:////./pipe/clibackend", "unix:////./pipe/clibackend"
+	}
+	socketName := fmt.Sprintf("unix:///%s/docker.sock", s.ConfigDir)
+	return socketName, socketName
+}
+
+func TestE2e(t *testing.T) {
+	suite.Run(t, new(NonWinCIE2eSuite))
+}
+
+func killProcess(process *os.Process) {
+	err := process.Kill()
+	Expect(err).To(BeNil())
+}
+
+func serveAPI(configDir string, address string) (*os.Process, error) {
+	cmd := exec.Command("../../bin/docker", "--config", configDir, "serve", "--address", address)
+	err := cmd.Start()
+	if err != nil {
+		return nil, err
+	}
+	return cmd.Process, nil
+}