...

Source file src/oss.terrastruct.com/d2/d2chaos/d2chaos_test.go

Documentation: oss.terrastruct.com/d2/d2chaos

     1  package d2chaos_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime/debug"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  
    16  	"oss.terrastruct.com/d2/d2chaos"
    17  	"oss.terrastruct.com/d2/d2compiler"
    18  	"oss.terrastruct.com/d2/d2exporter"
    19  	"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
    20  	"oss.terrastruct.com/d2/lib/log"
    21  	"oss.terrastruct.com/d2/lib/textmeasure"
    22  )
    23  
    24  // usage: D2_CHAOS_MAXI=100 D2_CHAOS_N=100 ./ci/test.sh ./d2chaos
    25  //
    26  // D2_CHAOS_MAXI controls the number of iterations that the dsl generator
    27  // should go through to generate each input D2. It's roughly equivalent to
    28  // the complexity level of each input D2.
    29  //
    30  // D2_CHAOS_N controls the number of D2 texts to generate and run the full
    31  // D2 flow on.
    32  //
    33  // All generated texts are stored in ./out/<n>.d2 and also ./out/<n>.d2.goenc
    34  // The goenc version is the text encoded as a Go string. It lets you replay
    35  // a test by adding it to testPinned below as you can just copy paste the go
    36  // string in.
    37  //
    38  // If D2Chaos fails on CI and you need to investigate the input text that caused the
    39  // failure, all generated texts will be available in the d2chaos-test and d2chaos-race
    40  // github actions artifacts.
    41  func TestD2Chaos(t *testing.T) {
    42  	t.Parallel()
    43  
    44  	const outDir = "out"
    45  	err := os.MkdirAll(outDir, 0755)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	t.Logf("writing generated files to %s", outDir)
    50  
    51  	t.Run("pinned", func(t *testing.T) {
    52  		testPinned(t, outDir)
    53  	})
    54  
    55  	n := 1
    56  	if os.Getenv("D2_CHAOS_N") != "" {
    57  		envn, err := strconv.Atoi(os.Getenv("D2_CHAOS_N"))
    58  		if err != nil {
    59  			t.Errorf("failed to atoi $D2_CHAOS_N: %v", err)
    60  		} else {
    61  			n = envn
    62  		}
    63  	}
    64  
    65  	maxi := 10
    66  	if os.Getenv("D2_CHAOS_MAXI") != "" {
    67  		envMaxi, err := strconv.Atoi(os.Getenv("D2_CHAOS_MAXI"))
    68  		if err != nil {
    69  			t.Errorf("failed to atoi $D2_CHAOS_MAXI: %v", err)
    70  		} else {
    71  			maxi = envMaxi
    72  		}
    73  	}
    74  
    75  	for i := 0; i < n; i++ {
    76  		i := i
    77  		t.Run("", func(t *testing.T) {
    78  			t.Parallel()
    79  
    80  			text, err := d2chaos.GenDSL(maxi)
    81  			if err != nil {
    82  				t.Fatal(err)
    83  			}
    84  
    85  			textPath := filepath.Join(outDir, fmt.Sprintf("%d.d2", i))
    86  			test(t, textPath, text)
    87  		})
    88  	}
    89  }
    90  
    91  func test(t *testing.T, textPath, text string) {
    92  	t.Logf("writing d2 to %v (%d bytes)", textPath, len(text))
    93  	err := ioutil.WriteFile(textPath, []byte(text), 0644)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	goencText := fmt.Sprintf("%#v", text)
    99  	t.Logf("writing d2.goenc to %v (%d bytes)", textPath+".goenc", len(goencText))
   100  	err = ioutil.WriteFile(textPath+".goenc", []byte(goencText), 0644)
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  
   105  	g, _, err := d2compiler.Compile("", strings.NewReader(text), nil)
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	t.Run("layout", func(t *testing.T) {
   111  		defer func() {
   112  			r := recover()
   113  			if r != nil {
   114  				t.Errorf("recovered layout engine panic: %#v\n%s", r, debug.Stack())
   115  			}
   116  		}()
   117  
   118  		ctx := log.WithTB(context.Background(), t, nil)
   119  
   120  		ruler, err := textmeasure.NewRuler()
   121  		assert.Nil(t, err)
   122  
   123  		err = g.SetDimensions(nil, ruler, nil)
   124  		assert.Nil(t, err)
   125  
   126  		err = d2dagrelayout.DefaultLayout(ctx, g)
   127  		if err != nil {
   128  			t.Fatal(err)
   129  		}
   130  
   131  		_, err = d2exporter.Export(ctx, g, nil)
   132  		if err != nil {
   133  			t.Fatal(err)
   134  		}
   135  	})
   136  }
   137  
   138  func testPinned(t *testing.T, outDir string) {
   139  	t.Parallel()
   140  
   141  	outDir = filepath.Join(outDir, t.Name())
   142  	err := os.MkdirAll(outDir, 0755)
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  	t.Logf("writing generated files to %v", outDir)
   147  
   148  	testCases := []struct {
   149  		name string
   150  		text string
   151  	}{
   152  		{
   153  			name: "internal class edge",
   154  			text: "a: {\n shape: class\n  b -> c\n }",
   155  		},
   156  		{
   157  			name: "table edge order",
   158  			text: "B: {shape: sql_table}\n B.C -- D\n B.C -- D\n D -- B.C\n B -- D\n A -- D",
   159  		},
   160  		{
   161  			name: "child to container edge",
   162  			text: "a.b -> a",
   163  		},
   164  		{
   165  			name: "sequence",
   166  			text: "a: {shape: step}\nb: {shape: step}\na -> b\n",
   167  		},
   168  		{
   169  			name: "orientation",
   170  			text: "a: {\n  b\n  c\n }\n  a <- a.c\n  a.b -> a\n",
   171  		},
   172  		{
   173  			name: "cannot create edge between boards",
   174  			text: `"" <-> ""`,
   175  		},
   176  	}
   177  
   178  	for _, tc := range testCases {
   179  		tc := tc
   180  		t.Run(tc.name, func(t *testing.T) {
   181  			t.Parallel()
   182  
   183  			textPath := filepath.Join(outDir, fmt.Sprintf("%s.d2", tc.name))
   184  			test(t, textPath, tc.text)
   185  		})
   186  	}
   187  }
   188  

View as plain text