fix: use single e2e and run all flavours#8319
Conversation
48362c1 to
ace4273
Compare
ace4273 to
90e1c0f
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces “scenario run type” fanout so a single e2e invocation can execute multiple execution profiles (default/scriptless/scriptless NBC CSE cmd), and updates e2e logic + pipeline to use per-profile run options instead of global-only flags.
Changes:
- Add scenario run types/profiles with support filtering and tag filter composition for fanout runs.
- Update VMSS/customData, validators, and scenario skipping/prep to use scenario-specific run options.
- Simplify pipeline to run a single e2e stage with
SCENARIO_RUN_TYPES=all.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| e2e/vmss.go | Use scenario-aware run options when deciding scriptless compilation / NBC hack behavior. |
| e2e/validators.go | Validate expected NBC cmd script path based on scenario-aware run options. |
| e2e/types.go | Add scenario fields to support per-run profiles and disallow unsupported run types. |
| e2e/test_helpers.go | Add fanout behavior in RunScenario and apply scenario-aware run options for skip/prep. |
| e2e/scenario_test.go | Update one test to cooperate with fanout and declare unsupported run type(s). |
| e2e/run_types.go | New: run type/profile definitions, option composition, and support checks. |
| e2e/run_types_test.go | New: unit tests for tag composition and support filtering. |
| e2e/config/config.go | Add SCENARIO_RUN_TYPES env var to control fanout. |
| .pipelines/.vsts-vhd-builder.yaml | Collapse multiple e2e stages into one using SCENARIO_RUN_TYPES=all. |
90e1c0f to
1adb440
Compare
1adb440 to
13660b5
Compare
| type ScenarioRunProfile struct { | ||
| Type ScenarioRunType | ||
| TagsToRun string | ||
| TagsToSkip string | ||
| DisableScriptLessCompilation bool | ||
| EnableScriptlessNBCCSECmd bool | ||
| } |
There was a problem hiding this comment.
The ScenarioRunTypeScriptless profile only adds tag filters + DisableScriptLessCompilation, but it does not enable the scriptless execution mode (EnableScriptlessCSECmd) used in prepareAKSNode. With pipelines now using SCENARIO_RUN_TYPES=all (and no per-stage ENABLE_SCRIPTLESS_CSE_CMD), the “scriptless” fanout run can end up executing without the scriptless CSE cmd behavior, effectively making the profile not apply its intended runtime switch. Add an EnableScriptlessCSECmd boolean to ScenarioRunProfile and compose it in buildScenarioRunOptions, then set it to true for the ScenarioRunTypeScriptless default profile.
| { | ||
| Type: ScenarioRunTypeScriptless, | ||
| TagsToRun: "scriptless=true", | ||
| DisableScriptLessCompilation: true, | ||
| }, |
There was a problem hiding this comment.
The ScenarioRunTypeScriptless profile only adds tag filters + DisableScriptLessCompilation, but it does not enable the scriptless execution mode (EnableScriptlessCSECmd) used in prepareAKSNode. With pipelines now using SCENARIO_RUN_TYPES=all (and no per-stage ENABLE_SCRIPTLESS_CSE_CMD), the “scriptless” fanout run can end up executing without the scriptless CSE cmd behavior, effectively making the profile not apply its intended runtime switch. Add an EnableScriptlessCSECmd boolean to ScenarioRunProfile and compose it in buildScenarioRunOptions, then set it to true for the ScenarioRunTypeScriptless default profile.
| func buildScenarioRunOptions(base scenarioRunOptions, s *Scenario) scenarioRunOptions { | ||
| if s == nil || s.RunProfile == nil { | ||
| return base | ||
| } | ||
|
|
||
| base.TagsToRun = joinTagFilters(base.TagsToRun, s.RunProfile.TagsToRun) | ||
| base.TagsToSkip = joinTagFilters(base.TagsToSkip, s.RunProfile.TagsToSkip) | ||
| base.DisableScriptLessCompilation = base.DisableScriptLessCompilation || s.RunProfile.DisableScriptLessCompilation | ||
| base.EnableScriptlessNBCCSECmd = base.EnableScriptlessNBCCSECmd || s.RunProfile.EnableScriptlessNBCCSECmd | ||
|
|
||
| return base | ||
| } |
There was a problem hiding this comment.
The ScenarioRunTypeScriptless profile only adds tag filters + DisableScriptLessCompilation, but it does not enable the scriptless execution mode (EnableScriptlessCSECmd) used in prepareAKSNode. With pipelines now using SCENARIO_RUN_TYPES=all (and no per-stage ENABLE_SCRIPTLESS_CSE_CMD), the “scriptless” fanout run can end up executing without the scriptless CSE cmd behavior, effectively making the profile not apply its intended runtime switch. Add an EnableScriptlessCSECmd boolean to ScenarioRunProfile and compose it in buildScenarioRunOptions, then set it to true for the ScenarioRunTypeScriptless default profile.
| scenarioRunProfilesByType = map[ScenarioRunType]ScenarioRunProfile{ | ||
| ScenarioRunTypeDefault: defaultScenarioRunProfiles[0], | ||
| ScenarioRunTypeScriptless: defaultScenarioRunProfiles[1], | ||
| ScenarioRunTypeScriptlessNBCCSECmd: defaultScenarioRunProfiles[2], | ||
| } |
There was a problem hiding this comment.
scenarioRunProfilesByType depends on hard-coded indices into defaultScenarioRunProfiles. This is brittle (reordering the slice or inserting a new profile will silently change semantics). Prefer defining the map with explicit struct literals (or building the map in a small init loop keyed by profile.Type) to avoid index coupling.
| scenarioRunProfilesByType = map[ScenarioRunType]ScenarioRunProfile{ | |
| ScenarioRunTypeDefault: defaultScenarioRunProfiles[0], | |
| ScenarioRunTypeScriptless: defaultScenarioRunProfiles[1], | |
| ScenarioRunTypeScriptlessNBCCSECmd: defaultScenarioRunProfiles[2], | |
| } | |
| scenarioRunProfilesByType = func() map[ScenarioRunType]ScenarioRunProfile { | |
| profilesByType := make(map[ScenarioRunType]ScenarioRunProfile, len(defaultScenarioRunProfiles)) | |
| for _, profile := range defaultScenarioRunProfiles { | |
| profilesByType[profile.Type] = profile | |
| } | |
| return profilesByType | |
| }() |
| func configuredScenarioRunProfiles() ([]ScenarioRunProfile, error) { | ||
| if !scenarioRunFanoutEnabled() { | ||
| return nil, nil | ||
| } | ||
|
|
||
| rawRunTypes := strings.TrimSpace(config.Config.ScenarioRunTypes) | ||
| if strings.EqualFold(rawRunTypes, "all") { | ||
| return append([]ScenarioRunProfile(nil), defaultScenarioRunProfiles...), nil | ||
| } | ||
|
|
||
| requestedProfiles := strings.Split(rawRunTypes, ",") | ||
| runProfiles := make([]ScenarioRunProfile, 0, len(requestedProfiles)) | ||
| seen := map[ScenarioRunType]struct{}{} | ||
| for _, requestedProfile := range requestedProfiles { | ||
| runType := ScenarioRunType(strings.TrimSpace(requestedProfile)) | ||
| if runType == "" { | ||
| continue | ||
| } | ||
| if _, ok := seen[runType]; ok { | ||
| continue | ||
| } | ||
| profile, ok := scenarioRunProfilesByType[runType] | ||
| if !ok { | ||
| return nil, fmt.Errorf("unsupported scenario run type %q", runType) | ||
| } | ||
| runProfiles = append(runProfiles, profile) | ||
| seen[runType] = struct{}{} | ||
| } | ||
|
|
||
| return runProfiles, nil | ||
| } |
There was a problem hiding this comment.
New parsing/validation behavior is introduced in configuredScenarioRunProfiles, but e2e/run_types_test.go doesn’t cover key cases like SCENARIO_RUN_TYPES=all, deduplication, and the error path for unsupported run types. Adding focused unit tests for these cases will help prevent regressions in pipeline configuration handling.
|
This looks way more complicated than it can be. For reference, the existing VHD caching pattern at test_helpers.go#L76-L89 already does exactly this — Something like: func RunScenario(t *testing.T, s *Scenario) error {
t.Parallel()
t.Run("original", func(t *testing.T) {
t.Parallel()
runScenario(t, copyScenario(s))
})
if config.EnableScriptless && s.AKSNodeConfigMutator != nil { // scriptless scenarios
t.Run("scriptless", func(t *testing.T) {
t.Parallel()
s := copyScenario(s)
s.DisableScriptlessCompilation = true
runScenario(t, s)
})
}
if config.EnableScriptlessNBC && s.AKSNodeConfigMutator == nil && !s.IsWindows() {
t.Run("scriptless_nbc", func(t *testing.T) {
t.Parallel()
s := copyScenario(s)
s.EnableScriptlessNBCCSECmd = true
s.DisableScriptlessCompilation = true
runScenario(t, s)
})
}
return nil
// ... existing code
}No new types, no new files — flags go directly on |
13660b5 to
d79e7bb
Compare
d79e7bb to
25eb8c2
Compare
| t.Run("default", func(t *testing.T) { | ||
| t.Parallel() | ||
| runScenario(t, copyScenario(s)) | ||
| }) |
There was a problem hiding this comment.
runScenario returns an error but the new subtests ignore it. There are code paths in runScenario that return an error without failing the test (e.g., if err != nil { return err }), which can lead to false-positive passes. Mandatory: check the returned error inside each subtest (e.g., require.NoError(...)), while still allowing ExpectedError cases to be handled by the existing assertions.
| t.Run("default", func(t *testing.T) { | ||
| t.Parallel() | ||
| runScenario(t, copyScenario(s)) | ||
| }) | ||
|
|
||
| if supportsScriptlessNBCCSECmd(s) { | ||
| t.Run("scriptless_nbc", func(t *testing.T) { | ||
| t.Parallel() | ||
| sCopy := copyScenario(s) | ||
| sCopy.EnableScriptlessNBCCSECmd = true | ||
| runScenario(t, sCopy) | ||
| }) | ||
| } |
There was a problem hiding this comment.
These subtests run in parallel and rely on copyScenario(s), but the provided copyScenario implementation (per context excerpt) is explicitly a shallow/placeholder copy. With parallel execution, any shared slice/map/pointer fields inside Scenario (or nested config) can race and cause flaky tests. Mandatory: either (a) implement a real deep copy for all mutated/shared nested fields and keep parallelism, or (b) remove t.Parallel() from these subtests to keep single-threaded execution per parent test.
| func supportsScriptlessNBCCSECmd(s *Scenario) bool { | ||
| return s.AKSNodeConfigMutator == nil && !s.IsWindows() && len(s.Config.CustomDataWriteFiles) <= 0 && !s.VHDCaching && !config.Config.TestPreProvision | ||
| } |
There was a problem hiding this comment.
supportsScriptlessNBCCSECmd doesn’t account for scenarios that are known to be unsupported in the NBC CSE cmd path. In this PR, Test_Ubuntu2204_ScriptlessCSECmd_Hotfix no longer skips for that condition, so the new scriptless_nbc subtest will run and likely fail despite the test’s intent. Mandatory: gate the scriptless_nbc variant with an explicit per-scenario opt-in/opt-out (e.g., a Scenario flag like EnableScriptlessNBCVariant / DisableScriptlessNBCVariant), or extend supportsScriptlessNBCCSECmd with a concrete signal used by the hotfix test (such as a dedicated tag/field) so unsupported scenarios don’t run the NBC variant.
25eb8c2 to
320e91a
Compare
320e91a to
fa7180d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
e2e/scenario_test.go:1
- This will not compile if
RunScenariostill returns anerror(a call returning values can’t be used as a bare statement in Go). Either capture the return value (e.g., assign to_or assert on it), or changeRunScenario’s signature to not return an error and update call sites consistently.
package e2e
e2e/test_helpers.go:76
RunScenarionow performs assertions inside subtests and always returnsnil, which makes theerrorreturn value misleading and (as seen in updated tests) easy to use incorrectly. Consider changing the signature tofunc RunScenario(t *testing.T, s *Scenario)and removing the unused return paths, or restore meaningful error propagation and keep assertions at the call site.
func RunScenario(t *testing.T, s *Scenario) error {
| t.Run("default", func(t *testing.T) { | ||
| t.Parallel() | ||
| err := runScenario(t, copyScenario(s)) | ||
| require.NoError(t, err) | ||
| }) | ||
|
|
||
| if supportsScriptlessNBCCSECmd(s) { | ||
| t.Run("scriptless_nbc", func(t *testing.T) { | ||
| t.Parallel() | ||
| sCopy := copyScenario(s) | ||
| sCopy.EnableScriptlessNBCCSECmd = true | ||
| err := runScenario(t, sCopy) | ||
| require.NoError(t, err) | ||
| }) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
The two flavour subtests run in parallel and rely on copyScenario(s) to isolate mutations. Given the current copyScenario context indicates it’s a shallow/placeholder copy, this can lead to data races and cross-test contamination (e.g., runScenario/prepareAKSNode mutate fields like s.T, s.Runtime, s.Location, and may mutate nested/slice fields). Make copyScenario a real deep copy of all mutated nested fields (and ensure Runtime starts nil per run), or remove t.Parallel() from these subtests so flavours run sequentially.
| require.NoError(t, err) | ||
| }) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
RunScenario now performs assertions inside subtests and always returns nil, which makes the error return value misleading and (as seen in updated tests) easy to use incorrectly. Consider changing the signature to func RunScenario(t *testing.T, s *Scenario) and removing the unused return paths, or restore meaningful error propagation and keep assertions at the call site.
| nbc.EnableScriptlessCSECmd = true | ||
| } | ||
| if config.Config.EnableScriptlessNBCCSECmd { | ||
| nbc.EnableScriptlessCSECmd = true |
There was a problem hiding this comment.
So, it's something we set always to true now?
Should it be inside NBC initialiser?
There was a problem hiding this comment.
Yep eventually will remove this parameter, will wait until then
| Location string | ||
|
|
||
| // EnableScriptlessNBCCSECmd indicates whether to enable scriptless NBCCSECmd in the scenario. | ||
| EnableScriptlessNBCCSECmd bool |
There was a problem hiding this comment.
Maybe move it to ScenarioRuntime to separate configurable and non-configurable options
| return ctx | ||
| } | ||
|
|
||
| func RunScenario(t *testing.T, s *Scenario) error { |
There was a problem hiding this comment.
I guess this now may have no return value?
There was a problem hiding this comment.
Agreed. Will change this in follow up PR
Use single e2e to run all scenarios, dont know if this will work