github.com/nathanstitt/genqlient@v0.3.1-0.20211028004951-a2bda3c41ab8/generate/generate_test.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/Khan/genqlient/internal/testutil"
    13  	"gopkg.in/yaml.v2"
    14  )
    15  
    16  const (
    17  	dataDir   = "testdata/queries"
    18  	errorsDir = "testdata/errors"
    19  )
    20  
    21  // buildGoFile returns an error if the given Go code is not valid.
    22  //
    23  // namePrefix is used for the temp-file, and is just for debugging.
    24  func buildGoFile(namePrefix string, content []byte) error {
    25  	// We need to put this within the current module, rather than in
    26  	// /tmp, so that it can access internal/testutil.
    27  	f, err := ioutil.TempFile("./testdata/tmp", namePrefix+"_*.go")
    28  	if err != nil {
    29  		return err
    30  	}
    31  	defer func() {
    32  		f.Close()
    33  		os.Remove(f.Name())
    34  	}()
    35  
    36  	_, err = f.Write(content)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	cmd := exec.Command("go", "build", f.Name())
    42  	cmd.Stdout = os.Stdout
    43  	cmd.Stderr = os.Stderr
    44  	err = cmd.Run()
    45  	if err != nil {
    46  		return fmt.Errorf("generated code does not compile: %w", err)
    47  	}
    48  	return nil
    49  }
    50  
    51  // TestGenerate is a snapshot-based test of code-generation.
    52  //
    53  // This file just has the test runner; the actual data is all in
    54  // testdata/queries.  Specifically, the schema used for all the queries is in
    55  // schema.graphql; the queries themselves are in TestName.graphql.  The test
    56  // asserts that running genqlient on that query produces the generated code in
    57  // the snapshot-file TestName.graphql.go.
    58  //
    59  // To update the snapshots (if the code-generator has changed), run the test
    60  // with `UPDATE_SNAPSHOTS=1`; it will fail the tests and print any diffs, but
    61  // update the snapshots.  Make sure to check that the output is sensible; the
    62  // snapshots don't even get compiled!
    63  func TestGenerate(t *testing.T) {
    64  	files, err := ioutil.ReadDir(dataDir)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	for _, file := range files {
    70  		sourceFilename := file.Name()
    71  		if sourceFilename == "schema.graphql" || !strings.HasSuffix(sourceFilename, ".graphql") {
    72  			continue
    73  		}
    74  		goFilename := sourceFilename + ".go"
    75  		queriesFilename := sourceFilename + ".json"
    76  
    77  		t.Run(sourceFilename, func(t *testing.T) {
    78  			generated, err := Generate(&Config{
    79  				Schema:           []string{filepath.Join(dataDir, "schema.graphql")},
    80  				Operations:       []string{filepath.Join(dataDir, sourceFilename)},
    81  				Package:          "test",
    82  				Generated:        goFilename,
    83  				ExportOperations: queriesFilename,
    84  				ContextType:      "-",
    85  				Bindings: map[string]*TypeBinding{
    86  					"ID":       {Type: "github.com/Khan/genqlient/internal/testutil.ID"},
    87  					"DateTime": {Type: "time.Time"},
    88  					"Date": {
    89  						Type:        "time.Time",
    90  						Marshaler:   "github.com/Khan/genqlient/internal/testutil.MarshalDate",
    91  						Unmarshaler: "github.com/Khan/genqlient/internal/testutil.UnmarshalDate",
    92  					},
    93  					"Junk":        {Type: "interface{}"},
    94  					"ComplexJunk": {Type: "[]map[string]*[]*map[string]interface{}"},
    95  					"Pokemon": {
    96  						Type:              "github.com/Khan/genqlient/internal/testutil.Pokemon",
    97  						ExpectExactFields: "{ species level }",
    98  					},
    99  					"PokemonInput": {Type: "github.com/Khan/genqlient/internal/testutil.Pokemon"},
   100  				},
   101  				AllowBrokenFeatures: true,
   102  			})
   103  			if err != nil {
   104  				t.Fatal(err)
   105  			}
   106  
   107  			for filename, content := range generated {
   108  				t.Run(filename, func(t *testing.T) {
   109  					testutil.Cupaloy.SnapshotT(t, string(content))
   110  				})
   111  			}
   112  
   113  			t.Run("Build", func(t *testing.T) {
   114  				if testing.Short() {
   115  					t.Skip("skipping build due to -short")
   116  				}
   117  
   118  				err := buildGoFile(sourceFilename, generated[goFilename])
   119  				if err != nil {
   120  					t.Error(err)
   121  				}
   122  			})
   123  		})
   124  	}
   125  }
   126  
   127  func defaultConfig(t *testing.T) *Config {
   128  	// Parse the config that `genqlient --init` generates, to make sure that
   129  	// works.
   130  	var config Config
   131  	b, err := ioutil.ReadFile("default_genqlient.yaml")
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	err = yaml.UnmarshalStrict(b, &config)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	return &config
   141  }
   142  
   143  // TestGenerateWithConfig tests several configuration options that affect
   144  // generated code but don't require particular query structures to test.
   145  //
   146  // It runs a simple query from TestGenerate with several different genqlient
   147  // configurations.  It uses snapshots, just like TestGenerate.
   148  func TestGenerateWithConfig(t *testing.T) {
   149  	tests := []struct {
   150  		name    string
   151  		baseDir string  // relative to dataDir
   152  		config  *Config // omits Schema and Operations, set below.
   153  	}{
   154  		{"DefaultConfig", "", defaultConfig(t)},
   155  		{"Subpackage", "", &Config{
   156  			Generated: "mypkg/myfile.go",
   157  		}},
   158  		{"SubpackageConfig", "mypkg", &Config{
   159  			Generated: "myfile.go", // (relative to genqlient.yaml)
   160  		}},
   161  		{"PackageName", "", &Config{
   162  			Generated: "myfile.go",
   163  			Package:   "mypkg",
   164  		}},
   165  		{"ExportOperations", "", &Config{
   166  			Generated:        "generated.go",
   167  			ExportOperations: "operations.json",
   168  		}},
   169  		{"CustomContext", "", &Config{
   170  			Generated:   "generated.go",
   171  			ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
   172  		}},
   173  		{"NoContext", "", &Config{
   174  			Generated:   "generated.go",
   175  			ContextType: "-",
   176  		}},
   177  		{"ClientGetter", "", &Config{
   178  			Generated:    "generated.go",
   179  			ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromContext",
   180  		}},
   181  		{"ClientGetterCustomContext", "", &Config{
   182  			Generated:    "generated.go",
   183  			ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromMyContext",
   184  			ContextType:  "github.com/Khan/genqlient/internal/testutil.MyContext",
   185  		}},
   186  		{"ClientGetterNoContext", "", &Config{
   187  			Generated:    "generated.go",
   188  			ClientGetter: "github.com/Khan/genqlient/internal/testutil.GetClientFromNowhere",
   189  			ContextType:  "-",
   190  		}},
   191  	}
   192  
   193  	sourceFilename := "SimpleQuery.graphql"
   194  
   195  	for _, test := range tests {
   196  		config := test.config
   197  		baseDir := filepath.Join(dataDir, test.baseDir)
   198  		t.Run(test.name, func(t *testing.T) {
   199  			err := config.ValidateAndFillDefaults(baseDir)
   200  			config.Schema = []string{filepath.Join(dataDir, "schema.graphql")}
   201  			config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
   202  			if err != nil {
   203  				t.Fatal(err)
   204  			}
   205  			generated, err := Generate(config)
   206  			if err != nil {
   207  				t.Fatal(err)
   208  			}
   209  
   210  			for filename, content := range generated {
   211  				t.Run(filename, func(t *testing.T) {
   212  					testutil.Cupaloy.SnapshotT(t, string(content))
   213  				})
   214  			}
   215  
   216  			t.Run("Build", func(t *testing.T) {
   217  				if testing.Short() {
   218  					t.Skip("skipping build due to -short")
   219  				}
   220  
   221  				err := buildGoFile(sourceFilename,
   222  					generated[config.Generated])
   223  				if err != nil {
   224  					t.Error(err)
   225  				}
   226  			})
   227  		})
   228  	}
   229  }
   230  
   231  // TestGenerate is a snapshot-based test of error text.
   232  //
   233  // For each .go or .graphql file in testdata/errors, and corresponding
   234  // .schema.graphql file, it asserts that the given query returns an error, and
   235  // that that error's string-text matches the snapshot.  The snapshotting is
   236  // useful to ensure we don't accidentally make the text less readable, drop the
   237  // line numbers, etc.  We include both .go and .graphql tests, to make sure the
   238  // line numbers work in both cases.
   239  func TestGenerateErrors(t *testing.T) {
   240  	files, err := ioutil.ReadDir(errorsDir)
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	for _, file := range files {
   246  		sourceFilename := file.Name()
   247  		if !strings.HasSuffix(sourceFilename, ".graphql") &&
   248  			!strings.HasSuffix(sourceFilename, ".go") ||
   249  			strings.HasSuffix(sourceFilename, ".schema.graphql") {
   250  			continue
   251  		}
   252  
   253  		baseFilename := strings.TrimSuffix(sourceFilename, filepath.Ext(sourceFilename))
   254  		schemaFilename := baseFilename + ".schema.graphql"
   255  		testFilename := strings.ReplaceAll(sourceFilename, ".", "/")
   256  
   257  		t.Run(testFilename, func(t *testing.T) {
   258  			_, err := Generate(&Config{
   259  				Schema:      []string{filepath.Join(errorsDir, schemaFilename)},
   260  				Operations:  []string{filepath.Join(errorsDir, sourceFilename)},
   261  				Package:     "test",
   262  				Generated:   os.DevNull,
   263  				ContextType: "context.Context",
   264  				Bindings: map[string]*TypeBinding{
   265  					"ValidScalar":   {Type: "string"},
   266  					"InvalidScalar": {Type: "bogus"},
   267  					"Pokemon": {
   268  						Type:              "github.com/Khan/genqlient/internal/testutil.Pokemon",
   269  						ExpectExactFields: "{ species level }",
   270  					},
   271  				},
   272  				AllowBrokenFeatures: true,
   273  			})
   274  			if err == nil {
   275  				t.Fatal("expected an error")
   276  			}
   277  
   278  			testutil.Cupaloy.SnapshotT(t, err.Error())
   279  		})
   280  	}
   281  }