d2/d2ir/compile_test.go

477 lines
12 KiB
Go
Raw Normal View History

package d2ir_test
import (
"fmt"
"math/big"
"path/filepath"
"strings"
"testing"
2023-01-11 19:05:50 +00:00
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser"
)
2023-01-16 11:52:37 +00:00
func TestCompile(t *testing.T) {
t.Parallel()
2023-01-18 10:06:44 +00:00
t.Run("fields", testCompileFields)
t.Run("edges", testCompileEdges)
2023-01-18 15:15:16 +00:00
t.Run("layers", testCompileLayers)
t.Run("scenarios", testCompileScenarios)
t.Run("steps", testCompileSteps)
}
2023-01-16 12:48:45 +00:00
type testCase struct {
name string
2023-01-18 01:39:31 +00:00
run func(testing.TB)
}
2023-01-11 19:05:50 +00:00
func runa(t *testing.T, tca []testCase) {
for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
2023-01-18 01:39:31 +00:00
tc.run(t)
})
}
}
2023-01-18 11:51:16 +00:00
func compile(t testing.TB, text string) (*d2ir.Map, error) {
2023-01-11 19:05:50 +00:00
t.Helper()
2023-01-18 01:39:31 +00:00
d2Path := fmt.Sprintf("%v.d2", t.Name())
2023-01-11 19:05:50 +00:00
ast, err := d2parser.Parse(d2Path, strings.NewReader(text), nil)
assert.Success(t, err)
2023-01-18 11:51:16 +00:00
m, err := d2ir.Compile(ast)
if err != nil {
2023-01-18 01:39:31 +00:00
return nil, err
}
2023-01-11 19:05:50 +00:00
2023-01-18 11:51:16 +00:00
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2ir", t.Name()), m)
2023-01-18 01:39:31 +00:00
if err != nil {
return nil, err
}
2023-01-18 11:51:16 +00:00
return m, nil
}
2023-01-18 15:20:21 +00:00
func assertQuery(t testing.TB, n d2ir.Node, nfields, nedges int, primary interface{}, idStr string) d2ir.Node {
t.Helper()
2023-01-18 15:15:16 +00:00
m := n.Map()
p := n.Primary()
2023-01-18 15:15:16 +00:00
if idStr != "" {
var err error
2023-01-18 15:20:21 +00:00
n, err = m.Query(idStr)
2023-01-18 15:15:16 +00:00
assert.Success(t, err)
assert.NotEqual(t, n, nil)
p = n.Primary()
m = n.Map()
}
2023-01-16 11:52:37 +00:00
assert.Equal(t, nfields, m.FieldCountRecursive())
assert.Equal(t, nedges, m.EdgeCountRecursive())
2023-01-11 21:20:05 +00:00
if !makeScalar(p).Equal(makeScalar(primary)) {
2023-01-11 23:50:49 +00:00
t.Fatalf("expected primary %#v but got %s", primary, p)
}
2023-01-18 15:15:16 +00:00
return n
}
func makeScalar(v interface{}) *d2ir.Scalar {
s := &d2ir.Scalar{}
switch v := v.(type) {
2023-01-11 21:20:05 +00:00
case *d2ir.Scalar:
if v == nil {
s.Value = &d2ast.Null{}
return s
}
return v
case bool:
s.Value = &d2ast.Boolean{
Value: v,
}
case float64:
bv := &big.Rat{}
bv.SetFloat64(v)
s.Value = &d2ast.Number{
Value: bv,
}
case int:
s.Value = &d2ast.Number{
Value: big.NewRat(int64(v), 1),
}
case string:
s.Value = d2ast.FlatDoubleQuotedString(v)
default:
if v != nil {
panic(fmt.Sprintf("d2ir: unexpected type to makeScalar: %#v", v))
}
s.Value = &d2ast.Null{}
}
return s
}
2023-01-16 12:48:45 +00:00
2023-01-18 10:06:44 +00:00
func testCompileFields(t *testing.T) {
2023-01-16 12:48:45 +00:00
t.Parallel()
tca := []testCase{
{
name: "root",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "x")
2023-01-16 12:48:45 +00:00
},
},
{
name: "label",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x: yes`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, "yes", "x")
2023-01-16 12:48:45 +00:00
},
},
{
name: "nested",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x.y: yes`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 2, 0, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "x")
assertQuery(t, m, 0, 0, "yes", "x.y")
2023-01-16 12:48:45 +00:00
},
},
{
name: "array",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x: [1;2;3;4]`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
f := assertQuery(t, m, 0, 0, nil, "x").(*d2ir.Field)
assert.String(t, `[1; 2; 3; 4]`, f.Composite.String())
2023-01-16 12:48:45 +00:00
},
},
{
2023-01-18 15:15:16 +00:00
name: "null",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 15:15:16 +00:00
m, err := compile(t, `pq: pq
pq: null`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "")
2023-01-18 15:15:16 +00:00
// null doesn't delete pq from *Map so that for language tooling
// we maintain the references.
// Instead d2compiler will ensure it doesn't get rendered.
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "pq")
2023-01-16 12:48:45 +00:00
},
},
}
runa(t, tca)
2023-01-18 15:15:16 +00:00
t.Run("primary", func(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "root",
run: func(t testing.TB) {
m, err := compile(t, `x: yes { pqrs }`)
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 2, 0, nil, "")
2023-01-18 15:15:16 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, "yes", "x")
assertQuery(t, m, 0, 0, nil, "x.pqrs")
2023-01-18 15:15:16 +00:00
},
},
{
name: "nested",
run: func(t testing.TB) {
m, err := compile(t, `x.y: yes { pqrs }`)
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 3, 0, nil, "")
2023-01-18 15:15:16 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 2, 0, nil, "x")
assertQuery(t, m, 1, 0, "yes", "x.y")
assertQuery(t, m, 0, 0, nil, "x.y.pqrs")
2023-01-18 15:15:16 +00:00
},
},
}
runa(t, tca)
})
2023-01-16 12:48:45 +00:00
}
2023-01-18 10:06:44 +00:00
func testCompileEdges(t *testing.T) {
2023-01-16 12:48:45 +00:00
t.Parallel()
tca := []testCase{
{
2023-01-17 12:44:14 +00:00
name: "root",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x -> y`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 2, 1, nil, "")
assertQuery(t, m, 0, 0, nil, `(x -> y)[0]`)
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "y")
2023-01-16 12:48:45 +00:00
},
},
{
name: "nested",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x.y -> z.p`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 4, 1, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "x.y")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 1, 0, nil, "z")
assertQuery(t, m, 0, 0, nil, "z.p")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "(x.y -> z.p)[0]")
2023-01-16 12:48:45 +00:00
},
},
{
name: "underscore",
2023-01-18 01:39:31 +00:00
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `p: { _.x -> z }`)
2023-01-16 12:48:45 +00:00
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 3, 1, nil, "")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 1, 0, nil, "p")
2023-01-16 12:48:45 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "(x -> p.z)[0]")
2023-01-18 15:15:16 +00:00
},
},
{
name: "chain",
run: func(t testing.TB) {
m, err := compile(t, `a -> b -> c -> d`)
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 4, 3, nil, "")
assertQuery(t, m, 0, 0, nil, "a")
assertQuery(t, m, 0, 0, nil, "b")
assertQuery(t, m, 0, 0, nil, "c")
assertQuery(t, m, 0, 0, nil, "d")
assertQuery(t, m, 0, 0, nil, "(a -> b)[0]")
assertQuery(t, m, 0, 0, nil, "(b -> c)[0]")
assertQuery(t, m, 0, 0, nil, "(c -> d)[0]")
2023-01-18 10:06:44 +00:00
},
},
}
runa(t, tca)
2023-01-18 15:15:16 +00:00
t.Run("errs", func(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "bad_edge",
run: func(t testing.TB) {
_, err := compile(t, `(x -> y): { p -> q }`)
assert.ErrorString(t, err, `TestCompile/edges/errs/bad_edge.d2:1:13: cannot create edge inside edge`)
},
},
}
runa(t, tca)
})
2023-01-18 10:06:44 +00:00
}
func testCompileLayers(t *testing.T) {
t.Parallel()
2023-01-18 15:15:16 +00:00
tca := []testCase{
{
name: "root",
run: func(t testing.TB) {
m, err := compile(t, `x -> y
layers: {
bingo: { p.q.z }
}`)
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 7, 1, nil, "")
assertQuery(t, m, 0, 0, nil, `(x -> y)[0]`)
2023-01-18 15:15:16 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "y")
2023-01-18 15:15:16 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 3, 0, nil, "layers.bingo")
2023-01-18 15:15:16 +00:00
},
},
}
runa(t, tca)
2023-01-18 12:32:12 +00:00
t.Run("errs", func(t *testing.T) {
2023-01-18 15:15:16 +00:00
t.Parallel()
2023-01-18 12:32:12 +00:00
tca := []testCase{
{
2023-01-28 01:19:12 +00:00
name: "1/bad_edge",
2023-01-18 12:32:12 +00:00
run: func(t testing.TB) {
_, err := compile(t, `layers.x -> layers.y`)
2023-01-28 01:19:12 +00:00
assert.ErrorString(t, err, `TestCompile/layers/errs/1/bad_edge.d2:1:1: cannot create edges between boards`)
2023-01-18 12:32:12 +00:00
},
},
{
2023-01-28 01:19:12 +00:00
name: "2/bad_edge",
2023-01-18 12:32:12 +00:00
run: func(t testing.TB) {
_, err := compile(t, `layers -> scenarios`)
2023-01-28 01:19:12 +00:00
assert.ErrorString(t, err, `TestCompile/layers/errs/2/bad_edge.d2:1:1: edge with board keyword alone doesn't make sense`)
2023-01-18 12:32:12 +00:00
},
},
2023-01-18 12:42:34 +00:00
{
2023-01-28 01:19:12 +00:00
name: "3/bad_edge",
2023-01-18 12:42:34 +00:00
run: func(t testing.TB) {
_, err := compile(t, `layers.x.y -> steps.z.p`)
2023-01-28 01:19:12 +00:00
assert.ErrorString(t, err, `TestCompile/layers/errs/3/bad_edge.d2:1:1: cannot create edges between boards`)
},
},
{
name: "4/good_edge",
run: func(t testing.TB) {
_, err := compile(t, `layers.x.y -> layers.x.y`)
assert.Success(t, err)
2023-01-18 12:42:34 +00:00
},
},
2023-01-18 12:32:12 +00:00
}
runa(t, tca)
})
2023-01-18 15:15:16 +00:00
}
func testCompileScenarios(t *testing.T) {
t.Parallel()
2023-01-18 10:06:44 +00:00
tca := []testCase{
{
name: "root",
run: func(t testing.TB) {
2023-01-18 11:51:16 +00:00
m, err := compile(t, `x -> y
2023-01-18 15:15:16 +00:00
scenarios: {
2023-01-18 10:06:44 +00:00
bingo: { p.q.z }
2023-01-18 15:15:16 +00:00
nuclear: { quiche }
2023-01-18 10:06:44 +00:00
}`)
assert.Success(t, err)
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 13, 3, nil, "")
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "y")
assertQuery(t, m, 0, 0, nil, `(x -> y)[0]`)
assertQuery(t, m, 5, 1, nil, "scenarios.bingo")
assertQuery(t, m, 0, 0, nil, "scenarios.bingo.x")
assertQuery(t, m, 0, 0, nil, "scenarios.bingo.y")
assertQuery(t, m, 0, 0, nil, `scenarios.bingo.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "scenarios.bingo.p")
assertQuery(t, m, 1, 0, nil, "scenarios.bingo.p.q")
assertQuery(t, m, 0, 0, nil, "scenarios.bingo.p.q.z")
assertQuery(t, m, 3, 1, nil, "scenarios.nuclear")
assertQuery(t, m, 0, 0, nil, "scenarios.nuclear.x")
assertQuery(t, m, 0, 0, nil, "scenarios.nuclear.y")
assertQuery(t, m, 0, 0, nil, `scenarios.nuclear.(x -> y)[0]`)
assertQuery(t, m, 0, 0, nil, "scenarios.nuclear.quiche")
2023-01-18 15:15:16 +00:00
},
},
}
runa(t, tca)
}
2023-01-18 10:06:44 +00:00
2023-01-18 15:15:16 +00:00
func testCompileSteps(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "root",
run: func(t testing.TB) {
m, err := compile(t, `x -> y
steps: {
bingo: { p.q.z }
nuclear: { quiche }
}`)
assert.Success(t, err)
2023-01-18 10:06:44 +00:00
2023-01-18 15:20:21 +00:00
assertQuery(t, m, 16, 3, nil, "")
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "y")
assertQuery(t, m, 0, 0, nil, `(x -> y)[0]`)
assertQuery(t, m, 5, 1, nil, "steps.bingo")
assertQuery(t, m, 0, 0, nil, "steps.bingo.x")
assertQuery(t, m, 0, 0, nil, "steps.bingo.y")
assertQuery(t, m, 0, 0, nil, `steps.bingo.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "steps.bingo.p")
assertQuery(t, m, 1, 0, nil, "steps.bingo.p.q")
assertQuery(t, m, 0, 0, nil, "steps.bingo.p.q.z")
assertQuery(t, m, 6, 1, nil, "steps.nuclear")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.x")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.y")
assertQuery(t, m, 0, 0, nil, `steps.nuclear.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "steps.nuclear.p")
assertQuery(t, m, 1, 0, nil, "steps.nuclear.p.q")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.p.q.z")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.quiche")
2023-01-16 12:48:45 +00:00
},
},
2023-01-18 15:44:34 +00:00
{
name: "recursive",
run: func(t testing.TB) {
m, err := compile(t, `x -> y
steps: {
bingo: { p.q.z }
nuclear: {
quiche
scenarios: {
bavarian: {
perseverance
}
}
}
}`)
assert.Success(t, err)
2023-01-18 15:47:42 +00:00
assertQuery(t, m, 25, 4, nil, "")
2023-01-18 15:44:34 +00:00
assertQuery(t, m, 0, 0, nil, "x")
assertQuery(t, m, 0, 0, nil, "y")
assertQuery(t, m, 0, 0, nil, `(x -> y)[0]`)
assertQuery(t, m, 5, 1, nil, "steps.bingo")
assertQuery(t, m, 0, 0, nil, "steps.bingo.x")
assertQuery(t, m, 0, 0, nil, "steps.bingo.y")
assertQuery(t, m, 0, 0, nil, `steps.bingo.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "steps.bingo.p")
assertQuery(t, m, 1, 0, nil, "steps.bingo.p.q")
assertQuery(t, m, 0, 0, nil, "steps.bingo.p.q.z")
2023-01-18 15:47:42 +00:00
assertQuery(t, m, 15, 2, nil, "steps.nuclear")
2023-01-18 15:44:34 +00:00
assertQuery(t, m, 0, 0, nil, "steps.nuclear.x")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.y")
assertQuery(t, m, 0, 0, nil, `steps.nuclear.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "steps.nuclear.p")
assertQuery(t, m, 1, 0, nil, "steps.nuclear.p.q")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.p.q.z")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.quiche")
2023-01-18 15:47:42 +00:00
assertQuery(t, m, 7, 1, nil, "steps.nuclear.scenarios.bavarian")
2023-01-18 15:44:34 +00:00
assertQuery(t, m, 0, 0, nil, "steps.nuclear.scenarios.bavarian.x")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.scenarios.bavarian.y")
assertQuery(t, m, 0, 0, nil, `steps.nuclear.scenarios.bavarian.(x -> y)[0]`)
assertQuery(t, m, 2, 0, nil, "steps.nuclear.scenarios.bavarian.p")
assertQuery(t, m, 1, 0, nil, "steps.nuclear.scenarios.bavarian.p.q")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.scenarios.bavarian.p.q.z")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.scenarios.bavarian.quiche")
assertQuery(t, m, 0, 0, nil, "steps.nuclear.scenarios.bavarian.perseverance")
},
},
2023-01-16 12:48:45 +00:00
}
runa(t, tca)
}