blob: ec4b3b42e8bec45c212ec066df61caa20fd41c44 [file] [log] [blame]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package main
import (
"path/filepath"
"strings"
"testing"
"text/template"
"dawn.googlesource.com/dawn/tools/src/oswrapper"
"github.com/ben-clayton/webidlparser/ast"
"github.com/ben-clayton/webidlparser/parser"
"github.com/stretchr/testify/require"
)
func TestRun_MissingArgs(t *testing.T) {
tests := []struct {
name string
args []string
}{
{
name: "No args",
args: []string{},
},
{
name: "Missing template",
args: []string{"--output=out.go", "input.idl"},
},
{
name: "Missing input file",
args: []string{"--template=tmpl.go", "--output=out.go"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
err := run(tt.args, wrapper)
require.ErrorContains(t, err, "idlgen is a tool used to generate code")
})
}
}
func TestRun_OutputToStdout(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{.}}"), 0644))
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {}; interface GPUDevice {};"), 0644))
args := []string{"--template=template.tmpl", "test.idl"}
err := run(args, wrapper)
require.NoError(t, err)
}
func TestRun_OutputToFile(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{range .Declarations}}{{.Name}} {{end}}"), 0644))
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {}; interface GPUDevice {};"), 0644))
outputPath := "subdir/out.txt"
args := []string{"--template=template.tmpl", "--output=" + outputPath, "test.idl"}
err := run(args, wrapper)
require.NoError(t, err)
// Verify file exists and has content
content, err := wrapper.ReadFile(outputPath)
require.NoError(t, err)
require.Contains(t, string(content), "Test GPUDevice")
}
func TestRun_OutputDirectoryCreationFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{.}}"), 0644))
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {};"), 0644))
// Create a file named "subdir" to block directory creation
require.NoError(t, wrapper.WriteFile("subdir", []byte("i am a file"), 0644))
outputPath := "subdir/out.txt"
args := []string{"--template=template.tmpl", "--output=" + outputPath, "test.idl"}
err := run(args, wrapper)
require.Error(t, err)
require.ErrorContains(t, err, "failed to create output directory")
}
func TestRun_OutputFileCreationFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{.}}"), 0644))
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {};"), 0644))
outputPath := "out.txt"
// Create a directory at the output path to cause Create to fail
require.NoError(t, wrapper.Mkdir(outputPath, 0755))
args := []string{"--template=template.tmpl", "--output=" + outputPath, "test.idl"}
err := run(args, wrapper)
require.Error(t, err)
require.ErrorContains(t, err, "failed to open output file")
}
func TestRun_TemplateReadFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
// Do not create the template file
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {};"), 0644))
args := []string{"--template=template.tmpl", "test.idl"}
err := run(args, wrapper)
require.ErrorContains(t, err, "failed to open template file")
}
func TestRun_IDLReadFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{.}}"), 0644))
// Provide a non-existent IDL file
args := []string{"--template=template.tmpl", "missing.idl"}
err := run(args, wrapper)
require.ErrorContains(t, err, "failed to open file 'missing.idl'")
}
func TestRun_IDLParseFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte("{{.}}"), 0644))
// Provide invalid IDL content
require.NoError(t, wrapper.WriteFile("invalid.idl", []byte("this is definitely not valid idl"), 0644))
args := []string{"--template=template.tmpl", "invalid.idl"}
err := run(args, wrapper)
require.ErrorContains(t, err, "errors found while parsing invalid.idl")
}
func TestRun_TemplateParseFailure(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
// Provide invalid template content (unclosed action)
require.NoError(t, wrapper.WriteFile("invalid.tmpl", []byte("{{.Name"), 0644))
require.NoError(t, wrapper.WriteFile("test.idl", []byte("interface Test {}; interface GPUDevice {};"), 0644))
args := []string{"--template=invalid.tmpl", "test.idl"}
err := run(args, wrapper)
require.ErrorContains(t, err, "failed to parse template file")
}
func TestRun_SuccessfulExecution(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
// A simple template that iterates over declarations and prints their names
templateContent := `{{range .Declarations}}{{.Name}};{{end}}`
require.NoError(t, wrapper.WriteFile("template.tmpl", []byte(templateContent), 0644))
// A simple IDL file. GPUDevice is required to avoid a panic in the patch function.
idlContent := `
interface MyInterface {
void myMethod();
};
interface GPUDevice {};
`
require.NoError(t, wrapper.WriteFile("test.idl", []byte(idlContent), 0644))
outputPath := "output.txt"
args := []string{"--template=template.tmpl", "--output=" + outputPath, "test.idl"}
err := run(args, wrapper)
require.NoError(t, err)
// Verify the output content
content, err := wrapper.ReadFile(outputPath)
require.NoError(t, err)
outputStr := string(content)
require.Contains(t, outputStr, "File generated by tools/cmd/idlgen.go")
require.Contains(t, outputStr, "\n\nMyInterface;GPUDevice;")
}
func TestSimplify(t *testing.T) {
idl := `
interface Dependent : Base {
void method();
};
interface Base {};
partial interface Partial {
void method2();
};
interface Partial {
void method1();
};
interface mixin MyMixin {
const long mixinConst = 1;
};
interface WithMixin {
void originalMethod();
};
WithMixin includes MyMixin;
enum MyEnum {
"value1"
};
enum MyEnum {
"value2"
};
dictionary MyDict {
long member1;
};
partial dictionary MyDict {
long member2;
};
typedef long MyTypedef;
dictionary BaseDict {
MyTypedef val;
};
dictionary DerivedDict : BaseDict {
long otherVal;
};
interface InterfaceUsingDict {
void method(DerivedDict d);
};
`
parsed := parser.Parse(idl)
require.Empty(t, parsed.Errors)
simplified, decls := simplify(parsed)
// Check Partial Interface Merging
partialDecl := decls["Partial"]
require.NotNil(t, partialDecl)
partialIface, ok := partialDecl.(*ast.Interface)
require.True(t, ok)
require.Equal(t, 2, len(partialIface.Members))
memberNames := make(map[string]bool)
for _, m := range partialIface.Members {
if member, ok := m.(*ast.Member); ok {
memberNames[member.Name] = true
}
}
require.True(t, memberNames["method1"])
require.True(t, memberNames["method2"])
// Check Mixin Embedding
withMixinDecl := decls["WithMixin"]
require.NotNil(t, withMixinDecl)
withMixinIface, ok := withMixinDecl.(*ast.Interface)
require.True(t, ok)
require.Equal(t, 2, len(withMixinIface.Members))
memberNames = make(map[string]bool)
for _, m := range withMixinIface.Members {
if member, ok := m.(*ast.Member); ok {
memberNames[member.Name] = true
}
}
require.True(t, memberNames["originalMethod"])
require.True(t, memberNames["mixinConst"])
// Check Partial Enum Merging
enumDecl := decls["MyEnum"]
require.NotNil(t, enumDecl)
enum, ok := enumDecl.(*ast.Enum)
require.True(t, ok)
require.Equal(t, 2, len(enum.Values))
enumValues := make(map[string]bool)
for _, v := range enum.Values {
if val, ok := v.(*ast.BasicLiteral); ok {
enumValues[val.Value] = true
}
}
require.True(t, enumValues["\"value1\""])
require.True(t, enumValues["\"value2\""])
// Check Partial Dictionary Merging
dictDecl := decls["MyDict"]
require.NotNil(t, dictDecl)
dict, ok := dictDecl.(*ast.Dictionary)
require.True(t, ok)
require.Equal(t, 2, len(dict.Members))
dictMembers := make(map[string]bool)
for _, m := range dict.Members {
dictMembers[m.Name] = true
}
require.True(t, dictMembers["member1"])
require.True(t, dictMembers["member2"])
// Check Dependency Ordering
// Base must precede Dependent
var names []string
for _, d := range simplified.Declarations {
names = append(names, nameOf(d))
}
checkOrder := func(first, second string) {
firstIdx := -1
secondIdx := -1
for i, name := range names {
if name == first {
firstIdx = i
}
if name == second {
secondIdx = i
}
}
require.NotEqual(t, -1, firstIdx, "%s not found in simplified declarations", first)
require.NotEqual(t, -1, secondIdx, "%s not found in simplified declarations", second)
require.True(t, firstIdx < secondIdx, "%s should precede %s, but found indices %d and %d", first, second, firstIdx, secondIdx)
}
checkOrder("Base", "Dependent")
checkOrder("MyTypedef", "BaseDict")
checkOrder("BaseDict", "DerivedDict")
checkOrder("DerivedDict", "InterfaceUsingDict")
}
func TestEnumEntryName(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"rgba8unorm", "kRgba8Unorm"},
{"\"bgra8unorm\"", "kBgra8Unorm"},
{"depth-stencil", "kDepthStencil"},
{"triangle-list", "kTriangleList"},
{"line-strip", "kLineStrip"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := enumEntryName(tt.input)
require.Equal(t, result, tt.expected)
})
}
}
func TestMemberExtraction(t *testing.T) {
idl := `
interface Base {
const long BaseConst = 1;
attribute long BaseAttr;
void baseMethod();
};
interface Derived : Base {
const long DerivedConst = 2;
attribute long DerivedAttr;
void derivedMethod();
};
namespace MyNamespace {
const long NamespaceConst = 3;
attribute long NamespaceAttr;
void namespaceMethod();
};
`
parsed := parser.Parse(idl)
require.Empty(t, parsed.Errors)
_, decls := simplify(parsed)
g := generator{
declarations: decls,
}
base := decls["Base"]
derived := decls["Derived"]
namespace := decls["MyNamespace"]
t.Run("constantsOf", func(t *testing.T) {
check := func(obj ast.Decl, expected []string) {
consts := constantsOf(obj)
require.Len(t, consts, len(expected))
for i, name := range expected {
require.Equal(t, name, consts[i].Name)
}
}
check(base, []string{"BaseConst"})
check(derived, []string{"DerivedConst"})
check(namespace, []string{"NamespaceConst"})
})
t.Run("flattenedConstantsOf", func(t *testing.T) {
consts := g.flattenedConstantsOf(derived)
require.Len(t, consts, 2)
require.Equal(t, "DerivedConst", consts[0].Name)
require.Equal(t, "BaseConst", consts[1].Name)
})
t.Run("attributesOf", func(t *testing.T) {
check := func(obj ast.Decl, expected []string) {
attrs := attributesOf(obj)
require.Len(t, attrs, len(expected))
for i, name := range expected {
require.Equal(t, name, attrs[i].Name)
}
}
check(base, []string{"BaseAttr"})
check(derived, []string{"DerivedAttr"})
check(namespace, []string{"NamespaceAttr"})
})
t.Run("flattenedAttributesOf", func(t *testing.T) {
attrs := g.flattenedAttributesOf(derived)
require.Len(t, attrs, 2)
require.Equal(t, "DerivedAttr", attrs[0].Name)
require.Equal(t, "BaseAttr", attrs[1].Name)
})
t.Run("methodsOf", func(t *testing.T) {
check := func(obj ast.Decl, expected []string) {
methods := methodsOf(obj)
require.Len(t, methods, len(expected))
for i, name := range expected {
require.Equal(t, name, methods[i].Name)
}
}
check(base, []string{"baseMethod"})
check(derived, []string{"derivedMethod"})
// methodsOf currently returns nil for namespaces
check(namespace, []string{})
})
t.Run("flattenedMethodsOf", func(t *testing.T) {
methods := g.flattenedMethodsOf(derived)
require.Len(t, methods, 2)
require.Equal(t, "derivedMethod", methods[0].Name)
require.Equal(t, "baseMethod", methods[1].Name)
})
}
func TestPatch(t *testing.T) {
lostMember := &ast.Member{
Name: "lost",
Type: &ast.TypeName{Name: "Promise"},
Annotations: []*ast.Annotation{},
}
otherMember := &ast.Member{
Name: "other",
Type: &ast.TypeName{Name: "void"},
Annotations: []*ast.Annotation{},
}
gpuDevice := &ast.Interface{
Name: "GPUDevice",
Members: []ast.InterfaceMember{lostMember, otherMember},
}
decls := declarations{
"GPUDevice": gpuDevice,
}
// Execute patch
patch(nil, decls)
// Verify [SameObject] was added to 'lost'
require.True(t, hasAnnotation(lostMember, "SameObject"))
// Verify 'other' was not touched
require.False(t, hasAnnotation(otherMember, "SameObject"))
}
func TestGeneratorEval(t *testing.T) {
tests := []struct {
name string
templateName string
args []interface{}
expected string
expectError string
setup func(*template.Template)
}{
{
name: "SingleArgument",
templateName: "Simple",
args: []interface{}{"Hello"},
expected: "Hello",
},
{
name: "MapArguments",
templateName: "WithMap",
args: []interface{}{"Key", "foo", "Value", "bar"},
expected: "foo=bar",
},
{
name: "TemplateNotFound",
templateName: "Missing",
args: []interface{}{"arg"},
expectError: "template 'Missing' not found",
},
{
name: "InvalidArgCount",
templateName: "WithMap",
args: []interface{}{"Key", "foo", "Value"},
expectError: "Eval expects a single argument or list name-value pairs",
},
{
name: "InvalidArgType",
templateName: "WithMap",
args: []interface{}{123, "foo", "Value", "bar"},
expectError: "Eval argument 0 is not a string",
},
{
name: "ExecutionError",
templateName: "Fail",
args: []interface{}{map[string]string{}},
expectError: "while evaluating 'Fail'",
setup: func(t *template.Template) {
t.Option("missingkey=error")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl := template.New("root")
_, err := tmpl.Parse(`
{{define "Simple"}}{{.}}{{end}}
{{define "WithMap"}}{{.Key}}={{.Value}}{{end}}
{{define "Fail"}}{{ .MissingField }}{{end}}
`)
require.NoError(t, err)
if tt.setup != nil {
tt.setup(tmpl)
}
g := generator{
t: tmpl,
}
out, err := g.eval(tt.templateName, tt.args...)
if tt.expectError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.expectError)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, out)
}
})
}
}
func TestGeneratorInclude(t *testing.T) {
tests := []struct {
name string
fileName string
fileContent string
includePath string
expectError string
verifyTemplate string // Template to execute to verify the include worked
verifyOutput string // Expected output from verifyTemplate
}{
{
name: "Success",
fileName: "included.tmpl",
fileContent: `{{define "Included"}}Included Content{{end}}`,
includePath: "included.tmpl",
verifyTemplate: `{{template "Included"}}`,
verifyOutput: "Included Content",
},
{
name: "MissingFile",
includePath: "missing.tmpl",
expectError: "open templates/missing.tmpl",
},
{
name: "ParseError",
fileName: "invalid.tmpl",
fileContent: "{{",
includePath: "invalid.tmpl",
expectError: "template: invalid.tmpl:1: unclosed action",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wrapper := oswrapper.CreateFSTestOSWrapper()
workingDir := "templates"
require.NoError(t, wrapper.Mkdir(workingDir, 0755))
if tt.fileName != "" {
err := wrapper.WriteFile(filepath.Join(workingDir, tt.fileName), []byte(tt.fileContent), 0644)
require.NoError(t, err)
}
tmpl := template.New("root")
g := generator{
t: tmpl,
workingDir: workingDir,
osWrapper: wrapper,
funcs: map[string]interface{}{},
}
out, err := g.include(tt.includePath)
if tt.expectError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tt.expectError)
} else {
require.NoError(t, err)
require.Empty(t, out)
if tt.verifyTemplate != "" {
t2, err := tmpl.Parse(tt.verifyTemplate)
require.NoError(t, err)
sb := strings.Builder{}
err = t2.Execute(&sb, nil)
require.NoError(t, err)
require.Equal(t, tt.verifyOutput, sb.String())
}
}
})
}
}
func TestMapGet(t *testing.T) {
m := newMap()
m.Put("key1", "value1")
m.Put("key2", 123)
m.Put(456, "value3")
require.Equal(t, "value1", m.Get("key1"))
require.Equal(t, 123, m.Get("key2"))
require.Equal(t, "value3", m.Get(456))
require.Nil(t, m.Get("missing"))
}
func TestIs(t *testing.T) {
isInterface := is(ast.Interface{})
isInterfaceOrNamespace := is(ast.Interface{}, ast.Namespace{})
iface := &ast.Interface{}
namespace := &ast.Namespace{}
dictionary := &ast.Dictionary{}
require.True(t, isInterface(iface))
require.True(t, isInterface(*iface))
require.False(t, isInterface(namespace))
require.False(t, isInterface(dictionary))
require.True(t, isInterfaceOrNamespace(iface))
require.True(t, isInterfaceOrNamespace(namespace))
require.False(t, isInterfaceOrNamespace(dictionary))
}
func TestIsInitializer(t *testing.T) {
constructor := &ast.Member{
Type: &ast.TypeName{Name: "constructor"},
}
regularMember := &ast.Member{
Type: &ast.TypeName{Name: "void"},
}
notMember := &ast.Interface{}
require.True(t, isInitializer(constructor))
require.False(t, isInitializer(regularMember))
require.False(t, isInitializer(notMember))
}
func TestIsUndefinedType(t *testing.T) {
undefined := &ast.TypeName{Name: "undefined"}
notUndefined := &ast.TypeName{Name: "void"}
notTypeName := &ast.SequenceType{}
require.True(t, isUndefinedType(undefined))
require.False(t, isUndefinedType(notUndefined))
require.False(t, isUndefinedType(notTypeName))
}
func TestHasAnnotation(t *testing.T) {
annotated := []*ast.Annotation{
{Name: "Annotated"},
{Name: "WithValue"},
}
t.Run("Interface", func(t *testing.T) {
node := &ast.Interface{Annotations: annotated}
require.True(t, hasAnnotation(node, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(node, "Missing"))
})
t.Run("Member", func(t *testing.T) {
node := &ast.Member{Annotations: annotated}
require.True(t, hasAnnotation(node, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(node, "Missing"))
})
t.Run("Namespace", func(t *testing.T) {
node := &ast.Namespace{Annotations: annotated}
require.True(t, hasAnnotation(node, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(node, "Missing"))
})
t.Run("Parameter", func(t *testing.T) {
node := &ast.Parameter{Annotations: annotated}
require.True(t, hasAnnotation(node, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(node, "Missing"))
})
t.Run("Typedef", func(t *testing.T) {
// Test Annotations field
node := &ast.Typedef{Annotations: annotated}
require.True(t, hasAnnotation(node, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(node, "Missing"))
// Test TypeAnnotations field
nodeTypeAnnote := &ast.Typedef{TypeAnnotations: annotated}
require.True(t, hasAnnotation(nodeTypeAnnote, "Annotated"))
require.True(t, hasAnnotation(node, "WithValue"))
require.False(t, hasAnnotation(nodeTypeAnnote, "Missing"))
// Test both
nodeBoth := &ast.Typedef{
Annotations: []*ast.Annotation{{Name: "A1"}},
TypeAnnotations: []*ast.Annotation{{Name: "A2"}},
}
require.True(t, hasAnnotation(nodeBoth, "A1"))
require.True(t, hasAnnotation(nodeBoth, "A2"))
require.False(t, hasAnnotation(nodeBoth, "Missing"))
})
}
func TestHasConstructor(t *testing.T) {
constructorMember := &ast.Member{
Type: &ast.TypeName{Name: "constructor"},
}
regularMember := &ast.Member{
Type: &ast.TypeName{Name: "void"},
}
withConstructor := &ast.Interface{
Members: []ast.InterfaceMember{regularMember, constructorMember},
}
withoutConstructor := &ast.Interface{
Members: []ast.InterfaceMember{regularMember},
}
require.True(t, hasConstructor(withConstructor))
require.False(t, hasConstructor(withoutConstructor))
}
func TestPromiseDetection_HappyPath(t *testing.T) {
tests := []struct {
method string
isPromise bool
}{
{"asyncMethod", true},
{"syncMethod", false},
{"asyncValue", true},
{"syncValue", false},
}
idl := `
interface Test {
Promise<void> asyncMethod();
void syncMethod();
Promise<long> asyncValue();
long syncValue();
};
`
parsed := parser.Parse(idl)
require.Empty(t, parsed.Errors)
_, decls := simplify(parsed)
testIface := decls["Test"].(*ast.Interface)
methods := methodsOf(testIface)
methodMap := make(map[string]*Method)
for _, m := range methods {
methodMap[m.Name] = m
}
for _, tt := range tests {
t.Run(tt.method, func(t *testing.T) {
method := methodMap[tt.method]
require.NotNil(t, method)
retType := method.Overloads[0].Type
require.Equal(t, tt.isPromise, isPromiseType(retType), "isPromiseType failed for %s", tt.method)
require.Equal(t, tt.isPromise, returnsPromise(method), "returnsPromise failed for %s", tt.method)
})
}
}
func TestPromiseDetection_InconsistentOverloads(t *testing.T) {
// Manually construct a method with inconsistent overloads
method := &Method{
Name: "mixedMethod",
Overloads: []*ast.Member{
{Type: &ast.ParametrizedType{Name: "Promise", Elems: []ast.Type{&ast.TypeName{Name: "void"}}}},
{Type: &ast.TypeName{Name: "void"}},
},
}
require.Panics(t, func() {
returnsPromise(method)
})
}
func TestSetlikeOf(t *testing.T) {
setlikePattern := &ast.Pattern{Type: ast.Setlike}
otherPattern := &ast.Pattern{Type: ast.Iterable}
withSetlike := &ast.Interface{
Patterns: []*ast.Pattern{otherPattern, setlikePattern},
}
withoutSetlike := &ast.Interface{
Patterns: []*ast.Pattern{otherPattern},
}
notInterface := &ast.Dictionary{}
require.Equal(t, setlikePattern, setlikeOf(withSetlike))
require.Nil(t, setlikeOf(withoutSetlike))
require.Nil(t, setlikeOf(notInterface))
}
func TestPascalCase(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"foo_bar", "FooBar"},
{"gl-Position", "GlPosition"},
{"texture_2d", "Texture2D"},
{"simple", "Simple"},
{"camelCase", "CamelCase"},
{"_leading_underscore", "LeadingUnderscore"},
{"trailing_underscore_", "TrailingUnderscore"},
{"multiple___underscores", "MultipleUnderscores"},
{"123_numbers", "123Numbers"},
{"mixed-separators_test", "MixedSeparatorsTest"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := pascalCase(tt.input)
require.Equal(t, result, tt.expected)
})
}
}