diff --git a/caddy/frankenphp/cbrotli.go b/caddy/frankenphp/cbrotli.go new file mode 100644 index 0000000000..ee84e60373 --- /dev/null +++ b/caddy/frankenphp/cbrotli.go @@ -0,0 +1,5 @@ +//go:build !nobrotli + +package main + +import _ "github.com/dunglas/caddy-cbrotli" diff --git a/caddy/frankenphp/main.go b/caddy/frankenphp/main.go index 5b5fc0d7c9..6b9d40561f 100644 --- a/caddy/frankenphp/main.go +++ b/caddy/frankenphp/main.go @@ -5,7 +5,6 @@ import ( // plug in Caddy modules here. _ "github.com/caddyserver/caddy/v2/modules/standard" - _ "github.com/dunglas/caddy-cbrotli" _ "github.com/dunglas/frankenphp/caddy" _ "github.com/dunglas/mercure/caddy" _ "github.com/dunglas/vulcain/caddy" diff --git a/caddy/go.mod b/caddy/go.mod index ae29d7565c..60fffe9c18 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -62,7 +62,7 @@ require ( github.com/dunglas/skipfilter v1.0.0 // indirect github.com/dunglas/vulcain v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 // indirect + github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index f190ab0fde..490909fafb 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -158,8 +158,8 @@ github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjsp github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 h1:azdbL1jat1ReH6wQrP+cawhsyXRLFRuCo0hGBUQHnv4= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/cgi.go b/cgi.go index 63fb1339b9..44e54786ff 100644 --- a/cgi.go +++ b/cgi.go @@ -8,8 +8,8 @@ package frankenphp // #cgo noescape frankenphp_register_variables_from_request_info // #cgo noescape frankenphp_register_variable_safe // #cgo noescape frankenphp_register_single -// #include // #include "frankenphp.h" +// #include import "C" import ( "context" diff --git a/cgo.go b/cgo.go index 2ec9586308..8dc2609007 100644 --- a/cgo.go +++ b/cgo.go @@ -1,9 +1,11 @@ package frankenphp // #cgo darwin pkg-config: libxml-2.0 -// #cgo CFLAGS: -Wall -Werror +// #cgo unix CFLAGS: -Wall -Werror // #cgo linux CFLAGS: -D_GNU_SOURCE -// #cgo LDFLAGS: -lphp -lm -lutil +// #cgo unix LDFLAGS: -lphp -lm -lutil // #cgo linux LDFLAGS: -ldl -lresolv // #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -liconv -ldl +// #cgo windows CFLAGS: -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 +// #cgo windows LDFLAGS: -lpthreadVC3 import "C" diff --git a/cli.go b/cli.go new file mode 100644 index 0000000000..96821a2392 --- /dev/null +++ b/cli.go @@ -0,0 +1,29 @@ +package frankenphp + +// #include "frankenphp.h" +import "C" +import "unsafe" + +// ExecuteScriptCLI executes the PHP script passed as parameter. +// It returns the exit status code of the script. +func ExecuteScriptCLI(script string, args []string) int { + // Ensure extensions are registered before CLI execution + registerExtensions() + + cScript := C.CString(script) + defer C.free(unsafe.Pointer(cScript)) + + argc, argv := convertArgs(args) + defer freeArgs(argv) + + return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false)) +} + +func ExecutePHPCode(phpCode string) int { + // Ensure extensions are registered before CLI execution + registerExtensions() + + cCode := C.CString(phpCode) + defer C.free(unsafe.Pointer(cCode)) + return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true)) +} diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000000..f9ee03fea2 --- /dev/null +++ b/cli_test.go @@ -0,0 +1,55 @@ +package frankenphp_test + +import ( + "errors" + "log" + "os" + "os/exec" + "testing" + + "github.com/dunglas/frankenphp" + "github.com/stretchr/testify/assert" +) + +func TestExecuteScriptCLI(t *testing.T) { + if _, err := os.Stat("internal/testcli/testcli"); err != nil { + t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") + } + + cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar") + stdoutStderr, err := cmd.CombinedOutput() + assert.Error(t, err) + + var exitError *exec.ExitError + if errors.As(err, &exitError) { + assert.Equal(t, 3, exitError.ExitCode()) + } + + stdoutStderrStr := string(stdoutStderr) + + assert.Contains(t, stdoutStderrStr, `"foo"`) + assert.Contains(t, stdoutStderrStr, `"bar"`) + assert.Contains(t, stdoutStderrStr, "From the CLI") +} + +func TestExecuteCLICode(t *testing.T) { + if _, err := os.Stat("internal/testcli/testcli"); err != nil { + t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") + } + + cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';") + stdoutStderr, err := cmd.CombinedOutput() + assert.NoError(t, err) + + stdoutStderrStr := string(stdoutStderr) + assert.Equal(t, stdoutStderrStr, `Hello World`) +} + +func ExampleExecuteScriptCLI() { + if len(os.Args) <= 1 { + log.Println("Usage: my-program script.php") + os.Exit(1) + } + + os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args)) +} diff --git a/ext.go b/ext.go index 1c0656c820..b993bf83df 100644 --- a/ext.go +++ b/ext.go @@ -1,6 +1,6 @@ package frankenphp -//#include "frankenphp.h" +// #include "frankenphp.h" import "C" import ( "sync" diff --git a/frankenphp.c b/frankenphp.c index fd487edb8e..d465299509 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1,14 +1,18 @@ +#include "frankenphp.h" #include #include #include #include -#include #include #include #include #include #include +#ifdef PHP_WIN32 +#include +#else #include +#endif #include #include #include @@ -19,7 +23,9 @@ #include #include #include +#ifndef ZEND_WIN32 #include +#endif #if defined(__linux__) #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) @@ -205,7 +211,7 @@ bool frankenphp_shutdown_dummy_request(void) { return true; } -PHPAPI void get_full_env(zval *track_vars_array) { +void get_full_env(zval *track_vars_array) { go_getfullenv(thread_index, track_vars_array); } @@ -959,6 +965,7 @@ static void *php_thread(void *arg) { } static void *php_main(void *arg) { +#ifndef ZEND_WIN32 /* * SIGPIPE must be masked in non-Go threads: * https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG @@ -971,6 +978,7 @@ static void *php_main(void *arg) { perror("failed to block SIGPIPE"); exit(EXIT_FAILURE); } +#endif set_thread_name("php-main"); @@ -1266,7 +1274,7 @@ static zend_module_entry **modules = NULL; static int modules_len = 0; static int (*original_php_register_internal_extensions_func)(void) = NULL; -PHPAPI int register_internal_extensions(void) { +int register_internal_extensions(void) { if (original_php_register_internal_extensions_func != NULL && original_php_register_internal_extensions_func() != SUCCESS) { return FAILURE; diff --git a/frankenphp.go b/frankenphp.go index 693870e1d0..b20d6f6681 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -14,10 +14,10 @@ package frankenphp // #include // #include +// #include "frankenphp.h" // #include // #include // #include -// #include "frankenphp.h" import "C" import ( "bytes" @@ -753,30 +753,6 @@ func go_is_context_done(threadIndex C.uintptr_t) C.bool { return C.bool(phpThreads[threadIndex].frankenPHPContext().isDone) } -// ExecuteScriptCLI executes the PHP script passed as parameter. -// It returns the exit status code of the script. -func ExecuteScriptCLI(script string, args []string) int { - // Ensure extensions are registered before CLI execution - registerExtensions() - - cScript := C.CString(script) - defer C.free(unsafe.Pointer(cScript)) - - argc, argv := convertArgs(args) - defer freeArgs(argv) - - return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false)) -} - -func ExecutePHPCode(phpCode string) int { - // Ensure extensions are registered before CLI execution - registerExtensions() - - cCode := C.CString(phpCode) - defer C.free(unsafe.Pointer(cCode)) - return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true)) -} - func convertArgs(args []string) (C.int, []*C.char) { argc := C.int(len(args)) argv := make([]*C.char, argc) diff --git a/frankenphp.h b/frankenphp.h index c833c44f97..51833cf6fc 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -1,6 +1,46 @@ #ifndef _FRANKENPHP_H #define _FRANKENPHP_H +#ifdef _WIN32 +// Define this to prevent windows.h from including legacy winsock.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Explicitly include Winsock2 BEFORE windows.h +#include +#include +#include +#include + +// Fix for missing IntSafe functions (LongLongAdd) when building with Clang +#ifdef __clang__ +#ifndef INTSAFE_E_ARITHMETIC_OVERFLOW +#define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) +#endif + +#ifndef LongLongAdd +static inline HRESULT LongLongAdd(LONGLONG llAugend, LONGLONG llAddend, + LONGLONG *pllResult) { + if (__builtin_add_overflow(llAugend, llAddend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; +} +#endif + +#ifndef LongLongSub +static inline HRESULT LongLongSub(LONGLONG llMinuend, LONGLONG llSubtrahend, + LONGLONG *pllResult) { + if (__builtin_sub_overflow(llMinuend, llSubtrahend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; +} +#endif +#endif +#endif + #include #include #include diff --git a/frankenphp_test.go b/frankenphp_test.go index 8c6f3c90da..fdebbfcdf7 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -733,40 +733,6 @@ func testFileUpload(t *testing.T, opts *testOptions) { }, opts) } -func TestExecuteScriptCLI(t *testing.T) { - if _, err := os.Stat("internal/testcli/testcli"); err != nil { - t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") - } - - cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar") - stdoutStderr, err := cmd.CombinedOutput() - assert.Error(t, err) - - var exitError *exec.ExitError - if errors.As(err, &exitError) { - assert.Equal(t, 3, exitError.ExitCode()) - } - - stdoutStderrStr := string(stdoutStderr) - - assert.Contains(t, stdoutStderrStr, `"foo"`) - assert.Contains(t, stdoutStderrStr, `"bar"`) - assert.Contains(t, stdoutStderrStr, "From the CLI") -} - -func TestExecuteCLICode(t *testing.T) { - if _, err := os.Stat("internal/testcli/testcli"); err != nil { - t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") - } - - cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';") - stdoutStderr, err := cmd.CombinedOutput() - assert.NoError(t, err) - - stdoutStderrStr := string(stdoutStderr) - assert.Equal(t, stdoutStderrStr, `Hello World`) -} - func ExampleServeHTTP() { if err := frankenphp.Init(); err != nil { panic(err) @@ -786,15 +752,6 @@ func ExampleServeHTTP() { log.Fatal(http.ListenAndServe(":8080", nil)) } -func ExampleExecuteScriptCLI() { - if len(os.Args) <= 1 { - log.Println("Usage: my-program script.php") - os.Exit(1) - } - - os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args)) -} - func BenchmarkHelloWorld(b *testing.B) { require.NoError(b, frankenphp.Init()) b.Cleanup(frankenphp.Shutdown) diff --git a/go.mod b/go.mod index 56bb84b591..5140b7e43b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ retract v1.0.0-rc.1 // Human error require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/dunglas/mercure v0.21.4 - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 + github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 github.com/maypok86/otter/v2 v2.2.1 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index af58b44cb3..83dd4e2a31 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2 github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 h1:azdbL1jat1ReH6wQrP+cawhsyXRLFRuCo0hGBUQHnv4= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/internal/cpu/cpu_unix.go b/internal/cpu/cpu_unix.go index 4d18215226..b33f9e3bf8 100644 --- a/internal/cpu/cpu_unix.go +++ b/internal/cpu/cpu_unix.go @@ -1,3 +1,5 @@ +//go:build unix + package cpu // #include diff --git a/internal/cpu/cpu_windows.go b/internal/cpu/cpu_windows.go index d09d552410..fae6689f08 100644 --- a/internal/cpu/cpu_windows.go +++ b/internal/cpu/cpu_windows.go @@ -5,7 +5,7 @@ import ( ) // ProbeCPUs fallback that always determines that the CPU limits are not reached -func ProbeCPUs(probeTime time.Duration, maxCPUUsage float64, abort chan struct{}) bool { +func ProbeCPUs(probeTime time.Duration, _ float64, abort chan struct{}) bool { select { case <-abort: return false diff --git a/internal/extgen/stub_test.go b/internal/extgen/stub_test.go index b9d689ffbc..67b4203eac 100644 --- a/internal/extgen/stub_test.go +++ b/internal/extgen/stub_test.go @@ -536,7 +536,12 @@ func TestStubGenerator_FileStructure(t *testing.T) { content, err := stubGen.buildContent() assert.NoError(t, err, "buildContent() failed") - lines := strings.Split(content, "\n") + sep := "\n" + if filepath.Separator == '\\' { + sep = "\r\n" + } + + lines := strings.Split(content, sep) assert.GreaterOrEqual(t, len(lines), 3, "Stub file should have multiple lines") assert.Equal(t, " extern zend_module_entry module1_entry; diff --git a/internal/testext/extensions.c b/internal/testext/extensions.c index 721955f621..749d00b56d 100644 --- a/internal/testext/extensions.c +++ b/internal/testext/extensions.c @@ -1,3 +1,4 @@ +#include "extension.h" #include #include diff --git a/internal/testext/exttest.go b/internal/testext/exttest.go index 1a8477d4a8..bbbe9bbd22 100644 --- a/internal/testext/exttest.go +++ b/internal/testext/exttest.go @@ -1,13 +1,14 @@ package testext // #cgo darwin pkg-config: libxml-2.0 -// #cgo CFLAGS: -Wall -Werror -// #cgo CFLAGS: -I/usr/local/include -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib +// #cgo unix CFLAGS: -Wall -Werror +// #cgo unix CFLAGS: -I/usr/local/include -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib // #cgo linux CFLAGS: -D_GNU_SOURCE // #cgo darwin CFLAGS: -I/opt/homebrew/include -// #cgo LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lm -lutil +// #cgo unix LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lm -lutil // #cgo linux LDFLAGS: -ldl -lresolv // #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -L/opt/homebrew/lib -L/opt/homebrew/opt/libiconv/lib -liconv -ldl +// #cgo windows CFLAGS: -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 // #include "extension.h" import "C" import ( diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 5e6fda282d..be045f747d 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -11,6 +11,8 @@ import ( "github.com/e-dant/watcher/watcher-go" ) +const sep = string(filepath.Separator) + type pattern struct { patternGroup *PatternGroup value string @@ -39,8 +41,11 @@ func (p *pattern) parse() (err error) { p.value = absPattern + volumeName := filepath.VolumeName(p.value) + p.value = strings.TrimPrefix(p.value, volumeName) + // then we split the pattern to determine where the directory ends and the pattern starts - splitPattern := strings.Split(absPattern, string(filepath.Separator)) + splitPattern := strings.Split(p.value, sep) patternWithoutDir := "" for i, part := range splitPattern { isFilename := i == len(splitPattern)-1 && strings.Contains(part, ".") @@ -57,11 +62,15 @@ func (p *pattern) parse() (err error) { // now we split the pattern according to the recursive '**' syntax p.parsedValues = strings.Split(patternWithoutDir, "**") for i, pp := range p.parsedValues { - p.parsedValues[i] = strings.Trim(pp, string(filepath.Separator)) + p.parsedValues[i] = strings.Trim(pp, sep) } - // remove the trailing separator and add leading separator - p.value = string(filepath.Separator) + strings.Trim(p.value, string(filepath.Separator)) + // remove the trailing separator and add leading separator (except on Windows) + if volumeName == "" { + p.value = sep + strings.Trim(p.value, sep) + } else { + p.value = volumeName + sep + strings.Trim(p.value, sep) + } // try to canonicalize the path canonicalPattern, err := filepath.EvalSymlinks(p.value) @@ -123,7 +132,7 @@ func (p *pattern) isValidPattern(fileName string) bool { } // remove the directory path and separator from the filename - fileNameWithoutDir := strings.TrimPrefix(strings.TrimPrefix(fileName, p.value), string(filepath.Separator)) + fileNameWithoutDir := strings.TrimPrefix(strings.TrimPrefix(fileName, p.value), sep) // if the pattern has size 1 we can match it directly against the filename if len(p.parsedValues) == 1 { @@ -134,12 +143,12 @@ func (p *pattern) isValidPattern(fileName string) bool { } func (p *pattern) matchPatterns(fileName string) bool { - partsToMatch := strings.Split(fileName, string(filepath.Separator)) + partsToMatch := strings.Split(fileName, sep) cursor := 0 // if there are multiple parsedValues due to '**' we need to match them individually for i, pattern := range p.parsedValues { - patternSize := strings.Count(pattern, string(filepath.Separator)) + 1 + patternSize := strings.Count(pattern, sep) + 1 // if we are at the last pattern we will start matching from the end of the filename if i == len(p.parsedValues)-1 { @@ -157,7 +166,7 @@ func (p *pattern) matchPatterns(fileName string) bool { } cursor = j - subPattern := strings.Join(partsToMatch[j:j+patternSize], string(filepath.Separator)) + subPattern := strings.Join(partsToMatch[j:j+patternSize], sep) if matchCurlyBracePattern(pattern, subPattern) { cursor = j + patternSize - 1 diff --git a/internal/watcher/pattern_test.go b/internal/watcher/pattern_test.go index 25b4dd58da..8ee3907c7e 100644 --- a/internal/watcher/pattern_test.go +++ b/internal/watcher/pattern_test.go @@ -4,6 +4,7 @@ package watcher import ( "path/filepath" + "strings" "testing" "github.com/e-dant/watcher/watcher-go" @@ -11,16 +12,42 @@ import ( "github.com/stretchr/testify/require" ) +func normalizePath(t *testing.T, path string) string { + t.Helper() + + if filepath.Separator == '/' { + return path + } + + path = filepath.FromSlash(path) + if strings.HasPrefix(path, "\\") { + path = "C:\\" + path[1:] + } + + return path +} + +func newPattern(t *testing.T, value string) pattern { + t.Helper() + + p := pattern{value: normalizePath(t, value)} + require.NoError(t, p.parse()) + + return p +} + func TestDisallowOnEventTypeBiggerThan3(t *testing.T) { - w := pattern{value: "/some/path"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t, "/some/path") assert.False(t, w.allowReload(&watcher.Event{PathName: "/some/path/watch-me.php", EffectType: watcher.EffectTypeOwner})) } func TestDisallowOnPathTypeBiggerThan2(t *testing.T) { - w := pattern{value: "/some/path"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t, "/some/path") assert.False(t, w.allowReload(&watcher.Event{PathName: "/some/path/watch-me.php", PathType: watcher.PathTypeSymLink})) } @@ -59,7 +86,7 @@ func TestValidRecursiveDirectories(t *testing.T) { data := []struct { pattern string - dir string + file string }{ {"/path", "/path/file.php"}, {"/path", "/path/subpath/file.php"}, @@ -77,7 +104,7 @@ func TestValidRecursiveDirectories(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.file) }) } } @@ -98,7 +125,7 @@ func TestInvalidRecursiveDirectories(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -122,7 +149,7 @@ func TestValidNonRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } @@ -145,7 +172,7 @@ func TestInValidNonRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -170,7 +197,7 @@ func TestValidRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } @@ -198,7 +225,7 @@ func TestInvalidRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -225,13 +252,14 @@ func TestValidDirectoryPatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } func TestInvalidDirectoryPatterns(t *testing.T) { t.Parallel() + data := []struct { pattern string dir string @@ -254,15 +282,17 @@ func TestInvalidDirectoryPatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } func TestValidCurlyBracePatterns(t *testing.T) { + t.Parallel() + data := []struct { pattern string - dir string + file string }{ {"/path/*.{php}", "/path/file.php"}, {"/path/*.{php,twig}", "/path/file.php"}, @@ -282,12 +312,14 @@ func TestValidCurlyBracePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.file) }) } } func TestInvalidCurlyBracePatterns(t *testing.T) { + t.Parallel() + data := []struct { pattern string dir string @@ -306,52 +338,52 @@ func TestInvalidCurlyBracePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } - } func TestAnAssociatedEventTriggersTheWatcher(t *testing.T) { - w := pattern{value: "/**/*.php"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t, "/**/*.php") w.events = make(chan eventHolder) - e := &watcher.Event{PathName: "/path/temporary_file", AssociatedPathName: "/path/file.php"} + e := &watcher.Event{PathName: normalizePath(t, "/path/temporary_file"), AssociatedPathName: normalizePath(t, "/path/file.php")} go w.handle(e) assert.Equal(t, e, (<-w.events).event) } func relativeDir(t *testing.T, relativePath string) string { + t.Helper() + dir, err := filepath.Abs("./" + relativePath) assert.NoError(t, err) + return dir } func hasDir(t *testing.T, p string, dir string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.Equal(t, dir, w.value) + assert.Equal(t, normalizePath(t, dir), w.value) } -func shouldMatch(t *testing.T, p string, fileName string) { +func assertPatternMatch(t *testing.T, p, fileName string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.True(t, w.allowReload(&watcher.Event{PathName: fileName})) + assert.True(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } -func shouldNotMatch(t *testing.T, p string, fileName string) { +func assertPatternNotMatch(t *testing.T, p, fileName string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.False(t, w.allowReload(&watcher.Event{PathName: fileName})) + assert.False(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } diff --git a/mercure.go b/mercure.go index 9dc27e2290..d7cf33609e 100644 --- a/mercure.go +++ b/mercure.go @@ -3,6 +3,7 @@ package frankenphp // #include +// #include "frankenphp.h" // #include import "C" import ( diff --git a/phpmainthread.go b/phpmainthread.go index cecadc1653..1d7383cc57 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -4,8 +4,8 @@ package frankenphp // #cgo nocallback frankenphp_init_persistent_string // #cgo noescape frankenphp_new_main_thread // #cgo noescape frankenphp_init_persistent_string -// #include // #include "frankenphp.h" +// #include import "C" import ( "log/slog" diff --git a/phpmainthread_test.go b/phpmainthread_test.go index 7e6bf32c1e..3282f46364 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -96,14 +96,14 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) { var ( isDone atomic.Bool - wg sync.WaitGroup + wg sync.WaitGroup ) numThreads := 10 numRequestsPerThread := 100 - worker1Path := testDataPath + "/transition-worker-1.php" + worker1Path := filepath.Join(testDataPath, "transition-worker-1.php") worker1Name := "worker-1" - worker2Path := testDataPath + "/transition-worker-2.php" + worker2Path := filepath.Join(testDataPath, "transition-worker-2.php") worker2Name := "worker-2" assert.NoError(t, Init( diff --git a/scaling.go b/scaling.go index 37e081abb9..51acd1cd37 100644 --- a/scaling.go +++ b/scaling.go @@ -1,8 +1,5 @@ package frankenphp -//#include "frankenphp.h" -//#include -import "C" import ( "errors" "log/slog" diff --git a/scaling_test.go b/scaling_test.go index f7ecc05e05..c79ae6e044 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -1,6 +1,7 @@ package frankenphp import ( + "path/filepath" "testing" "time" @@ -33,7 +34,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) { t.Cleanup(Shutdown) workerName := "worker1" - workerPath := testDataPath + "/transition-worker-1.php" + workerPath := filepath.Join(testDataPath, "transition-worker-1.php") assert.NoError(t, Init( WithNumThreads(2), WithMaxThreads(3), diff --git a/testdata/session.php b/testdata/session.php index dacc631151..9598359c6a 100644 --- a/testdata/session.php +++ b/testdata/session.php @@ -11,5 +11,5 @@ $_SESSION['count'] = 0; } - echo 'Count: '.$_SESSION['count'].PHP_EOL; + echo 'Count: '.$_SESSION['count']."\n"; }; diff --git a/types.h b/types.h index 552ddfe7fa..619603ab97 100644 --- a/types.h +++ b/types.h @@ -1,11 +1,11 @@ #ifndef TYPES_H #define TYPES_H +#include "frankenphp.h" #include #include #include #include -#include zval *get_ht_packed_data(HashTable *, uint32_t index); Bucket *get_ht_bucket_data(HashTable *, uint32_t index); diff --git a/watcher_test.go b/watcher_test.go index 3e0d9d108a..6ccca16aec 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -50,7 +50,7 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re // now we spam file updates and check if the request counter resets for range limit { - updateTestFile("./testdata/files/test.txt", "updated", t) + updateTestFile(t, filepath.Join(".", "testdata", "files", "test.txt"), "updated") time.Sleep(pollingTime * time.Millisecond) body, _ := testGet("http://example.com/worker-with-counter.php", handler, t) if body == "requests:1" { @@ -61,15 +61,10 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re return false } -func updateTestFile(fileName string, content string, t *testing.T) { +func updateTestFile(t *testing.T, fileName, content string) { absFileName, err := filepath.Abs(fileName) require.NoError(t, err) - dirName := filepath.Dir(absFileName) - if _, err = os.Stat(dirName); os.IsNotExist(err) { - err = os.MkdirAll(dirName, 0700) - } - require.NoError(t, err) - + require.NoError(t, os.MkdirAll(filepath.Dir(absFileName), 0700)) require.NoError(t, os.WriteFile(absFileName, []byte(content), 0644)) }