Implement feature installation order and enhance test suite#500
Implement feature installation order and enhance test suite#500prulloac wants to merge 9 commits intocoder:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for devcontainer Feature installation ordering semantics, including a user-specified override order and dependency metadata read from devcontainer-feature.json, and extends the test suite and docs accordingly.
Changes:
- Add
overrideFeatureInstallOrdertodevcontainer.Specand implement order resolution (resolveInstallOrder) plus dependency validation (validateDependencies). - Extend
devcontainer/features.Specto parseinstallsAfter(soft ordering) anddependsOn(hard dependency). - Add tests covering override ordering,
installsAfterbehavior, hard-dependency presence validation, and cycle detection; update spec support documentation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| docs/devcontainer-spec-support.md | Updates documented support matrix for devcontainer properties/features. |
| devcontainer/features/features.go | Adds JSON fields for installsAfter and dependsOn to feature metadata parsing. |
| devcontainer/devcontainer.go | Implements feature extraction pre-pass, install-order resolution, and hard dependency validation. |
| devcontainer/devcontainer_test.go | Adds tests for override ordering, installsAfter ordering, dependsOn validation, and cycle detection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Extract featurePlan type, extractAndRegister, collectFeaturePlan, normalizeFeatureOptions, emitFeatureDockerfile, and supporting methods. Reduces compileFeatures cognitive complexity from 65 to below threshold. Fix WithBuildContexts test to use hash-suffixed feature names.
…lpers Extract depRefResolver, depGraph, newDepGraph, resolveOverrides, validatePinnedOrder, and topoSortRounds. Reduces resolveInstallOrder from cognitive complexity 81 / cyclomatic 44 to 3 / 3.
|
🤖 Deep Review — multi-reviewer analysis with RED-test validation Clean refactoring of the monolithic 6 findings (all validated with RED tests), 3 nits. Pre-existing P1 — Non-deterministic map iteration affects dedup winner
The old code iterated Validated: RED test calls Fix: Sort the worklist returned by (Go Architect P1, Edge Case Analyst P1, Performance Analyst Obs — 3 independent reviewers) P2 —
|
johnstcn
left a comment
There was a problem hiding this comment.
Thanks for the contribution! I think the P1 non-determinism is the main blocker here; the other issues could reasonably be follow-ups.
mafredri
left a comment
There was a problem hiding this comment.
Netero first pass (round 1). One concern, two nits, three notes.
The dependency resolution algorithm is well-structured: round-based topological sort with override priority, canonical ref matching, feature equality deduplication, and cycle detection. Test coverage is solid at ~1.8:1 test-to-production ratio across both internal and external tests.
Severity breakdown: 1x P2 (line endings), 2x P3 (dead code, nondeterminism), 3x Notes.
"File converted from LF to CRLF line endings... Every line appears changed in the diff due to the line-ending flip, obscuring the actual content changes" -- Netero, squinting at a 200-line diff that changed 3 icons
🤖 This review was automatically generated with Coder Agents.
| | 🔴 | `legacyIds` | Used when renaming a feature | - | | ||
| | 🔴 | `deprecated` | Whether the feature is deprecated | - | | ||
| | 🔴 | `mounts` | Cross-orchestrator mounts to add to the container | - | | ||
| # Support for Dev Container Specification |
There was a problem hiding this comment.
P2 The entire file has been converted from LF to CRLF line endings. No other .md in docs/ uses CRLF. This makes the diff appear as though every line changed, obscuring the actual content changes (three status icon updates). (Netero)
file docs/devcontainer-spec-support.md confirms "with CRLF line terminators". Fix with dos2unix or equivalent before merging.
Additionally, the status icons on line 36 (overrideFeatureInstallOrder), line 100 (version), line 119 (dependsOn), and line 120 (installsAfter) all render as the Unicode replacement character (U+FFFD / "\ufffd") instead of the intended emoji. The prior copilot review flagged this as well. These need to be corrected to the appropriate status emoji.
🤖
| opts map[string]any | ||
| // fromDep is true when this feature was added automatically to satisfy | ||
| // a dependsOn hard dependency (not explicitly listed by the user). | ||
| fromDep bool |
There was a problem hiding this comment.
P3 fromDep is written but never read. The field is set during extractAndRegister and propagated through featureWorkItem, but no code path ever reads ef.fromDep after construction. (Netero)
Either remove the field and its plumbing, or use it (e.g., to skip user-option merging on auto-added deps, or to annotate logging).
🤖
| // validateBuildContexts rejects ambiguous canonical references when build | ||
| // contexts are enabled, since each produces a Docker stage alias. | ||
| func (p *featurePlan) validateBuildContexts() error { | ||
| for canonical, refs := range p.ambiguousCanonicals { |
There was a problem hiding this comment.
P3 validateBuildContexts iterates p.ambiguousCanonicals and returns on the first entry. If multiple canonicals are ambiguous, which one gets reported depends on Go map iteration order, producing nondeterministic error messages across runs. (Netero)
Not a correctness issue (any single error suffices to reject), but it makes debugging harder when users see different messages on each attempt. Could sort the keys and report the first alphabetically, or collect all ambiguous entries into one message.
🤖
| var lines []string | ||
|
|
||
| for _, featureRefRaw := range featureOrder { | ||
| ef := extracted[featureRefRaw] |
There was a problem hiding this comment.
Note ef := extracted[featureRefRaw] returns nil if the key is missing. The next line ef.spec.Compile(...) would panic. In practice this is safe today because resolveInstallOrder operates on the same key set, but there's no defensive check at the boundary between two independent functions. A one-line nil guard would prevent a confusing nil-pointer panic if a future refactor breaks the invariant. (Netero)
🤖
| lines = append(lines, "\nUSER root") | ||
| lines = append(lines, featureDirectives...) | ||
| if remoteUser != "" { | ||
| // TODO: We should warn that because we were unable to find the remote user, |
There was a problem hiding this comment.
Note The prior code had a TODO here: "We should warn that because we were unable to find the remote user, we're going to run as root." This PR deleted it without implementing the warning. The behavior is unchanged (still silently falls back to root), but the signal for the next reader encountering the silent root fallback is now gone. Consider either restoring the TODO or implementing the warning. (Netero)
🤖
This pull request adds support for the
installsAfter(soft dependency) anddependsOn(hard dependency) fields to the devcontainer features specification, and introduces comprehensive internal tests to validate feature installation order logic, including handling of dependency resolution, override semantics, and cycle detection. Documentation has also been updated to reflect the new support status for these fields.Feature dependency enhancements:
installsAfter(soft ordering hint) anddependsOn(hard dependency) fields to theSpecstruct infeatures.go, enabling features to declare installation order preferences and requirements.Testing and validation:
install_order_internal_test.goto thoroughly test the dependency and installation order resolution logic, covering cases such as ambiguous references, override precedence, cycle detection, and complex dependency trees.Documentation updates:
docs/devcontainer-spec-support.mdto indicate support for theoverrideFeatureInstallOrder,dependsOn, andinstallsAfterfields, replacing the previous "unsupported" status. [1] [2]