Files
archived-frankenphp/internal/extgen/cfile_namespace_test.go
Alexandre Daubois 2fa7663d3b fix(extgen): use REGISTER(_NS)_BOOL_CONSTANT (#2047)
Spotted in #1984, this is the right macros to declare boolean constants
2025-12-01 15:35:34 +01:00

341 lines
9.5 KiB
Go

package extgen
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNamespacedClassName(t *testing.T) {
tests := []struct {
name string
namespace string
className string
expected string
}{
{
name: "no namespace",
namespace: "",
className: "MySuperClass",
expected: "MySuperClass",
},
{
name: "single level namespace",
namespace: "MyNamespace",
className: "MySuperClass",
expected: "MyNamespace_MySuperClass",
},
{
name: "multi level namespace",
namespace: `Go\Extension`,
className: "MySuperClass",
expected: "Go_Extension_MySuperClass",
},
{
name: "deep namespace",
namespace: `My\Deep\Nested\Namespace`,
className: "TestClass",
expected: "My_Deep_Nested_Namespace_TestClass",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NamespacedName(tt.namespace, tt.className)
require.Equal(t, tt.expected, result, "expected %q, got %q", tt.expected, result)
})
}
}
func TestCFileGenerationWithNamespace(t *testing.T) {
content := `package main
//export_php:namespace Go\Extension
//export_php:class MySuperClass
type MySuperClass struct{}
//export_php:method MySuperClass test(): string
func (m *MySuperClass) Test() string {
return "test"
}
`
tmpfile, err := os.CreateTemp("", "test_cfile_namespace_*.go")
require.NoError(t, err, "Failed to create temp file")
defer func() {
err := os.Remove(tmpfile.Name())
assert.NoError(t, err, "Failed to remove temp file: %v", err)
}()
_, err = tmpfile.Write([]byte(content))
require.NoError(t, err, "Failed to write to temp file")
err = tmpfile.Close()
require.NoError(t, err, "Failed to close temp file")
generator := &Generator{
BaseName: "test_extension",
SourceFile: tmpfile.Name(),
BuildDir: t.TempDir(),
Namespace: `Go\Extension`,
Classes: []phpClass{
{
Name: "MySuperClass",
GoStruct: "MySuperClass",
Methods: []phpClassMethod{
{
Name: "test",
PhpName: "test",
Signature: "test(): string",
ReturnType: "string",
ClassName: "MySuperClass",
},
},
},
},
}
cFileGen := cFileGenerator{generator: generator}
contentResult, err := cFileGen.getTemplateContent()
require.NoError(t, err, "error generating C file")
expectedCall := "register_class_Go_Extension_MySuperClass()"
require.Contains(t, contentResult, expectedCall, "C file should contain the standard function call")
oldCall := "register_class_MySuperClass()"
require.NotContains(t, contentResult, oldCall, "C file should not contain old non-namespaced call")
}
func TestCFileGenerationWithoutNamespace(t *testing.T) {
generator := &Generator{
BaseName: "test_extension",
BuildDir: t.TempDir(),
Namespace: "",
Classes: []phpClass{
{
Name: "MySuperClass",
GoStruct: "MySuperClass",
},
},
}
cFileGen := cFileGenerator{generator: generator}
contentResult, err := cFileGen.getTemplateContent()
require.NoError(t, err, "error generating C file")
expectedCall := "register_class_MySuperClass()"
require.Contains(t, contentResult, expectedCall, "C file should not contain the standard function call")
}
func TestCFileGenerationWithNamespacedConstants(t *testing.T) {
tests := []struct {
name string
namespace string
constants []phpConstant
contains []string
}{
{
name: "integer constant with namespace",
namespace: `Go\Extension`,
constants: []phpConstant{
{Name: "TEST_INT", Value: "42", PhpType: phpInt},
},
contains: []string{
`REGISTER_NS_LONG_CONSTANT("Go\\Extension", "TEST_INT", 42, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "string constant with namespace",
namespace: `Go\Extension`,
constants: []phpConstant{
{Name: "TEST_STRING", Value: `"hello"`, PhpType: phpString},
},
contains: []string{
`REGISTER_NS_STRING_CONSTANT("Go\\Extension", "TEST_STRING", "hello", CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "float constant with namespace",
namespace: `Go\Extension`,
constants: []phpConstant{
{Name: "TEST_FLOAT", Value: "3.14", PhpType: phpFloat},
},
contains: []string{
`REGISTER_NS_DOUBLE_CONSTANT("Go\\Extension", "TEST_FLOAT", 3.14, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "bool constant with namespace",
namespace: `Go\Extension`,
constants: []phpConstant{
{Name: "TEST_BOOL", Value: "true", PhpType: phpBool},
},
contains: []string{
`REGISTER_NS_BOOL_CONSTANT("Go\\Extension", "TEST_BOOL", true, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "iota constant with namespace",
namespace: `Go\Extension`,
constants: []phpConstant{
{Name: "STATUS_OK", Value: "STATUS_OK", PhpType: phpInt, IsIota: true},
},
contains: []string{
`REGISTER_NS_LONG_CONSTANT("Go\\Extension", "STATUS_OK", STATUS_OK, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "multiple constants with deep namespace",
namespace: `My\Deep\Namespace`,
constants: []phpConstant{
{Name: "CONST_INT", Value: "100", PhpType: phpInt},
{Name: "CONST_STR", Value: `"value"`, PhpType: phpString},
{Name: "CONST_FLOAT", Value: "1.5", PhpType: phpFloat},
},
contains: []string{
`REGISTER_NS_LONG_CONSTANT("My\\Deep\\Namespace", "CONST_INT", 100, CONST_CS | CONST_PERSISTENT);`,
`REGISTER_NS_STRING_CONSTANT("My\\Deep\\Namespace", "CONST_STR", "value", CONST_CS | CONST_PERSISTENT);`,
`REGISTER_NS_DOUBLE_CONSTANT("My\\Deep\\Namespace", "CONST_FLOAT", 1.5, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "single level namespace",
namespace: `TestNamespace`,
constants: []phpConstant{
{Name: "MY_CONST", Value: "999", PhpType: phpInt},
},
contains: []string{
`REGISTER_NS_LONG_CONSTANT("TestNamespace", "MY_CONST", 999, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "namespace with trailing backslash",
namespace: `TestIntegration\Extension`,
constants: []phpConstant{
{Name: "VERSION", Value: `"1.0.0"`, PhpType: phpString},
},
contains: []string{
`REGISTER_NS_STRING_CONSTANT("TestIntegration\\Extension", "VERSION", "1.0.0", CONST_CS | CONST_PERSISTENT);`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
generator := &Generator{
BaseName: "test_ext",
Namespace: tt.namespace,
Constants: tt.constants,
}
cGen := cFileGenerator{generator: generator}
content, err := cGen.buildContent()
require.NoError(t, err, "Failed to build C file content")
for _, expected := range tt.contains {
assert.Contains(t, content, expected, "Generated C content should contain '%s'", expected)
}
})
}
}
func TestCFileGenerationWithoutNamespacedConstants(t *testing.T) {
tests := []struct {
name string
namespace string
constants []phpConstant
contains []string
}{
{
name: "integer constant without namespace",
namespace: "",
constants: []phpConstant{
{Name: "GLOBAL_INT", Value: "42", PhpType: phpInt},
},
contains: []string{
`REGISTER_LONG_CONSTANT("GLOBAL_INT", 42, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "string constant without namespace",
namespace: "",
constants: []phpConstant{
{Name: "GLOBAL_STRING", Value: `"test"`, PhpType: phpString},
},
contains: []string{
`REGISTER_STRING_CONSTANT("GLOBAL_STRING", "test", CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "float constant without namespace",
namespace: "",
constants: []phpConstant{
{Name: "GLOBAL_FLOAT", Value: "2.71", PhpType: phpFloat},
},
contains: []string{
`REGISTER_DOUBLE_CONSTANT("GLOBAL_FLOAT", 2.71, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "bool constant without namespace",
namespace: "",
constants: []phpConstant{
{Name: "GLOBAL_BOOL", Value: "false", PhpType: phpBool},
},
contains: []string{
`REGISTER_BOOL_CONSTANT("GLOBAL_BOOL", false, CONST_CS | CONST_PERSISTENT);`,
},
},
{
name: "iota constant without namespace",
namespace: "",
constants: []phpConstant{
{Name: "ERROR_CODE", Value: "ERROR_CODE", PhpType: phpInt, IsIota: true},
},
contains: []string{
`REGISTER_LONG_CONSTANT("ERROR_CODE", ERROR_CODE, CONST_CS | CONST_PERSISTENT);`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
generator := &Generator{
BaseName: "test_ext",
Namespace: tt.namespace,
Constants: tt.constants,
}
cGen := cFileGenerator{generator: generator}
content, err := cGen.buildContent()
require.NoError(t, err, "Failed to build C file content")
for _, expected := range tt.contains {
assert.Contains(t, content, expected, "Generated C content should contain '%s'", expected)
}
assert.NotContains(t, content, "REGISTER_NS_", "Content should NOT contain namespaced constant macros when namespace is empty")
})
}
}
func TestCFileTemplateFunctionMapCString(t *testing.T) {
generator := &Generator{
BaseName: "test_ext",
Namespace: `My\Namespace\Test`,
Constants: []phpConstant{
{Name: "MY_CONST", Value: "123", PhpType: phpInt},
},
}
cGen := cFileGenerator{generator: generator}
content, err := cGen.getTemplateContent()
require.NoError(t, err, "Failed to get template content")
assert.Contains(t, content, `"My\\Namespace\\Test"`, "Template should properly escape namespace backslashes using cString filter")
assert.NotContains(t, content, `"My\Namespace\Test"`, "Template should not contain unescaped namespace (single backslashes)")
}