Repository: php/pie (php)

Issue:
UnixBuild does not use target platform's phpize
`\Php\Pie\Building\UnixBuild::phpize` just uses the `phpize` from the path, it does not actually use the target platform's phpize.

Pull request description:
Fix phpize path used for UnixBuild & a bunch of test fixes
Fixes #58 

Patch:
diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php
index 61c42c5..b965f8b 100644
--- a/src/Building/UnixBuild.php
+++ b/src/Building/UnixBuild.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Php\Pie\Building;
 
 use Php\Pie\Downloading\DownloadedPackage;
+use Php\Pie\Platform\TargetPhp\PhpizePath;
 use Php\Pie\Platform\TargetPlatform;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Process\Process;
@@ -24,7 +25,14 @@ final class UnixBuild implements Build
         array $configureOptions,
         OutputInterface $output,
     ): void {
-        $this->phpize($downloadedPackage);
+        $phpizeOutput = $this->phpize(
+            PhpizePath::guessFrom($targetPlatform->phpBinaryPath),
+            $downloadedPackage,
+        );
+        if ($output->isVeryVerbose()) {
+            $output->writeln($phpizeOutput);
+        }
+
         $output->writeln('<info>phpize complete</info>.');
 
         $phpConfigPath = $targetPlatform->phpBinaryPath->phpConfigPath();
@@ -32,11 +40,18 @@ final class UnixBuild implements Build
             $configureOptions[] = '--with-php-config=' . $phpConfigPath;
         }
 
-        $this->configure($downloadedPackage, $configureOptions);
+        $configureOutput = $this->configure($downloadedPackage, $configureOptions);
+        if ($output->isVeryVerbose()) {
+            $output->writeln($configureOutput);
+        }
+
         $optionsOutput = count($configureOptions) ? ' with options: ' . implode(' ', $configureOptions) : '.';
         $output->writeln('<info>Configure complete</info>' . $optionsOutput);
 
-        $this->make($downloadedPackage);
+        $makeOutput = $this->make($downloadedPackage);
+        if ($output->isVeryVerbose()) {
+            $output->writeln($makeOutput);
+        }
 
         $expectedSoFile = $downloadedPackage->extractedSourcePath . '/modules/' . $downloadedPackage->package->extensionName->name() . '.so';
 
@@ -55,22 +70,25 @@ final class UnixBuild implements Build
         ));
     }
 
-    private function phpize(DownloadedPackage $downloadedPackage): void
+    private function phpize(PhpizePath $phpize, DownloadedPackage $downloadedPackage): string
     {
-        (new Process(['phpize'], $downloadedPackage->extractedSourcePath))
-            ->mustRun();
+        return (new Process([$phpize->phpizeBinaryPath], $downloadedPackage->extractedSourcePath))
+            ->mustRun()
+            ->getOutput();
     }
 
     /** @param list<non-empty-string> $configureOptions */
-    private function configure(DownloadedPackage $downloadedPackage, array $configureOptions = []): void
+    private function configure(DownloadedPackage $downloadedPackage, array $configureOptions = []): string
     {
-        (new Process(['./configure', ...$configureOptions], $downloadedPackage->extractedSourcePath))
-            ->mustRun();
+        return (new Process(['./configure', ...$configureOptions], $downloadedPackage->extractedSourcePath))
+            ->mustRun()
+            ->getOutput();
     }
 
-    private function make(DownloadedPackage $downloadedPackage): void
+    private function make(DownloadedPackage $downloadedPackage): string
     {
-        (new Process(['make'], $downloadedPackage->extractedSourcePath))
-            ->mustRun();
+        return (new Process(['make'], $downloadedPackage->extractedSourcePath))
+            ->mustRun()
+            ->getOutput();
     }
 }
diff --git a/src/Installing/UnixInstall.php b/src/Installing/UnixInstall.php
index c76f8f4..f354cb9 100644
--- a/src/Installing/UnixInstall.php
+++ b/src/Installing/UnixInstall.php
@@ -44,10 +44,14 @@ final class UnixInstall implements Install
             array_unshift($makeInstallCommand, 'sudo');
         }
 
