github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/tests/e2e/framework/common.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package framework
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  	"time"
    25  )
    26  
    27  const (
    28  	NginxImage   = "docker.io/nginx:1.14.2"
    29  	BusyboxImage = "docker.io/busybox:1.30.0"
    30  )
    31  
    32  // ErrTimeout is the timeout error returned from functions wrapped by WithTimeout
    33  var ErrTimeout = fmt.Errorf("timeout")
    34  
    35  // CommandError holds an exit code for commands finished without any Executor error
    36  type CommandError struct {
    37  	ExitCode int
    38  }
    39  
    40  func (e CommandError) Error() string {
    41  	return fmt.Sprintf("command finished with %d exit code", e.ExitCode)
    42  }
    43  
    44  var _ error = CommandError{}
    45  
    46  // Command is the interface to control the command started with an Executor
    47  type Command interface {
    48  	Kill() error
    49  	Wait() error
    50  }
    51  
    52  // Executor is the interface to run shell commands in arbitrary places
    53  type Executor interface {
    54  	io.Closer
    55  	Run(stdin io.Reader, stdout, stderr io.Writer, command ...string) error
    56  	Start(stdin io.Reader, stdout, stderr io.Writer, command ...string) (Command, error)
    57  	Logs() (string, error)
    58  }
    59  
    60  // Run executes command with the given executor, returns stdout/stderr as strings
    61  // and exit code in CommandError
    62  func Run(executor Executor, input string, command ...string) (string, string, error) {
    63  	outBuf := &bytes.Buffer{}
    64  	errBuf := &bytes.Buffer{}
    65  	inBuf := bytes.NewBufferString(input)
    66  	err := executor.Run(inBuf, outBuf, errBuf, command...)
    67  	return outBuf.String(), errBuf.String(), err
    68  }
    69  
    70  // RunSimple is a simplified version of Run that verifies exit code/stderr internally and returns stdout only
    71  func RunSimple(executor Executor, command ...string) (string, error) {
    72  	stdout, stderr, err := Run(executor, "", command...)
    73  	if err != nil {
    74  		if ce, ok := err.(CommandError); ok {
    75  			if ce.ExitCode != 0 {
    76  				return "", fmt.Errorf("command exited with code %d, stderr: %s", ce.ExitCode, strings.TrimSpace(stderr)+strings.TrimSpace(stdout))
    77  			}
    78  			return strings.TrimSpace(stdout), nil
    79  		}
    80  		return "", err
    81  	}
    82  	return strings.TrimSpace(stdout), nil
    83  }
    84  
    85  func trimBlock(s string) string {
    86  	lines := strings.Split(s, "\n")
    87  	for i, line := range lines {
    88  		lines[i] = strings.TrimSpace(line)
    89  	}
    90  	return strings.Join(lines, "\n")
    91  }
    92  
    93  func waitFor(f func() error, wait, poll time.Duration, waitFailure bool) error {
    94  	if poll <= 0 || wait <= 0 {
    95  		wait = time.Duration(time.Hour)
    96  		poll = 0
    97  	}
    98  	timeout := time.After(wait)
    99  	err := f()
   100  	if err == nil && !waitFailure || err != nil && waitFailure {
   101  		return err
   102  	}
   103  	result := err
   104  	for {
   105  		select {
   106  		case <-time.After(poll):
   107  			err := f()
   108  			if err == nil && !waitFailure || err != nil && waitFailure {
   109  				return err
   110  			}
   111  			result = err
   112  			if poll == 0 {
   113  				return result
   114  			}
   115  		case <-timeout:
   116  			return result
   117  		}
   118  	}
   119  }
   120  
   121  func waitForConsistentState(f func() error, timing ...time.Duration) error {
   122  	if len(timing) == 0 {
   123  		panic("timing is not provided")
   124  	}
   125  	var pollPeriod time.Duration
   126  	if len(timing) == 1 || timing[1] <= 0 {
   127  		pollPeriod = time.Duration(timing[0].Nanoseconds() / 10)
   128  	} else {
   129  		pollPeriod = timing[1]
   130  	}
   131  	var err error
   132  	for timing[0] > 0 {
   133  		now := time.Now()
   134  		if err = waitFor(f, timing[0], pollPeriod, false); err != nil {
   135  			timing[0] -= time.Now().Sub(now)
   136  			continue
   137  		}
   138  
   139  		if len(timing) >= 2 {
   140  			now := time.Now()
   141  			if err = waitFor(f, timing[2], pollPeriod, true); err != nil {
   142  				timing[0] -= time.Now().Sub(now)
   143  				continue
   144  			}
   145  		}
   146  		break
   147  	}
   148  	return err
   149  }
   150  
   151  // WithTimeout adds timeout to synchronous function
   152  func WithTimeout(timeout time.Duration, fn func() error) func() error {
   153  	return func() error {
   154  		res := make(chan error, 1)
   155  		go func() {
   156  			res <- fn()
   157  		}()
   158  		timer := time.After(timeout)
   159  		select {
   160  		case e := <-res:
   161  			return e
   162  		case <-timer:
   163  			return ErrTimeout
   164  		}
   165  	}
   166  }