github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/internal/core/export/export_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 export_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/joomcode/cue/cue"
    21  	"github.com/joomcode/cue/cue/ast"
    22  	"github.com/joomcode/cue/cue/cuecontext"
    23  	"github.com/joomcode/cue/cue/errors"
    24  	"github.com/joomcode/cue/cue/format"
    25  	"github.com/joomcode/cue/cue/parser"
    26  	"github.com/joomcode/cue/encoding/gocode/gocodec"
    27  	"github.com/joomcode/cue/internal/astinternal"
    28  	"github.com/joomcode/cue/internal/core/adt"
    29  	"github.com/joomcode/cue/internal/core/compile"
    30  	"github.com/joomcode/cue/internal/core/convert"
    31  	"github.com/joomcode/cue/internal/core/eval"
    32  	"github.com/joomcode/cue/internal/core/export"
    33  	"github.com/joomcode/cue/internal/core/runtime"
    34  	"github.com/joomcode/cue/internal/cuetest"
    35  	"github.com/joomcode/cue/internal/cuetxtar"
    36  	"github.com/joomcode/cue/internal/value"
    37  	"github.com/rogpeppe/go-internal/txtar"
    38  )
    39  
    40  func TestDefinition(t *testing.T) {
    41  	test := cuetxtar.TxTarTest{
    42  		Root:   "./testdata",
    43  		Name:   "definition",
    44  		Update: cuetest.UpdateGoldenFiles,
    45  	}
    46  
    47  	r := runtime.New()
    48  
    49  	test.Run(t, func(t *cuetxtar.Test) {
    50  		a := t.ValidInstances()
    51  
    52  		v, errs := compile.Files(nil, r, "", a[0].Files...)
    53  		if errs != nil {
    54  			t.Fatal(errs)
    55  		}
    56  		v.Finalize(eval.NewContext(r, v))
    57  
    58  		// TODO: do we need to evaluate v? In principle not necessary.
    59  		// v.Finalize(eval.NewContext(r, v))
    60  
    61  		file, errs := export.Def(r, "", v)
    62  		errors.Print(t, errs, nil)
    63  		_, _ = t.Write(formatNode(t.T, file))
    64  	})
    65  }
    66  
    67  func formatNode(t *testing.T, n ast.Node) []byte {
    68  	t.Helper()
    69  
    70  	b, err := format.Node(n)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	return b
    75  }
    76  
    77  // TestGenerated tests conversions of generated Go structs, which may be
    78  // different from parsed or evaluated CUE, such as having Vertex values.
    79  func TestGenerated(t *testing.T) {
    80  	ctx := cuecontext.New()
    81  
    82  	testCases := []struct {
    83  		in  func(ctx *adt.OpContext) (adt.Expr, error)
    84  		out string
    85  		p   *export.Profile
    86  	}{{
    87  		in: func(ctx *adt.OpContext) (adt.Expr, error) {
    88  			in := &C{
    89  				Terminals: []*A{{Name: "Name", Description: "Desc"}},
    90  			}
    91  			return convert.GoValueToValue(ctx, in, false), nil
    92  		},
    93  		out: `Terminals: [{Name: "Name", Description: "Desc"}]`,
    94  	}, {
    95  		in: func(ctx *adt.OpContext) (adt.Expr, error) {
    96  			in := &C{
    97  				Terminals: []*A{{Name: "Name", Description: "Desc"}},
    98  			}
    99  			return convert.GoTypeToExpr(ctx, in)
   100  		},
   101  		out: `*null|{Terminals?: *null|[...*null|{Name: string, Description: string}]}`,
   102  	}, {
   103  		in: func(ctx *adt.OpContext) (adt.Expr, error) {
   104  			in := []*A{{Name: "Name", Description: "Desc"}}
   105  			return convert.GoValueToValue(ctx, in, false), nil
   106  		},
   107  		out: `[{Name: "Name", Description: "Desc"}]`,
   108  	}, {
   109  		in: func(ctx *adt.OpContext) (adt.Expr, error) {
   110  			in := []*A{{Name: "Name", Description: "Desc"}}
   111  			return convert.GoTypeToExpr(ctx, in)
   112  		},
   113  		out: `*null|[...*null|{Name: string, Description: string}]`,
   114  	}, {
   115  		in: func(ctx *adt.OpContext) (adt.Expr, error) {
   116  			expr, err := parser.ParseExpr("test", `{
   117  				x: Guide.#Terminal
   118  				Guide: {}
   119  			}`)
   120  			if err != nil {
   121  				return nil, err
   122  			}
   123  			c, err := compile.Expr(nil, ctx, "_", expr)
   124  			if err != nil {
   125  				return nil, err
   126  			}
   127  			root := &adt.Vertex{}
   128  			root.AddConjunct(c)
   129  			root.Finalize(ctx)
   130  
   131  			// Simulate Value.Unify of Lookup("x") and Lookup("Guide").
   132  			n := &adt.Vertex{}
   133  			n.AddConjunct(adt.MakeRootConjunct(nil, root.Arcs[0]))
   134  			n.AddConjunct(adt.MakeRootConjunct(nil, root.Arcs[1]))
   135  			n.Finalize(ctx)
   136  
   137  			return n, nil
   138  		},
   139  		out: `<[l2// x: undefined field: #Terminal] _|_>`,
   140  		p:   export.Final,
   141  	}, {
   142  		in: func(r *adt.OpContext) (adt.Expr, error) {
   143  			v := ctx.CompileString(`
   144  				#Provider: {
   145  					ID: string
   146  					notConcrete: bool
   147  					a: int
   148  					b: a + 1
   149  				}`)
   150  
   151  			spec := v.LookupPath(cue.ParsePath("#Provider"))
   152  			spec2 := spec.FillPath(cue.ParsePath("ID"), "12345")
   153  			root := v.FillPath(cue.ParsePath("providers.foo"), spec2)
   154  			_, n := value.ToInternal(root)
   155  
   156  			return n, nil
   157  		},
   158  		out: `#Provider: {ID: string, notConcrete: bool, a: int, b: a+1}, providers: {foo: {ID: "12345", notConcrete: bool, a: int, b: a+1}}`,
   159  		p:   export.All,
   160  	}, {
   161  		// Issue #882
   162  		in: func(r *adt.OpContext) (adt.Expr, error) {
   163  			valA := ctx.CompileString(`
   164  				#One: { version: string }
   165  			`)
   166  
   167  			valB := ctx.CompileString(`
   168  				#One: _
   169  				ones: {[string]: #One}
   170  			`)
   171  			v := valB.Unify(valA)
   172  			_, n := value.ToInternal(v)
   173  			return n, nil
   174  		},
   175  		out: `#One: {version: string}, ones: {[string]: #One}`,
   176  		p:   export.All,
   177  	}, {
   178  		// Indicate closedness in an element that is closed and misses parent
   179  		// context.
   180  		// Issue #882
   181  		in: func(r *adt.OpContext) (adt.Expr, error) {
   182  			v := ctx.CompileString(`
   183  					#A: b: c: string
   184  				`)
   185  			v = v.LookupPath(cue.ParsePath("#A.b"))
   186  
   187  			_, n := value.ToInternal(v)
   188  			return n, nil
   189  		},
   190  		out: `_#def, _#def: {c: string}`,
   191  		p:   export.All,
   192  	}, {
   193  		// Don't wrap in def if the if the value is an embedded scalar.
   194  		// Issue #977
   195  		in: func(r *adt.OpContext) (adt.Expr, error) {
   196  			v := ctx.CompileString(`
   197  					#A: { "foo", #enum: 2 }
   198  				`)
   199  			v = v.LookupPath(cue.ParsePath("#A"))
   200  
   201  			_, n := value.ToInternal(v)
   202  			return n, nil
   203  		},
   204  		out: `"foo", #enum: 2`,
   205  		p:   export.All,
   206  	}, {
   207  		// Issue #1131
   208  		in: func(r *adt.OpContext) (adt.Expr, error) {
   209  			m := make(map[string]interface{})
   210  			v := ctx.Encode(m)
   211  			_, x := value.ToInternal(v)
   212  			return x, nil
   213  		},
   214  		out: ``, // empty file
   215  	}, {
   216  		in: func(r *adt.OpContext) (adt.Expr, error) {
   217  			v := &adt.Vertex{}
   218  			v.SetValue(r, adt.Finalized, &adt.StructMarker{})
   219  			return v, nil
   220  		},
   221  		out: ``, // empty file
   222  	}}
   223  	for _, tc := range testCases {
   224  		t.Run("", func(t *testing.T) {
   225  			ctx := adt.NewContext((*runtime.Runtime)(ctx), &adt.Vertex{})
   226  
   227  			v, err := tc.in(ctx)
   228  			if err != nil {
   229  				t.Fatal("failed test case: ", err)
   230  			}
   231  
   232  			p := tc.p
   233  			if p == nil {
   234  				p = export.Simplified
   235  			}
   236  
   237  			var n ast.Node
   238  			switch x := v.(type) {
   239  			case *adt.Vertex:
   240  				n, err = p.Def(ctx, "", x)
   241  			default:
   242  				n, err = p.Expr(ctx, "", v)
   243  			}
   244  			if err != nil {
   245  				t.Fatal("failed export: ", err)
   246  			}
   247  			got := astinternal.DebugStr(n)
   248  			if got != tc.out {
   249  				t.Errorf("got:  %s\nwant: %s", got, tc.out)
   250  			}
   251  		})
   252  	}
   253  }
   254  
   255  type A struct {
   256  	Name        string
   257  	Description string
   258  }
   259  
   260  type B struct {
   261  	Image string
   262  }
   263  
   264  type C struct {
   265  	Terminals []*A
   266  }
   267  
   268  // For debugging purposes. Do not delete.
   269  func TestX(t *testing.T) {
   270  	t.Skip()
   271  
   272  	in := `
   273  -- in.cue --
   274  package test
   275  
   276  // // Foo
   277  // a: [X=string]: [Y=string]: {
   278  // 	name: X+Y
   279  // }
   280  
   281  // [Y=string]: [X=string]: name: {Y+X}
   282  // {
   283  // 	name:  X.other + Y
   284  // 	other: string
   285  // }
   286  
   287  // c: [X=string]: X
   288  
   289  // #pkg1: Object
   290  
   291  // "Hello \(#pkg1)!"
   292  
   293  
   294  // Object: "World"
   295  
   296  // // A Foo fooses stuff.
   297  // foos are instances of Foo.
   298  // foos: [string]: {}
   299  
   300  // // // My first little foo.
   301  // foos: MyFoo: {}
   302  	`
   303  
   304  	archive := txtar.Parse([]byte(in))
   305  	a := cuetxtar.Load(archive, "/tmp/test")
   306  	if err := a[0].Err; err != nil {
   307  		t.Fatal(err)
   308  	}
   309  
   310  	// x := a[0].Files[0]
   311  	// astutil.Sanitize(x)
   312  
   313  	r := runtime.New()
   314  	v, errs := compile.Files(nil, r, "", a[0].Files...)
   315  	if errs != nil {
   316  		t.Fatal(errs)
   317  	}
   318  	v.Finalize(eval.NewContext(r, v))
   319  
   320  	file, errs := export.Def(r, "main", v)
   321  	if errs != nil {
   322  		t.Fatal(errs)
   323  	}
   324  
   325  	t.Error(string(formatNode(t, file)))
   326  }
   327  
   328  func TestFromGo(t *testing.T) {
   329  	type Struct struct {
   330  		A string
   331  		B string
   332  	}
   333  
   334  	m := make(map[string]Struct)
   335  	m["hello"] = Struct{
   336  		A: "a",
   337  		B: "b",
   338  	}
   339  	var r cue.Runtime
   340  	codec := gocodec.New(&r, nil)
   341  	v, err := codec.Decode(m)
   342  	if err != nil {
   343  		panic(err)
   344  	}
   345  
   346  	syn, _ := format.Node(v.Syntax())
   347  	if got := string(syn); got != `{
   348  	hello: {
   349  		A: "a"
   350  		B: "b"
   351  	}
   352  }` {
   353  		t.Errorf("incorrect ordering: %s\n", got)
   354  	}
   355  }
   356  
   357  func TestFromAPI(t *testing.T) {
   358  	testCases := []struct {
   359  		expr ast.Expr
   360  		out  string
   361  	}{{
   362  		expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct()),
   363  		out:  `close({})`,
   364  	}, {
   365  		expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct(
   366  			"a", ast.NewString("foo"),
   367  		)),
   368  		out: `close({a: "foo"})`,
   369  	}, {
   370  		expr: ast.NewCall(ast.NewIdent("close"), ast.NewStruct(
   371  			ast.Embed(ast.NewStruct("a", ast.NewString("foo"))),
   372  		)),
   373  		out: `close({a: "foo"})`,
   374  	}}
   375  	// Issue #1204
   376  	for _, tc := range testCases {
   377  		t.Run("", func(t *testing.T) {
   378  			ctx := cuecontext.New()
   379  
   380  			v := ctx.BuildExpr(tc.expr)
   381  
   382  			r, x := value.ToInternal(v)
   383  			file, err := export.Def(r, "foo", x)
   384  
   385  			if err != nil {
   386  				t.Fatal(err)
   387  			}
   388  
   389  			got := astinternal.DebugStr(file)
   390  			if got != tc.out {
   391  				t.Errorf("got:  %s\nwant: %s", got, tc.out)
   392  			}
   393  
   394  		})
   395  	}
   396  }