Skip to content

Commit 2dd102f

Browse files
feat(extgen): add PHP code tests
1 parent b784ea3 commit 2dd102f

File tree

3 files changed

+276
-2
lines changed

3 files changed

+276
-2
lines changed

internal/extgen/integration_test.go

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,21 @@ func (s *IntegrationTestSuite) verifyPHPSymbols(functions []string, classes []st
229229
return nil
230230
}
231231

232+
func (s *IntegrationTestSuite) verifyFunctionBehavior(phpCode string, expectedOutput string) error {
233+
s.t.Helper()
234+
235+
output, err := s.runPHPCode(phpCode)
236+
if err != nil {
237+
return err
238+
}
239+
240+
if !strings.Contains(output, expectedOutput) {
241+
return fmt.Errorf("unexpected output.\nExpected to contain: %q\nGot: %q", expectedOutput, output)
242+
}
243+
244+
return nil
245+
}
246+
232247
func TestBasicFunction(t *testing.T) {
233248
suite := setupTest(t)
234249

@@ -268,6 +283,41 @@ func TestBasicFunction(t *testing.T) {
268283
[]string{},
269284
)
270285
require.NoError(t, err, "all functions should be accessible from PHP")
286+
287+
err = suite.verifyFunctionBehavior(`<?php
288+
$result = test_uppercase("hello world");
289+
if ($result !== "HELLO WORLD") {
290+
echo "FAIL: test_uppercase expected 'HELLO WORLD', got '$result'";
291+
exit(1);
292+
}
293+
294+
$result = test_uppercase("");
295+
if ($result !== "") {
296+
echo "FAIL: test_uppercase with empty string expected '', got '$result'";
297+
exit(1);
298+
}
299+
300+
$sum = test_add_numbers(5, 7);
301+
if ($sum !== 12) {
302+
echo "FAIL: test_add_numbers(5, 7) expected 12, got $sum";
303+
exit(1);
304+
}
305+
306+
$result = test_is_enabled(true);
307+
if ($result !== false) {
308+
echo "FAIL: test_is_enabled(true) expected false, got " . ($result ? "true" : "false");
309+
exit(1);
310+
}
311+
312+
$result = test_is_enabled(false);
313+
if ($result !== true) {
314+
echo "FAIL: test_is_enabled(false) expected true, got " . ($result ? "true" : "false");
315+
exit(1);
316+
}
317+
318+
echo "OK";
319+
`, "OK")
320+
require.NoError(t, err, "all function calls should work correctly")
271321
}
272322

273323
func TestClassMethodsIntegration(t *testing.T) {
@@ -292,6 +342,93 @@ func TestClassMethodsIntegration(t *testing.T) {
292342
[]string{},
293343
)
294344
require.NoError(t, err, "all classes should be accessible from PHP")
345+
346+
err = suite.verifyFunctionBehavior(`<?php
347+
$counter = new Counter();
348+
if ($counter->getValue() !== 0) {
349+
echo "FAIL: Counter initial value expected 0, got " . $counter->getValue();
350+
exit(1);
351+
}
352+
353+
$counter->increment();
354+
if ($counter->getValue() !== 1) {
355+
echo "FAIL: Counter after increment expected 1, got " . $counter->getValue();
356+
exit(1);
357+
}
358+
359+
$counter->decrement();
360+
if ($counter->getValue() !== 0) {
361+
echo "FAIL: Counter after decrement expected 0, got " . $counter->getValue();
362+
exit(1);
363+
}
364+
365+
$counter->setValue(10);
366+
if ($counter->getValue() !== 10) {
367+
echo "FAIL: Counter after setValue(10) expected 10, got " . $counter->getValue();
368+
exit(1);
369+
}
370+
371+
$newValue = $counter->addValue(5);
372+
if ($newValue !== 15) {
373+
echo "FAIL: Counter addValue(5) expected to return 15, got $newValue";
374+
exit(1);
375+
}
376+
if ($counter->getValue() !== 15) {
377+
echo "FAIL: Counter value after addValue(5) expected 15, got " . $counter->getValue();
378+
exit(1);
379+
}
380+
381+
$counter->updateWithNullable(50);
382+
if ($counter->getValue() !== 50) {
383+
echo "FAIL: Counter after updateWithNullable(50) expected 50, got " . $counter->getValue();
384+
exit(1);
385+
}
386+
387+
$counter->updateWithNullable(null);
388+
if ($counter->getValue() !== 50) {
389+
echo "FAIL: Counter after updateWithNullable(null) expected 50 (unchanged), got " . $counter->getValue();
390+
exit(1);
391+
}
392+
393+
$counter->reset();
394+
if ($counter->getValue() !== 0) {
395+
echo "FAIL: Counter after reset expected 0, got " . $counter->getValue();
396+
exit(1);
397+
}
398+
399+
$counter1 = new Counter();
400+
$counter2 = new Counter();
401+
$counter1->setValue(100);
402+
$counter2->setValue(200);
403+
if ($counter1->getValue() !== 100 || $counter2->getValue() !== 200) {
404+
echo "FAIL: Multiple Counter instances should be independent";
405+
exit(1);
406+
}
407+
408+
$holder = new StringHolder();
409+
$holder->setData("test string");
410+
if ($holder->getData() !== "test string") {
411+
echo "FAIL: StringHolder getData expected 'test string', got '" . $holder->getData() . "'";
412+
exit(1);
413+
}
414+
if ($holder->getLength() !== 11) {
415+
echo "FAIL: StringHolder getLength expected 11, got " . $holder->getLength();
416+
exit(1);
417+
}
418+
419+
$holder->setData("");
420+
if ($holder->getData() !== "") {
421+
echo "FAIL: StringHolder empty string expected '', got '" . $holder->getData() . "'";
422+
exit(1);
423+
}
424+
if ($holder->getLength() !== 0) {
425+
echo "FAIL: StringHolder empty string length expected 0, got " . $holder->getLength();
426+
exit(1);
427+
}
428+
429+
echo "OK";
430+
`, "OK")
431+
require.NoError(t, err, "all class methods should work correctly")
295432
}
296433

