2023-02-27 22:04:02 +00:00
|
|
|
package e2etests_cli
|
|
|
|
|
|
|
|
|
|
import (
|
2023-03-03 20:46:53 +00:00
|
|
|
"bytes"
|
2023-02-27 22:04:02 +00:00
|
|
|
"context"
|
2023-03-03 04:29:56 +00:00
|
|
|
"os"
|
2023-03-03 03:41:24 +00:00
|
|
|
"path/filepath"
|
2023-02-27 22:04:02 +00:00
|
|
|
"testing"
|
|
|
|
|
"time"
|
2023-03-03 03:41:24 +00:00
|
|
|
|
|
|
|
|
"oss.terrastruct.com/d2/d2cli"
|
2023-04-06 18:16:32 +00:00
|
|
|
"oss.terrastruct.com/d2/lib/pptx"
|
2023-04-14 13:52:27 +00:00
|
|
|
"oss.terrastruct.com/d2/lib/xgif"
|
2023-03-03 03:41:24 +00:00
|
|
|
"oss.terrastruct.com/util-go/assert"
|
2023-03-03 05:20:58 +00:00
|
|
|
"oss.terrastruct.com/util-go/diff"
|
2023-03-03 03:41:24 +00:00
|
|
|
"oss.terrastruct.com/util-go/xmain"
|
|
|
|
|
"oss.terrastruct.com/util-go/xos"
|
2023-02-27 22:04:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestCLI_E2E(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
tca := []struct {
|
2023-03-03 05:28:33 +00:00
|
|
|
name string
|
|
|
|
|
skipCI bool
|
2023-03-31 03:39:01 +00:00
|
|
|
skip bool
|
2023-03-03 05:28:33 +00:00
|
|
|
run func(t *testing.T, ctx context.Context, dir string, env *xos.Env)
|
2023-02-27 22:04:02 +00:00
|
|
|
}{
|
|
|
|
|
{
|
2023-03-03 05:28:33 +00:00
|
|
|
name: "hello_world_png",
|
|
|
|
|
skipCI: true,
|
2023-03-03 03:41:24 +00:00
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
2023-03-03 04:02:36 +00:00
|
|
|
writeFile(t, dir, "hello-world.d2", `x -> y`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "hello-world.d2", "hello-world.png")
|
2023-03-03 03:41:24 +00:00
|
|
|
assert.Success(t, err)
|
2023-03-03 04:02:36 +00:00
|
|
|
png := readFile(t, dir, "hello-world.png")
|
2023-03-03 05:20:58 +00:00
|
|
|
testdataIgnoreDiff(t, ".png", png)
|
2023-03-03 04:02:36 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
2023-03-03 05:28:33 +00:00
|
|
|
name: "hello_world_png_pad",
|
|
|
|
|
skipCI: true,
|
2023-03-03 04:02:36 +00:00
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "hello-world.d2", `x -> y`)
|
2023-03-03 04:09:05 +00:00
|
|
|
err := runTestMain(t, ctx, dir, env, "--pad=400", "hello-world.d2", "hello-world.png")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
png := readFile(t, dir, "hello-world.png")
|
2023-03-03 05:20:58 +00:00
|
|
|
testdataIgnoreDiff(t, ".png", png)
|
2023-03-03 04:09:05 +00:00
|
|
|
},
|
|
|
|
|
},
|
2023-03-18 05:29:51 +00:00
|
|
|
{
|
|
|
|
|
name: "center",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "hello-world.d2", `x -> y`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--center=true", "hello-world.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
svg := readFile(t, dir, "hello-world.svg")
|
|
|
|
|
assert.Testdata(t, ".svg", svg)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-04-07 17:50:13 +00:00
|
|
|
{
|
|
|
|
|
name: "empty-layer",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "empty-layer.d2", `layers: { x: {} }`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "empty-layer.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
assert.TestdataDir(t, filepath.Join(dir, "empty-layer"))
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-23 20:37:28 +00:00
|
|
|
{
|
|
|
|
|
name: "animation",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "animation.d2", `Chicken's plan: {
|
|
|
|
|
style.font-size: 35
|
|
|
|
|
near: top-center
|
|
|
|
|
shape: text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
steps: {
|
|
|
|
|
1: {
|
|
|
|
|
Approach road
|
|
|
|
|
}
|
|
|
|
|
2: {
|
|
|
|
|
Approach road -> Cross road
|
|
|
|
|
}
|
|
|
|
|
3: {
|
|
|
|
|
Cross road -> Make you wonder why
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--animate-interval=1400", "animation.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
svg := readFile(t, dir, "animation.svg")
|
|
|
|
|
assert.Testdata(t, ".svg", svg)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-31 00:37:55 +00:00
|
|
|
{
|
|
|
|
|
name: "linked-path",
|
2023-03-31 03:39:01 +00:00
|
|
|
// TODO tempdir is random, resulting in different test results each time with the links
|
|
|
|
|
skip: true,
|
2023-03-31 00:37:55 +00:00
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "linked.d2", `cat: how does the cat go? {
|
|
|
|
|
link: layers.cat
|
|
|
|
|
}
|
|
|
|
|
layers: {
|
|
|
|
|
cat: {
|
|
|
|
|
home: {
|
|
|
|
|
link: _
|
|
|
|
|
}
|
|
|
|
|
the cat -> meow: goes
|
|
|
|
|
|
|
|
|
|
scenarios: {
|
|
|
|
|
big cat: {
|
|
|
|
|
the cat -> roar: goes
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "linked.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
assert.TestdataDir(t, filepath.Join(dir, "linked"))
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-30 00:20:41 +00:00
|
|
|
{
|
|
|
|
|
name: "with-font",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "font.d2", `a: Why do computers get sick often?
|
|
|
|
|
b: Because their Windows are always open!
|
|
|
|
|
a -> b: italic font
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--font-bold=./RockSalt-Regular.ttf", "font.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
svg := readFile(t, dir, "font.svg")
|
|
|
|
|
assert.Testdata(t, ".svg", svg)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-23 20:37:28 +00:00
|
|
|
{
|
|
|
|
|
name: "incompatible-animation",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "x.d2", `x -> y`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--animate-interval=2", "x.d2", "x.png")
|
2023-04-14 13:52:27 +00:00
|
|
|
assert.ErrorString(t, err, `failed to wait xmain test: e2etests-cli/d2: bad usage: -animate-interval can only be used when exporting to SVG or GIF.
|
2023-03-23 20:37:28 +00:00
|
|
|
You provided: .png`)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-03 04:09:05 +00:00
|
|
|
{
|
2023-03-03 05:28:33 +00:00
|
|
|
name: "hello_world_png_sketch",
|
|
|
|
|
skipCI: true,
|
2023-03-03 04:09:05 +00:00
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "hello-world.d2", `x -> y`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--sketch", "hello-world.d2", "hello-world.png")
|
2023-03-03 04:02:36 +00:00
|
|
|
assert.Success(t, err)
|
|
|
|
|
png := readFile(t, dir, "hello-world.png")
|
2023-03-03 05:20:58 +00:00
|
|
|
// https://github.com/terrastruct/d2/pull/963#pullrequestreview-1323089392
|
|
|
|
|
testdataIgnoreDiff(t, ".png", png)
|
2023-03-03 03:41:24 +00:00
|
|
|
},
|
2023-02-27 22:04:02 +00:00
|
|
|
},
|
2023-03-03 04:29:56 +00:00
|
|
|
{
|
|
|
|
|
name: "multiboard/life",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
2023-03-03 04:39:00 +00:00
|
|
|
writeFile(t, dir, "life.d2", `x -> y
|
|
|
|
|
layers: {
|
|
|
|
|
core: {
|
|
|
|
|
belief
|
|
|
|
|
food
|
|
|
|
|
diet
|
|
|
|
|
}
|
|
|
|
|
broker: {
|
|
|
|
|
mortgage
|
|
|
|
|
realtor
|
|
|
|
|
}
|
|
|
|
|
stocks: {
|
|
|
|
|
TSX
|
|
|
|
|
NYSE
|
|
|
|
|
NASDAQ
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scenarios: {
|
|
|
|
|
why: {
|
|
|
|
|
y -> x
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "life.d2")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
assert.TestdataDir(t, filepath.Join(dir, "life"))
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "multiboard/life_index_d2",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
2023-03-03 04:29:56 +00:00
|
|
|
writeFile(t, dir, "life/index.d2", `x -> y
|
|
|
|
|
layers: {
|
|
|
|
|
core: {
|
|
|
|
|
belief
|
|
|
|
|
food
|
|
|
|
|
diet
|
|
|
|
|
}
|
|
|
|
|
broker: {
|
|
|
|
|
mortgage
|
|
|
|
|
realtor
|
|
|
|
|
}
|
|
|
|
|
stocks: {
|
|
|
|
|
TSX
|
|
|
|
|
NYSE
|
|
|
|
|
NASDAQ
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scenarios: {
|
|
|
|
|
why: {
|
|
|
|
|
y -> x
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "life")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
assert.TestdataDir(t, filepath.Join(dir, "life"))
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-03 06:07:41 +00:00
|
|
|
{
|
|
|
|
|
name: "internal_linked_pdf",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "in.d2", `cat: how does the cat go? {
|
|
|
|
|
link: layers.cat
|
|
|
|
|
}
|
|
|
|
|
layers: {
|
|
|
|
|
cat: {
|
|
|
|
|
home: {
|
|
|
|
|
link: _
|
|
|
|
|
}
|
|
|
|
|
the cat -> meow: goes
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "in.d2", "out.pdf")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
pdf := readFile(t, dir, "out.pdf")
|
|
|
|
|
testdataIgnoreDiff(t, ".pdf", pdf)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-04-06 18:16:32 +00:00
|
|
|
{
|
2023-04-10 19:29:27 +00:00
|
|
|
name: "how_to_solve_problems_pptx",
|
|
|
|
|
skipCI: true,
|
2023-04-06 18:16:32 +00:00
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
2023-04-10 20:16:01 +00:00
|
|
|
writeFile(t, dir, "in.d2", `how to solve a hard problem? {
|
|
|
|
|
link: steps.2
|
|
|
|
|
}
|
2023-04-06 18:16:32 +00:00
|
|
|
steps: {
|
|
|
|
|
1: {
|
|
|
|
|
w: write down the problem
|
|
|
|
|
}
|
|
|
|
|
2: {
|
|
|
|
|
w -> t
|
|
|
|
|
t: think really hard about it
|
|
|
|
|
}
|
|
|
|
|
3: {
|
|
|
|
|
t -> w2
|
|
|
|
|
w2: write down the solution
|
2023-04-10 20:16:01 +00:00
|
|
|
w2: {
|
|
|
|
|
link: https://d2lang.com
|
|
|
|
|
}
|
2023-04-06 18:16:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "in.d2", "how_to_solve_problems.pptx")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
file := readFile(t, dir, "how_to_solve_problems.pptx")
|
|
|
|
|
err = pptx.Validate(file, 4)
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-04-14 13:52:27 +00:00
|
|
|
{
|
|
|
|
|
name: "how_to_solve_problems_gif",
|
|
|
|
|
skipCI: true,
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "in.d2", `how to solve a hard problem? {
|
|
|
|
|
link: steps.2
|
|
|
|
|
}
|
|
|
|
|
steps: {
|
|
|
|
|
1: {
|
|
|
|
|
w: write down the problem
|
|
|
|
|
}
|
|
|
|
|
2: {
|
|
|
|
|
w -> t
|
|
|
|
|
t: think really hard about it
|
|
|
|
|
}
|
|
|
|
|
3: {
|
|
|
|
|
t -> w2
|
|
|
|
|
w2: write down the solution
|
|
|
|
|
w2: {
|
|
|
|
|
link: https://d2lang.com
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, "--animate-interval=10", "in.d2", "how_to_solve_problems.gif")
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
gifBytes := readFile(t, dir, "how_to_solve_problems.gif")
|
|
|
|
|
err = xgif.Validate(gifBytes, 4, 10)
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-03 20:46:53 +00:00
|
|
|
{
|
|
|
|
|
name: "stdin",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
stdin := bytes.NewBufferString(`x -> y`)
|
|
|
|
|
stdout := &bytes.Buffer{}
|
|
|
|
|
tms := testMain(dir, env, "-")
|
|
|
|
|
tms.Stdin = stdin
|
|
|
|
|
tms.Stdout = stdout
|
|
|
|
|
tms.Start(t, ctx)
|
|
|
|
|
defer tms.Cleanup(t)
|
|
|
|
|
err := tms.Wait(ctx)
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Testdata(t, ".svg", stdout.Bytes())
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-03-05 21:45:20 +00:00
|
|
|
{
|
|
|
|
|
name: "abspath",
|
|
|
|
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
|
|
|
|
writeFile(t, dir, "hello-world.d2", `x -> y`)
|
|
|
|
|
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
|
|
|
|
|
assert.Success(t, err)
|
|
|
|
|
svg := readFile(t, dir, "hello-world.svg")
|
|
|
|
|
assert.Testdata(t, ".svg", svg)
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-02-27 22:04:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
for _, tc := range tca {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
2023-03-03 05:28:33 +00:00
|
|
|
if tc.skipCI && os.Getenv("CI") != "" {
|
|
|
|
|
t.SkipNow()
|
|
|
|
|
}
|
2023-03-31 03:39:01 +00:00
|
|
|
if tc.skip {
|
|
|
|
|
t.SkipNow()
|
|
|
|
|
}
|
2023-03-03 05:28:33 +00:00
|
|
|
|
2023-02-27 22:04:02 +00:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, time.Minute*5)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
2023-03-03 03:41:24 +00:00
|
|
|
dir, cleanup := assert.TempDir(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
env := xos.NewEnv(nil)
|
|
|
|
|
|
|
|
|
|
tc.run(t, ctx, dir, env)
|
2023-02-27 22:04:02 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-03 03:41:24 +00:00
|
|
|
|
|
|
|
|
// We do not run the CLI in its own process even though that makes it not truly e2e to
|
|
|
|
|
// test whether we're cleaning up state correctly.
|
|
|
|
|
func testMain(dir string, env *xos.Env, args ...string) *xmain.TestState {
|
|
|
|
|
return &xmain.TestState{
|
|
|
|
|
Run: d2cli.Run,
|
|
|
|
|
Env: env,
|
|
|
|
|
Args: append([]string{"e2etests-cli/d2"}, args...),
|
|
|
|
|
PWD: dir,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runTestMain(tb testing.TB, ctx context.Context, dir string, env *xos.Env, args ...string) error {
|
|
|
|
|
tms := testMain(dir, env, args...)
|
|
|
|
|
tms.Start(tb, ctx)
|
|
|
|
|
defer tms.Cleanup(tb)
|
2023-03-03 04:29:56 +00:00
|
|
|
err := tms.Wait(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
removeD2Files(tb, dir)
|
|
|
|
|
return nil
|
2023-03-03 03:41:24 +00:00
|
|
|
}
|
2023-03-03 04:02:36 +00:00
|
|
|
|
|
|
|
|
func writeFile(tb testing.TB, dir, fp, data string) {
|
|
|
|
|
tb.Helper()
|
2023-03-03 04:29:56 +00:00
|
|
|
err := os.MkdirAll(filepath.Dir(filepath.Join(dir, fp)), 0755)
|
|
|
|
|
assert.Success(tb, err)
|
2023-03-03 04:02:36 +00:00
|
|
|
assert.WriteFile(tb, filepath.Join(dir, fp), []byte(data), 0644)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readFile(tb testing.TB, dir, fp string) []byte {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
return assert.ReadFile(tb, filepath.Join(dir, fp))
|
|
|
|
|
}
|
2023-03-03 04:29:56 +00:00
|
|
|
|
|
|
|
|
func removeD2Files(tb testing.TB, dir string) {
|
|
|
|
|
ea, err := os.ReadDir(dir)
|
|
|
|
|
assert.Success(tb, err)
|
|
|
|
|
|
|
|
|
|
for _, e := range ea {
|
|
|
|
|
if e.IsDir() {
|
|
|
|
|
removeD2Files(tb, filepath.Join(dir, e.Name()))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ext := filepath.Ext(e.Name())
|
|
|
|
|
if ext == ".d2" {
|
|
|
|
|
assert.Remove(tb, filepath.Join(dir, e.Name()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-03 05:20:58 +00:00
|
|
|
|
|
|
|
|
func testdataIgnoreDiff(tb testing.TB, ext string, got []byte) {
|
|
|
|
|
_ = diff.Testdata(filepath.Join("testdata", tb.Name()), ext, got)
|
|
|
|
|
}
|