mirror of
https://github.com/php/frankenphp.git
synced 2026-03-24 00:52:11 +01:00
Closes #83 #880 #1286. Working patch for Windows support. Supports linking to the [official PHP release (TS version)](https://www.php.net/downloads.php). Includes some work from #1286 (thanks @TenHian!!) This patch allows using Visual Studio to compile the cgo code. To do so, it must be compiled with Go 1.26 (RC) with the following setup: ```powershell winget install -e --id Microsoft.VisualStudio.2022.Community --override "--passive --wait --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended" winget install -e --id GoLang.Go $env:PATH += ';C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin' cd c:\ gh repo clone microsoft/vcpkg .\vcpkg\bootstrap-vcpkg.bat .\vcpkg\vcpkg install pthreads brotli # build watcher Invoke-WebRequest -Uri "https://github.com/e-dant/watcher/releases/download/0.14.3/x86_64-pc-windows-msvc.tar" -OutFile "$env:TEMP\watcher.tar" tar -xf "$env:TEMP\watcher.tar" -C C:\ Rename-Item -Path "C:\x86_64-pc-windows-msvc" -NewName "watcher-x86_64-pc-windows-msvc" Remove-Item "$env:TEMP\watcher.tar" # download php Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php.zip" Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "C:\" Remove-Item "$env:TEMP\php.zip" # download php development package Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-devel-pack-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php-devel.zip" Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "C:\" Remove-Item "$env:TEMP\php-devel.zip" $env:GOTOOLCHAIN = 'go1.26rc1' $env:CC = 'clang' $env:CXX = 'clang++' $env:CGO_CFLAGS = "-I$env:C:\vcpkg\installed\x64-windows\include -IC:\watcher-x86_64-pc-windows-msvc -IC:\php-8.5.1-devel-vs17-x64\include -IC:\php-8.5.1-devel-vs17-x64\include\main -IC:\php-8.5.1-devel-vs17-x64\include\TSRM -IC:\php-8.5.1-devel-vs17-x64\include\Zend -IC:\php-8.5.1-devel-vs17-x64\include\ext" $env:CGO_LDFLAGS = '-LC:\vcpkg\installed\x64-windows\lib -lbrotlienc -LC:\watcher-x86_64-pc-windows-msvc -llibwatcher-c -LC:\php-8.5.1-Win32-vs17-x64 -LC:\php-8.5.1-devel-vs17-x64\lib -lphp8ts -lphp8embed' # clone frankenphp and build git clone -b windows https://github.com/php/frankenphp.git cd frankenphp\caddy\frankenphp go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx # Tests $env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin;C:\watcher-x86_64-pc-windows-msvc";C:\php-8.5.1-Win32-vs17-x64" "opcache.enable=0`r`nopcache.enable_cli=0" | Out-File -Encoding ascii php.ini $env:PHPRC = Get-Location go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx . ``` TODO: - [x] Fix remaining skipped tests (scaling and watcher) - [x] Test if the watcher mode works as expected - [x] Automate the build with GitHub Actions --------- Signed-off-by: Marc <m@pyc.ac> Co-authored-by: Kévin Dunglas <kevin@dunglas.dev> Co-authored-by: DubbleClick <m@pyc.ac>
248 lines
5.2 KiB
Go
248 lines
5.2 KiB
Go
package extgen
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestWriteFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
filename string
|
|
content string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "write simple file",
|
|
filename: "test.txt",
|
|
content: "hello world",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "write empty file",
|
|
filename: "empty.txt",
|
|
content: "",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "write file with special characters",
|
|
filename: "special.txt",
|
|
content: "hello\nworld\t!@#$%^&*()",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "write to invalid directory",
|
|
filename: "/nonexistent/directory/file.txt",
|
|
content: "test",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var filename string
|
|
if !tt.expectError {
|
|
tempDir := t.TempDir()
|
|
filename = filepath.Join(tempDir, tt.filename)
|
|
} else {
|
|
filename = tt.filename
|
|
}
|
|
|
|
err := writeFile(filename, tt.content)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, "writeFile() should return an error")
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err, "writeFile() should not return an error")
|
|
|
|
content, err := os.ReadFile(filename)
|
|
assert.NoError(t, err, "Failed to read written file")
|
|
assert.Equal(t, tt.content, string(content), "writeFile() content mismatch")
|
|
|
|
info, err := os.Stat(filename)
|
|
assert.NoError(t, err, "Failed to stat file")
|
|
|
|
expectedMode := os.FileMode(0644)
|
|
if runtime.GOOS == "windows" {
|
|
expectedMode = os.FileMode(0666)
|
|
}
|
|
|
|
assert.Equal(t, expectedMode, info.Mode().Perm(), "writeFile() wrong permissions")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReadFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
content string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "read simple file",
|
|
content: "hello world",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "read empty file",
|
|
content: "",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "read file with special characters",
|
|
content: "hello\nworld\t!@#$%^&*()",
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
filename := filepath.Join(tempDir, "test.txt")
|
|
|
|
err := os.WriteFile(filename, []byte(tt.content), 0644)
|
|
assert.NoError(t, err, "Failed to create test file")
|
|
|
|
content, err := readFile(filename)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, "readFile() should return an error")
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err, "readFile() should not return an error")
|
|
assert.Equal(t, tt.content, content, "readFile() content mismatch")
|
|
})
|
|
}
|
|
|
|
t.Run("read nonexistent file", func(t *testing.T) {
|
|
_, err := readFile("/nonexistent/file.txt")
|
|
assert.Error(t, err, "readFile() should return an error for nonexistent file")
|
|
})
|
|
}
|
|
|
|
func TestSanitizePackageName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "simple valid name",
|
|
input: "mypackage",
|
|
expected: "mypackage",
|
|
},
|
|
{
|
|
name: "name with hyphens",
|
|
input: "my-package",
|
|
expected: "my_package",
|
|
},
|
|
{
|
|
name: "name with dots",
|
|
input: "my.package",
|
|
expected: "my_package",
|
|
},
|
|
{
|
|
name: "name with both hyphens and dots",
|
|
input: "my-package.name",
|
|
expected: "my_package_name",
|
|
},
|
|
{
|
|
name: "name starting with number",
|
|
input: "123package",
|
|
expected: "_123package",
|
|
},
|
|
{
|
|
name: "name starting with underscore",
|
|
input: "_package",
|
|
expected: "_package",
|
|
},
|
|
{
|
|
name: "name starting with letter",
|
|
input: "Package",
|
|
expected: "Package",
|
|
},
|
|
{
|
|
name: "name starting with special character",
|
|
input: "@package",
|
|
expected: "_@package",
|
|
},
|
|
{
|
|
name: "complex name",
|
|
input: "123my-complex.package@name",
|
|
expected: "_123my_complex_package@name",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
input: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "single character letter",
|
|
input: "a",
|
|
expected: "a",
|
|
},
|
|
{
|
|
name: "single character number",
|
|
input: "1",
|
|
expected: "_1",
|
|
},
|
|
{
|
|
name: "single character underscore",
|
|
input: "_",
|
|
expected: "_",
|
|
},
|
|
{
|
|
name: "single character special",
|
|
input: "@",
|
|
expected: "_@",
|
|
},
|
|
{
|
|
name: "multiple consecutive hyphens",
|
|
input: "my--package",
|
|
expected: "my__package",
|
|
},
|
|
{
|
|
name: "multiple consecutive dots",
|
|
input: "my..package",
|
|
expected: "my__package",
|
|
},
|
|
{
|
|
name: "mixed case with special chars",
|
|
input: "MyPackage-name.version",
|
|
expected: "MyPackage_name_version",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := SanitizePackageName(tt.input)
|
|
assert.Equal(t, tt.expected, result, "SanitizePackageName(%q)", tt.input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkSanitizePackageName(b *testing.B) {
|
|
testCases := []string{
|
|
"simple",
|
|
"my-package",
|
|
"my.package.name",
|
|
"123complex-package.name@version",
|
|
"very-long-package-name-with-many-special-characters.and.dots",
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
b.Run(tc, func(b *testing.B) {
|
|
for b.Loop() {
|
|
SanitizePackageName(tc)
|
|
}
|
|
})
|
|
}
|
|
}
|