@@ -118,6 +118,30 @@ func (s *IntegrationTestSuite) runExtensionInit(sourceFile string) error {
118118 return nil
119119}
120120
121+ // cleanupGeneratedFiles removes generated files from the original source directory
122+ func (s * IntegrationTestSuite ) cleanupGeneratedFiles (originalSourceFile string ) {
123+ s .t .Helper ()
124+
125+ sourceDir := filepath .Dir (originalSourceFile )
126+ baseName := SanitizePackageName (strings .TrimSuffix (filepath .Base (originalSourceFile ), ".go" ))
127+
128+ generatedFiles := []string {
129+ baseName + ".stub.php" ,
130+ baseName + "_arginfo.h" ,
131+ baseName + ".h" ,
132+ baseName + ".c" ,
133+ baseName + ".go" ,
134+ "README.md" ,
135+ }
136+
137+ for _ , file := range generatedFiles {
138+ fullPath := filepath .Join (sourceDir , file )
139+ if _ , err := os .Stat (fullPath ); err == nil {
140+ os .Remove (fullPath )
141+ }
142+ }
143+ }
144+
121145// compileFrankenPHP compiles FrankenPHP with the generated extension
122146func (s * IntegrationTestSuite ) compileFrankenPHP (moduleDir string ) (string , error ) {
123147 s .t .Helper ()
@@ -250,6 +274,7 @@ func TestBasicFunction(t *testing.T) {
250274 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "basic_function.go" )
251275 sourceFile , err := filepath .Abs (sourceFile )
252276 require .NoError (t , err )
277+ defer suite .cleanupGeneratedFiles (sourceFile )
253278
254279 targetFile , err := suite .createGoModule (sourceFile )
255280 require .NoError (t , err )
@@ -326,6 +351,7 @@ func TestClassMethodsIntegration(t *testing.T) {
326351 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "class_methods.go" )
327352 sourceFile , err := filepath .Abs (sourceFile )
328353 require .NoError (t , err )
354+ defer suite .cleanupGeneratedFiles (sourceFile )
329355
330356 targetFile , err := suite .createGoModule (sourceFile )
331357 require .NoError (t , err )
@@ -437,6 +463,7 @@ func TestConstants(t *testing.T) {
437463 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "constants.go" )
438464 sourceFile , err := filepath .Abs (sourceFile )
439465 require .NoError (t , err )
466+ defer suite .cleanupGeneratedFiles (sourceFile )
440467
441468 targetFile , err := suite .createGoModule (sourceFile )
442469 require .NoError (t , err )
@@ -536,6 +563,7 @@ func TestNamespace(t *testing.T) {
536563 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "namespace.go" )
537564 sourceFile , err := filepath .Abs (sourceFile )
538565 require .NoError (t , err )
566+ defer suite .cleanupGeneratedFiles (sourceFile )
539567
540568 targetFile , err := suite .createGoModule (sourceFile )
541569 require .NoError (t , err )
@@ -625,6 +653,7 @@ func TestInvalidSignature(t *testing.T) {
625653 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "invalid_signature.go" )
626654 sourceFile , err := filepath .Abs (sourceFile )
627655 require .NoError (t , err )
656+ defer suite .cleanupGeneratedFiles (sourceFile )
628657
629658 targetFile , err := suite .createGoModule (sourceFile )
630659 require .NoError (t , err )
@@ -640,6 +669,7 @@ func TestTypeMismatch(t *testing.T) {
640669 sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "type_mismatch.go" )
641670 sourceFile , err := filepath .Abs (sourceFile )
642671 require .NoError (t , err )
672+ defer suite .cleanupGeneratedFiles (sourceFile )
643673
644674 targetFile , err := suite .createGoModule (sourceFile )
645675 require .NoError (t , err )
@@ -681,3 +711,83 @@ func dummy() {}
681711 assert .Error (t , err , "should fail when gen_stub.php is missing" )
682712 assert .Contains (t , err .Error (), "gen_stub.php" , "error should mention missing script" )
683713}
714+
715+ func TestCallable (t * testing.T ) {
716+ suite := setupTest (t )
717+
718+ sourceFile := filepath .Join (".." , ".." , "testdata" , "integration" , "callable.go" )
719+ sourceFile , err := filepath .Abs (sourceFile )
720+ require .NoError (t , err )
721+ defer suite .cleanupGeneratedFiles (sourceFile )
722+
723+ targetFile , err := suite .createGoModule (sourceFile )
724+ require .NoError (t , err )
725+
726+ err = suite .runExtensionInit (targetFile )
727+ require .NoError (t , err )
728+
729+ _ , err = suite .compileFrankenPHP (filepath .Dir (targetFile ))
730+ require .NoError (t , err )
731+
732+ err = suite .verifyPHPSymbols (
733+ []string {"my_array_map" , "my_filter" },
734+ []string {"Processor" },
735+ []string {},
736+ )
737+ require .NoError (t , err , "all functions and classes should be accessible from PHP" )
738+
739+ err = suite .verifyFunctionBehavior (`<?php
740+
741+ $result = my_array_map([1, 2, 3], function($x) { return $x * 2; });
742+ if ($result !== [2, 4, 6]) {
743+ echo "FAIL: my_array_map with closure expected [2, 4, 6], got " . json_encode($result);
744+ exit(1);
745+ }
746+
747+ $result = my_array_map(['hello', 'world'], 'strtoupper');
748+ if ($result !== ['HELLO', 'WORLD']) {
749+ echo "FAIL: my_array_map with function name expected ['HELLO', 'WORLD'], got " . json_encode($result);
750+ exit(1);
751+ }
752+
753+ $result = my_array_map([], function($x) { return $x; });
754+ if ($result !== []) {
755+ echo "FAIL: my_array_map with empty array expected [], got " . json_encode($result);
756+ exit(1);
757+ }
758+
759+ $result = my_filter([1, 2, 3, 4, 5, 6], function($x) { return $x % 2 === 0; });
760+ if ($result !== [2, 4, 6]) {
761+ echo "FAIL: my_filter expected [2, 4, 6], got " . json_encode($result);
762+ exit(1);
763+ }
764+
765+ $result = my_filter([1, 2, 3, 4], null);
766+ if ($result !== [1, 2, 3, 4]) {
767+ echo "FAIL: my_filter with null callback expected [1, 2, 3, 4], got " . json_encode($result);
768+ exit(1);
769+ }
770+
771+ $processor = new Processor();
772+ $result = $processor->transform('hello', function($s) { return strtoupper($s); });
773+ if ($result !== 'HELLO') {
774+ echo "FAIL: Processor::transform with closure expected 'HELLO', got '$result'";
775+ exit(1);
776+ }
777+
778+ $result = $processor->transform('world', 'strtoupper');
779+ if ($result !== 'WORLD') {
780+ echo "FAIL: Processor::transform with function name expected 'WORLD', got '$result'";
781+ exit(1);
782+ }
783+
784+ $result = $processor->transform(' test ', 'trim');
785+ if ($result !== 'test') {
786+ echo "FAIL: Processor::transform with trim expected 'test', got '$result'";
787+ exit(1);
788+ }
789+
790+ echo "OK";
791+ ` , "OK" )
792+ require .NoError (t , err , "all callable tests should pass" )
793+ }
0 commit comments