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-11-06 19:38:15 +00:00
"errors"
2023-10-31 22:52:48 +00:00
"fmt"
"net/http"
2023-03-03 04:29:56 +00:00
"os"
2023-03-03 03:41:24 +00:00
"path/filepath"
2023-10-31 22:52:48 +00:00
"regexp"
2023-06-01 05:25:43 +00:00
"strings"
2023-02-27 22:04:02 +00:00
"testing"
"time"
2023-03-03 03:41:24 +00:00
2025-02-04 14:24:55 +00:00
"github.com/coder/websocket"
2023-11-06 19:38:15 +00:00
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-06-06 23:59:15 +00:00
"oss.terrastruct.com/d2/d2cli"
"oss.terrastruct.com/d2/lib/pptx"
"oss.terrastruct.com/d2/lib/xgif"
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
2023-12-10 17:56:09 +00:00
serial bool
2023-03-03 05:28:33 +00:00
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-30 04:24:03 +00:00
{
name : "flags-panic" ,
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 , "layout" , "dagre" , "--dagre-nodesep" , "50" , "hello-world.d2" )
assert . ErrorString ( t , err , ` failed to wait xmain test: e2etests-cli/d2: failed to unmarshal input to graph: ` )
} ,
} ,
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 )
} ,
} ,
2023-07-27 06:10:05 +00:00
{
name : "layer-link" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "test.d2" , ` doh: { link: layers.test2 }; layers: { test2: @test2.d2 } ` )
writeFile ( t , dir , "test2.d2" , ` x: I'm a Mac { link: https://example.com } ` )
err := runTestMain ( t , ctx , dir , env , "test.d2" , "layer-link.svg" )
assert . Success ( t , err )
assert . TestdataDir ( t , filepath . Join ( dir , "layer-link" ) )
} ,
} ,
2024-09-11 17:05:04 +00:00
{
name : "sequence-layer" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , ` k; layers: { seq: @seq.d2 } ` )
writeFile ( t , dir , "seq.d2" , ` shape : sequence_diagram
a : me
b : github . com / terrastruct / d2
a - > b : issue about a bug
a . "some note about the bug"
if i ' m right : {
a <- b : fix
}
2024-09-19 14:57:33 +00:00
if i ' m wrong : {
a <- b : nah , intended
} ` )
err := runTestMain ( t , ctx , dir , env , "index.d2" )
assert . Success ( t , err )
assert . TestdataDir ( t , filepath . Join ( dir , "index" ) )
} ,
} ,
{
name : "sequence-spread-layer" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , ` k; layers: { seq: { ...@seq.d2} } ` )
writeFile ( t , dir , "seq.d2" , ` shape : sequence_diagram
a : me
b : github . com / terrastruct / d2
a - > b : issue about a bug
a . "some note about the bug"
if i ' m right : {
a <- b : fix
}
2024-09-11 17:05:04 +00:00
if i ' m wrong : {
a <- b : nah , intended
} ` )
err := runTestMain ( t , ctx , dir , env , "index.d2" )
assert . Success ( t , err )
assert . TestdataDir ( t , filepath . Join ( dir , "index" ) )
} ,
} ,
2023-06-01 05:25:43 +00:00
{
// Skip the empty base board so the animation doesn't show blank for 1400ms
name : "empty-base" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "empty-base.d2" , ` steps : {
1 : {
a - > b
}
2 : {
b - > d
c - > d
}
3 : {
d - > e
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--animate-interval=1400" , "empty-base.d2" )
assert . Success ( t , err )
svg := readFile ( t , dir , "empty-base.svg" )
assert . Testdata ( t , ".svg" , svg )
assert . Equal ( t , 3 , getNumBoards ( string ( svg ) ) )
} ,
} ,
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
}
2023-07-14 20:08:26 +00:00
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 )
} ,
} ,
{
name : "vars-animation" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "animation.d2" , ` vars : {
d2 - config : {
theme - id : 300
}
}
Chicken ' s plan : {
style . font - size : 35
near : top - center
shape : text
}
2023-03-23 20:37:28 +00:00
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-11-14 20:42:02 +00:00
{
name : "target-root" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-root.d2" , ` title : {
label : Main Plan
}
scenarios : {
b : {
title . label : Backup Plan
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--target" , "" , "target-root.d2" , "target-root.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "target-root.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "target-b" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-b.d2" , ` title : {
label : Main Plan
}
scenarios : {
b : {
title . label : Backup Plan
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--target" , "b" , "target-b.d2" , "target-b.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "target-b.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "target-nested-with-special-chars" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-nested-with-special-chars.d2" , ` layers : {
a : {
layers : {
"x / y . z" : {
mad
}
}
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--target" , ` layers.a.layers."x / y . z" ` , "target-nested-with-special-chars.d2" , "target-nested-with-special-chars.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "target-nested-with-special-chars.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "target-invalid" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-invalid.d2" , ` x -> y ` )
err := runTestMain ( t , ctx , dir , env , "--target" , "b" , "target-invalid.d2" , "target-invalid.svg" )
assert . ErrorString ( t , err , ` failed to wait xmain test: e2etests-cli/d2: failed to compile target-invalid.d2: render target "b" not found ` )
} ,
} ,
2023-12-07 21:21:38 +00:00
{
name : "target-nested-index" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-nested-index.d2" , ` a
layers : {
l1 : {
b
layers : {
index : {
c
layers : {
l3 : {
d
}
}
}
}
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--target" , ` l1.index.l3 ` , "target-nested-index.d2" , "target-nested-index.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "target-nested-index.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "target-nested-index2" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "target-nested-index2.d2" , ` a
layers : {
index : {
b
layers : {
nest1 : {
c
scenarios : {
nest2 : {
d
}
}
}
}
}
} ` )
err := runTestMain ( t , ctx , dir , env , "--target" , ` index.nest1.nest2 ` , "target-nested-index2.d2" , "target-nested-index2.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "target-nested-index2.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
2023-12-14 19:53:44 +00:00
{
name : "theme-override" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "theme-override.d2" , `
direction : right
vars : {
d2 - config : {
theme - overrides : {
B1 : "#2E7D32"
B2 : "#66BB6A"
B3 : "#A5D6A7"
B4 : "#C5E1A5"
B5 : "#E6EE9C"
B6 : "#FFF59D"
AA2 : "#0D47A1"
AA4 : "#42A5F5"
AA5 : "#90CAF9"
AB4 : "#F44336"
AB5 : "#FFCDD2"
N1 : "#2E2E2E"
N2 : "#2E2E2E"
N3 : "#595959"
N4 : "#858585"
N5 : "#B1B1B1"
N6 : "#DCDCDC"
N7 : "#DCDCDC"
}
dark - theme - overrides : {
B1 : "#2E7D32"
B2 : "#66BB6A"
B3 : "#A5D6A7"
B4 : "#C5E1A5"
B5 : "#E6EE9C"
B6 : "#FFF59D"
AA2 : "#0D47A1"
AA4 : "#42A5F5"
AA5 : "#90CAF9"
AB4 : "#F44336"
AB5 : "#FFCDD2"
N1 : "#2E2E2E"
N2 : "#2E2E2E"
N3 : "#595959"
N4 : "#858585"
N5 : "#B1B1B1"
N6 : "#DCDCDC"
N7 : "#DCDCDC"
}
}
}
logs : {
shape : page
style . multiple : true
}
user : User { shape : person }
network : Network {
tower : Cell Tower {
satellites : {
shape : stored_data
style . multiple : true
}
satellites - > transmitter
satellites - > transmitter
satellites - > transmitter
transmitter
}
processor : Data Processor {
storage : Storage {
shape : cylinder
style . multiple : true
}
}
portal : Online Portal {
UI
}
tower . transmitter - > processor : phone logs
}
server : API Server
user - > network . tower : Make call
network . processor - > server
network . processor - > server
network . processor - > server
server - > logs
server - > logs
server - > logs : persist
server - > network . portal . UI : display
user - > network . portal . UI : access {
style . stroke - dash : 3
}
costumes : {
shape : sql_table
id : int { constraint : primary_key }
silliness : int
monster : int
last_updated : timestamp
}
monsters : {
shape : sql_table
id : int { constraint : primary_key }
movie : string
weight : int
last_updated : timestamp
}
costumes . monster - > monsters . id
` )
err := runTestMain ( t , ctx , dir , env , "theme-override.d2" , "theme-override.svg" )
assert . Success ( t , err )
svg := readFile ( t , dir , "theme-override.svg" )
assert . Testdata ( t , ".svg" , svg )
// theme color is used in SVG
assert . NotEqual ( t , - 1 , strings . Index ( string ( svg ) , "#2E2E2E" ) )
} ,
} ,
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-14 22:44:01 +00:00
{
name : "export_ppt" ,
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 , "x.d2" , "x.ppt" )
assert . ErrorString ( t , err , ` failed to wait xmain test: e2etests-cli/d2: bad usage: D2 does not support ppt exports, did you mean "pptx"? ` )
} ,
} ,
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 )
} ,
} ,
2024-09-16 15:11:20 +00:00
{
name : "pptx-theme-overrides" ,
skipCI : true ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "in.d2" , ` vars : {
d2 - config : {
theme - overrides : {
# All red
N1 : "#ff0000"
B1 : "#ff0000"
B2 : "#ff0000"
AA2 : "#ff0000"
N2 : "#ff0000"
N6 : "#ff0000"
B4 : "#ff0000"
B5 : "#ff0000"
B3 : "#ff0000"
N4 : "#ff0000"
N5 : "#ff0000"
AA4 : "#ff0000"
AB4 : "#ff0000"
B6 : "#ff0000"
N7 : "#ff0000"
AA5 : "#ff0000"
AB5 : "#ff0000"
}
}
}
a - > z
a . b . c . d
` )
err := runTestMain ( t , ctx , dir , env , "in.d2" , "all_red.pptx" )
assert . Success ( t , err )
pptx := readFile ( t , dir , "all_red.pptx" )
testdataIgnoreDiff ( t , ".pptx" , pptx )
} ,
} ,
2023-04-28 01:48:48 +00:00
{
name : "one-layer-gif" ,
skipCI : true ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "in.d2" , ` x ` )
err := runTestMain ( t , ctx , dir , env , "--animate-interval=10" , "in.d2" , "out.gif" )
assert . Success ( t , err )
gifBytes := readFile ( t , dir , "out.gif" )
err = xgif . Validate ( gifBytes , 1 , 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" ) )
2023-06-06 20:33:56 +00:00
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "import" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` x: @x; y: @y; ...@p ` )
writeFile ( t , dir , "x.d2" , ` shape: circle ` )
writeFile ( t , dir , "y.d2" , ` shape: square ` )
writeFile ( t , dir , "p.d2" , ` x -> y ` )
err := runTestMain ( t , ctx , dir , env , filepath . Join ( dir , "hello-world.d2" ) )
2023-06-06 22:09:27 +00:00
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
2023-07-14 20:08:26 +00:00
{
name : "import_vars" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` vars: { d2-config: @config }; x -> y ` )
writeFile ( t , dir , "config.d2" , ` theme-id: 200 ` )
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-06-06 22:09:27 +00:00
{
name : "import_spread_nested" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` ...@x.y ` )
writeFile ( t , dir , "x.d2" , ` y: { jon; jan } ` )
err := runTestMain ( t , ctx , dir , env , filepath . Join ( dir , "hello-world.d2" ) )
2024-08-24 17:09:04 +00:00
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "import_icon_relative" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` ...@asdf/x ` )
2024-08-25 02:02:52 +00:00
writeFile ( t , filepath . Join ( dir , "asdf" ) , "x.d2" , ` y: { icon: ./blah.svg }; z: { icon: ../root.svg } ` )
2024-08-24 17:09:04 +00:00
writeFile ( t , filepath . Join ( dir , "asdf" ) , "blah.svg" , ` ` )
2024-08-25 02:02:52 +00:00
writeFile ( t , dir , "root.svg" , ` ` )
2024-08-24 17:09:04 +00:00
err := runTestMain ( t , ctx , dir , env , filepath . Join ( dir , "hello-world.d2" ) )
2023-06-06 23:59:15 +00:00
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
{
name : "chain_import" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` ...@x ` )
writeFile ( t , dir , "x.d2" , ` ...@y ` )
writeFile ( t , dir , "y.d2" , ` meow ` )
err := runTestMain ( t , ctx , dir , env , filepath . Join ( dir , "hello-world.d2" ) )
2023-06-07 00:24:31 +00:00
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
2024-11-11 01:55:20 +00:00
{
name : "chain_icon_import" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` ... @ y
hello . class : Ecs ` )
writeFile ( t , dir , "y.d2" , `
... @ x
classes : {
Ecs : {
shape : "circle"
icon : $ { icons . ecs }
}
}
` )
writeFile ( t , dir , "x.d2" , `
vars : {
icons : {
ecs : "https://icons.terrastruct.com/aws%2FCompute%2FAmazon-Elastic-Container-Service.svg"
}
}
` )
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-06-07 00:24:31 +00:00
{
name : "board_import" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` x.link: layers.x; layers: { x: @x } ` )
writeFile ( t , dir , "x.d2" , ` y.link: layers.y; layers: { y: @y } ` )
writeFile ( t , dir , "y.d2" , ` meow ` )
err := runTestMain ( t , ctx , dir , env , filepath . Join ( dir , "hello-world.d2" ) )
2023-03-05 21:45:20 +00:00
assert . Success ( t , err )
2023-06-07 23:23:20 +00:00
t . Run ( "hello-world-x-y" , func ( t * testing . T ) {
svg := readFile ( t , dir , "hello-world/x/y.svg" )
assert . Testdata ( t , ".svg" , svg )
} )
t . Run ( "hello-world-x" , func ( t * testing . T ) {
svg := readFile ( t , dir , "hello-world/x/index.svg" )
assert . Testdata ( t , ".svg" , svg )
} )
t . Run ( "hello-world" , func ( t * testing . T ) {
svg := readFile ( t , dir , "hello-world/index.svg" )
assert . Testdata ( t , ".svg" , svg )
} )
2023-03-05 21:45:20 +00:00
} ,
} ,
2023-07-14 20:08:26 +00:00
{
name : "vars-config" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` vars : {
d2 - config : {
sketch : true
layout - engine : elk
}
}
x - > y - > a . dream
it - > was - > all - > a . dream
i used to read
` )
env . Setenv ( "D2_THEME" , "1" )
err := runTestMain ( t , ctx , dir , env , "--pad=10" , "hello-world.d2" )
assert . Success ( t , err )
svg := readFile ( t , dir , "hello-world.svg" )
assert . Testdata ( t , ".svg" , svg )
} ,
} ,
2024-04-09 16:51:14 +00:00
{
name : "theme-pdf" ,
skipCI : true ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "in.d2" , ` x -> y ` )
err := runTestMain ( t , ctx , dir , env , "--theme=5" , "in.d2" , "out.pdf" )
assert . Success ( t , err )
pdf := readFile ( t , dir , "out.pdf" )
testdataIgnoreDiff ( t , ".pdf" , pdf )
} ,
} ,
2023-12-07 21:31:05 +00:00
{
name : "renamed-board" ,
skipCI : true ,
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
}
a . link : "https://www.google.com/maps/place/Smoked+Out+BBQ/@37.3848007,-121.9513887,17z/data=!3m1!4b1!4m6!3m5!1s0x808fc9182ad4d38d:0x8e2f39c3e927b296!8m2!3d37.3848007!4d-121.9492!16s%2Fg%2F11gjt85zvf"
label : blah
layers : {
cat : {
label : dog
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 )
} ,
} ,
{
name : "no-nav-pdf" ,
skipCI : true ,
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
}
a . link : "https://www.google.com/maps/place/Smoked+Out+BBQ/@37.3848007,-121.9513887,17z/data=!3m1!4b1!4m6!3m5!1s0x808fc9182ad4d38d:0x8e2f39c3e927b296!8m2!3d37.3848007!4d-121.9492!16s%2Fg%2F11gjt85zvf"
label : ""
layers : {
cat : {
label : dog
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 )
} ,
} ,
{
name : "no-nav-pptx" ,
skipCI : true ,
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
}
a . link : "https://www.google.com/maps/place/Smoked+Out+BBQ/@37.3848007,-121.9513887,17z/data=!3m1!4b1!4m6!3m5!1s0x808fc9182ad4d38d:0x8e2f39c3e927b296!8m2!3d37.3848007!4d-121.9492!16s%2Fg%2F11gjt85zvf"
label : ""
layers : {
cat : {
label : dog
home : {
link : _
}
the cat - > meow : goes
}
}
` )
err := runTestMain ( t , ctx , dir , env , "in.d2" , "out.pptx" )
assert . Success ( t , err )
file := readFile ( t , dir , "out.pptx" )
// err = pptx.Validate(file, 2)
assert . Success ( t , err )
testdataIgnoreDiff ( t , ".pptx" , file )
} ,
} ,
2025-01-24 05:27:34 +00:00
{
name : "no_xml_tag" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "test.d2" , ` x -> y ` )
err := runTestMain ( t , ctx , dir , env , "--no-xml-tag" , "test.d2" , "no-xml.svg" )
assert . Success ( t , err )
noXMLSvg := readFile ( t , dir , "no-xml.svg" )
assert . False ( t , strings . Contains ( string ( noXMLSvg ) , "<?xml" ) )
writeFile ( t , dir , "test.d2" , ` x -> y ` )
err = runTestMain ( t , ctx , dir , env , "test.d2" , "with-xml.svg" )
assert . Success ( t , err )
withXMLSvg := readFile ( t , dir , "with-xml.svg" )
assert . True ( t , strings . Contains ( string ( withXMLSvg ) , "<?xml" ) )
env . Setenv ( "D2_NO_XML_TAG" , "1" )
writeFile ( t , dir , "test.d2" , ` x -> y ` )
err = runTestMain ( t , ctx , dir , env , "test.d2" , "no-xml-env.svg" )
assert . Success ( t , err )
noXMLEnvSvg := readFile ( t , dir , "no-xml-env.svg" )
assert . False ( t , strings . Contains ( string ( noXMLEnvSvg ) , "<?xml" ) )
} ,
} ,
2023-08-02 18:38:53 +00:00
{
name : "basic-fmt" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "hello-world.d2" , ` x ---> y ` )
err := runTestMainPersist ( t , ctx , dir , env , "fmt" , "hello-world.d2" )
assert . Success ( t , err )
got := readFile ( t , dir , "hello-world.d2" )
assert . Equal ( t , "x -> y\n" , string ( got ) )
} ,
} ,
2023-08-02 01:31:38 +00:00
{
name : "fmt-multiple-files" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "foo.d2" , ` a ---> b ` )
writeFile ( t , dir , "bar.d2" , ` x ---> y ` )
err := runTestMainPersist ( t , ctx , dir , env , "fmt" , "foo.d2" , "bar.d2" )
assert . Success ( t , err )
gotFoo := readFile ( t , dir , "foo.d2" )
gotBar := readFile ( t , dir , "bar.d2" )
assert . Equal ( t , "a -> b\n" , string ( gotFoo ) )
assert . Equal ( t , "x -> y\n" , string ( gotBar ) )
} ,
} ,
2024-12-16 23:29:21 +00:00
{
2024-12-17 17:45:50 +00:00
name : "fmt-check-unformatted" ,
2024-12-16 23:29:21 +00:00
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "foo.d2" , ` a ---> b ` )
writeFile ( t , dir , "bar.d2" , ` x ---> y ` )
2024-12-17 17:45:50 +00:00
writeFile ( t , dir , "baz.d2" , "a -> z\n" )
err := runTestMainPersist ( t , ctx , dir , env , "fmt" , "--check" , "foo.d2" , "bar.d2" , "baz.d2" )
2024-12-16 23:29:21 +00:00
assert . ErrorString ( t , err , "failed to wait xmain test: e2etests-cli/d2: failed to fmt: exiting with code 1: found 2 unformatted files. Run d2 fmt to fix." )
gotFoo := readFile ( t , dir , "foo.d2" )
gotBar := readFile ( t , dir , "bar.d2" )
assert . Equal ( t , "a ---> b" , string ( gotFoo ) )
assert . Equal ( t , "x ---> y" , string ( gotBar ) )
} ,
} ,
2024-12-17 17:45:50 +00:00
{
name : "fmt-check-formatted" ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "foo.d2" , "a -> b\n" )
writeFile ( t , dir , "bar.d2" , "x -> y\n" )
err := runTestMainPersist ( t , ctx , dir , env , "fmt" , "--check" , "foo.d2" , "bar.d2" )
assert . Success ( t , err )
} ,
} ,
2023-10-31 22:52:48 +00:00
{
2023-12-10 17:56:09 +00:00
name : "watch-regular" ,
serial : true ,
2023-10-31 22:52:48 +00:00
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , `
a - > b
2023-11-06 19:38:15 +00:00
b . link : layers . cream
2023-10-31 22:52:48 +00:00
layers : {
cream : {
c - > b
}
} ` )
2023-12-18 22:46:56 +00:00
stderr := & stderrWrapper { }
2023-10-31 22:52:48 +00:00
tms := testMain ( dir , env , "--watch" , "--browser=0" , "index.d2" )
tms . Stderr = stderr
tms . Start ( t , ctx )
2023-11-06 19:38:15 +00:00
defer func ( ) {
// Manually close, since watcher is daemon
err := tms . Signal ( ctx , os . Interrupt )
assert . Success ( t , err )
} ( )
2023-10-31 22:52:48 +00:00
2023-11-06 19:38:15 +00:00
// Wait for watch server to spin up and listen
urlRE := regexp . MustCompile ( ` 127.0.0.1:([0-9]+) ` )
2023-11-09 02:34:01 +00:00
watchURL , err := waitLogs ( ctx , stderr , urlRE )
assert . Success ( t , err )
2023-11-06 19:38:15 +00:00
stderr . Reset ( )
// Start a client
c , _ , err := websocket . Dial ( ctx , fmt . Sprintf ( "ws://%s/watch" , watchURL ) , nil )
assert . Success ( t , err )
defer c . CloseNow ( )
// Get the link
_ , msg , err := c . Read ( ctx )
assert . Success ( t , err )
aRE := regexp . MustCompile ( ` href=\\"([^\"]*)\\" ` )
match := aRE . FindSubmatch ( msg )
assert . Equal ( t , 2 , len ( match ) )
linkedPath := match [ 1 ]
err = getWatchPage ( ctx , t , fmt . Sprintf ( "http://%s/%s" , watchURL , linkedPath ) )
assert . Success ( t , err )
successRE := regexp . MustCompile ( ` broadcasting update to 1 client ` )
2023-11-09 02:34:01 +00:00
_ , err = waitLogs ( ctx , stderr , successRE )
assert . Success ( t , err )
2023-11-06 19:38:15 +00:00
} ,
} ,
{
2023-12-10 17:56:09 +00:00
name : "watch-ok-link" ,
serial : true ,
2023-11-06 19:38:15 +00:00
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , `
a - > b
2024-09-25 15:53:46 +00:00
b . link : layers . cream
2023-10-31 22:52:48 +00:00
2023-11-06 19:38:15 +00:00
layers : {
cream : {
c - > b
}
} ` )
2023-12-18 22:46:56 +00:00
stderr := & stderrWrapper { }
2023-11-06 19:38:15 +00:00
tms := testMain ( dir , env , "--watch" , "--browser=0" , "index.d2" )
tms . Stderr = stderr
tms . Start ( t , ctx )
defer func ( ) {
// Manually close, since watcher is daemon
err := tms . Signal ( ctx , os . Interrupt )
assert . Success ( t , err )
} ( )
// Wait for watch server to spin up and listen
2023-10-31 22:52:48 +00:00
urlRE := regexp . MustCompile ( ` 127.0.0.1:([0-9]+) ` )
2023-11-09 02:34:01 +00:00
watchURL , err := waitLogs ( ctx , stderr , urlRE )
assert . Success ( t , err )
2023-11-06 19:38:15 +00:00
stderr . Reset ( )
// Start a client
c , _ , err := websocket . Dial ( ctx , fmt . Sprintf ( "ws://%s/watch" , watchURL ) , nil )
assert . Success ( t , err )
defer c . CloseNow ( )
// Get the link
_ , msg , err := c . Read ( ctx )
assert . Success ( t , err )
aRE := regexp . MustCompile ( ` href=\\"([^\"]*)\\" ` )
match := aRE . FindSubmatch ( msg )
assert . Equal ( t , 2 , len ( match ) )
linkedPath := match [ 1 ]
err = getWatchPage ( ctx , t , fmt . Sprintf ( "http://%s/%s" , watchURL , linkedPath ) )
assert . Success ( t , err )
successRE := regexp . MustCompile ( ` broadcasting update to 1 client ` )
2023-11-09 02:34:01 +00:00
_ , err = waitLogs ( ctx , stderr , successRE )
assert . Success ( t , err )
2023-11-06 19:38:15 +00:00
} ,
} ,
2024-08-06 00:40:00 +00:00
{
name : "watch-underscore-link" ,
serial : true ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , `
bobby
layers : {
cream : {
back . link : _
}
} ` )
stderr := & stderrWrapper { }
tms := testMain ( dir , env , "--watch" , "--browser=0" , "index.d2" )
tms . Stderr = stderr
tms . Start ( t , ctx )
defer func ( ) {
// Manually close, since watcher is daemon
err := tms . Signal ( ctx , os . Interrupt )
assert . Success ( t , err )
} ( )
// Wait for watch server to spin up and listen
urlRE := regexp . MustCompile ( ` 127.0.0.1:([0-9]+) ` )
watchURL , err := waitLogs ( ctx , stderr , urlRE )
assert . Success ( t , err )
stderr . Reset ( )
// Start a client
c , _ , err := websocket . Dial ( ctx , fmt . Sprintf ( "ws://%s/watch" , watchURL ) , nil )
assert . Success ( t , err )
defer c . CloseNow ( )
_ , _ , err = c . Read ( ctx )
assert . Success ( t , err )
err = getWatchPage ( ctx , t , fmt . Sprintf ( "http://%s/%s" , watchURL , "cream" ) )
assert . Success ( t , err )
// Get the link
_ , msg , err := c . Read ( ctx )
aRE := regexp . MustCompile ( ` href=\\"([^\"]*)\\" ` )
match := aRE . FindSubmatch ( msg )
assert . Equal ( t , 2 , len ( match ) )
2024-08-25 04:15:01 +00:00
link := string ( match [ 1 ] )
err = getWatchPage ( ctx , t , fmt . Sprintf ( "http://%s/%s" , watchURL , link ) )
assert . Success ( t , err )
_ , _ , err = c . Read ( ctx )
assert . Success ( t , err )
successRE := regexp . MustCompile ( ` broadcasting update to 1 client ` )
_ , err = waitLogs ( ctx , stderr , successRE )
assert . Success ( t , err )
} ,
} ,
{
name : "watch-nested-layer-link" ,
serial : true ,
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "index.d2" , `
a : {
link : layers . b
}
layers : {
b : {
hi
layers : {
hey : {
hey
}
}
}
} ` )
stderr := & stderrWrapper { }
tms := testMain ( dir , env , "--watch" , "--browser=0" , "index.d2" )
tms . Stderr = stderr
tms . Start ( t , ctx )
defer func ( ) {
// Manually close, since watcher is daemon
err := tms . Signal ( ctx , os . Interrupt )
assert . Success ( t , err )
} ( )
// Wait for watch server to spin up and listen
urlRE := regexp . MustCompile ( ` 127.0.0.1:([0-9]+) ` )
watchURL , err := waitLogs ( ctx , stderr , urlRE )
assert . Success ( t , err )
stderr . Reset ( )
// Start a client
c , _ , err := websocket . Dial ( ctx , fmt . Sprintf ( "ws://%s/watch" , watchURL ) , nil )
assert . Success ( t , err )
defer c . CloseNow ( )
// Get the link
_ , msg , err := c . Read ( ctx )
aRE := regexp . MustCompile ( ` href=\\"([^\"]*)\\" ` )
match := aRE . FindSubmatch ( msg )
assert . Equal ( t , 2 , len ( match ) )
link := string ( match [ 1 ] )
err = getWatchPage ( ctx , t , fmt . Sprintf ( "http://%s/%s" , watchURL , link ) )
assert . Success ( t , err )
_ , _ , err = c . Read ( ctx )
assert . Success ( t , err )
2024-08-06 00:40:00 +00:00
successRE := regexp . MustCompile ( ` broadcasting update to 1 client ` )
_ , err = waitLogs ( ctx , stderr , successRE )
assert . Success ( t , err )
} ,
} ,
2023-11-09 02:34:01 +00:00
{
2023-12-10 17:56:09 +00:00
name : "watch-imported-file" ,
serial : true ,
2023-11-09 02:34:01 +00:00
run : func ( t * testing . T , ctx context . Context , dir string , env * xos . Env ) {
writeFile ( t , dir , "a.d2" , `
... @ b
` )
writeFile ( t , dir , "b.d2" , `
x
` )
2023-12-18 22:46:56 +00:00
stderr := & stderrWrapper { }
2023-11-09 02:34:01 +00:00
tms := testMain ( dir , env , "--watch" , "--browser=0" , "a.d2" )
tms . Stderr = stderr
tms . Start ( t , ctx )
defer func ( ) {
err := tms . Signal ( ctx , os . Interrupt )
assert . Success ( t , err )
} ( )
// Wait for first compilation to finish
doneRE := regexp . MustCompile ( ` successfully compiled a.d2 ` )
_ , err := waitLogs ( ctx , stderr , doneRE )
assert . Success ( t , err )
stderr . Reset ( )
// Test that writing an imported file will cause recompilation
writeFile ( t , dir , "b.d2" , `
x - > y
` )
bRE := regexp . MustCompile ( ` detected change in b.d2 ` )
_ , err = waitLogs ( ctx , stderr , bRE )
assert . Success ( t , err )
stderr . Reset ( )
// Test burst of both files changing
writeFile ( t , dir , "a.d2" , `
... @ b
hey
` )
writeFile ( t , dir , "b.d2" , `
x
hi
` )
bothRE := regexp . MustCompile ( ` detected change in a.d2, b.d2 ` )
_ , err = waitLogs ( ctx , stderr , bothRE )
assert . Success ( t , err )
// Wait for that compilation to fully finish
_ , err = waitLogs ( ctx , stderr , doneRE )
assert . Success ( t , err )
stderr . Reset ( )
// Update the main file to no longer have that dependency
writeFile ( t , dir , "a.d2" , `
a
` )
_ , err = waitLogs ( ctx , stderr , doneRE )
assert . Success ( t , err )
stderr . Reset ( )
// Change b
writeFile ( t , dir , "b.d2" , `
y
` )
// Change a to retrigger compilation
// The test works by seeing that the report only says "a" changed, otherwise testing for omission of compilation from "b" would require waiting
writeFile ( t , dir , "a.d2" , `
c
` )
_ , err = waitLogs ( ctx , stderr , doneRE )
assert . Success ( t , err )
2023-10-31 22:52:48 +00:00
} ,
} ,
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 ) {
2023-12-10 17:56:09 +00:00
if ! tc . serial {
t . Parallel ( )
}
2023-02-27 22:04:02 +00:00
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 {
2023-08-02 18:38:53 +00:00
err := runTestMainPersist ( tb , ctx , dir , env , args ... )
if err != nil {
return err
}
removeD2Files ( tb , dir )
return nil
}
func runTestMainPersist ( tb testing . TB , ctx context . Context , dir string , env * xos . Env , args ... string ) error {
2023-03-03 03:41:24 +00:00
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
}
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 )
}
2023-06-01 05:25:43 +00:00
// getNumBoards gets the number of boards in an SVG file through a non-robust pattern search
// If the renderer changes, this must change
func getNumBoards ( svg string ) int {
2025-01-27 01:39:07 +00:00
re := regexp . MustCompile ( ` class="d2-\d+ ` )
matches := re . FindAllString ( svg , - 1 )
return len ( matches )
2023-06-01 05:25:43 +00:00
}
2023-11-06 19:38:15 +00:00
2023-11-09 02:34:01 +00:00
var errRE = regexp . MustCompile ( ` err: ` )
2023-12-18 22:46:56 +00:00
func waitLogs ( ctx context . Context , stream * stderrWrapper , pattern * regexp . Regexp ) ( string , error ) {
2023-11-06 19:38:15 +00:00
ticker := time . NewTicker ( 10 * time . Millisecond )
2023-11-07 18:43:19 +00:00
defer ticker . Stop ( )
2023-11-06 19:38:15 +00:00
var match string
2023-12-12 00:14:43 +00:00
for i := 0 ; i < 1000 && match == "" ; i ++ {
2023-11-06 19:38:15 +00:00
select {
case <- ticker . C :
2023-12-18 22:46:56 +00:00
out := stream . Read ( )
2023-11-06 19:38:15 +00:00
match = pattern . FindString ( out )
2023-11-09 02:34:01 +00:00
errMatch := errRE . FindString ( out )
if errMatch != "" {
2023-12-18 22:46:56 +00:00
return "" , errors . New ( out )
2023-11-09 02:34:01 +00:00
}
2023-11-06 19:38:15 +00:00
case <- ctx . Done ( ) :
ticker . Stop ( )
2023-12-18 22:46:56 +00:00
return "" , fmt . Errorf ( "could not match pattern in log. logs: %s" , stream . Read ( ) )
2023-11-06 19:38:15 +00:00
}
}
2023-11-09 02:34:01 +00:00
if match == "" {
2023-12-18 22:46:56 +00:00
return "" , errors . New ( stream . Read ( ) )
2023-11-09 02:34:01 +00:00
}
2023-11-06 19:38:15 +00:00
2023-11-09 02:34:01 +00:00
return match , nil
2023-11-06 19:38:15 +00:00
}
func getWatchPage ( ctx context . Context , t * testing . T , page string ) error {
req , err := http . NewRequestWithContext ( ctx , "GET" , page , nil )
if err != nil {
return err
}
var httpClient = & http . Client { }
resp , err := httpClient . Do ( req )
if err != nil {
return err
}
defer resp . Body . Close ( )
if resp . StatusCode != 200 {
return fmt . Errorf ( "status code: %d" , resp . StatusCode )
}
return nil
}