func (suite *TestSuite) GetFunctionPath(functionRelativePath ...string) string {
	functionPath := []string{suite.FunctionDir}
	functionPath = append(functionPath, functionRelativePath...)
	return path.Join(functionPath...)
}
GetFunctionPath returns the non-relative function path (given a relative path)
package processorsuite
import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"time"
	"github.com/nuclio/nuclio/pkg/common"
	"github.com/nuclio/nuclio/pkg/dockerclient"
	"github.com/nuclio/nuclio/pkg/functionconfig"
	"github.com/nuclio/nuclio/pkg/platform"
	"github.com/nuclio/nuclio/pkg/platform/factory"
	"github.com/nuclio/nuclio/pkg/platformconfig"
	"github.com/nuclio/logger"
	"github.com/nuclio/zap"
	"github.com/rs/xid"
	"github.com/stretchr/testify/suite"
	"github.com/tsenart/vegeta/v12/lib"
)
const (
	keepDockerEnvKey = "NUCLIO_TEST_KEEP_DOCKER"
)
type RunOptions struct {
	dockerclient.RunOptions
}
type BlastFunctionHTTPFunc func(configuration *BlastConfiguration) *vegeta.Metrics
type OnAfterContainerRun func(deployResult *platform.CreateFunctionResult) bool
type TestSuite struct {
	suite.Suite
	Logger                logger.Logger
	LoggerName            string
	DockerClient          dockerclient.Client
	Platform              platform.Platform
	TestID                string
	Runtime               string
	RuntimeDir            string
	FunctionDir           string
	PlatformType          string
	Namespace             string
	PlatformConfiguration *platformconfig.Config
	FunctionNameUniquify  bool
	containerID            string
	createdTempDirs        []string
	cleanupCreatedTempDirs bool
}
type BlastConfiguration struct {
	Duration      time.Duration
	TimeOut       time.Duration
	URL           string
	Method        string
	FunctionName  string
	FunctionPath  string
	Handler       string
	RatePerWorker int
	Workers       int
}
func (suite *TestSuite) SetupSuite() {
	var err error
	if suite.RuntimeDir == "" {
		suite.RuntimeDir = suite.Runtime
	}
	if suite.PlatformType == "" {
		suite.PlatformType = "local"
	}
	if suite.Namespace == "" {
		suite.Namespace = "default"
	}
	if suite.LoggerName == "" {
		suite.LoggerName = "test"
	}
	suite.FunctionNameUniquify = true
	suite.Logger, err = nucliozap.NewNuclioZapTest(suite.LoggerName)
	suite.Require().NoError(err)
	suite.DockerClient, err = dockerclient.NewShellClient(suite.Logger, nil)
	suite.Require().NoError(err)
	if suite.PlatformConfiguration == nil {
		suite.PlatformConfiguration, err = platformconfig.NewPlatformConfig("")
		suite.Require().NoError(err)
	}
	suite.Platform, err = factory.CreatePlatform(suite.Logger,
		suite.PlatformType,
		suite.PlatformConfiguration,
		suite.Namespace)
	suite.Require().NoError(err)
	suite.Require().NotNil(suite.Platform)
}
func (suite *TestSuite) SetupTest() {
	suite.TestID = xid.New().String()
	suite.Logger.InfoWith("Running test",
		"name", suite.T().Name(),
		"id", suite.TestID)
}
func (suite *TestSuite) BlastHTTP(configuration BlastConfiguration) {
	createFunctionOptions, err := suite.blastConfigurationToDeployOptions(&configuration)
	suite.Require().NoError(err)
	totalResults := suite.DeployFunctionAndBlastHTTP(configuration,
		createFunctionOptions,
		suite.blastFunctionHTTP)
	suite.Logger.DebugWith("BlastHTTP results",
		"totalResults.Success", totalResults.Success*100.00,
		"totalResults.Errors", totalResults.Errors)
	suite.Require().GreaterOrEqual(totalResults.Success, 0.95, "Success rate should be higher")
}
func (suite *TestSuite) BlastHTTPThroughput(firstCreateFunctionOptions *platform.CreateFunctionOptions,
	secondCreateFunctionOptions *platform.CreateFunctionOptions,
	allowedThroughputMarginPercentage float64,
	numWorkers int) []*vegeta.Metrics {
	var results []*vegeta.Metrics
	blastConfiguration := BlastConfiguration{
		Duration: 10 * time.Second,
		Method:   http.MethodGet,
		Workers:  numWorkers,
	}
	suite.Logger.InfoWith("Blasting functions", "blastConfiguration", blastConfiguration)
	firstBlastResults := suite.DeployFunctionAndBlastHTTP(blastConfiguration,
		firstCreateFunctionOptions,
		suite.blastFunctionThroughput)
	suite.Logger.InfoWith("Successfully blasted first function",
		"requests", firstBlastResults.Requests,
		"success", firstBlastResults.Success,
		"blastResults", firstBlastResults)
	results = append(results, firstBlastResults)
	sleepTimeout := 10 * time.Second
	suite.Logger.InfoWith("Letting system to cool down", "sleepTimeout", sleepTimeout)
	time.Sleep(sleepTimeout)
	secondBlastResults := suite.DeployFunctionAndBlastHTTP(blastConfiguration,
		secondCreateFunctionOptions,
		suite.blastFunctionThroughput)
	suite.Logger.InfoWith("Successfully blasted second function",
		"requests", secondBlastResults.Requests,
		"success", secondBlastResults.Success,
		"blastResults", secondBlastResults)
	results = append(results, secondBlastResults)
	suite.Require().GreaterOrEqual(secondBlastResults.Throughput,
		firstBlastResults.Throughput*(1-allowedThroughputMarginPercentage/100))
	return results
}
func (suite *TestSuite) NewBlastConfiguration() BlastConfiguration {
	return BlastConfiguration{
		Method:        http.MethodGet,
		Workers:       32,
		RatePerWorker: 5,
		Duration:      10 * time.Second,
		URL:           suite.GetTestHost(),
		FunctionName:  "outputter",
		FunctionPath:  "outputter",
		TimeOut:       600 * time.Second,
	}
}
func (suite *TestSuite) DeployFunctionAndBlastHTTP(blastConfiguration BlastConfiguration,
	createFunctionOptions *platform.CreateFunctionOptions,
	blastFunc BlastFunctionHTTPFunc) *vegeta.Metrics {
	var totalResults *vegeta.Metrics
	suite.DeployFunction(createFunctionOptions, func(deployResult *platform.CreateFunctionResult) bool {
		blastConfiguration.URL = fmt.Sprintf("http://%s:%d", suite.GetTestHost(), deployResult.Port)
		err := suite.probeAndWaitForFunctionReadiness(&blastConfiguration)
		suite.Require().NoError(err, "Failed to probe and wait for function readiness")
		totalResults = blastFunc(&blastConfiguration)
		return true
	})
	return totalResults
}
func (suite *TestSuite) TearDownTest() {
	suite.Logger.InfoWith("Tearing down test", "testName", suite.T().Name())
	if suite.containerID != "" {
		if suite.T().Failed() {
			time.Sleep(2 * time.Second)
			if logs, err := suite.DockerClient.GetContainerLogs(suite.containerID); err == nil {
				suite.Logger.WarnWith("Test failed, retrieved logs", "logs", logs)
			} else {
				suite.Logger.WarnWith("Failed to get docker logs on failure", "err", err)
			}
		}
		if os.Getenv(keepDockerEnvKey) == "" {
			err := suite.DockerClient.RemoveContainer(suite.containerID)
			suite.Require().NoError(err)
		}
	}
	if !suite.T().Skipped() && suite.cleanupCreatedTempDirs {
		for _, tempDir := range suite.createdTempDirs {
			if common.FileExists(tempDir) {
				suite.Failf("", "Temporary dir %s was not cleaned", tempDir)
			}
		}
	}
}
func (suite *TestSuite) GetFunction(getFunctionOptions *platform.GetFunctionsOptions) platform.Function {
	functions, err := suite.Platform.GetFunctions(getFunctionOptions)
	suite.Require().NoError(err, "Failed to get functions")
	suite.Len(functions, 1, "Expected to find one function")
	return functions[0]
}
func (suite *TestSuite) WaitForFunctionState(getFunctionOptions *platform.GetFunctionsOptions,
	desiredFunctionState functionconfig.FunctionState,
	duration time.Duration) {
	err := common.RetryUntilSuccessful(duration,
		1*time.Second,
		func() bool {
			function := suite.GetFunction(getFunctionOptions)
			suite.Logger.InfoWith("Waiting for function state",
				"currentFunctionState", function.GetStatus().State,
				"desiredFunctionState", desiredFunctionState)
			return function.GetStatus().State == desiredFunctionState
		})
	suite.Require().NoError(err, "Function did not reach its desired state")
}
func (suite *TestSuite) DeployFunction(createFunctionOptions *platform.CreateFunctionOptions,
	onAfterContainerRun OnAfterContainerRun) *platform.CreateFunctionResult {
	deployResult, err := suite.deployFunctionPopulateMissingFields(createFunctionOptions, onAfterContainerRun)
	suite.Require().NoError(err)
	return deployResult
}
func (suite *TestSuite) DeployFunctionExpectError(createFunctionOptions *platform.CreateFunctionOptions,
	onAfterContainerRun OnAfterContainerRun) (*platform.CreateFunctionResult, error) {
	deployResult, err := suite.deployFunctionPopulateMissingFields(createFunctionOptions, onAfterContainerRun)
	suite.Require().Error(err)
	return deployResult, err
}
func (suite *TestSuite) DeployFunctionAndRedeploy(createFunctionOptions *platform.CreateFunctionOptions,
	onAfterFirstContainerRun OnAfterContainerRun,
	onAfterSecondContainerRun OnAfterContainerRun) {
	suite.PopulateDeployOptions(createFunctionOptions)
	defer suite.Platform.DeleteFunction(&platform.DeleteFunctionOptions{  
		FunctionConfig: createFunctionOptions.FunctionConfig,
	})
	_, err := suite.deployFunction(createFunctionOptions, onAfterFirstContainerRun)
	suite.Require().NoError(err)
	_, err = suite.deployFunction(createFunctionOptions, onAfterSecondContainerRun)
	suite.Require().NoError(err)
}
func (suite *TestSuite) DeployFunctionExpectErrorAndRedeploy(createFunctionOptions *platform.CreateFunctionOptions,
	onAfterFirstContainerRun OnAfterContainerRun,
	onAfterSecondContainerRun OnAfterContainerRun) {
	suite.PopulateDeployOptions(createFunctionOptions)
	defer suite.Platform.DeleteFunction(&platform.DeleteFunctionOptions{  
		FunctionConfig: createFunctionOptions.FunctionConfig,
	})
	_, err := suite.deployFunction(createFunctionOptions, onAfterFirstContainerRun)
	suite.Require().Error(err)
	_, err = suite.deployFunction(createFunctionOptions, onAfterSecondContainerRun)
	suite.Require().NoError(err)
}
func (suite *TestSuite) DeployFunctionAndRedeployExpectError(createFunctionOptions *platform.CreateFunctionOptions,
	onAfterFirstContainerRun OnAfterContainerRun,
	onAfterSecondContainerRun OnAfterContainerRun) {
	suite.PopulateDeployOptions(createFunctionOptions)
	defer suite.Platform.DeleteFunction(&platform.DeleteFunctionOptions{  
		FunctionConfig: createFunctionOptions.FunctionConfig,
	})
	_, err := suite.deployFunction(createFunctionOptions, onAfterFirstContainerRun)
	suite.Require().NoError(err)
	_, err = suite.deployFunction(createFunctionOptions, onAfterSecondContainerRun)
	suite.Require().Error(err)
}
func (suite *TestSuite) GetNuclioSourceDir() string {
	return common.GetSourceDir()
}
func (suite *TestSuite) GetNuclioHostSourceDir() string {
	return common.GetEnvOrDefaultString("NUCLIO_TEST_HOST_PATH", suite.GetNuclioSourceDir())
}
func (suite *TestSuite) GetTestFunctionsDir() string {
	return path.Join(suite.GetNuclioSourceDir(), "test", "_functions")
}
func (suite *TestSuite) GetTestHost() string {
	return common.GetEnvOrDefaultString("NUCLIO_TEST_HOST", "127.0.0.1")
}
func (suite *TestSuite) GetDeployOptions(functionName string, functionPath string) *platform.CreateFunctionOptions {
	functionConfig := *functionconfig.NewConfig()
	functionConfig.Spec.ReadinessTimeoutSeconds = 60
	createFunctionOptions := &platform.CreateFunctionOptions{
		Logger:         suite.Logger,
		FunctionConfig: functionConfig,
	}
	createFunctionOptions.FunctionConfig.Meta.Name = functionName
	createFunctionOptions.FunctionConfig.Spec.Runtime = suite.Runtime
	createFunctionOptions.FunctionConfig.Spec.Build.Path = functionPath
	createFunctionOptions.FunctionConfig.Spec.Triggers = map[string]functionconfig.Trigger{}
	createFunctionOptions.FunctionConfig.Spec.Build.TempDir = suite.CreateTempDir()
	return createFunctionOptions
}