cmd/cue/cmd: add safeguard to trim operation

Compare output of generated trim to ensure
the results are identical.

Issue #277

Change-Id: I96c0eeb960d34141dc8de0f60c2d5102b164501c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4909
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index 44d4e36..3df6902 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -15,6 +15,7 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -22,7 +23,9 @@
 	"github.com/spf13/cobra"
 
 	"cuelang.org/go/cue/format"
+	"cuelang.org/go/cue/load"
 	"cuelang.org/go/internal"
+	"cuelang.org/go/internal/diff"
 	"cuelang.org/go/tools/trim"
 )
 
@@ -117,6 +120,8 @@
 		}
 	}
 
+	overlay := map[string]load.Source{}
+
 	for i, inst := range binst {
 		root := instances[i]
 		err := trim.Files(inst.Files, root, &trim.Config{
@@ -126,10 +131,35 @@
 			return err
 		}
 
-		if flagDryrun.Bool(cmd) {
-			continue
+		for _, f := range inst.Files {
+			overlay[f.Filename] = load.FromFile(f)
 		}
 
+	}
+
+	cfg := *defaultConfig
+	cfg.Overlay = overlay
+	tinsts := buildInstances(cmd, load.Instances(args, &cfg))
+	if len(tinsts) != len(binst) {
+		return errors.New("unexpected number of new instances")
+	}
+	if !flagIgnore.Bool(cmd) {
+		for i, p := range instances {
+			k, script := diff.Final.Diff(p.Value(), tinsts[i].Value())
+			if k != diff.Identity {
+				diff.Print(os.Stdout, script)
+				fmt.Println("Aborting trim, output differs after trimming. This is a bug! Use -i to force trim.")
+				fmt.Println("You can file a bug here: https://github.com/cuelang/cue/issues/new?assignees=&labels=NeedsInvestigation&template=bug_report.md&title=")
+				os.Exit(1)
+			}
+		}
+	}
+
+	if flagDryrun.Bool(cmd) {
+		return nil
+	}
+
+	for _, inst := range binst {
 		for _, f := range inst.Files {
 			filename := f.Filename
 
diff --git a/internal/diff/diff.go b/internal/diff/diff.go
index 2344a22..e3a5013 100644
--- a/internal/diff/diff.go
+++ b/internal/diff/diff.go
@@ -22,9 +22,29 @@
 	"cuelang.org/go/cue/errors"
 )
 
-// Diff returns an edit script representing the difference between x and y.
+// Profile configures a diff operation.
+type Profile struct {
+	Concrete bool
+}
+
+var (
+	// Schema is the standard profile used for comparing schema.
+	Schema = &Profile{}
+
+	// Final is the standard profile for comparing data.
+	Final = &Profile{
+		Concrete: true,
+	}
+)
+
+// Diff is a shorthand for Schema.Diff.
 func Diff(x, y cue.Value) (Kind, *EditScript) {
-	d := differ{}
+	return Schema.Diff(x, y)
+}
+
+// Diff returns an edit script representing the difference between x and y.
+func (p *Profile) Diff(x, y cue.Value) (Kind, *EditScript) {
+	d := differ{cfg: p}
 	return d.diffValue(x, y)
 }
 
@@ -137,11 +157,16 @@
 func (e Edit) YPos() int  { return int(e.yPos - 1) }
 
 type differ struct {
+	cfg     *Profile
 	options []cue.Option
 	errs    errors.Error
 }
 
 func (d *differ) diffValue(x, y cue.Value) (Kind, *EditScript) {
+	if d.cfg.Concrete {
+		x, _ = x.Default()
+		y, _ = y.Default()
+	}
 	if x.IncompleteKind() != y.IncompleteKind() {
 		return Modified, nil
 	}