github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/test/integration/performance_expansion_int_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/ActiveState/cli/internal/config"
    12  	"github.com/ActiveState/cli/internal/errs"
    13  	"github.com/ActiveState/cli/internal/fileutils"
    14  	"github.com/ActiveState/cli/internal/osutils"
    15  	"github.com/ActiveState/cli/internal/testhelpers/e2e"
    16  	"github.com/ActiveState/cli/internal/testhelpers/suite"
    17  	"github.com/ActiveState/cli/internal/testhelpers/tagsuite"
    18  	"github.com/ActiveState/cli/pkg/projectfile"
    19  	"gopkg.in/yaml.v2"
    20  )
    21  
    22  // Configuration values for the performance tests
    23  const (
    24  	DefaultProject  = "https://platform.activestate.com/ActiveState-CLI/Yaml-Test/?branch=main"
    25  	DefaultCommitID = "0476ac66-007c-4da7-8922-d6ea9b284fae"
    26  
    27  	DefaultMaxTime        = 1000 * time.Millisecond
    28  	DefaultSamples        = 10
    29  	DefaultVariance       = 0.75
    30  	DefaultSecretsMaxTime = 4500 * time.Millisecond
    31  	// Add other configuration values on per-test basis if needed
    32  )
    33  
    34  type PerformanceExpansionIntegrationTestSuite struct {
    35  	tagsuite.Suite
    36  }
    37  
    38  func (suite *PerformanceExpansionIntegrationTestSuite) startSvc(ts *e2e.Session) {
    39  	// Start svc first, as we don't want to measure svc startup time which would only happen the very first invocation
    40  	stdout, stderr, err := osutils.ExecSimple(ts.SvcExe, []string{"start"}, []string{})
    41  	suite.Require().NoError(err, fmt.Sprintf("Full error:\n%v\nstdout:\n%s\nstderr:\n%s", errs.JoinMessage(err), stdout, stderr))
    42  }
    43  
    44  func (suite *PerformanceExpansionIntegrationTestSuite) TestExpansionPerformance() {
    45  	suite.OnlyRunForTags(tagsuite.Performance)
    46  
    47  	// Establish baseline
    48  	// Must not be called as a subtest as it breaks the running of other subtests
    49  	median := suite.testScriptPerformance(scriptPerformanceOptions{
    50  		script: projectfile.Script{
    51  			NameVal: projectfile.NameVal{
    52  				Name:  "call-script",
    53  				Value: `echo "Hello World"`,
    54  			},
    55  			ScriptFields: projectfile.ScriptFields{
    56  				Language: "bash",
    57  			},
    58  		},
    59  		expect:  "Hello World",
    60  		samples: DefaultSamples,
    61  		max:     DefaultMaxTime,
    62  		verbose: true,
    63  	})
    64  	variance := float64(median) + (float64(median) * DefaultVariance)
    65  	baseline := time.Duration(variance)
    66  
    67  	suite.Require().NotEqual(DefaultMaxTime, baseline)
    68  
    69  	suite.Run("CallScriptFromMerged", func() {
    70  		additionalYamls := make(map[string]projectfile.Project)
    71  		additionalYamls["activestate.test.yaml"] = projectfile.Project{
    72  			Scripts: []projectfile.Script{
    73  				{NameVal: projectfile.NameVal{Name: "call-script", Value: `echo "Hello World"`}},
    74  			},
    75  		}
    76  		suite.testScriptPerformance(scriptPerformanceOptions{
    77  			script: projectfile.Script{
    78  				NameVal: projectfile.NameVal{
    79  					Name:  "merged-script",
    80  					Value: `echo "Hello World"`,
    81  				},
    82  				ScriptFields: projectfile.ScriptFields{
    83  					Language: "bash",
    84  				},
    85  			},
    86  			expect:              "Hello World",
    87  			samples:             DefaultSamples,
    88  			max:                 baseline,
    89  			additionalYamlFiles: additionalYamls,
    90  		})
    91  	})
    92  
    93  	suite.Run("EvaluateProjectPath", func() {
    94  		suite.testScriptPerformance(scriptPerformanceOptions{
    95  			script: projectfile.Script{
    96  				NameVal: projectfile.NameVal{
    97  					Name:  "evaluate-project-path",
    98  					Value: `echo $project.path()`,
    99  				},
   100  				ScriptFields: projectfile.ScriptFields{
   101  					Language: "bash",
   102  				},
   103  			},
   104  			samples: DefaultSamples,
   105  			max:     baseline,
   106  		})
   107  	})
   108  
   109  	suite.Run("ExpandProjectBranch", func() {
   110  		suite.testScriptPerformance(scriptPerformanceOptions{
   111  			script: projectfile.Script{
   112  				NameVal: projectfile.NameVal{
   113  					Name:  "expand-project-branch",
   114  					Value: `echo $project.branch()`,
   115  				},
   116  				ScriptFields: projectfile.ScriptFields{
   117  					Language: "bash",
   118  				},
   119  			},
   120  			expect:  "main",
   121  			samples: DefaultSamples,
   122  			max:     baseline,
   123  		})
   124  	})
   125  
   126  	suite.Run("ExpandProjectCommit", func() {
   127  		suite.testScriptPerformance(scriptPerformanceOptions{
   128  			script: projectfile.Script{
   129  				NameVal: projectfile.NameVal{
   130  					Name:  "expand-project-commit",
   131  					Value: `echo $project.commit()`,
   132  				},
   133  				ScriptFields: projectfile.ScriptFields{
   134  					Language: "bash",
   135  				},
   136  			},
   137  			expect:  "0476ac66-007c-4da7-8922-d6ea9b284fae",
   138  			samples: DefaultSamples,
   139  			max:     baseline,
   140  		})
   141  	})
   142  
   143  	suite.Run("ExpandProjectName", func() {
   144  		suite.testScriptPerformance(scriptPerformanceOptions{
   145  			script: projectfile.Script{
   146  				NameVal: projectfile.NameVal{
   147  					Name:  "expand-project-name",
   148  					Value: `echo $project.name()`,
   149  				},
   150  				ScriptFields: projectfile.ScriptFields{
   151  					Language: "bash",
   152  				},
   153  			},
   154  			expect:  "Yaml-Test",
   155  			samples: DefaultSamples,
   156  			max:     baseline,
   157  		})
   158  	})
   159  
   160  	suite.Run("ExpandProjectNamespace", func() {
   161  		suite.testScriptPerformance(scriptPerformanceOptions{
   162  			script: projectfile.Script{
   163  				NameVal: projectfile.NameVal{
   164  					Name:  "expand-project-namespace",
   165  					Value: `echo $project.namespace()`,
   166  				},
   167  				ScriptFields: projectfile.ScriptFields{
   168  					Language: "bash",
   169  				},
   170  			},
   171  			expect:  "ActiveState-CLI/Yaml-Test",
   172  			samples: DefaultSamples,
   173  			max:     baseline,
   174  		})
   175  	})
   176  
   177  	suite.Run("ExpandProjectOwner", func() {
   178  		suite.testScriptPerformance(scriptPerformanceOptions{
   179  			script: projectfile.Script{
   180  				NameVal: projectfile.NameVal{
   181  					Name:  "expand-project-owner",
   182  					Value: `echo $project.owner()`,
   183  				},
   184  				ScriptFields: projectfile.ScriptFields{
   185  					Language: "bash",
   186  				},
   187  			},
   188  			expect:  "ActiveState-CLI",
   189  			samples: DefaultSamples,
   190  			max:     baseline,
   191  		})
   192  	})
   193  
   194  	suite.Run("ExpandProjectURL", func() {
   195  		suite.testScriptPerformance(scriptPerformanceOptions{
   196  			script: projectfile.Script{
   197  				NameVal: projectfile.NameVal{
   198  					Name:  "expand-project-url",
   199  					Value: `echo $project.url()`,
   200  				},
   201  				ScriptFields: projectfile.ScriptFields{
   202  					Language: "bash",
   203  				},
   204  			},
   205  			expect:  "https://platform.activestate.com/ActiveState-CLI/Yaml-Test",
   206  			samples: DefaultSamples,
   207  			max:     baseline,
   208  			verbose: true,
   209  		})
   210  	})
   211  
   212  	suite.Run("ExpandSecret", func() {
   213  		suite.testScriptPerformance(scriptPerformanceOptions{
   214  			script: projectfile.Script{
   215  				NameVal: projectfile.NameVal{
   216  					Name:  "expand-secret",
   217  					Value: `echo $secrets.project.HELLO`,
   218  				},
   219  				ScriptFields: projectfile.ScriptFields{
   220  					Language: "bash",
   221  				},
   222  			},
   223  			expect:       "WORLD",
   224  			samples:      DefaultSamples,
   225  			max:          DefaultSecretsMaxTime,
   226  			authRequired: true,
   227  		})
   228  	})
   229  
   230  	suite.Run("ExpandSecretMultiple", func() {
   231  		secretsMultipleVariance := float64(DefaultSecretsMaxTime) * 1.25
   232  		secretsMultipleBaseline := time.Duration(secretsMultipleVariance)
   233  		suite.testScriptPerformance(scriptPerformanceOptions{
   234  			script: projectfile.Script{
   235  				NameVal: projectfile.NameVal{
   236  					Name:  "expand-secret",
   237  					Value: `echo $secrets.project.FOO $secrets.project.BAR $secrets.project.BAZ`,
   238  				},
   239  				ScriptFields: projectfile.ScriptFields{
   240  					Language: "bash",
   241  				},
   242  			},
   243  			expect:       "FOO BAR BAZ",
   244  			samples:      DefaultSamples,
   245  			max:          secretsMultipleBaseline,
   246  			authRequired: true,
   247  		})
   248  	})
   249  
   250  	suite.Run("GetScriptPath", func() {
   251  		expect := ".sh"
   252  		if runtime.GOOS == "windows" {
   253  			expect = ".bat"
   254  		}
   255  		suite.testScriptPerformance(scriptPerformanceOptions{
   256  			script: projectfile.Script{
   257  				NameVal: projectfile.NameVal{
   258  					Name:  "script-path",
   259  					Value: `echo $scripts.hello-world.path()`,
   260  				},
   261  				ScriptFields: projectfile.ScriptFields{
   262  					Language: "bash",
   263  				},
   264  			},
   265  			expect:  expect,
   266  			samples: DefaultSamples,
   267  			max:     baseline,
   268  			additionalScripts: projectfile.Scripts{
   269  				{NameVal: projectfile.NameVal{Name: "hello-world", Value: `echo "Hello World"`}},
   270  			},
   271  		})
   272  	})
   273  
   274  	suite.Run("UseConstant", func() {
   275  		suite.testScriptPerformance(scriptPerformanceOptions{
   276  			script: projectfile.Script{
   277  				NameVal: projectfile.NameVal{
   278  					Name:  "use-constant",
   279  					Value: `echo $constants.foo`,
   280  				},
   281  				ScriptFields: projectfile.ScriptFields{
   282  					Language: "bash",
   283  				},
   284  			},
   285  			expect:  "foo",
   286  			samples: DefaultSamples,
   287  			max:     baseline,
   288  			constants: projectfile.Constants{
   289  				{NameVal: projectfile.NameVal{Name: "foo", Value: "foo"}},
   290  			},
   291  		})
   292  	})
   293  
   294  	suite.Run("UseConstantMultiple", func() {
   295  		suite.testScriptPerformance(scriptPerformanceOptions{
   296  			script: projectfile.Script{
   297  				NameVal: projectfile.NameVal{
   298  					Name:  "use-constant-multiple",
   299  					Value: `echo $constants.foo $constants.bar $constants.baz`,
   300  				},
   301  				ScriptFields: projectfile.ScriptFields{
   302  					Language: "bash",
   303  				},
   304  			},
   305  			expect:  "foo",
   306  			samples: DefaultSamples,
   307  			max:     baseline,
   308  			constants: projectfile.Constants{
   309  				{NameVal: projectfile.NameVal{Name: "foo", Value: "foo"}},
   310  				{NameVal: projectfile.NameVal{Name: "bar", Value: "bar"}},
   311  				{NameVal: projectfile.NameVal{Name: "baz", Value: "baz"}},
   312  			},
   313  		})
   314  	})
   315  
   316  	suite.Run("UseConstantFromMerged", func() {
   317  		additionalYaml := make(map[string]projectfile.Project)
   318  		additionalYaml["activestate.test.yaml"] = projectfile.Project{
   319  			Constants: projectfile.Constants{
   320  				{NameVal: projectfile.NameVal{Name: "merged", Value: "merged"}},
   321  			},
   322  		}
   323  		suite.testScriptPerformance(scriptPerformanceOptions{
   324  			script: projectfile.Script{
   325  				NameVal: projectfile.NameVal{
   326  					Name:  "use-constant-merged",
   327  					Value: `echo $constants.merged`,
   328  				},
   329  				ScriptFields: projectfile.ScriptFields{
   330  					Language: "bash",
   331  				},
   332  			},
   333  			expect:              "merged",
   334  			samples:             DefaultSamples,
   335  			max:                 baseline,
   336  			additionalYamlFiles: additionalYaml,
   337  		})
   338  	})
   339  
   340  }
   341  
   342  type scriptPerformanceOptions struct {
   343  	script              projectfile.Script
   344  	expect              string
   345  	samples             int
   346  	max                 time.Duration
   347  	authRequired        bool
   348  	additionalScripts   projectfile.Scripts
   349  	constants           projectfile.Constants
   350  	additionalYamlFiles map[string]projectfile.Project
   351  	verbose             bool
   352  }
   353  
   354  func (suite *PerformanceExpansionIntegrationTestSuite) testScriptPerformance(opts scriptPerformanceOptions) time.Duration {
   355  	suite.OnlyRunForTags(tagsuite.Performance)
   356  	ts := e2e.New(suite.T(), true)
   357  	defer ts.Close()
   358  
   359  	suite.startSvc(ts)
   360  
   361  	if opts.authRequired {
   362  		ts.LoginAsPersistentUser()
   363  	}
   364  
   365  	projectFile := projectfile.Project{
   366  		Project:   DefaultProject,
   367  		Constants: opts.constants,
   368  		Scripts:   opts.additionalScripts,
   369  	}
   370  	projectFile.Scripts = append(projectFile.Scripts, opts.script)
   371  
   372  	contents, err := yaml.Marshal(projectFile)
   373  	suite.NoError(err)
   374  
   375  	ts.PrepareActiveStateYAML(string(contents))
   376  	ts.PrepareCommitIdFile(DefaultCommitID)
   377  
   378  	for name, file := range opts.additionalYamlFiles {
   379  		contents, err := yaml.Marshal(file)
   380  		suite.NoError(err)
   381  		suite.prepareAlternateActiveStateYaml(name, string(contents), ts)
   382  	}
   383  
   384  	return performanceTest([]string{"run", opts.script.Name}, opts.expect, opts.samples, opts.max, opts.verbose, &suite.Suite, ts)
   385  }
   386  
   387  func (suite *PerformanceExpansionIntegrationTestSuite) prepareAlternateActiveStateYaml(name, contents string, ts *e2e.Session) {
   388  	msg := "cannot setup activestate.yaml file"
   389  
   390  	contents = strings.TrimSpace(contents)
   391  	projectFile := &projectfile.Project{}
   392  
   393  	err := yaml.Unmarshal([]byte(contents), projectFile)
   394  	suite.NoError(err, msg)
   395  
   396  	cfg, err := config.New()
   397  	suite.NoError(err)
   398  	defer func() { suite.NoError(cfg.Close()) }()
   399  
   400  	path := filepath.Join(ts.Dirs.Work, name)
   401  	err = fileutils.WriteFile(path, []byte(contents))
   402  	suite.NoError(err, msg)
   403  	suite.True(fileutils.FileExists(path))
   404  }
   405  
   406  func TestPerformanceYamlIntegrationTestSuite(t *testing.T) {
   407  	suite.Run(t, new(PerformanceExpansionIntegrationTestSuite))
   408  }