-        (new Process($makeInstallCommand, $downloadedPackage->extractedSourcePath))
+        $makeInstallOutput = (new Process($makeInstallCommand, $downloadedPackage->extractedSourcePath))
             ->mustRun()
             ->getOutput();
 
+        if ($output->isVeryVerbose()) {
+            $output->writeln($makeInstallOutput);
+        }
+
         if (! file_exists($expectedSharedObjectLocation)) {
             throw new RuntimeException('Install failed, ' . $expectedSharedObjectLocation . ' was not installed.');
         }
diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php
index d7da68b..8f99f0d 100644
--- a/src/Platform/TargetPhp/PhpBinaryPath.php
+++ b/src/Platform/TargetPhp/PhpBinaryPath.php
@@ -67,6 +67,20 @@ class PhpBinaryPath
         }
     }
 
+    /** @return non-empty-string */
+    public function phpApiVersion(): string
+    {
+        if (
+            preg_match('/PHP API([ =>\t]*)(.*)/', $this->phpinfo(), $m)
+            && array_key_exists(2, $m)
+            && $m[2] !== ''
+        ) {
+            return $m[2];
+        }
+
+        throw new RuntimeException('Failed to find PHP API version...');
+    }
+
     /** @return non-empty-string */
     public function extensionPath(): string
     {
diff --git a/src/Platform/TargetPhp/PhpizePath.php b/src/Platform/TargetPhp/PhpizePath.php
new file mode 100644
index 0000000..7251e2f
--- /dev/null
+++ b/src/Platform/TargetPhp/PhpizePath.php
@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Php\Pie\Platform\TargetPhp;
+
+use RuntimeException;
+use Symfony\Component\Process\Process;
+
+use function array_key_exists;
+use function assert;
+use function file_exists;
+use function is_executable;
+use function preg_match;
+use function preg_replace;
+use function trim;
+
+/**
+ * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
+ *
+ * @immutable
+ */
+final class PhpizePath
+{
+    /** @param non-empty-string $phpizeBinaryPath */
+    public function __construct(public readonly string $phpizeBinaryPath)
+    {
+    }
+
+    public static function guessFrom(PhpBinaryPath $phpBinaryPath): self
+    {
+        $expectedApiVersion = $phpBinaryPath->phpApiVersion();
+
+        $phpizeAttempts = [];
+
+        // Try to add `phpize` from path
+        $whichPhpize = new Process(['which', 'phpize']);
+        if ($whichPhpize->run() === 0) {
+            $phpizeAttempts[] = trim($whichPhpize->getOutput());
+        }
+
+        // Try to guess based on the `php` path itself
+        $phpizeAttempts[] = preg_replace('((.*)php)', '$1phpize', $phpBinaryPath->phpBinaryPath);
+
+        foreach ($phpizeAttempts as $phpizeAttempt) {
+            assert($phpizeAttempt !== '');
+            if (! file_exists($phpizeAttempt) || ! is_executable($phpizeAttempt)) {
+                continue;
+            }
+
+            $phpizeProcess = new Process([$phpizeAttempt, '--version']);
+            if ($phpizeProcess->run() !== 0) {
+                continue;
+            }
+
+            if (
+                ! preg_match('/PHP Api Version:\s*(.*)/', $phpizeProcess->getOutput(), $m)
+                || ! array_key_exists(1, $m)
+                || $m[1] === ''
+            ) {
+                continue;
+            }
+
+            if ($expectedApiVersion === $m[1]) {
+                return new self($phpizeAttempt);
+            }
+        }
+
+        // @todo add a way to specify --phpize or similar
+        throw new RuntimeException('did not find a good phpize, oh');
+    }
+}


Test patch:
diff --git a/test/integration/Command/BuildCommandTest.php b/test/integration/Command/BuildCommandTest.php
index 9abb9ba..e1cb9da 100644
--- a/test/integration/Command/BuildCommandTest.php
+++ b/test/integration/Command/BuildCommandTest.php
@@ -11,9 +11,6 @@ use PHPUnit\Framework\Attributes\CoversClass;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Tester\CommandTester;
 
-use const PHP_VERSION;
-use const PHP_VERSION_ID;
-
 #[CoversClass(BuildCommand::class)]
 class BuildCommandTest extends TestCase
 {
@@ -28,10 +25,6 @@ class BuildCommandTest extends TestCase
 
     public function testBuildCommandWillBuildTheExtension(): void
     {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE]);
 
         $this->commandTester->assertCommandIsSuccessful();
diff --git a/test/integration/Command/DownloadCommandTest.php b/test/integration/Command/DownloadCommandTest.php
index 8cc5127..07e836d 100644
--- a/test/integration/Command/DownloadCommandTest.php
+++ b/test/integration/Command/DownloadCommandTest.php
@@ -46,14 +46,18 @@ class DownloadCommandTest extends TestCase
     public static function validVersionsList(): array
     {
         $versionsAndExpected = [
-            [self::TEST_PACKAGE, self::TEST_PACKAGE . ':1.0.1'],
-            [self::TEST_PACKAGE . ':^1.0', self::TEST_PACKAGE . ':1.0.1'],
-            [self::TEST_PACKAGE . ':1.0.1-alpha.3@alpha', self::TEST_PACKAGE . ':1.0.1-alpha.3'],
-            [self::TEST_PACKAGE . ':*', self::TEST_PACKAGE . ':1.0.1'],
-            [self::TEST_PACKAGE . ':~1.0.0@alpha', self::TEST_PACKAGE . ':1.0.1'],
-            [self::TEST_PACKAGE . ':~1.0.0', self::TEST_PACKAGE . ':1.0.1'],
+            [self::TEST_PACKAGE, self::TEST_PACKAGE . ':2.0.0'],
+            [self::TEST_PACKAGE . ':*', self::TEST_PACKAGE . ':2.0.0'],
+            [self::TEST_PACKAGE . ':^2.0', self::TEST_PACKAGE . ':2.0.0'],
         ];
 
+        if (PHP_VERSION_ID >= 80300 && PHP_VERSION_ID <= 80400) {
+            $versionsAndExpected[] = [self::TEST_PACKAGE . ':^1.0', self::TEST_PACKAGE . ':1.0.1'];
+            $versionsAndExpected[] = [self::TEST_PACKAGE . ':1.0.1-alpha.3@alpha', self::TEST_PACKAGE . ':1.0.1-alpha.3'];
+            $versionsAndExpected[] = [self::TEST_PACKAGE . ':~1.0.0@alpha', self::TEST_PACKAGE . ':1.0.1'];
+            $versionsAndExpected[] = [self::TEST_PACKAGE . ':~1.0.0', self::TEST_PACKAGE . ':1.0.1'];
+        }
+
         return array_combine(
             array_map(static fn ($item) => $item[0], $versionsAndExpected),
             $versionsAndExpected,
@@ -65,10 +69,6 @@ class DownloadCommandTest extends TestCase
         string $requestedVersion,
         string $expectedVersion,
     ): void {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         $this->commandTester->execute(['requested-package-and-version' => $requestedVersion]);
 
         $this->commandTester->assertCommandIsSuccessful();
@@ -84,10 +84,6 @@ class DownloadCommandTest extends TestCase
             self::markTestSkipped('This test can only run on non-Windows systems');
         }
 
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         $this->commandTester->execute(['requested-package-and-version' => 'asgrim/example-pie-extension:dev-main#9b5e6c80a1e05556e4e6824f0c112a4992cee001']);
 
         $this->commandTester->assertCommandIsSuccessful();
diff --git a/test/integration/Command/InfoCommandTest.php b/test/integration/Command/InfoCommandTest.php
index ab50561..2db28b5 100644
--- a/test/integration/Command/InfoCommandTest.php
+++ b/test/integration/Command/InfoCommandTest.php
@@ -10,9 +10,6 @@ use PHPUnit\Framework\Attributes\CoversClass;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Tester\CommandTester;
 
-use const PHP_VERSION;
-use const PHP_VERSION_ID;
-
 #[CoversClass(InfoCommand::class)]
 final class InfoCommandTest extends TestCase
 {
@@ -25,10 +22,6 @@ final class InfoCommandTest extends TestCase
 
     public function testInfoCommandDisplaysInformation(): void
     {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         $this->commandTester->execute(['requested-package-and-version' => 'asgrim/example-pie-extension:dev-main#9b5e6c80a1e05556e4e6824f0c112a4992cee001']);
 
         $this->commandTester->assertCommandIsSuccessful();
diff --git a/test/integration/Command/InstallCommandTest.php b/test/integration/Command/InstallCommandTest.php
index 4fad738..e648887 100644
--- a/test/integration/Command/InstallCommandTest.php
+++ b/test/integration/Command/InstallCommandTest.php
@@ -8,22 +8,25 @@ use Composer\Util\Platform;
 use Php\Pie\Command\InstallCommand;
 use Php\Pie\Container;
 use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Output\BufferedOutput;
 use Symfony\Component\Console\Tester\CommandTester;
 use Symfony\Component\Process\Process;
 
+use function array_combine;
+use function array_filter;
 use function array_key_exists;
+use function array_map;
 use function array_unshift;
 use function assert;
 use function file_exists;
+use function is_executable;
 use function is_file;
 use function is_string;
 use function is_writable;
 use function preg_match;
 
-use const PHP_VERSION;
-use const PHP_VERSION_ID;
-
 #[CoversClass(InstallCommand::class)]
 class InstallCommandTest extends TestCase
 {
@@ -36,17 +39,48 @@ class InstallCommandTest extends TestCase
         $this->commandTester = new CommandTester(Container::factory()->get(InstallCommand::class));
     }
 
-    public function testInstallCommandWillInstallCompatibleExtensionNonWindows(): void
+    /**
+     * @return array<string, array{0: string}>
+     *
+     * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
+     */
+    public static function phpPathProvider(): array
     {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
+        // data providers cannot return empty, even if the test is skipped
+        if (Platform::isWindows()) {
+            return ['skip' => ['skip']];
         }
 
+        $possiblePhpConfigPaths = array_filter(
+            [
+                '/usr/bin/php-config',
+                '/usr/bin/php-config8.3',
+                '/usr/bin/php-config8.2',
+                '/usr/bin/php-config8.1',
+                '/usr/bin/php-config8.0',
+                '/usr/bin/php-config7.4',
+            ],
+            static fn (string $phpConfigPath) => file_exists($phpConfigPath)
+                && is_executable($phpConfigPath),
+        );
+
+        return array_combine(
+            $possiblePhpConfigPaths,
+            array_map(static fn (string $phpConfigPath) => [$phpConfigPath], $possiblePhpConfigPaths),
+        );
+    }
+
+    #[DataProvider('phpPathProvider')]
+    public function testInstallCommandWillInstallCompatibleExtensionNonWindows(string $phpConfigPath): void
+    {
         if (Platform::isWindows()) {
             self::markTestSkipped('This test can only run on non-Windows systems');
         }
 
-        $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE]);
+        $this->commandTester->execute(
+            ['requested-package-and-version' => self::TEST_PACKAGE, '--with-php-config' => $phpConfigPath],
+            ['verbosity' => BufferedOutput::VERBOSITY_VERY_VERBOSE],
+        );
 
         $this->commandTester->assertCommandIsSuccessful();
 
@@ -76,10 +110,6 @@ class InstallCommandTest extends TestCase
 
     public function testInstallCommandWillInstallCompatibleExtensionWindows(): void
     {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         if (! Platform::isWindows()) {
             self::markTestSkipped('This test can only run on Windows systems');
         }
diff --git a/test/integration/DependencyResolver/ResolveDependencyWithComposerTest.php b/test/integration/DependencyResolver/ResolveDependencyWithComposerTest.php
index 4a1d4e9..b194a57 100644
--- a/test/integration/DependencyResolver/ResolveDependencyWithComposerTest.php
+++ b/test/integration/DependencyResolver/ResolveDependencyWithComposerTest.php
@@ -16,7 +16,6 @@ use PHPUnit\Framework\TestCase;
 use function array_combine;
 use function array_map;
 
-use const PHP_VERSION;
 use const PHP_VERSION_ID;
 
 #[CoversClass(ResolveDependencyWithComposer::class)]
@@ -35,22 +34,25 @@ final class ResolveDependencyWithComposerTest extends TestCase
     public static function validVersionsList(): array
     {
         $versionsAndExpected = [
-            [null, '1.0.1', self::DOWNLOAD_URL_ANY],
-            ['*', '1.0.1', self::DOWNLOAD_URL_ANY],
-            ['1.0.1-alpha.3@alpha', '1.0.1-alpha.3', self::DOWNLOAD_URL_1_0_1_ALPHA_3],
-            ['^1.0', '1.0.1', self::DOWNLOAD_URL_1_0_1],
-            ['^1.1.0@alpha', '1.1.0-beta.1', self::DOWNLOAD_URL_1_1_0_BETA_1],
-            ['^1.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1],
-            ['^1.1@beta', '1.1.0-beta.1', self::DOWNLOAD_URL_1_1_0_BETA_1],
-            ['~1.0.0', '1.0.1', self::DOWNLOAD_URL_1_0_1],
-            ['~1.0.0@alpha', '1.0.1', self::DOWNLOAD_URL_1_0_1],
-            ['~1.0.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1],
-            ['~1.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1],
+            [null, '2.0.0', self::DOWNLOAD_URL_ANY],
+            ['*', '2.0.0', self::DOWNLOAD_URL_ANY],
             ['dev-main', 'dev-main', self::DOWNLOAD_URL_ANY],
             ['dev-main#769f906413d6d1e12152f6d34134cbcd347ca253', 'dev-main', self::DOWNLOAD_URL_1_0_1],
-            ['v1.x-dev', 'v1.x-dev', self::DOWNLOAD_URL_1_1_0_BETA_1],
         ];
 
+        if (PHP_VERSION_ID >= 80300 && PHP_VERSION_ID <= 80400) {
+            $versionsAndExpected[] = ['1.0.1-alpha.3@alpha', '1.0.1-alpha.3', self::DOWNLOAD_URL_1_0_1_ALPHA_3];
+            $versionsAndExpected[] = ['^1.0', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['^1.1.0@alpha', '1.1.0-beta.1', self::DOWNLOAD_URL_1_1_0_BETA_1];
+            $versionsAndExpected[] = ['^1.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['^1.1@beta', '1.1.0-beta.1', self::DOWNLOAD_URL_1_1_0_BETA_1];
+            $versionsAndExpected[] = ['~1.0.0', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['~1.0.0@alpha', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['~1.0.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['~1.0@beta', '1.0.1', self::DOWNLOAD_URL_1_0_1];
+            $versionsAndExpected[] = ['v1.x-dev', 'v1.x-dev', self::DOWNLOAD_URL_1_1_0_BETA_1];
+        }
+
         return array_combine(
             array_map(static fn ($item) => $item[0] ?? 'null', $versionsAndExpected),
             $versionsAndExpected,
@@ -63,10 +65,6 @@ final class ResolveDependencyWithComposerTest extends TestCase
         string $expectedVersion,
         string $expectedDownloadUrl,
     ): void {
-        if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) {
-            self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION);
-        }
-
         $container = Container::factory();
         $resolve   = $container->get(DependencyResolver::class);
 
diff --git a/test/integration/Installing/UnixInstallTest.php b/test/integration/Installing/UnixInstallTest.php
index 58c24f4..44fce17 100644
--- a/test/integration/Installing/UnixInstallTest.php
+++ b/test/integration/Installing/UnixInstallTest.php
@@ -15,12 +15,18 @@ use Php\Pie\Installing\UnixInstall;
 use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
 use Php\Pie\Platform\TargetPlatform;
 use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Console\Output\BufferedOutput;
-use Symfony\Component\Console\Output\NullOutput;
 use Symfony\Component\Process\Process;
 
+use function array_combine;
+use function array_filter;
+use function array_map;
 use function array_unshift;
+use function assert;
+use function file_exists;
+use function is_executable;
 use function is_writable;
 
 #[CoversClass(UnixInstall::class)]
@@ -28,14 +34,48 @@ final class UnixInstallTest extends TestCase
 {
     private const TEST_EXTENSION_PATH = __DIR__ . '/../../assets/pie_test_ext';
 
-    public function testUnixInstallCanInstallExtension(): void
+    /**
+     * @return array<string, array{0: non-empty-string}>
+     *
+     * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
+     */
+    public static function phpPathProvider(): array
     {
+        // data providers cannot return empty, even if the test is skipped
+        if (Platform::isWindows()) {
+            return ['skip' => ['skip']];
+        }
+
+        $possiblePhpConfigPaths = array_filter(
+            [
+                '/usr/bin/php-config',
+                '/usr/bin/php-config8.4',
+                '/usr/bin/php-config8.3',
+                '/usr/bin/php-config8.2',
+                '/usr/bin/php-config8.1',
+                '/usr/bin/php-config8.0',
+                '/usr/bin/php-config7.4',
+            ],
+            static fn (string $phpConfigPath) => file_exists($phpConfigPath)
+                && is_executable($phpConfigPath),
+        );
+
+        return array_combine(
+            $possiblePhpConfigPaths,
+            array_map(static fn (string $phpConfigPath) => [$phpConfigPath], $possiblePhpConfigPaths),
+        );
+    }
+
+    #[DataProvider('phpPathProvider')]
+    public function testUnixInstallCanInstallExtension(string $phpConfig): void
+    {
+        assert($phpConfig !== '');
         if (Platform::isWindows()) {
             self::markTestSkipped('Unix build test cannot be run on Windows');
         }
 
         $output         = new BufferedOutput();
-        $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess());
+        $targetPlatform = TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromPhpConfigExecutable($phpConfig));
         $extensionPath  = $targetPlatform->phpBinaryPath->extensionPath();
 
         $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
@@ -58,7 +98,7 @@ final class UnixInstallTest extends TestCase
             $downloadedPackage,
             $targetPlatform,
             ['--enable-pie_test_ext'],
-            new NullOutput(),
+            $output,
         );
 
         $installedSharedObject = (new UnixInstall())->__invoke(
@@ -66,8 +106,7 @@ final class UnixInstallTest extends TestCase
             $targetPlatform,
             $output,
         );
-
-        $outputString = $output->fetch();
+        $outputString          = $output->fetch();
 
         self::assertStringContainsString('Install complete: ' . $extensionPath . '/pie_test_ext.so', $outputString);
         self::assertStringContainsString('You must now add "extension=pie_test_ext" to your php.ini', $outputString);
diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
index a7c2f5a..01cd1da 100644
--- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
+++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php
@@ -13,8 +13,8 @@ use PHPUnit\Framework\Attributes\CoversClass;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Process\PhpExecutableFinder;
-use Symfony\Component\Process\Process;
 
+use function array_column;
 use function array_combine;
 use function array_filter;
 use function array_map;
@@ -30,7 +30,6 @@ use function is_executable;
 use function php_uname;
 use function phpversion;
 use function sprintf;
-use function trim;
 
 use const DIRECTORY_SEPARATOR;
 use const PHP_INT_SIZE;
@@ -87,28 +86,52 @@ final class PhpBinaryPathTest extends TestCase
         self::assertNull($phpBinary->phpConfigPath());
     }
 
-    public function testFromPhpConfigExecutable(): void
+    /**
+     * @return array<string, array{0: non-empty-string, 1: non-empty-string}>
+     *
+     * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
+     */
+    public static function phpConfigPathProvider(): array
     {
-        $process             = (new Process(['which', 'php-config']));
-        $exitCode            = $process->run();
-        $phpConfigExecutable = trim($process->getOutput());
+        // data providers cannot return empty, even if the test is skipped
+        if (Platform::isWindows()) {
+            return ['skip' => ['skip', 'skip']];
+        }
+
+        $possiblePhpConfigPaths = array_filter(
+            [
+                ['/usr/bin/php-config8.3', '8.3'],
+                ['/usr/bin/php-config8.2', '8.2'],
+                ['/usr/bin/php-config8.1', '8.1'],
+                ['/usr/bin/php-config8.0', '8.0'],
+                ['/usr/bin/php-config7.4', '7.4'],
+            ],
+            static fn (array $phpConfigPath) => file_exists($phpConfigPath[0])
+                && is_executable($phpConfigPath[0]),
+        );
 
-        if ($exitCode !== 0 || ! file_exists($phpConfigExecutable) || ! is_executable($phpConfigExecutable) || $phpConfigExecutable === '') {
-            self::markTestSkipped('Needs php-config in path to run this test');
+        return array_combine(
+            array_column($possiblePhpConfigPaths, 0),
+            $possiblePhpConfigPaths,
+        );
+    }
+
+    #[DataProvider('phpConfigPathProvider')]
+    public function testFromPhpConfigExecutable(string $phpConfigPath, string $expectedMajorMinor): void
+    {
+        if (Platform::isWindows()) {
+            self::markTestSkipped('Do not need to test php-config on Windows as we are not building on Windows.');
         }
 
-        $phpBinary = PhpBinaryPath::fromPhpConfigExecutable($phpConfigExecutable);
+        assert($phpConfigPath !== '');
+        $phpBinary = PhpBinaryPath::fromPhpConfigExecutable($phpConfigPath);
 
-        // NOTE: this makes an assumption that the `php-config` in path is the same as the version being executed
-        // In most cases, this will be the cases (e.g. in CI, running locally), but if you're trying to test this and
-        // the versions are not matching, that's probably why.
-        // @todo improve this assertion in future, if it becomes problematic
         self::assertSame(
-            sprintf('%s.%s.%s', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION),
-            $phpBinary->version(),
+            $expectedMajorMinor,
+            $phpBinary->majorMinorVersion(),
         );
 
-        self::assertSame($phpConfigExecutable, $phpBinary->phpConfigPath());
+        self::assertSame($phpConfigPath, $phpBinary->phpConfigPath());
     }
 
     public function testExtensions(): void
@@ -195,7 +218,7 @@ final class PhpBinaryPathTest extends TestCase
      *
      * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
      */
-    public function phpPathProvider(): array
+    public static function phpPathProvider(): array
     {
         $possiblePhpBinaries = array_filter(
             array_unique([
diff --git a/test/unit/Platform/TargetPhp/PhpizePathTest.php b/test/unit/Platform/TargetPhp/PhpizePathTest.php
new file mode 100644
index 0000000..74237cb
--- /dev/null
+++ b/test/unit/Platform/TargetPhp/PhpizePathTest.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Php\PieUnitTest\Platform\TargetPhp;
+
+use Composer\Util\Platform;
+use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
+use Php\Pie\Platform\TargetPhp\PhpizePath;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+use function array_combine;
+use function array_filter;
+use function array_map;
+use function array_unique;
+use function file_exists;
+use function is_executable;
+
+#[CoversClass(PhpizePath::class)]
+final class PhpizePathTest extends TestCase
+{
+    /**
+     * @return array<string, array{0: string}>
+     *
+     * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131
+     */
+    public static function phpPathProvider(): array
+    {
+        $possiblePhpBinaries = array_filter(
+            array_unique([
+                '/usr/bin/php',
+                (string) (new PhpExecutableFinder())->find(),
+                '/usr/bin/php8.4',
+                '/usr/bin/php8.3',
+                '/usr/bin/php8.2',
+                '/usr/bin/php8.1',
+                '/usr/bin/php8.0',
+                '/usr/bin/php7.4',
+                '/usr/bin/php7.3',
+                '/usr/bin/php7.2',
+                '/usr/bin/php7.1',
+                '/usr/bin/php7.0',
+                '/usr/bin/php5.6',
+            ]),
+            static fn (string $phpPath) => file_exists($phpPath) && is_executable($phpPath),
+        );
+
+        return array_combine(
+            $possiblePhpBinaries,
+            array_map(static fn (string $phpPath) => [$phpPath], $possiblePhpBinaries),
+        );
+    }
+
+    #[DataProvider('phpPathProvider')]
+    public function testGuessingFindsPhpizePath(): void
+    {
+        if (Platform::isWindows()) {
+            self::markTestSkipped('Guessing phpize path is not done for Windows as we are not building for Windows.');
+        }
+
+        $phpize = PhpizePath::guessFrom(PhpBinaryPath::fromCurrentProcess());
+
+        self::assertNotEmpty($phpize->phpizeBinaryPath);
+    }
+}