cuelang.org/go@v0.10.1/internal/core/adt/eval_test.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt_test
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"path/filepath"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  
    25  	"golang.org/x/tools/txtar"
    26  
    27  	"cuelang.org/go/cue"
    28  	"cuelang.org/go/cue/ast"
    29  	"cuelang.org/go/cue/cuecontext"
    30  	"cuelang.org/go/cue/errors"
    31  	"cuelang.org/go/cue/stats"
    32  	"cuelang.org/go/cue/token"
    33  	"cuelang.org/go/internal"
    34  	"cuelang.org/go/internal/core/adt"
    35  	"cuelang.org/go/internal/core/debug"
    36  	"cuelang.org/go/internal/core/eval"
    37  	"cuelang.org/go/internal/core/runtime"
    38  	"cuelang.org/go/internal/core/validate"
    39  	"cuelang.org/go/internal/cuedebug"
    40  	"cuelang.org/go/internal/cuetxtar"
    41  	_ "cuelang.org/go/pkg"
    42  )
    43  
    44  var (
    45  	todo = flag.Bool("todo", false, "run tests marked with #todo-compile")
    46  )
    47  
    48  // TestEval tests the default implementation of the evaluator.
    49  func TestEval(t *testing.T) {
    50  	test := cuetxtar.TxTarTest{
    51  		Root: "../../../cue/testdata",
    52  		Name: "eval",
    53  		Skip: alwaysSkip,
    54  		ToDo: needFix,
    55  	}
    56  
    57  	if *todo {
    58  		test.ToDo = nil
    59  	}
    60  
    61  	test.Run(t, func(tc *cuetxtar.Test) {
    62  		runEvalTest(tc, internal.DefaultVersion, cuedebug.Config{})
    63  	})
    64  }
    65  
    66  var alwaysSkip = map[string]string{
    67  	"compile/erralias": "compile error",
    68  }
    69  
    70  var needFix = map[string]string{
    71  	"DIR/NAME": "reason",
    72  }
    73  
    74  func TestEvalAlpha(t *testing.T) {
    75  	// TODO: remove use of externalDeps for processing. Currently, enabling
    76  	// this would fix some issues, but also introduce some closedness bugs.
    77  	// As a first step, we should ensure that the temporary hack of using
    78  	// externalDeps to agitate pending dependencies is replaced with a
    79  	// dedicated mechanism.
    80  	//
    81  	// adt.DebugDeps = true // check unmatched dependencies.
    82  
    83  	flags := cuedebug.Config{
    84  		Sharing: true,
    85  	}
    86  
    87  	var todoAlpha = map[string]string{}
    88  
    89  	test := cuetxtar.TxTarTest{
    90  		Root:     "../../../cue/testdata",
    91  		Name:     "evalalpha",
    92  		Fallback: "eval", // Allow eval golden files to pass these tests.
    93  		Skip:     alwaysSkip,
    94  		ToDo:     todoAlpha,
    95  	}
    96  
    97  	if *todo {
    98  		test.ToDo = nil
    99  	}
   100  
   101  	var ran, skipped, errorCount int
   102  
   103  	test.Run(t, func(t *cuetxtar.Test) {
   104  		if reason := skipFiles(t.Instance().Files...); reason != "" {
   105  			skipped++
   106  			t.Skip(reason)
   107  		}
   108  		ran++
   109  
   110  		errorCount += runEvalTest(t, internal.DevVersion, flags)
   111  	})
   112  
   113  	t.Logf("todo: %d, ran: %d, skipped: %d, nodeErrors: %d",
   114  		len(todoAlpha), ran, skipped, errorCount)
   115  }
   116  
   117  // skipFiles returns true if the given files contain CUE that is not yet handled
   118  // by the development version of the evaluator.
   119  func skipFiles(a ...*ast.File) (reason string) {
   120  	// Skip disjunctions.
   121  	fn := func(n ast.Node) bool {
   122  		switch x := n.(type) {
   123  		case *ast.BinaryExpr:
   124  			if x.Op == token.OR {
   125  				// Uncomment to disable disjunction testing.
   126  				// NOTE: keep around until implementation of disjunctions
   127  				// is complete.
   128  				// reason = "disjunctions"
   129  			}
   130  		}
   131  		return true
   132  	}
   133  	for _, f := range a {
   134  		ast.Walk(f, fn, nil)
   135  	}
   136  	return reason
   137  }
   138  
   139  func runEvalTest(t *cuetxtar.Test, version internal.EvaluatorVersion, flags cuedebug.Config) (errorCount int) {
   140  	a := t.Instance()
   141  	r := runtime.NewWithSettings(version, flags)
   142  
   143  	v, err := r.Build(nil, a)
   144  	if err != nil {
   145  		t.WriteErrors(err)
   146  		return
   147  	}
   148  
   149  	e := eval.New(r)
   150  	ctx := e.NewContext(v)
   151  	ctx.Version = version
   152  	ctx.Config = flags
   153  	v.Finalize(ctx)
   154  
   155  	// Print discrepancies in dependencies.
   156  	if m := ctx.ErrorGraphs; len(m) > 0 {
   157  		errorCount += 1 // Could use len(m), but this seems more useful.
   158  		i := 0
   159  		keys := make([]string, len(m))
   160  		for k := range m {
   161  			keys[i] = k
   162  			i++
   163  		}
   164  		t.Errorf("unexpected node errors: %d", len(ctx.ErrorGraphs))
   165  		sort.Strings(keys)
   166  		for _, s := range keys {
   167  			t.Errorf("  -- path: %s", s)
   168  		}
   169  	}
   170  
   171  	switch counts := ctx.Stats(); {
   172  	case version == internal.DevVersion:
   173  		for _, f := range t.Archive.Files {
   174  			if f.Name != "out/eval/stats" {
   175  				continue
   176  			}
   177  			c := cuecontext.New()
   178  			v := c.CompileBytes(f.Data)
   179  			var orig stats.Counts
   180  			v.Decode(&orig)
   181  
   182  			// TODO: do something more principled.
   183  			switch {
   184  			case orig.Disjuncts < counts.Disjuncts,
   185  				orig.Disjuncts > counts.Disjuncts*5 &&
   186  					counts.Disjuncts > 20,
   187  				orig.Conjuncts > counts.Conjuncts*2:
   188  				// For now, we only care about disjuncts.
   189  				// TODO: add triggers once the disjunction issues have bene
   190  				// solved.
   191  				w := t.Writer("stats")
   192  				fmt.Fprintln(w, counts)
   193  			}
   194  			break
   195  		}
   196  
   197  	default:
   198  		w := t.Writer("stats")
   199  		fmt.Fprintln(w, counts)
   200  	}
   201  
   202  	// if n := stats.Leaks(); n > 0 {
   203  	// 	t.Skipf("%d leaks reported", n)
   204  	// }
   205  
   206  	if b := validate.Validate(ctx, v, &validate.Config{
   207  		AllErrors: true,
   208  	}); b != nil {
   209  		fmt.Fprintln(t, "Errors:")
   210  		t.WriteErrors(b.Err)
   211  		fmt.Fprintln(t, "")
   212  		fmt.Fprintln(t, "Result:")
   213  	}
   214  
   215  	if v == nil {
   216  		return
   217  	}
   218  
   219  	t.Write(debug.AppendNode(nil, r, v, &debug.Config{Cwd: t.Dir}))
   220  	fmt.Fprintln(t)
   221  
   222  	return
   223  }
   224  
   225  // TestX is for debugging. Do not delete.
   226  func TestX(t *testing.T) {
   227  	adt.DebugDeps = true
   228  	// adt.OpenGraphs = true
   229  
   230  	flags := cuedebug.Config{
   231  		Sharing: true, // Uncomment to turn sharing off.
   232  		LogEval: 1,    // Uncomment to turn logging off
   233  	}
   234  
   235  	var version internal.EvaluatorVersion
   236  	version = internal.DevVersion // comment to use default implementation.
   237  
   238  	in := `
   239  -- cue.mod/module.cue --
   240  module: "mod.test"
   241  
   242  language: version: "v0.9.0"
   243  
   244  -- in.cue --
   245  	`
   246  
   247  	if strings.HasSuffix(strings.TrimSpace(in), ".cue --") {
   248  		t.Skip()
   249  	}
   250  
   251  	a := txtar.Parse([]byte(in))
   252  	instance := cuetxtar.Load(a, t.TempDir())[0]
   253  	if instance.Err != nil {
   254  		t.Fatal(instance.Err)
   255  	}
   256  
   257  	r := runtime.NewWithSettings(version, flags)
   258  
   259  	v, err := r.Build(nil, instance)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	e := eval.New(r)
   265  	ctx := e.NewContext(v)
   266  	ctx.Config = flags
   267  	v.Finalize(ctx)
   268  
   269  	out := debug.NodeString(r, v, nil)
   270  	if adt.OpenGraphs {
   271  		for p, g := range ctx.ErrorGraphs {
   272  			path := filepath.Join(".debug/TestX", p)
   273  			adt.OpenNodeGraph("TestX", path, in, out, g)
   274  		}
   275  	}
   276  
   277  	if b := validate.Validate(ctx, v, &validate.Config{
   278  		AllErrors: true,
   279  	}); b != nil {
   280  		t.Log(errors.Details(b.Err, nil))
   281  	}
   282  
   283  	t.Error(out)
   284  
   285  	t.Log(ctx.Stats())
   286  }
   287  
   288  func BenchmarkUnifyAPI(b *testing.B) {
   289  	for i := 0; i < b.N; i++ {
   290  		b.StopTimer()
   291  		ctx := cuecontext.New()
   292  		v := ctx.CompileString("")
   293  		for j := 0; j < 500; j++ {
   294  			if j == 400 {
   295  				b.StartTimer()
   296  			}
   297  			v = v.FillPath(cue.ParsePath(fmt.Sprintf("i_%d", i)), i)
   298  		}
   299  	}
   300  }
   301  
   302  func TestIssue2293(t *testing.T) {
   303  	ctx := cuecontext.New()
   304  	c := `a: {}, a`
   305  	v1 := ctx.CompileString(c)
   306  	v2 := ctx.CompileString(c)
   307  
   308  	v1.Unify(v2)
   309  }