| // 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) |
| }) |
| } |
| } |