## Summary: In this commit I remove one of the limitations of our support for interfaces, from #52, by adding support for list-of-interface fields. This was surprisingly complex! The issue is that, as before, it's the containing type that has to do all the glue work -- and it's that glue work that is complicated by list-of-interface fields. All in all, it's not that much new code, and by far the hard part is just 20 lines in the UnmarshalJSON template (which come with almost twice as many lines of comments to explain them). It may be easiest to start by reading some of the generated code, and then read the template. I also added support for such fields with `pointer: true` specified, such that the type is `[][]...[]*MyInterface`, although I don't know why you would want that. This does *not* allow e.g. `*[]*[][]*MyInterface`; that would require a way to specify it (see #16) but also add some extra complexity (as we'd have to actually walk the type-unwrap chain properly, instead of just counting the number of slices and whether there's a pointer). Issue: https://github.com/Khan/genqlient/issues/8 ## Test plan: make check Author: benjaminjkraft Reviewers: dnerdy, benjaminjkraft, aberkan, csilvers, MiguelCastillo Required Reviewers: Approved by: dnerdy Checks: ⌛ Test (1.17), ⌛ Test (1.16), ⌛ Test (1.15), ⌛ Test (1.14), ⌛ Test (1.13), ⌛ Lint, ⌛ Lint, ⌛ Test (1.17), ⌛ Test (1.16), ⌛ Test (1.15), ⌛ Test (1.14), ⌛ Test (1.13) Pull request URL: https://github.com/Khan/genqlient/pull/54
145 lines
3.9 KiB
Go
145 lines
3.9 KiB
Go
package generate
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Khan/genqlient/internal/testutil"
|
|
)
|
|
|
|
const (
|
|
dataDir = "testdata/queries"
|
|
errorsDir = "testdata/errors"
|
|
)
|
|
|
|
// TestGenerate is a snapshot-based test of code-generation.
|
|
//
|
|
// This file just has the test runner; the actual data is all in
|
|
// testdata/queries. Specifically, the schema used for all the queries is in
|
|
// schema.graphql; the queries themselves are in TestName.graphql. The test
|
|
// asserts that running genqlient on that query produces the generated code in
|
|
// the snapshot-file TestName.graphql.go.
|
|
//
|
|
// To update the snapshots (if the code-generator has changed), run the test
|
|
// with `UPDATE_SNAPSHOTS=1`; it will fail the tests and print any diffs, but
|
|
// update the snapshots. Make sure to check that the output is sensible; the
|
|
// snapshots don't even get compiled!
|
|
func TestGenerate(t *testing.T) {
|
|
files, err := ioutil.ReadDir(dataDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, file := range files {
|
|
sourceFilename := file.Name()
|
|
if sourceFilename == "schema.graphql" || !strings.HasSuffix(sourceFilename, ".graphql") {
|
|
continue
|
|
}
|
|
goFilename := sourceFilename + ".go"
|
|
queriesFilename := sourceFilename + ".json"
|
|
|
|
t.Run(sourceFilename, func(t *testing.T) {
|
|
generated, err := Generate(&Config{
|
|
Schema: filepath.Join(dataDir, "schema.graphql"),
|
|
Operations: []string{filepath.Join(dataDir, sourceFilename)},
|
|
Package: "test",
|
|
Generated: goFilename,
|
|
ExportOperations: queriesFilename,
|
|
Scalars: map[string]string{
|
|
"ID": "github.com/Khan/genqlient/internal/testutil.ID",
|
|
"DateTime": "time.Time",
|
|
"Junk": "interface{}",
|
|
"ComplexJunk": "[]map[string]*[]*map[string]interface{}",
|
|
},
|
|
AllowBrokenFeatures: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for filename, content := range generated {
|
|
t.Run(filename, func(t *testing.T) {
|
|
testutil.Cupaloy.SnapshotT(t, string(content))
|
|
})
|
|
}
|
|
|
|
t.Run("Build", func(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping build due to -short")
|
|
} else if sourceFilename == "Omitempty.graphql" {
|
|
t.Skip("TODO: enable after fixing " +
|
|
"https://github.com/Khan/genqlient/issues/43")
|
|
}
|
|
|
|
goContent := generated[goFilename]
|
|
// We need to put this within the current module, rather than in
|
|
// /tmp, so that it can access internal/testutil.
|
|
f, err := ioutil.TempFile("./testdata/tmp", sourceFilename+"_*.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
f.Close()
|
|
os.Remove(f.Name())
|
|
}()
|
|
|
|
_, err = f.Write(goContent)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cmd := exec.Command("go", "build", f.Name())
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
t.Fatal(fmt.Errorf("generated code does not compile: %w", err))
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateErrors(t *testing.T) {
|
|
files, err := ioutil.ReadDir(errorsDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, file := range files {
|
|
sourceFilename := file.Name()
|
|
if !strings.HasSuffix(sourceFilename, ".graphql") &&
|
|
!strings.HasSuffix(sourceFilename, ".go") ||
|
|
strings.HasSuffix(sourceFilename, ".schema.graphql") {
|
|
continue
|
|
}
|
|
|
|
baseFilename := strings.TrimSuffix(sourceFilename, filepath.Ext(sourceFilename))
|
|
schemaFilename := baseFilename + ".schema.graphql"
|
|
|
|
t.Run(sourceFilename, func(t *testing.T) {
|
|
_, err := Generate(&Config{
|
|
Schema: filepath.Join(errorsDir, schemaFilename),
|
|
Operations: []string{filepath.Join(errorsDir, sourceFilename)},
|
|
Package: "test",
|
|
Generated: os.DevNull,
|
|
Scalars: map[string]string{
|
|
"ValidScalar": "string",
|
|
"InvalidScalar": "bogus",
|
|
},
|
|
AllowBrokenFeatures: true,
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
|
|
testutil.Cupaloy.SnapshotT(t, err.Error())
|
|
})
|
|
}
|
|
}
|