diff --git a/etcdctl/ctlv2/ctl.go b/etcdctl/ctlv2/ctl.go index 06adec2ab88..4cbfd9415ac 100644 --- a/etcdctl/ctlv2/ctl.go +++ b/etcdctl/ctlv2/ctl.go @@ -26,7 +26,7 @@ import ( "github.com/urfave/cli" ) -func Start() { +func Start() error { app := cli.NewApp() app.Name = "etcdctl" app.Version = version.Version @@ -72,8 +72,11 @@ func Start() { command.NewRoleCommands(), command.NewAuthCommands(), } + return app.Run(os.Args) +} - err := runCtlV2(app) +func MustStart() { + err := Start() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/etcdctl/ctlv2/ctl_cov.go b/etcdctl/ctlv2/ctl_cov.go deleted file mode 100644 index e9f22f25d84..00000000000 --- a/etcdctl/ctlv2/ctl_cov.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 The etcd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build cov - -package ctlv2 - -import ( - "os" - "strings" - - "github.com/urfave/cli" -) - -func runCtlV2(app *cli.App) error { - return app.Run(strings.Split(os.Getenv("ETCDCTL_ARGS"), "\xe7\xcd")) -} diff --git a/etcdctl/ctlv2/ctl_nocov.go b/etcdctl/ctlv2/ctl_nocov.go deleted file mode 100644 index 1591360e58a..00000000000 --- a/etcdctl/ctlv2/ctl_nocov.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2017 The etcd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !cov - -package ctlv2 - -import ( - "os" - - "github.com/urfave/cli" -) - -func runCtlV2(app *cli.App) error { - return app.Run(os.Args) -} diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index 748449af565..e16e8e3c287 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -95,6 +95,19 @@ func init() { ) } +func Start() error { + rootCmd.SetUsageFunc(usageFunc) + // Make help just show the usage + rootCmd.SetHelpTemplate(`{{.UsageString}}`) + return rootCmd.Execute() +} + +func MustStart() { + if err := Start(); err != nil { + command.ExitWithError(command.ExitError, err) + } +} + func init() { cobra.EnablePrefixMatching = true } diff --git a/etcdctl/ctlv3/ctl_cov.go b/etcdctl/ctlv3/ctl_cov.go deleted file mode 100644 index 5ca7a513be6..00000000000 --- a/etcdctl/ctlv3/ctl_cov.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 The etcd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build cov - -package ctlv3 - -import ( - "os" - "strings" - - "go.etcd.io/etcd/v3/etcdctl/ctlv3/command" -) - -func Start() { - // ETCDCTL_ARGS=etcdctl_test arg1 arg2... - // SetArgs() takes arg1 arg2... - rootCmd.SetArgs(strings.Split(os.Getenv("ETCDCTL_ARGS"), "\xe7\xcd")[1:]) - os.Unsetenv("ETCDCTL_ARGS") - if err := rootCmd.Execute(); err != nil { - command.ExitWithError(command.ExitError, err) - } -} diff --git a/etcdctl/ctlv3/ctl_nocov.go b/etcdctl/ctlv3/ctl_nocov.go deleted file mode 100644 index 2d6e04808f0..00000000000 --- a/etcdctl/ctlv3/ctl_nocov.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 The etcd Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !cov - -package ctlv3 - -import "go.etcd.io/etcd/v3/etcdctl/ctlv3/command" - -func Start() { - rootCmd.SetUsageFunc(usageFunc) - // Make help just show the usage - rootCmd.SetHelpTemplate(`{{.UsageString}}`) - if err := rootCmd.Execute(); err != nil { - command.ExitWithError(command.ExitError, err) - } -} diff --git a/etcdctl/main.go b/etcdctl/main.go index 1ae3ee8e4ee..754088df753 100644 --- a/etcdctl/main.go +++ b/etcdctl/main.go @@ -27,20 +27,44 @@ const ( apiEnv = "ETCDCTL_API" ) +/** +mainWithError is fully analogous to main, but instead of signaling errors +by os.Exit, it exposes the error explicitly, such that test-logic can intercept +control to e.g. dump coverage data (even for test-for-failure scenarios). +*/ +func mainWithError() error { + apiv := os.Getenv(apiEnv) + + // unset apiEnv to avoid side-effect for future env and flag parsing. + os.Unsetenv(apiEnv) + + if len(apiv) == 0 || apiv == "3" { + return ctlv3.Start() + } + + if apiv == "2" { + return ctlv2.Start() + } + + fmt.Fprintf(os.Stderr, "unsupported API version: %s\n", apiv) + return fmt.Errorf("unsupported API version: %s", apiv) +} + func main() { apiv := os.Getenv(apiEnv) + // unset apiEnv to avoid side-effect for future env and flag parsing. os.Unsetenv(apiEnv) if len(apiv) == 0 || apiv == "3" { - ctlv3.Start() + ctlv3.MustStart() return } if apiv == "2" { - ctlv2.Start() + ctlv2.MustStart() return } - fmt.Fprintln(os.Stderr, "unsupported API version", apiv) + fmt.Fprintf(os.Stderr, "unsupported API version: %v\n", apiv) os.Exit(1) } diff --git a/etcdctl/main_test.go b/etcdctl/main_test.go index ccd3ba0ac62..604241cacaf 100644 --- a/etcdctl/main_test.go +++ b/etcdctl/main_test.go @@ -15,15 +15,52 @@ package main import ( + "log" "os" "strings" "testing" ) -func TestMain(t *testing.T) { +func SplitTestArgs(args []string) (testArgs, appArgs []string) { + for i, arg := range os.Args { + switch { + case strings.HasPrefix(arg, "-test."): + testArgs = append(testArgs, arg) + case i == 0: + appArgs = append(appArgs, arg) + testArgs = append(testArgs, arg) + default: + appArgs = append(appArgs, arg) + } + } + return +} + +// Empty test to avoid no-tests warning. +func TestEmpty(t *testing.T) {} + +/** + * The purpose of this "test" is to run etcdctl with code-coverage + * collection turned on. The technique is documented here: + * + * https://www.cyphar.com/blog/post/20170412-golang-integration-coverage + */ +func TestMain(m *testing.M) { // don't launch etcdctl when invoked via go test if strings.HasSuffix(os.Args[0], "etcdctl.test") { return } - main() + + testArgs, appArgs := SplitTestArgs(os.Args) + + os.Args = appArgs + + err := mainWithError() + if err != nil { + log.Fatalf("etcdctl failed with: %v", err) + } + + // This will generate coverage files: + os.Args = testArgs + m.Run() } diff --git a/etcdmain/etcd.go b/etcdmain/etcd.go index 72b88a3545f..de024dc72db 100644 --- a/etcdmain/etcd.go +++ b/etcdmain/etcd.go @@ -49,13 +49,13 @@ var ( dirEmpty = dirType("empty") ) -func startEtcdOrProxyV2() { +func startEtcdOrProxyV2(args []string) { grpc.EnableTracing = false cfg := newConfig() defaultInitialCluster := cfg.ec.InitialCluster - err := cfg.parse(os.Args[1:]) + err := cfg.parse(args[1:]) lg := cfg.ec.GetLogger() if lg == nil { var zapError error @@ -66,6 +66,7 @@ func startEtcdOrProxyV2() { os.Exit(1) } } + lg.Info("Running: ", zap.Strings("args", args)) if err != nil { lg.Warn("failed to verify flags", zap.Error(err)) switch err { diff --git a/etcdmain/gateway.go b/etcdmain/gateway.go index 631e5e3446d..20fd8e18d39 100644 --- a/etcdmain/gateway.go +++ b/etcdmain/gateway.go @@ -99,6 +99,9 @@ func startGateway(cmd *cobra.Command, args []string) { os.Exit(1) } + // We use os.Args to show all the arguments (not only passed-through Cobra). + lg.Info("Running: ", zap.Strings("args", os.Args)) + srvs := discoverEndpoints(lg, gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery, gatewayDNSClusterServiceName) if len(srvs.Endpoints) == 0 { // no endpoints discovered, fall back to provided endpoints diff --git a/etcdmain/main.go b/etcdmain/main.go index 5d911fbe3ea..2e67a137cc3 100644 --- a/etcdmain/main.go +++ b/etcdmain/main.go @@ -17,22 +17,16 @@ package etcdmain import ( "fmt" "os" - "strings" "github.com/coreos/go-systemd/v22/daemon" "go.uber.org/zap" ) -func Main() { +func Main(args []string) { checkSupportArch() - if len(os.Args) > 1 { - cmd := os.Args[1] - if covArgs := os.Getenv("ETCDCOV_ARGS"); len(covArgs) > 0 { - args := strings.Split(os.Getenv("ETCDCOV_ARGS"), "\xe7\xcd")[1:] - rootCmd.SetArgs(args) - cmd = "grpc-proxy" - } + if len(args) > 1 { + cmd := args[1] switch cmd { case "gateway", "grpc-proxy": if err := rootCmd.Execute(); err != nil { @@ -43,7 +37,7 @@ func Main() { } } - startEtcdOrProxyV2() + startEtcdOrProxyV2(args) } func notifySystemd(lg *zap.Logger) { diff --git a/main.go b/main.go index 2284ebdcb42..9ec6bf1e8eb 100644 --- a/main.go +++ b/main.go @@ -22,8 +22,12 @@ // package main -import "go.etcd.io/etcd/v3/etcdmain" +import ( + "os" + + "go.etcd.io/etcd/v3/etcdmain" +) func main() { - etcdmain.Main() + etcdmain.Main(os.Args) } diff --git a/main_test.go b/main_test.go index 62cf5f1e98c..060f7c55ce1 100644 --- a/main_test.go +++ b/main_test.go @@ -15,25 +15,54 @@ package main import ( + "log" "os" "os/signal" "strings" "syscall" "testing" + + "go.etcd.io/etcd/v3/etcdmain" ) -func TestMain(t *testing.T) { - // don't launch etcd server when invoked via go test - // Note: module name has /v3 now - if strings.HasSuffix(os.Args[0], "v3.test") { - t.Skip("skip launching etcd server when invoked via go test") +func SplitTestArgs(args []string) (testArgs, appArgs []string) { + for i, arg := range os.Args { + switch { + case strings.HasPrefix(arg, "-test."): + testArgs = append(testArgs, arg) + case i == 0: + appArgs = append(appArgs, arg) + testArgs = append(testArgs, arg) + default: + appArgs = append(appArgs, arg) + } } - if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-test.") { - t.Skip("skip launching etcd server when invoked via go test") + return +} + +func TestEmpty(t *testing.T) {} + +/** + * The purpose of this "test" is to run etcd server with code-coverage + * collection turned on. The technique is documented here: + * + * https://www.cyphar.com/blog/post/20170412-golang-integration-coverage + */ +func TestMain(m *testing.M) { + // don't launch etcd server when invoked via go test + if strings.HasSuffix(os.Args[0], ".test") { + log.Printf("skip launching etcd server when invoked via go test") + return } + testArgs, appArgs := SplitTestArgs(os.Args) + notifier := make(chan os.Signal, 1) signal.Notify(notifier, syscall.SIGINT, syscall.SIGTERM) - go main() + go etcdmain.Main(appArgs) <-notifier + + // This will generate coverage files: + os.Args = testArgs + m.Run() } diff --git a/test b/test index bccdc941ae2..247aa389b10 100755 --- a/test +++ b/test @@ -304,6 +304,9 @@ function cov_pass { # strip out generated files (using GNU-style sed) sed --in-place '/generated.go/d' "$COVERDIR"/cover.out || true + echo -e "\nTo generate coverage report use:" + echo -e " go tool cover --html=$COVERDIR/cover.out\n" + # held failures to generate the full coverage file, now fail if [ -n "$failed" ]; then for f in $failed; do @@ -665,6 +668,7 @@ function build_cov_pass { out="bin" if [ -n "${BINDIR}" ]; then out="${BINDIR}"; fi go test -mod=mod -tags cov -c -covermode=set -coverpkg="$PKGS_COMMA" -o "${out}/etcd_test" + go test -mod=mod -tags cov -c -covermode=set -coverpkg="$PKGS_COMMA" -o "${out}/etcdctl_test" "${REPO_PATH}/etcdctl" } diff --git a/tests/e2e/ctl_v2_test.go b/tests/e2e/ctl_v2_test.go index 9af2fe01aea..4f4409ef6f2 100644 --- a/tests/e2e/ctl_v2_test.go +++ b/tests/e2e/ctl_v2_test.go @@ -21,7 +21,6 @@ import ( "testing" "time" - "go.etcd.io/etcd/v3/pkg/fileutil" "go.etcd.io/etcd/v3/pkg/testutil" ) @@ -37,11 +36,7 @@ func testCtlV2Set(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) { cfg.enableV2 = true epc := setupEtcdctlTest(t, cfg, quorum) - defer func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }() + defer cleanupEtcdProcessCluster(epc, t) key, value := "foo", "bar" @@ -64,11 +59,7 @@ func testCtlV2Mk(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) { cfg.enableV2 = true epc := setupEtcdctlTest(t, cfg, quorum) - defer func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }() + defer cleanupEtcdProcessCluster(epc, t) key, value := "foo", "bar" @@ -93,11 +84,7 @@ func testCtlV2Rm(t *testing.T, cfg *etcdProcessClusterConfig) { cfg.enableV2 = true epc := setupEtcdctlTest(t, cfg, true) - defer func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }() + defer cleanupEtcdProcessCluster(epc, t) key, value := "foo", "bar" @@ -123,11 +110,7 @@ func testCtlV2Ls(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) { cfg.enableV2 = true epc := setupEtcdctlTest(t, cfg, quorum) - defer func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }() + defer cleanupEtcdProcessCluster(epc, t) key, value := "foo", "bar" @@ -150,11 +133,7 @@ func testCtlV2Watch(t *testing.T, cfg *etcdProcessClusterConfig, noSync bool) { cfg.enableV2 = true epc := setupEtcdctlTest(t, cfg, true) - defer func() { - if errC := epc.Close(); errC != nil { - t.Fatalf("error closing etcd processes (%v)", errC) - } - }() + defer cleanupEtcdProcessCluster(epc, t) key, value := "foo", "bar" errc := etcdctlWatch(epc, key, value, noSync) @@ -180,11 +159,7 @@ func TestCtlV2GetRoleUser(t *testing.T) { copied := configNoTLS copied.enableV2 = true epc := setupEtcdctlTest(t, &copied, false) - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() + defer cleanupEtcdProcessCluster(epc, t) if err := etcdctlRoleAdd(epc, "foo"); err != nil { t.Fatalf("failed to add role (%v)", err) @@ -217,11 +192,7 @@ func testCtlV2UserList(t *testing.T, username string) { copied := configNoTLS copied.enableV2 = true epc := setupEtcdctlTest(t, &copied, false) - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() + defer cleanupEtcdProcessCluster(epc, t) if err := etcdctlUserAdd(epc, username, "password"); err != nil { t.Fatalf("failed to add user (%v)", err) @@ -239,11 +210,7 @@ func TestCtlV2RoleList(t *testing.T) { copied := configNoTLS copied.enableV2 = true epc := setupEtcdctlTest(t, &copied, false) - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() + defer cleanupEtcdProcessCluster(epc, t) if err := etcdctlRoleAdd(epc, "foo"); err != nil { t.Fatalf("failed to add role (%v)", err) @@ -307,6 +274,8 @@ func testCtlV2Backup(t *testing.T, snapCount int, v3 bool) { cfg2.forceNewCluster = true cfg2.enableV2 = true epc2 := setupEtcdctlTest(t, &cfg2, false) + // Make sure a failing test is not leaking resources (running server). + defer epc2.Close() // check if backup went through correctly if err := etcdctlGet(epc2, "foo1", "bar1", false); err != nil { @@ -348,11 +317,7 @@ func TestCtlV2AuthWithCommonName(t *testing.T) { copiedCfg.clientCertAuthEnabled = true copiedCfg.enableV2 = true epc := setupEtcdctlTest(t, &copiedCfg, false) - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() + defer cleanupEtcdProcessCluster(epc, t) if err := etcdctlRoleAdd(epc, "testrole"); err != nil { t.Fatalf("failed to add role (%v)", err) @@ -385,11 +350,7 @@ func TestCtlV2ClusterHealth(t *testing.T) { copied := configNoTLS copied.enableV2 = true epc := setupEtcdctlTest(t, &copied, true) - defer func() { - if err := epc.Close(); err != nil { - t.Fatalf("error closing etcd processes (%v)", err) - } - }() + defer cleanupEtcdProcessCluster(epc, t) // all members available if err := etcdctlClusterHealth(epc, "cluster is healthy"); err != nil { @@ -536,14 +497,7 @@ func etcdctlBackup(clus *etcdProcessCluster, dataDir, backupDir string, v3 bool) return proc.Close() } -func mustEtcdctl(t *testing.T) { - if !fileutil.Exist(binDir + "/etcdctl") { - t.Fatalf("could not find etcdctl binary") - } -} - func setupEtcdctlTest(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) *etcdProcessCluster { - mustEtcdctl(t) if !quorum { cfg = configStandalone(*cfg) } @@ -553,3 +507,9 @@ func setupEtcdctlTest(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) } return epc } + +func cleanupEtcdProcessCluster(epc *etcdProcessCluster, t *testing.T) { + if errC := epc.Close(); errC != nil { + t.Fatalf("error closing etcd processes (%v)", errC) + } +} diff --git a/tests/e2e/ctl_v3_snapshot_test.go b/tests/e2e/ctl_v3_snapshot_test.go index 9941cb1f00e..68347fb6a3d 100644 --- a/tests/e2e/ctl_v3_snapshot_test.go +++ b/tests/e2e/ctl_v3_snapshot_test.go @@ -155,7 +155,6 @@ func getSnapshotStatus(cx ctlCtx, fpath string) (snapshot.Status, error) { // syncs up with other members and serve correct data. func TestIssue6361(t *testing.T) { defer testutil.AfterTest(t) - mustEtcdctl(t) os.Setenv("ETCDCTL_API", "3") defer os.Unsetenv("ETCDCTL_API") diff --git a/tests/e2e/ctl_v3_test.go b/tests/e2e/ctl_v3_test.go index 849c48b19ef..e97ef7163cd 100644 --- a/tests/e2e/ctl_v3_test.go +++ b/tests/e2e/ctl_v3_test.go @@ -205,7 +205,6 @@ func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { } ret.applyOpts(opts) - mustEtcdctl(t) if !ret.quorum { ret.cfg = *configStandalone(ret.cfg) } diff --git a/tests/e2e/etcd_process.go b/tests/e2e/etcd_process.go index c6a03ecefaa..57a09020f02 100644 --- a/tests/e2e/etcd_process.go +++ b/tests/e2e/etcd_process.go @@ -70,7 +70,7 @@ type etcdServerProcessConfig struct { func newEtcdServerProcess(cfg *etcdServerProcessConfig) (*etcdServerProcess, error) { if !fileutil.Exist(cfg.execPath) { - return nil, fmt.Errorf("could not find etcd binary") + return nil, fmt.Errorf("could not find etcd binary: %s", cfg.execPath) } if !cfg.keepDataDir { if err := os.RemoveAll(cfg.dataDirPath); err != nil { @@ -117,7 +117,7 @@ func (ep *etcdServerProcess) Stop() (err error) { ep.donec = make(chan struct{}) if ep.cfg.purl.Scheme == "unix" || ep.cfg.purl.Scheme == "unixs" { err = os.Remove(ep.cfg.purl.Host + ep.cfg.purl.Path) - if err != nil { + if err != nil && !os.IsNotExist(err) { return err } } diff --git a/tests/e2e/etcd_spawn_cov.go b/tests/e2e/etcd_spawn_cov.go index f845490d44f..91fe505d528 100644 --- a/tests/e2e/etcd_spawn_cov.go +++ b/tests/e2e/etcd_spawn_cov.go @@ -18,73 +18,44 @@ package e2e import ( "fmt" + "log" "os" "path/filepath" - "strings" "syscall" "time" "go.etcd.io/etcd/v3/pkg/expect" "go.etcd.io/etcd/v3/pkg/fileutil" - "go.etcd.io/etcd/v3/pkg/flags" ) const noOutputLineCount = 2 // cov-enabled binaries emit PASS and coverage count lines func spawnCmd(args []string) (*expect.ExpectProcess, error) { - if args[0] == binPath { - return spawnEtcd(args) + cmd := args[0] + env := make([]string, 0) + switch cmd { + case binPath: + cmd = "../../bin/etcd_test" + case ctlBinPath: + cmd = "../../bin/etcdctl_test" + case ctlBinPath + "3": + cmd = "../../bin/etcdctl_test" + env = append(env, "ETCDCTL_API=3") } - if args[0] == ctlBinPath || args[0] == ctlBinPath+"3" { - // avoid test flag conflicts in coverage enabled etcdctl by putting flags in ETCDCTL_ARGS - env := []string{ - // was \xff, but that's used for testing boundary conditions; 0xe7cd should be safe - "ETCDCTL_ARGS=" + strings.Join(args, "\xe7\xcd"), - } - if args[0] == ctlBinPath+"3" { - env = append(env, "ETCDCTL_API=3") - } - covArgs, err := getCovArgs() - if err != nil { - return nil, err - } - // when withFlagByEnv() is used in testCtl(), env variables for ctl is set to os.env. - // they must be included in ctl_cov_env. - env = append(env, os.Environ()...) - ep, err := expect.NewExpectWithEnv(binDir+"/etcdctl_test", covArgs, env) - if err != nil { - return nil, err - } - ep.StopSignal = syscall.SIGTERM - return ep, nil - } - - return expect.NewExpect(args[0], args[1:]...) -} - -func spawnEtcd(args []string) (*expect.ExpectProcess, error) { covArgs, err := getCovArgs() if err != nil { return nil, err } - - var env []string - if args[1] == "grpc-proxy" { - // avoid test flag conflicts in coverage enabled etcd by putting flags in ETCDCOV_ARGS - env = append(os.Environ(), "ETCDCOV_ARGS="+strings.Join(args, "\xe7\xcd")) - } else { - env = args2env(args[1:]) - } - - ep, err := expect.NewExpectWithEnv(binDir+"/etcd_test", covArgs, env) + // when withFlagByEnv() is used in testCtl(), env variables for ctl is set to os.env. + // they must be included in ctl_cov_env. + env = append(env, os.Environ()...) + all_args := append(args[1:], covArgs...) + log.Printf("Executing %v %v", cmd, all_args) + ep, err := expect.NewExpectWithEnv(cmd, all_args, env) if err != nil { return nil, err } - // ep sends SIGTERM to etcd_test process on ep.close() - // allowing the process to exit gracefully in order to generate a coverage report. - // note: go runtime ignores SIGINT but not SIGTERM - // if e2e test is run as a background process. ep.StopSignal = syscall.SIGTERM return ep, nil } @@ -105,29 +76,3 @@ func getCovArgs() ([]string, error) { } return covArgs, nil } - -func args2env(args []string) []string { - var covEnvs []string - for i := range args { - if !strings.HasPrefix(args[i], "--") { - continue - } - flag := strings.Split(args[i], "--")[1] - val := "true" - // split the flag that has "=" - // e.g --auto-tls=true" => flag=auto-tls and val=true - if strings.Contains(args[i], "=") { - split := strings.Split(flag, "=") - flag = split[0] - val = split[1] - } - - if i+1 < len(args) { - if !strings.HasPrefix(args[i+1], "--") { - val = args[i+1] - } - } - covEnvs = append(covEnvs, flags.FlagToEnv("ETCD", flag)+"="+val) - } - return covEnvs -} diff --git a/tests/e2e/util.go b/tests/e2e/util.go index de73e511ab0..64b2fbc57d2 100644 --- a/tests/e2e/util.go +++ b/tests/e2e/util.go @@ -64,7 +64,7 @@ func spawnWithExpectLines(args []string, xs ...string) ([]string, error) { l, lerr := proc.ExpectFunc(lineFunc) if lerr != nil { proc.Close() - return nil, fmt.Errorf("%v (expected %q, got %q)", lerr, txt, lines) + return nil, fmt.Errorf("%v %v (expected %q, got %q). Try EXPECT_DEBUG=TRUE", args, lerr, txt, lines) } lines = append(lines, l) if strings.Contains(l, txt) { @@ -73,8 +73,9 @@ func spawnWithExpectLines(args []string, xs ...string) ([]string, error) { } } perr := proc.Close() - if len(xs) == 0 && proc.LineCount() != noOutputLineCount { // expect no output - return nil, fmt.Errorf("unexpected output (got lines %q, line count %d)", lines, proc.LineCount()) + l := proc.LineCount() + if len(xs) == 0 && l != noOutputLineCount { // expect no output + return nil, fmt.Errorf("unexpected output from %v (got lines %q, line count %d) %v. Try EXPECT_DEBUG=TRUE", args, lines, l, l != noOutputLineCount) } return lines, perr }