tests/e2e,etcdctl,etcdmain: Fix go test --tags cov -v ./tests/e2e

This CL fixes:
  COVERDIR=./coverage PASSES="build_cov" && go test --tags cov -v ./tests/e2e
and is part of the effort to make:
  COVERDIR=coverage PASSES="build_cov cov" ./test
fully pass.

The args passed to ./bin/etcd_test and ./bin/etcdctl_test binaries were
mismatched. The protocol of passing the arguments using
environment variables has been replaces with proper passing of flags.

How the measurement of coverage by e2e tests works:
  1. COVERDIR=./coverage PASSES="build_cov" are generating
./bin/etcd_test and ./bin/etcdctl_test binaries.

  2. These binaries are tests (as coverage can be computed only for
tests) [see ./main_test.go ./etcdctl/main_test.go], but this tests are
running the main logic of the server and uppon termination (or SIGTERM
signal) are writting proper .coverprofile files in the $COVERDIR folder.
The binaries used to take arguments using env variables, but its not
needed any longer. The binaries can consume any command line arguments
that either test (so --test.fooo) or the original binary can consume.

 3.  The tests/e2e (when compiled with the --tags cov) are starting the
_test binaries instead of the original binaries, such that the coverage
is being collected.
release-3.5
Piotr Tabor 2020-09-07 14:05:08 +02:00
parent c20cc05fc5
commit c32180d772
20 changed files with 180 additions and 281 deletions

View File

@ -26,7 +26,7 @@ import (
"github.com/urfave/cli"
)
func Start() {
func StartWithError() 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 Start() {
err := StartWithError()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)

View File

@ -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"))
}

View File

@ -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)
}

View File

@ -95,6 +95,19 @@ func init() {
)
}
func StartWithError() error {
rootCmd.SetUsageFunc(usageFunc)
// Make help just show the usage
rootCmd.SetHelpTemplate(`{{.UsageString}}`)
return rootCmd.Execute()
}
func Start() {
if err := StartWithError(); err != nil {
command.ExitWithError(command.ExitError, err)
}
}
func init() {
cobra.EnablePrefixMatching = true
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -27,8 +27,32 @@ 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.StartWithError()
}
if apiv == "2" {
return ctlv2.StartWithError()
}
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" {
@ -41,6 +65,6 @@ func main() {
return
}
fmt.Fprintln(os.Stderr, "unsupported API version", apiv)
fmt.Fprintf(os.Stderr, "unsupported API version: %v\n", apiv)
os.Exit(1)
}

View File

@ -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()
}

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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)
}

View File

@ -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) {
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
}
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
// 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")
}
if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-test.") {
t.Skip("skip launching 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()
}

4
test
View File

@ -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"
}

View File

@ -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)
}
}

View File

@ -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")

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
}
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
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")
}
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
}

View File

@ -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
}