fix: make --find-renames work with --output=dyff#961
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes dyff output behavior so helm-diff’s --find-renames option can control rename detection (instead of dyff doing its own rename pairing), and adds/updates tests around dyff report generation.
Changes:
- Disable dyff’s internal rename detection in
printDyffReportto restore--find-renamessemantics for--output=dyff. - Simplify
doDiffby removing a tautologicaloldContent != nilcheck in the remove case. - Add unit tests covering basic dyff report output paths.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| diff/report.go | Removes dyff.DetectRenames(true) by switching to an explicit options slice passed into dyff comparison. |
| diff/diff.go | Simplifies remove-case logic in doDiff by eliminating redundant nil checks. |
| diff/report_test.go | Adds unit tests exercising printDyffReport output generation. |
Comments suppressed due to low confidence (1)
diff/report.go:124
dyff.CompareInputFilesandreportWriter.WriteReporterrors are being ignored. If parsing/comparison fails,reportmay be nil andWriteReportcan panic or silently emit incomplete output. Please handle these errors (e.g., write an error message totoand return, or changeprintDyffReportto return an error and propagate it).
var compareOptions []dyff.CompareOption
compareOptions = append(compareOptions,
dyff.IgnoreWhitespaceChanges(true),
dyff.KubernetesEntityDetection(true),
)
report, _ := dyff.CompareInputFiles(currentInputFile, newInputFile, compareOptions...)
reportWriter := &dyff.HumanReport{
Report: report,
OmitHeader: true,
MinorChangeThreshold: 0.1,
}
_ = reportWriter.WriteReport(to)
| func TestPrintDyffReportWithAddAndRemove(t *testing.T) { | ||
| report := &Report{ | ||
| Entries: []ReportEntry{ | ||
| { | ||
| Key: "default, old-app, Deployment (apps)", | ||
| Kind: "Deployment", | ||
| ChangeType: "REMOVE", | ||
| Diffs: []difflib.DiffRecord{ | ||
| {Payload: "apiVersion: apps/v1", Delta: difflib.LeftOnly}, | ||
| {Payload: "kind: Deployment", Delta: difflib.LeftOnly}, | ||
| {Payload: "metadata:", Delta: difflib.LeftOnly}, | ||
| {Payload: " name: old-app", Delta: difflib.LeftOnly}, | ||
| }, | ||
| }, | ||
| { | ||
| Key: "default, new-app, Deployment (apps)", | ||
| Kind: "Deployment", | ||
| ChangeType: "ADD", | ||
| Diffs: []difflib.DiffRecord{ | ||
| {Payload: "apiVersion: apps/v1", Delta: difflib.RightOnly}, | ||
| {Payload: "kind: Deployment", Delta: difflib.RightOnly}, | ||
| {Payload: "metadata:", Delta: difflib.RightOnly}, | ||
| {Payload: " name: new-app", Delta: difflib.RightOnly}, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| var buf bytes.Buffer | ||
| printDyffReport(report, &buf) | ||
|
|
||
| output := buf.String() | ||
| require.NotEmpty(t, output) | ||
| } |
There was a problem hiding this comment.
The new dyff tests don't assert the regression fix described in #632. In particular, TestPrintDyffReportWithAddAndRemove only checks non-empty output, so it would pass even if dyff still reorders/pairs removed+added resources (the original bug). Please strengthen this test (or add another) to assert that add/remove output is distinguishable from a rename-as-modify scenario (e.g., compare outputs for REMOVE+ADD vs a single MODIFY entry and assert they differ / contain expected identifiers).
diff/report_test.go
Outdated
| require.True(t, strings.Contains(output, "replicas") || strings.Contains(output, "2") || strings.Contains(output, "3"), | ||
| "Expected dyff output to contain information about replicas change") |
There was a problem hiding this comment.
require.True(strings.Contains(output, "replicas") || strings.Contains(output, "2") || strings.Contains(output, "3")) is very permissive and can pass even if the dyff output isn't actually describing the replicas change (e.g., if some unrelated "2" appears). Please tighten the assertion to check for a more specific/robust signal of the replicas field change (for example requiring the field name and at least one of the expected values).
| require.True(t, strings.Contains(output, "replicas") || strings.Contains(output, "2") || strings.Contains(output, "3"), | |
| "Expected dyff output to contain information about replicas change") | |
| require.Contains(t, output, "replicas: 2", "Expected dyff output to show original replicas value") | |
| require.Contains(t, output, "replicas: 3", "Expected dyff output to show updated replicas value") |
7dbe3f3 to
b52aaab
Compare
diff/report_test.go
Outdated
| {Payload: "- name: old-app", Delta: difflib.LeftOnly}, | ||
| {Payload: "+ name: new-app", Delta: difflib.RightOnly}, |
There was a problem hiding this comment.
In modifyReport, the DiffRecord payloads include diff markers (- / + ). difflib.Diff payloads are raw input lines (the delta encodes left/right), so these prefixes make the YAML content here unrepresentative and can change YAML structure compared to real manifests. Consider using the actual YAML lines (with consistent indentation) and rely on Delta to indicate removal/addition.
| {Payload: "- name: old-app", Delta: difflib.LeftOnly}, | |
| {Payload: "+ name: new-app", Delta: difflib.RightOnly}, | |
| {Payload: " name: old-app", Delta: difflib.LeftOnly}, | |
| {Payload: " name: new-app", Delta: difflib.RightOnly}, |
The dyff output format was always using dyff.DetectRenames(true), which overrode helm-diff's --find-renames option. This caused renames detected by helm-diff to be re-evaluated by dyff, resulting in inconsistent output compared to other formats (diff, simple). Fixes #632 Signed-off-by: yxxhero <aiopsclub@163.com>
b52aaab to
9d5217b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
diff/report.go:124
- Errors from dyff.CompareInputFiles and reportWriter.WriteReport are discarded. If dyff fails to compare or render (e.g., due to YAML parsing issues), the command can appear to succeed while producing incomplete output. Please handle these errors (at least surface them to stderr / the writer) so failures are diagnosable.
report, _ := dyff.CompareInputFiles(currentInputFile, newInputFile, compareOptions...)
reportWriter := &dyff.HumanReport{
Report: report,
OmitHeader: true,
MinorChangeThreshold: 0.1,
}
_ = reportWriter.WriteReport(to)
| currentInputFile, newInputFile, _ := ytbx.LoadFiles(currentFile.Name(), newFile.Name()) | ||
|
|
||
| report, _ := dyff.CompareInputFiles( | ||
| currentInputFile, newInputFile, | ||
| var compareOptions []dyff.CompareOption | ||
| compareOptions = append(compareOptions, | ||
| dyff.IgnoreWhitespaceChanges(true), |
There was a problem hiding this comment.
The return error from ytbx.LoadFiles is currently ignored. If loading/parsing either temp file fails, dyff comparison may silently produce empty/incorrect output. Consider checking the error and writing a clear message (or falling back to the default diff output) instead of proceeding with a potentially invalid input state.
| func TestPrintDyffReportEmpty(t *testing.T) { | ||
| report := &Report{ | ||
| Entries: []ReportEntry{}, | ||
| } | ||
|
|
||
| var buf bytes.Buffer | ||
| printDyffReport(report, &buf) | ||
|
|
||
| output := buf.String() | ||
| require.Equal(t, "\n", output) | ||
| } |
There was a problem hiding this comment.
The empty-report assertion is very strict (expects exactly a single newline). Since dyff's human output formatting can change across dyff versions, this may be brittle. Consider asserting that the output is empty/whitespace-only (or that it contains no diff markers) rather than matching an exact string.
Summary
dyff.DetectRenames(true)from dyff output format to allow helm-diff's--find-renamesoption to work correctlydoDifffunctionFixes #632