Files
go/idl/parser_test.go
zeekay 03451a9a1b The C++ backend on the parser package
The parser moved to idl and the C++ backend was still holding the old one,
so the two lines of work could not both be had. They meet here: emitcpp.go
imports idl like emit.go does, and structSize moves to idl beside the
Struct whose width it reports — a stated `@N` or where the last field ends
is a property of the declaration, not of whoever is printing it.

The grammar tests move with the grammar. Emission tests stay with the
emitter. Every golden, Go and C++, is unchanged: the merge is a move, not
a change of what the generator prints.
2026-09-06 13:15:57 -07:00

131 lines
4.1 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package idl
import (
"strings"
"testing"
)
// TestParseInterfaceBrace parses the brace form of an interface and checks
// the method ordinals (1-based, declaration order) and param shapes.
func TestParseInterfaceBrace(t *testing.T) {
src := `package demo
struct A { X u32 @0 }
struct B { Y u32 @0 }
interface Svc {
foo(in: A) returns (out: B)
bar(in: A)
baz() returns (out: B)
quux()
}
`
f, err := Parse("svc.zap", []byte(src))
if err != nil {
t.Fatalf("parse: %v", err)
}
if len(f.Interfaces) != 1 {
t.Fatalf("want 1 interface, got %d", len(f.Interfaces))
}
iface := f.Interfaces[0]
if iface.Name != "Svc" {
t.Errorf("name = %q, want Svc", iface.Name)
}
want := []struct {
name string
ord int
req, resp bool
reqStr, respSt string
}{
{"foo", 1, true, true, "A", "B"},
{"bar", 2, true, false, "A", ""},
{"baz", 3, false, true, "", "B"},
{"quux", 4, false, false, "", ""},
}
if len(iface.Methods) != len(want) {
t.Fatalf("want %d methods, got %d", len(want), len(iface.Methods))
}
for i, w := range want {
m := iface.Methods[i]
if m.Name != w.name || m.Ordinal != w.ord {
t.Errorf("method %d = (%s @%d), want (%s @%d)", i, m.Name, m.Ordinal, w.name, w.ord)
}
if (m.Request != nil) != w.req || (m.Response != nil) != w.resp {
t.Errorf("method %s req/resp presence = %v/%v, want %v/%v",
m.Name, m.Request != nil, m.Response != nil, w.req, w.resp)
}
if w.req && m.Request.StructName != w.reqStr {
t.Errorf("method %s req struct = %q, want %q", m.Name, m.Request.StructName, w.reqStr)
}
if w.resp && m.Response.StructName != w.respSt {
t.Errorf("method %s resp struct = %q, want %q", m.Name, m.Response.StructName, w.respSt)
}
}
}
func paramStruct(p *Param) string {
if p == nil {
return ""
}
return p.StructName
}
// TestInterfaceWhitespaceBlockOpener proves `interface X` opens a
// whitespace block (methods indented under it) and that the brace is
// synthesized, while a struct field merely NAMED `interface` is still a
// field (the keyword+one-identifier+EOL rule).
func TestInterfaceWhitespaceBlockOpener(t *testing.T) {
// `interface Svc` then indented methods -> interface with 1 method.
src := "package p\ninterface Svc\n ping()\n"
f, err := Parse("p.zap", []byte(src))
if err != nil {
t.Fatalf("parse: %v", err)
}
if len(f.Interfaces) != 1 || len(f.Interfaces[0].Methods) != 1 {
t.Fatalf("want 1 interface w/ 1 method, got %+v", f.Interfaces)
}
// `interface text` as a struct field (has a type) stays a field.
src2 := "package p\nstruct S\n interface text\n B u8\n"
f2, err := Parse("p2.zap", []byte(src2))
if err != nil {
t.Fatalf("parse: %v", err)
}
if len(f2.Interfaces) != 0 {
t.Fatalf("`interface text` field must not parse as a service: %+v", f2.Interfaces)
}
if len(f2.Structs) != 1 || len(f2.Structs[0].Fields) != 2 {
t.Fatalf("want struct S with 2 fields, got %+v", f2.Structs)
}
if f2.Structs[0].Fields[0].Name != "interface" {
t.Errorf("field 0 name = %q, want interface", f2.Structs[0].Fields[0].Name)
}
}
// TestInterfaceErrors covers malformed interface input.
// A field named as its struct is refused, because the accessor a backend
// prints for it is a member named as the type is — which C++ reads as a
// constructor. Caught once, in the front end, rather than once per backend.
func TestAFieldMayNotRepeatItsStructsName(t *testing.T) {
_, err := Parse("x.zap", []byte("package p\nstruct Hash {\n Kind u8 @0\n Hash bytes_fixed[32] @1\n}\n"))
if err == nil {
t.Fatal("a field named as its struct was accepted")
}
if !strings.Contains(err.Error(), "repeats the struct's name") {
t.Fatalf("error does not say what is wrong: %v", err)
}
}
// Two fields of one name would print one accessor twice.
func TestTwoFieldsOfOneNameAreRefused(t *testing.T) {
_, err := Parse("x.zap", []byte("package p\nstruct S {\n A u8 @0\n A u8 @1\n}\n"))
if err == nil {
t.Fatal("a duplicate field name was accepted")
}
if !strings.Contains(err.Error(), "duplicate field") {
t.Fatalf("error does not say what is wrong: %v", err)
}
}