297434
func TestConstants(t *testing.T) {
@@ -319,6 +456,78 @@ func TestConstants(t *testing.T) {
319456
},
320457
)
321458
require.NoError(t, err, "all constants, functions, and classes should be accessible from PHP")
459+
460+
err = suite.verifyFunctionBehavior(`<?php
461+
if (TEST_MAX_RETRIES !== 100) {
462+
echo "FAIL: TEST_MAX_RETRIES expected 100, got " . TEST_MAX_RETRIES;
463+
exit(1);
464+
}
465+
466+
if (TEST_API_VERSION !== "2.0.0") {
467+
echo "FAIL: TEST_API_VERSION expected '2.0.0', got '" . TEST_API_VERSION . "'";
468+
exit(1);
469+
}
470+
471+
if (TEST_ENABLED !== true) {
472+
var_dump(TEST_ENABLED);
473+
echo "FAIL: TEST_ENABLED expected true, got " . (TEST_ENABLED ? "true" : "false");
474+
exit(1);
475+
}
476+
477+
if (abs(TEST_PI - 3.14159) > 0.00001) {
478+
echo "FAIL: TEST_PI expected 3.14159, got " . TEST_PI;
479+
exit(1);
480+
}
481+
482+
if (Config::MODE_DEBUG !== 1) {
483+
echo "FAIL: Config::MODE_DEBUG expected 1, got " . Config::MODE_DEBUG;
484+
exit(1);
485+
}
486+
487+
if (Config::MODE_PRODUCTION !== 2) {
488+
echo "FAIL: Config::MODE_PRODUCTION expected 2, got " . Config::MODE_PRODUCTION;
489+
exit(1);
490+
}
491+
492+
if (Config::DEFAULT_TIMEOUT !== 30) {
493+
echo "FAIL: Config::DEFAULT_TIMEOUT expected 30, got " . Config::DEFAULT_TIMEOUT;
494+
exit(1);
495+
}
496+
497+
$config = new Config();
498+
$config->setMode(Config::MODE_DEBUG);
499+
if ($config->getMode() !== Config::MODE_DEBUG) {
500+
echo "FAIL: Config getMode expected MODE_DEBUG, got " . $config->getMode();
501+
exit(1);
502+
}
503+
504+
$result = test_with_constants(STATUS_PENDING);
505+
if ($result !== "pending") {
506+
echo "FAIL: test_with_constants(STATUS_PENDING) expected 'pending', got '$result'";
507+
exit(1);
508+
}
509+
510+
$result = test_with_constants(STATUS_PROCESSING);
511+
if ($result !== "processing") {
512+
echo "FAIL: test_with_constants(STATUS_PROCESSING) expected 'processing', got '$result'";
513+
exit(1);
514+
}
515+
516+
$result = test_with_constants(STATUS_COMPLETED);
517+
if ($result !== "completed") {
518+
echo "FAIL: test_with_constants(STATUS_COMPLETED) expected 'completed', got '$result'";
519+
exit(1);
520+
}
521+
522+
$result = test_with_constants(999);
523+
if ($result !== "unknown") {
524+
echo "FAIL: test_with_constants(999) expected 'unknown', got '$result'";
525+
exit(1);
526+
}
527+
528+
echo "OK";
529+
`, "OK")
530+
require.NoError(t, err, "all constants should have correct values and functions should work")
322531
}
323532

