blob: 760a1351f2fa7ef96c339180734d4f1a7132be22 [file] [log] [blame]
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cue
import (
"fmt"
"log"
"strings"
"testing"
"cuelang.org/go/cue/format"
)
func TestExport(t *testing.T) {
testCases := []struct {
raw bool // skip evaluation the root, fully raw
eval bool // evaluate the full export
in, out string
}{{
in: `"hello"`,
out: `"hello"`,
}, {
in: `'hello'`,
out: `'hello'`,
}, {
in: `'hello\nworld'`,
out: "'''" +
multiSep + "hello" +
multiSep + "world" +
multiSep + "'''",
}, {
in: `"hello\nworld"`,
out: `"""` +
multiSep + "hello" +
multiSep + "world" +
multiSep + `"""`,
}, {
in: `{
"_": int
"_foo": int
_bar: int
}`,
out: unindent(`
{
"_": int
"_foo": int
_bar: int
}`),
}, {
in: "{ a: 1, b: a + 2, c: null, d: true, e: _, f: string }",
out: unindent(`
{
a: 1
b: 3
c: null
d: true
e: _
f: string
}`),
}, {
// Here the failed lookups are not considered permanent
// failures, as the structs are open.
in: `{ a: { b: 2.0, s: "abc" }, b: a.b, c: a.c, d: a["d"], e: a.t[2:3] }`,
out: unindent(`
{
a: {
b: 2.0
s: "abc"
}
b: 2.0
c: a.c
d: a["d"]
e: a.t[2:3]
}`),
}, {
// Here the failed lookups are permanent failures as the structs are
// closed.
in: `{ a :: { b: 2.0, s: "abc" }, b: a.b, c: a.c, d: a["d"], e: a.t[2:3] }`,
out: unindent(`
{
a :: {
b: 2.0
s: "abc"
}
b: 2.0
c: _|_ /* undefined field "c" */
d: _|_ /* undefined field "d" */
e: _|_ /* undefined field "t" */
}`),
}, {
// a closed struct with template restrictions is exported as a
// conjunction of two structs.
in: `{
A :: { b: int }
a: A & { <_>: <10 }
B :: a
}`,
out: unindent(`
{
A :: {
b: int
}
a: close({
b: <10
}) & {
<_>: <10
}
B :: {
b: <10
} & {
<_>: <10
}
}`),
}, {
in: `{
a: 5*[int]
a: [1, 2, ...]
b: <=5*[int]
b: [1, 2, ...]
c: (>=3 & <=5)*[int]
c: [1, 2, ...]
d: >=2*[int]
d: [1, 2, ...]
e: [...int]
e: [1, 2, ...]
f: [1, 2, ...]
}`,
out: unindent(`
{
a: [1, 2, int, int, int]
b: <=5*[int] & [1, 2, ...]
c: (>=3 & <=5)*[int] & [1, 2, ...]
d: >=2*[int] & [1, 2, ...]
e: [1, 2]
f: [1, 2]
}`),
}, {
raw: true,
in: `{
a: 5*[int]
a: [1, 2, ...]
b: <=5*[int]
b: [1, 2, ...]
c: (>=3 & <=5)*[int]
c: [1, 2, ...]
d: >=2*[int]
d: [1, 2, ...]
e: [...int]
e: [1, 2, ...]
f: [1, 2, ...]
}`,
out: unindent(`
{
a: 5*[int] & [1, 2, ...]
b: <=5*[int] & [1, 2, ...]
c: (>=3 & <=5)*[int] & [1, 2, ...]
d: >=2*[int] & [1, 2, ...]
e: [...int] & [1, 2, ...]
f: [1, 2, ...]
}`),
}, {
raw: true,
in: `{ a: { b: [] }, c: a.b, d: a["b"] }`,
out: unindent(`
{
a b: []
c: a.b
d: a["b"]
}`),
}, {
raw: true,
in: `{ a: *"foo" | *"bar" | *string | int, b: a[2:3] }`,
out: unindent(`
{
a: *"foo" | *"bar" | *string | int
b: a[2:3]
}`),
}, {
in: `{
a: >=0 & <=10 & !=1
}`,
out: unindent(`
{
a: >=0 & <=10 & !=1
}`),
}, {
raw: true,
in: `{
a: >=0 & <=10 & !=1
}`,
out: unindent(`
{
a: >=0 & <=10 & !=1
}`),
}, {
raw: true,
eval: true,
in: `{
a: (*1 | 2) & (1 | *2)
b: [(*1 | 2) & (1 | *2)]
}`,
out: unindent(`
{
a: 1 | 2
b: [1 | 2]
}`),
}, {
raw: true,
eval: true,
in: `{
u16: int & >=0 & <=65535
u32: uint32
u64: uint64
u128: uint128
u8: uint8
ua: uint16 & >0
us: >=0 & <10_000 & int
i16: >=-32768 & int & <=32767
i32: int32 & > 0
i64: int64
i128: int128
f64: float64
fi: float64 & int
}`,
out: unindent(`
{
u16: uint16
u32: uint32
u64: uint64
u128: uint128
u8: uint8
ua: uint16 & >0
us: uint & <10000
i16: int16
i32: int32 & >0
i64: int64
i128: int128
f64: float64
fi: int & float64
}`),
}, {
raw: true,
in: `{ a: [1, 2], b: { "\(k)": v for k, v in a if v > 1 } }`,
out: unindent(`
{
a: [1, 2]
b: {
"\(k)": v for k, v in a if v > 1
}
}`),
}, {
raw: true,
in: `{ a: [1, 2], b: [ v for k, v in a ] }`,
out: unindent(`
{
a: [1, 2]
b: [ v for k, v in a ]
}`),
}, {
raw: true,
in: `{ a: >=0 & <=10, b: "Count: \(a) times" }`,
out: unindent(`
{
a: >=0 & <=10
b: "Count: \(a) times"
}`),
}, {
raw: true,
in: `{ a: "", b: len(a) }`,
out: unindent(`
{
a: ""
b: len(a)
}`),
}, {
raw: true,
eval: true,
in: `{
b: {
idx: a[str]
str: string
}
b a b: 4
a b: 3
}`,
// reference to a must be redirected to outer a through alias
out: unindent(`
{
A = a
b: {
idx: A[str]
a b: 4
str: string
}
a b: 3
}`),
}, {
raw: true,
eval: true,
in: `{
job <Name>: {
name: Name
replicas: uint | *1 @protobuf(10)
command: string
}
job list command: "ls"
job nginx: {
command: "nginx"
replicas: 2
}
}`,
out: unindent(`
{
job: {
list: {
name: "list"
replicas: 1 @protobuf(10)
command: "ls"
}
nginx: {
name: "nginx"
replicas: 2 @protobuf(10)
command: "nginx"
}
}
}`),
}, {
raw: true,
in: `{
emb :: {
a: 1
sub: {
f: 3
}
}
def :: {
emb
b: 2
}
f :: { a: 10 }
e :: {
f
b: int
<_>: <100
}
}`,
out: unindent(`
{
emb :: {
a: 1
sub f: 3
}
f :: {
a: 10
}
def :: {
emb
b: 2
}
e :: {
f
<_>: <100
b: int
}
}`),
}, {
raw: true,
eval: true,
in: `{
reg: { foo: 1, bar: { baz: 3 } }
def :: {
a: 1
sub: reg
}
val: def
}`,
out: unindent(`
{
reg: {
foo: 1
bar baz: 3
}
def :: {
a: 1
sub: {
foo: 1
bar: {
baz: 3
...
}
...
}
}
val: close({
a: 1
sub: {
foo: 1
bar baz: 3
}
})
}`),
}, {
raw: true,
eval: true,
in: `{
b: [{
<X>: int
f: 4 if a > 4
}][a]
a: int
c: *1 | 2
}`,
// reference to a must be redirected to outer a through alias
out: unindent(`
{
b: [{
<X>: int
f: 4 if a > 4
}][a]
a: int
c: 1
}`),
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
body := fmt.Sprintf("Test: %s", tc.in)
ctx, obj := compileFile(t, body)
ctx.trace = *traceOn
var root value = obj
if !tc.raw {
root = testResolve(ctx, obj, evalFull)
}
t.Log(debugStr(ctx, root))
n := root.(*structLit).arcs[0].v
v := newValueRoot(ctx, n)
opts := options{raw: !tc.eval}
node, _ := export(ctx, v.eval(ctx), opts)
b, err := format.Node(node, format.Simplify())
if err != nil {
log.Fatal(err)
}
if got := string(b); got != tc.out {
t.Errorf("\ngot %v;\nwant %v", got, tc.out)
}
})
}
}
func TestExportFile(t *testing.T) {
testCases := []struct {
eval bool // evaluate the full export
in, out string
}{{
in: `
import "strings"
a: strings.ContainsAny("c")
`,
out: unindent(`
import "strings"
a: strings.ContainsAny("c")`),
}, {
in: `
import "time"
a: time.Time
`,
out: unindent(`
import "time"
a: time.Time`),
}, {
in: `
import "time"
{
a: time.Time
} & {
time: int
} `,
out: unindent(`
import timex "time"
time: int
a: timex.Time`),
}, {
in: `
import time2 "time"
a: time2.Time`,
out: unindent(`
import "time"
a: time.Time`),
}, {
in: `
import time2 "time"
time: int
a: time2.Time`,
out: unindent(`
import time2 "time"
time: int
a: time2.Time`),
}, {
in: `
import "strings"
a: strings.TrimSpace(" c ")
`,
out: unindent(`
import "strings"
a: strings.TrimSpace(" c ")`),
}, {
in: `
import "strings"
stringsx = strings
a: {
strings: stringsx.ContainsAny("c")
}
`,
out: unindent(`
import "strings"
STRINGS = strings
a strings: STRINGS.ContainsAny("c")`),
}, {
in: `
a: b - 100
b: a + 100
`,
out: unindent(`
{
a: b - 100
b: a + 100
}`),
}, {
in: `A: {
<_>: B
} @protobuf(1,"test")
B: {}
B: {a: int} | {b: int}
`,
out: unindent(`
{
A: {
<_>: B
} @protobuf(1,"test")
B: {
} & ({
a: int
} | {
b: int
})
}`),
}, {
in: `
import "time"
a: { b: time.Duration } | { c: time.Duration }
`,
out: unindent(`
import "time"
a: {
b: time.Duration
} | {
c: time.Duration
}`),
}}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
var r Runtime
inst, err := r.Compile("test", tc.in)
if err != nil {
t.Fatal(err)
}
b, err := format.Node(inst.Value().Syntax(Raw()), format.Simplify())
if err != nil {
log.Fatal(err)
}
if got := strings.TrimSpace(string(b)); got != tc.out {
t.Errorf("\ngot:\n%v\nwant:\n%v", got, tc.out)
}
})
}
}
func unindent(s string) string {
lines := strings.Split(s, "\n")[1:]
ws := lines[0][:len(lines[0])-len(strings.TrimLeft(lines[0], " \t"))]
for i, s := range lines {
if s == "" {
continue
}
if !strings.HasPrefix(s, ws) {
panic("invalid indentation")
}
lines[i] = lines[i][len(ws):]
}
return strings.Join(lines, "\n")
}