Skip to content

Commit

Permalink
use difflib.Diff instead of cmp.Diff
Browse files Browse the repository at this point in the history
  • Loading branch information
jlandowner committed Nov 13, 2023
1 parent 635bb1a commit 1b88b6e
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 26 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/jlandowner/helm-chartsnap
go 1.21

require (
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/evanphx/json-patch/v5 v5.7.0
github.com/fatih/color v1.15.0
github.com/google/go-cmp v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down
32 changes: 22 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ var (
)

type option struct {
ReleaseName string
Chart string
ValuesFile string
UpdateSnapshot bool
OutputDir string
ReleaseName string
Chart string
ValuesFile string
UpdateSnapshot bool
OutputDir string
DiffContextLineN int

// Below properties are the same as helm global options
// They are passed to the plugin as environment variables
Expand Down Expand Up @@ -64,7 +65,7 @@ func (o *option) HelmBin() string {

func main() {
rootCmd := &cobra.Command{
Use: "chartsnap",
Use: "chartsnap -c CHART",
Short: "Snapshot testing tool for Helm charts",
Long: `
Snapshot testing tool like Jest for Helm charts.
Expand Down Expand Up @@ -113,7 +114,10 @@ MIT 2023 jlandowner/helm-chartsnap
chartsnap -c YOUR_CHART -f YOUR_TEST_VALUES_FILES_DIRECTOY
# Set addtional args or flags for 'helm template' command:
chartsnap -c YOUR_CHART -f YOUR_TEST_VALUES_FILE -- --skip-tests`,
chartsnap -c YOUR_CHART -f YOUR_TEST_VALUES_FILE -- --skip-tests
# Output with no colors:
NO_COLOR=1 chartsnap -c YOUR_CHART`,
Version: fmt.Sprintf("version=%s commit=%s date=%s", version, commit, date),
RunE: run,
PreRunE: prerun,
Expand All @@ -136,6 +140,7 @@ MIT 2023 jlandowner/helm-chartsnap
if err := rootCmd.MarkPersistentFlagDirname("output-dir"); err != nil {
panic(err)
}
rootCmd.PersistentFlags().IntVarP(&o.DiffContextLineN, "ctx-lines", "N", 3, "number of lines to show in diff output. 0 for full output")

if err := rootCmd.Execute(); err != nil {
slog.New(slogHandler()).Error(err.Error())
Expand Down Expand Up @@ -235,14 +240,21 @@ func run(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to replace snapshot file: %w", err)
}
}
matched, failureMessage, err := charts.Snap(ctx, snapshotFilePath, ht)

opts := charts.ChartSnapOptions{
HelmTemplateCmdOptions: ht,
SnapshotFile: snapshotFilePath,
DiffContextLineN: o.DiffContextLineN,
}
matched, failureMessage, err := charts.Snap(ctx, opts)
if err != nil {
bannerPrintln("FAIL", fmt.Sprintf("chart=%s values=%s err=%v", ht.Chart, ht.ValuesFile, err), color.FgRed, color.BgRed)
return fmt.Errorf("failed to get snapshot chart=%s values=%s: %w", ht.Chart, ht.ValuesFile, err)
}
if !matched {
bannerPrintln("FAIL", failureMessage, color.FgRed, color.BgRed)
return fmt.Errorf("not match snapshot chart=%s values=%s", ht.Chart, ht.ValuesFile)
bannerPrintln("FAIL", "Snapshot does not match", color.FgRed, color.BgRed)
fmt.Println(failureMessage)
return fmt.Errorf("snapshot does not match chart=%s values=%s", ht.Chart, ht.ValuesFile)
}
return nil
})
Expand Down
27 changes: 16 additions & 11 deletions pkg/charts/snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ func Log() *slog.Logger {
return log
}

func Snap(ctx context.Context, snapFile string, o HelmTemplateCmdOptions) (match bool, failureMessage string, err error) {
type ChartSnapOptions struct {
HelmTemplateCmdOptions HelmTemplateCmdOptions
SnapshotFile string
DiffContextLineN int
}

func Snap(ctx context.Context, o ChartSnapOptions) (match bool, failureMessage string, err error) {
sv := SnapshotValues{}
if o.ValuesFile != "" {
f, err := os.Open(o.ValuesFile)
if o.HelmTemplateCmdOptions.ValuesFile != "" {
f, err := os.Open(o.HelmTemplateCmdOptions.ValuesFile)
if err != nil {
return match, "", fmt.Errorf("failed to open values file: %w", err)
}
Expand All @@ -42,7 +48,7 @@ func Snap(ctx context.Context, snapFile string, o HelmTemplateCmdOptions) (match
}
Log().Debug("test spec from values file", "spec", sv.TestSpec)

out, err := o.Execute(ctx)
out, err := o.HelmTemplateCmdOptions.Execute(ctx)
if err != nil {
return match, "", fmt.Errorf("'helm template' command failed: %w: %s", err, out)
}
Expand All @@ -68,13 +74,12 @@ func Snap(ctx context.Context, snapFile string, o HelmTemplateCmdOptions) (match
}
}
}
res, err := unstructured.Encode(manifests)
if err != nil {
return match, "", fmt.Errorf("failed to encode manifests: %w", err)
}

s := snap.SnapShotMatcher(snapFile, SnapshotID(o.ValuesFile))
match, err = s.Match(string(res))
snap.SetLogger(Log())
s := snap.UnstructuredSnapShotMatcher(
o.SnapshotFile,
SnapshotID(o.HelmTemplateCmdOptions.ValuesFile),
snap.WithDiffContextLineN(o.DiffContextLineN))
match, err = snap.UnstructuredMatch(s, manifests)

if err != nil {
return match, "", fmt.Errorf("failed to get snapshot: %w", err)
Expand Down
100 changes: 100 additions & 0 deletions pkg/snap/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package snap

import (
"fmt"
"strings"

"github.com/aryann/difflib"
"github.com/fatih/color"
)

type DiffOptions struct {
DiffContextLineN int
}

func (o *DiffOptions) ContextLineN() int {
if o.DiffContextLineN < 0 {
return 0
}
return o.DiffContextLineN
}

func WithDiffContextLineN(n int) DiffOptions {
return DiffOptions{DiffContextLineN: n}
}

func mergeDiffOpts(opts []DiffOptions) DiffOptions {
var merged DiffOptions
for _, v := range opts {
if v.DiffContextLineN > merged.DiffContextLineN {
merged.DiffContextLineN = v.DiffContextLineN
}
}
return merged
}

func Diff(x, y string, o DiffOptions) string {
diffs := difflib.Diff(strings.Split(x, "\n"), strings.Split(y, "\n"))

var (
sb strings.Builder
isDiffSequence bool
)

for i, v := range diffs {
if o.ContextLineN() < 1 {
// all records
sb.WriteString(diffString(v))
continue
}

if v.Delta != difflib.Common {
isDiffSequence = true

// if first diff, add a header and previous lines
if i > 0 && diffs[i-1].Delta == difflib.Common {
// header
sb.WriteString(color.New(color.FgCyan).Sprintf("--- line=%d\n", i))

// previous lines
for j := intInRange(0, len(diffs), i-o.DiffContextLineN); j < i; j++ {
sb.WriteString(fmt.Sprintf("%s\n", diffs[j]))
}
}
sb.WriteString(diffString(v))
} else {
if isDiffSequence {
isDiffSequence = false

// subsequent lines
for j := i; j < intInRange(0, len(diffs), i+o.DiffContextLineN); j++ {
sb.WriteString(fmt.Sprintf("%s\n", diffs[j]))
}
// divider
sb.WriteString("\n")
}
}
}
return sb.String()
}

func intInRange(min, max, v int) int {
if v >= min && v <= max {
return v
} else if v < min {
return min
} else {
return max
}
}

func diffString(d difflib.DiffRecord) string {
switch d.Delta {
case difflib.LeftOnly:
return color.New(color.FgRed).Sprintf("%s\n", d)
case difflib.RightOnly:
return color.New(color.FgGreen).Sprintf("%s\n", d)
default:
return fmt.Sprintf("%s\n", d)
}
}
Loading

0 comments on commit 1b88b6e

Please sign in to comment.