5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 07:29:56 +08:00
wails/v3/internal/generator/generate_test.go
Fabio Massaioli 37673eb24d
[v3] Fix binding generator bugs and prepare for Go 1.24 (#4045)
* Rename predicates source file

* Overhaul and document type predicates

* Fix model collection logic for named types

* Fix map key type rendering

* Fix map creation code

* Fix rendering of structs that implement marshaler interfaces

* Fix type cycle detection to take type args into account

* Fix enum and typeparam field initialisation

* Improve unsupported type warnings

* Remove internal models file

* Deduplicate template code

* Accept generic aliases in static analyser

* Support new `encoding/json` flag `omitzero`

* Handle special cases when rendering generic aliases

* Update npm test dependencies

* Test class aliases and implicit private dependencies

* Test marshaler combinations

* Test map key types

* Remove bad map keys from unrelated tests

* Test service discovery through generic aliases

* Test generic aliases

* Test warning messages

* Disable go1.24 tests

* Update changelog

* Restore rendering of injected lines in index file

* Test directives

* Add wails:ignore directive

* Fix typo

* Move injections to the bottom of service files

* Handle errors from closing files

* Do not emit messages when services define only lifecycle methods

* Add internal directive for services and models

* Update changelog

* Fix error in service templates

* Test internal directive on services/models

* Fix error in index template

* Base testdata updates

* Testdata for class aliases and implicit private dependencies

* Testdata for marshaler combinations

* Testdata for map key types

* Testdata for bad map key fixes

* Add weakly typed enums aka alias constants

* Testdata for enum and typeparam field fixes

* Testdata for generic aliases

* Testdata for warning messages

* Testdata for directives

* Testdata for weakly typed enums

* Update binding example

* Update services example

* Remove go1.24 testdata

* Update cli doc

* Fix analyser tests

* Fix windows tests... hopefully

* go mod tidy on examples

* Update bindings guide

---------

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
2025-02-09 09:44:34 +11:00

242 lines
5.4 KiB
Go

package generator
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/internal/generator/render"
"io"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/generator/config"
)
const testcases = "github.com/wailsapp/wails/v3/internal/generator/testcases/..."
type testParams struct {
name string
options *flags.GenerateBindingsOptions
outputDir string
want map[string]bool
}
func TestGenerator(t *testing.T) {
const (
useNamesBit = 1 << iota
useInterfacesBit
tsBit
)
// Generate configuration matrix.
tests := make([]*testParams, 1<<3)
for i := range tests {
options := &flags.GenerateBindingsOptions{
ModelsFilename: "models",
IndexFilename: "index",
UseBundledRuntime: true,
TS: i&tsBit != 0,
UseInterfaces: i&useInterfacesBit != 0,
UseNames: i&useNamesBit != 0,
}
name := configString(options)
tests[i] = &testParams{
name: name,
options: options,
outputDir: filepath.Join("testdata/output", name),
want: make(map[string]bool),
}
}
for _, test := range tests {
// Create output dir.
if err := os.MkdirAll(test.outputDir, 0777); err != nil {
t.Fatal(err)
}
// Walk output dir.
err := filepath.WalkDir(test.outputDir, func(path string, d fs.DirEntry, err error) error {
// Skip directories.
if d.IsDir() {
return nil
}
// Skip got files.
if strings.HasSuffix(d.Name(), ".got.js") || strings.HasSuffix(d.Name(), ".got.ts") {
return nil
}
// Record file.
test.want[filepath.Clean("."+path[len(test.outputDir):])] = false
return nil
})
if err != nil {
t.Fatal(err)
}
}
// Run tests.
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
creator := outputCreator(t, test)
generator := NewGenerator(
test.options,
creator,
config.DefaultPtermLogger(nil),
)
_, err := generator.Generate(testcases)
if report := (*ErrorReport)(nil); errors.As(err, &report) {
if report.HasErrors() {
t.Error(report)
} else if report.HasWarnings() {
pterm.Warning.Println(report)
}
// Log warnings and compare with reference output.
if log, err := creator.Create("warnings.log"); err != nil {
t.Error(err)
} else {
func() {
defer log.Close()
warnings := report.Warnings()
slices.Sort(warnings)
for _, msg := range warnings {
fmt.Fprint(log, msg, render.Newline)
}
}()
}
} else if err != nil {
t.Error(err)
}
for path, present := range test.want {
if !present {
t.Errorf("Missing output file '%s'", path)
}
}
})
}
}
// configString computes a subtest name from the given configuration.
func configString(options *flags.GenerateBindingsOptions) string {
lang := "JS"
if options.TS {
lang = "TS"
}
return fmt.Sprintf("lang=%s/UseInterfaces=%v/UseNames=%v", lang, options.UseInterfaces, options.UseNames)
}
// outputCreator returns a FileCreator that detects want/got pairs
// and schedules them for comparison.
//
// If no corresponding want file exists, it is created and reported.
func outputCreator(t *testing.T, params *testParams) config.FileCreator {
var mu sync.Mutex
return config.FileCreatorFunc(func(path string) (io.WriteCloser, error) {
path = filepath.Clean(path)
prefixedPath := filepath.Join(params.outputDir, path)
// Protect want map accesses.
mu.Lock()
defer mu.Unlock()
if seen, ok := params.want[path]; ok {
// File exists: mark as seen and compare.
if seen {
t.Errorf("Duplicate output file '%s'", path)
}
params.want[path] = true
// Open want file.
wf, err := os.Open(prefixedPath)
if err != nil {
return nil, err
}
// Create or truncate got file.
ext := filepath.Ext(prefixedPath)
gf, err := os.Create(fmt.Sprintf("%s.got%s", prefixedPath[:len(prefixedPath)-len(ext)], ext))
if err != nil {
return nil, err
}
// Initialise comparer.
return &outputComparer{t, path, wf, gf}, nil
} else {
// File does not exist: create it.
t.Errorf("Unexpected output file '%s'", path)
params.want[path] = true
if err := os.MkdirAll(filepath.Dir(prefixedPath), 0777); err != nil {
return nil, err
}
return os.Create(prefixedPath)
}
})
}
// outputComparer is a io.WriteCloser that writes to got.
//
// When Close is called, it compares want to got; if they are identical,
// it deletes got; otherwise it reports a testing error.
type outputComparer struct {
t *testing.T
path string
want *os.File
got *os.File
}
func (comparer *outputComparer) Write(data []byte) (int, error) {
return comparer.got.Write(data)
}
func (comparer *outputComparer) Close() error {
defer comparer.want.Close()
defer comparer.got.Close()
comparer.got.Seek(0, io.SeekStart)
// Read want data.
want, err := io.ReadAll(comparer.want)
if err != nil {
comparer.t.Error(err)
return nil
}
got, err := io.ReadAll(comparer.got)
if err != nil {
comparer.t.Error(err)
return nil
}
if diff := cmp.Diff(want, got); diff != "" {
comparer.t.Errorf("Output file '%s' mismatch (-want +got):\n%s", comparer.path, diff)
} else {
// On success, delete got file.
comparer.got.Close()
if err := os.Remove(comparer.got.Name()); err != nil {
comparer.t.Error(err)
}
}
return nil
}