324533
func TestNamespace(t *testing.T) {
@@ -343,6 +552,71 @@ func TestNamespace(t *testing.T) {
343552
[]string{`\\TestIntegration\\Extension\\NAMESPACE_VERSION`},
344553
)
345554
require.NoError(t, err, "all namespaced symbols should be accessible from PHP")
555+
556+
err = suite.verifyFunctionBehavior(`<?php
557+
use TestIntegration\Extension;
558+
559+
if (Extension\NAMESPACE_VERSION !== "1.0.0") {
560+
echo "FAIL: NAMESPACE_VERSION expected '1.0.0', got '" . Extension\NAMESPACE_VERSION . "'";
561+
exit(1);
562+
}
563+
564+
$greeting = Extension\greet("Alice");
565+
if ($greeting !== "Hello, Alice!") {
566+
echo "FAIL: greet('Alice') expected 'Hello, Alice!', got '$greeting'";
567+
exit(1);
568+
}
569+
570+
$greeting = Extension\greet("");
571+
if ($greeting !== "Hello, !") {
572+
echo "FAIL: greet('') expected 'Hello, !', got '$greeting'";
573+
exit(1);
574+
}
575+
576+
if (Extension\Person::DEFAULT_AGE !== 18) {
577+
echo "FAIL: Person::DEFAULT_AGE expected 18, got " . Extension\Person::DEFAULT_AGE;
578+
exit(1);
579+
}
580+
581+
$person = new Extension\Person();
582+
$person->setName("Bob");
583+
$person->setAge(25);
584+
585+
if ($person->getName() !== "Bob") {
586+
echo "FAIL: Person getName expected 'Bob', got '" . $person->getName() . "'";
587+
exit(1);
588+
}
589+
590+
if ($person->getAge() !== 25) {
591+
echo "FAIL: Person getAge expected 25, got " . $person->getAge();
592+
exit(1);
593+
}
594+
595+
$person->setAge(Extension\Person::DEFAULT_AGE);
596+
if ($person->getAge() !== 18) {
597+
echo "FAIL: Person setAge(DEFAULT_AGE) expected 18, got " . $person->getAge();
598+
exit(1);
599+
}
600+
601+
$person1 = new Extension\Person();
602+
$person2 = new Extension\Person();
603+
$person1->setName("Alice");
604+
$person1->setAge(30);
605+
$person2->setName("Charlie");
606+
$person2->setAge(40);
607+
608+
if ($person1->getName() !== "Alice" || $person1->getAge() !== 30) {
609+
echo "FAIL: person1 should have independent state";
610+
exit(1);
611+
}
612+
if ($person2->getName() !== "Charlie" || $person2->getAge() !== 40) {
613+
echo "FAIL: person2 should have independent state";
614+
exit(1);
615+
}
616+
617+
echo "OK";
618+
`, "OK")
619+
require.NoError(t, err, "all namespaced symbols should work correctly")
346620
}
347621

348622
func TestInvalidSignature(t *testing.T) {

internal/extgen/templates/extension.c.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ PHP_MINIT_FUNCTION({{.BaseName}}) {
162162
{{- if $.Namespace}}
163163
{{if .IsIota}}REGISTER_NS_LONG_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{.Name}}, CONST_CS | CONST_PERSISTENT);
164164
{{else if eq .PhpType "string"}}REGISTER_NS_STRING_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{.CValue}}, CONST_CS | CONST_PERSISTENT);
165-
{{else if eq .PhpType "bool"}}REGISTER_NS_LONG_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{if eq .Value "true"}}1{{else}}0{{end}}, CONST_CS | CONST_PERSISTENT);
165+
{{else if eq .PhpType "bool"}}REGISTER_NS_LONG_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{if eq .Value "true"}}ZEND_TRUE{{else}}ZEND_FALSE{{end}}, CONST_CS | CONST_PERSISTENT);
166166
{{else if eq .PhpType "float"}}REGISTER_NS_DOUBLE_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{.CValue}}, CONST_CS | CONST_PERSISTENT);
167167
{{else}}REGISTER_NS_LONG_CONSTANT("{{cString $.Namespace}}", "{{.Name}}", {{.CValue}}, CONST_CS | CONST_PERSISTENT);
168168
{{- end}}

testdata/integration/namespace.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ func (p *PersonStruct) GetAge() int64 {
4646
return int64(p.Age)
4747
}
4848

49-
// export_php:classconstant Person
49+
// export_php:classconst Person
5050
const DEFAULT_AGE = 18

0 commit comments

Comments
 (0)