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 }