Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified builds/cpx
Binary file not shown.
28 changes: 14 additions & 14 deletions tests/Feature/BinaryResolutionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['package'], [
'package' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'package' => argvLoggingBinary($logFile),
]);

[$status] = runCpxCommand(['vendor/package', '--flag']);
Expand All @@ -24,8 +24,8 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['foo', 'bar'], [
'foo' => "#!/usr/bin/env php\n<?php exit(99);\n",
'bar' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'foo' => noopBinary(99),
'bar' => argvLoggingBinary($logFile),
]);

[$status] = runCpxCommand(['vendor/package', 'bar', '--flag']);
Expand All @@ -39,8 +39,8 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['package', 'other'], [
'package' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'other' => "#!/usr/bin/env php\n<?php exit(99);\n",
'package' => argvLoggingBinary($logFile),
'other' => noopBinary(99),
]);

[$status] = runCpxCommand(['vendor/package', 'tests/Sub']);
Expand Down Expand Up @@ -72,8 +72,8 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/pkg', ['pkg', 'other'], [
'pkg' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'other' => "#!/usr/bin/env php\n<?php exit(99);\n",
'pkg' => argvLoggingBinary($logFile),
'other' => noopBinary(99),
]);

[$status] = runCpxCommand(['vendor/pkg', 'pkg', 'tests/Sub']);
Expand All @@ -86,8 +86,8 @@
$this->useIsolatedComposerHome();

prepareCachedPackage('vendor/package', ['foo', 'bar'], [
'foo' => "#!/usr/bin/env php\n<?php exit(0);\n",
'bar' => "#!/usr/bin/env php\n<?php exit(0);\n",
'foo' => noopBinary(),
'bar' => noopBinary(),
]);
$root = $this->prepareLocalPackage(['bin/foo', 'bin/bar'], 'vendor/local');

Expand Down Expand Up @@ -115,8 +115,8 @@
$this->useIsolatedComposerHome();

prepareCachedPackage('vendor/package', ['foo', 'bar'], [
'foo' => "#!/usr/bin/env php\n<?php exit(0);\n",
'bar' => "#!/usr/bin/env php\n<?php exit(0);\n",
'foo' => noopBinary(),
'bar' => noopBinary(),
]);

[$status, $output] = runCpxCommand(['vendor/package']);
Expand All @@ -130,8 +130,8 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['foo', 'bar'], [
'foo' => "#!/usr/bin/env php\n<?php exit(99);\n",
'bar' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'foo' => noopBinary(99),
'bar' => argvLoggingBinary($logFile),
]);

UserAliases::open()
Expand All @@ -150,7 +150,7 @@
$this->useIsolatedComposerHome();

prepareCachedPackage('vendor/package', ['foo'], [
'foo' => "#!/usr/bin/env php\n<?php exit(0);\n",
'foo' => noopBinary(),
]);

UserAliases::open()
Expand Down
3 changes: 0 additions & 3 deletions tests/Feature/CleanCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
use Cpx\Cache\Metadata;
use Laravel\Prompts\Key;
use Laravel\Prompts\Prompt;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

uses(MockeryPHPUnitIntegration::class);

test('the all option removes every tracked package and exec cache directory', function () {
$this->useIsolatedComposerHome();
Expand Down
6 changes: 3 additions & 3 deletions tests/Feature/CliArgumentForwardingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['package'], [
'package' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'package' => argvLoggingBinary($logFile),
]);

[$status] = runCpxCommand(['vendor/package', '--name=two words', '--', '--literal', '-x']);
Expand All @@ -24,7 +24,7 @@
$logFile = $this->temporaryDirectory('cpx-log').'/argv.json';

prepareCachedPackage('vendor/package', ['package'], [
'package' => "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n",
'package' => argvLoggingBinary($logFile),
]);

[$status] = runCpxCommand(['vendor/package', '-x', '--flag', '--filter=one', '--filter=two', 'value with spaces', 'semi;colon', 'pipe|value', '$(touch injected)']);
Expand All @@ -47,7 +47,7 @@
$this->useIsolatedComposerHome();

prepareCachedPackage('vendor/package', ['package'], [
'package' => "#!/usr/bin/env php\n<?php exit(23);\n",
'package' => noopBinary(23),
]);

[$status] = runCpxCommand(['vendor/package']);
Expand Down
47 changes: 10 additions & 37 deletions tests/Feature/CommandBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
use Cpx\Input\PackageInvocation;
use Cpx\Packages\LocalPackage;
use Cpx\Packages\Package;
use Cpx\Packages\PackageCommandRunner;
use Cpx\Packages\UserAliases;
use Cpx\Process\ProcessResult;
use Cpx\Support\Filesystem;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\ApplicationTester;

test('it can run through Symfony tester utilities without exiting', function () {
Expand Down Expand Up @@ -144,7 +142,7 @@
$this->useIsolatedComposerHome();

$packageDirectory = prepareCachedPackage('vendor/custom-pint', ['pint']);
writeExecutable($packageDirectory.'/pint', "#!/usr/bin/env php\n<?php exit(0);\n");
writeExecutable($packageDirectory.'/pint', noopBinary());
UserAliases::open()->put('pint', Package::parse('vendor/custom-pint'))->save();

[$status, $output] = runCpxCommand(['pint']);
Expand All @@ -157,7 +155,7 @@
$this->useIsolatedComposerHome();

$packageDirectory = prepareCachedPackage('vendor/custom-pint', ['pint']);
writeExecutable($packageDirectory.'/pint', "#!/usr/bin/env php\n<?php exit(0);\n");
writeExecutable($packageDirectory.'/pint', noopBinary());
UserAliases::open()->put('pint', Package::parse('vendor/custom-pint'))->save();

[$unaliasStatus, $unaliasOutput] = runCpxCommand(['unalias', 'pint']);
Expand Down Expand Up @@ -302,17 +300,7 @@
});

test('unknown package targets route to the package fallback command', function () {
$runner = new class extends PackageCommandRunner
{
public ?PackageInvocation $invocation = null;

public function run(PackageInvocation $invocation, OutputInterface $output, bool $skipLocal = false): int
{
$this->invocation = $invocation;

return 0;
}
};
$runner = recordingPackageRunner();
$application = new Application($runner);

$status = $application->run(new ArgvInput(['cpx', 'vendor/package', '--flag', 'value']), new BufferedOutput);
Expand All @@ -324,17 +312,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool
});

test('package fallback accepts arbitrary package options without Symfony validation errors', function () {
$runner = new class extends PackageCommandRunner
{
public ?PackageInvocation $invocation = null;

public function run(PackageInvocation $invocation, OutputInterface $output, bool $skipLocal = false): int
{
$this->invocation = $invocation;

return 0;
}
};
$runner = recordingPackageRunner();
$application = new Application($runner);

$status = $application->run(new ArgvInput([
Expand Down Expand Up @@ -363,17 +341,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool
});

test('package-target version options are forwarded instead of rendering cpx version', function () {
$runner = new class extends PackageCommandRunner
{
public ?PackageInvocation $invocation = null;

public function run(PackageInvocation $invocation, OutputInterface $output, bool $skipLocal = false): int
{
$this->invocation = $invocation;

return 0;
}
};
$runner = recordingPackageRunner();
$application = new Application($runner);
$output = new BufferedOutput;

Expand Down Expand Up @@ -419,6 +387,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool

test('package-looking values with shell metacharacters fail before composer execution', function () {
$this->useIsolatedComposerHome();
$this->useWorkingDirectory($this->temporaryDirectory('cpx-project'));

$calls = [];
fakeComposer($calls);
Expand All @@ -432,6 +401,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool

test('an unparseable slash target surfaces the package format hint', function () {
$this->useIsolatedComposerHome();
$this->useWorkingDirectory($this->temporaryDirectory('cpx-project'));

[$status, $output] = runCpxCommand(['bad//ref']);

Expand All @@ -442,6 +412,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool

test('invalid fallback commands return a failure status with help output', function () {
$this->useIsolatedComposerHome();
$this->useWorkingDirectory($this->temporaryDirectory('cpx-project'));

[$status, $output] = runCpxCommand(['not-a-package']);

Expand All @@ -451,6 +422,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool

test('a package that composer cannot install renders a package-not-found error', function () {
$this->useIsolatedComposerHome();
$this->useWorkingDirectory($this->temporaryDirectory('cpx-project'));

$calls = [];
fakeComposer($calls, exitCode: 1);
Expand All @@ -468,6 +440,7 @@ public function run(PackageInvocation $invocation, OutputInterface $output, bool

test('a package-not-found error mentions the requested version constraint', function () {
$this->useIsolatedComposerHome();
$this->useWorkingDirectory($this->temporaryDirectory('cpx-project'));

$calls = [];
fakeComposer($calls, exitCode: 1);
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/JsonOutputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ function runCpxJsonCommand(array $arguments): array
Interactivity::fake(false);

prepareCachedPackage('vendor/tool', ['tool'], [
'tool' => "#!/usr/bin/env php\n<?php exit(7);\n",
'tool' => noopBinary(7),
]);

[$status, $output] = runCpxCommand(['vendor/tool']);
Expand Down
21 changes: 21 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

use Cpx\Application;
use Cpx\Composer\ComposerRunner;
use Cpx\Input\PackageInvocation;
use Cpx\Packages\PackageCommandRunner;
use Cpx\Process\ProcessResult;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Tests\TestCase;

pest()->extend(TestCase::class)->in('Feature', 'Unit');
Expand Down Expand Up @@ -36,6 +39,24 @@ function writeAliasesFile(array $aliases): void
file_put_contents($file, json_encode($aliases, JSON_THROW_ON_ERROR));
}

/**
* A PackageCommandRunner that records the invocation on a public property instead of running it.
*/
function recordingPackageRunner(): PackageCommandRunner
{
return new class extends PackageCommandRunner
{
public ?PackageInvocation $invocation = null;

public function run(PackageInvocation $invocation, OutputInterface $output, bool $skipLocal = false): int
{
$this->invocation = $invocation;

return 0;
}
};
}

function writeExecutable(string $path, string $contents): void
{
file_put_contents($path, $contents);
Expand Down
1 change: 1 addition & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ protected function setUp(): void
// The suite itself often runs inside an agent with piped stdin; keep detection deterministic.
Interactivity::fake(true);

// Prompts has no reset API for this static state (fallbackWhen() only latches true), so clear it by reflection.
(new ReflectionProperty(Prompt::class, 'terminal'))->setValue(null, new Terminal);
(new ReflectionProperty(Prompt::class, 'shouldFallback'))->setValue(null, false);
(new ReflectionProperty(Prompt::class, 'fallbacks'))->setValue(null, []);
Expand Down
23 changes: 23 additions & 0 deletions tests/Unit/LockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@
expect($result)->toBe('done');
});

test('the lock excludes a second handle while held and frees it after release', function () {
$lockFile = $this->temporaryDirectory('cpx-lock').'/cpx.lock';

$heldDuring = null;

Lock::run($lockFile, function () use ($lockFile, &$heldDuring): void {
$probe = fopen($lockFile, 'c');
assert(is_resource($probe));

$heldDuring = flock($probe, LOCK_EX | LOCK_NB);
fclose($probe);
});

$probe = fopen($lockFile, 'c');
assert(is_resource($probe));
$freedAfter = flock($probe, LOCK_EX | LOCK_NB);
flock($probe, LOCK_UN);
fclose($probe);

expect($heldDuring)->toBeFalse()
->and($freedAfter)->toBeTrue();
});

test('it releases the lock so a later acquisition succeeds', function () {
$lockFile = $this->temporaryDirectory('cpx-lock').'/cpx.lock';

Expand Down
8 changes: 4 additions & 4 deletions tests/Unit/ProcessRunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
$directory = $this->temporaryDirectory('cpx-process');
$binary = "{$directory}/exit-code";

writeExecutable($binary, "#!/usr/bin/env php\n<?php exit(37);\n");
writeExecutable($binary, noopBinary(37));

expect((new ProcessRunner)->run([PHP_BINARY, $binary]))->toBe(37);
});
Expand Down Expand Up @@ -77,7 +77,7 @@ public function line(string $message): void
$binary = "{$directory}/argv";
$logFile = "{$directory}/argv.json";

writeExecutable($binary, "#!/usr/bin/env php\n<?php file_put_contents('{$logFile}', json_encode(array_slice(\$argv, 1), JSON_THROW_ON_ERROR)); exit(0);\n");
writeExecutable($binary, argvLoggingBinary($logFile));

$status = (new ProcessRunner)->run([PHP_BINARY, $binary, 'two words', 'semi;colon', 'pipe|value', '$(touch injected)']);

Expand Down Expand Up @@ -119,7 +119,7 @@ public function line(string $message): void
$directory = $this->temporaryDirectory('cpx-process');
$binary = "{$directory}/no-read";

writeExecutable($binary, "#!/usr/bin/env php\n<?php exit(5);\n");
writeExecutable($binary, noopBinary(5));

ProcessRunner::fakeInput("pending data\n");

Expand Down Expand Up @@ -173,7 +173,7 @@ public function line(string $message): void
$directory = $this->temporaryDirectory('cpx-process');
$binary = "{$directory}/exit-code";

writeExecutable($binary, "#!/usr/bin/env php\n<?php exit(11);\n");
writeExecutable($binary, noopBinary(11));

expect((new ProcessRunner)->run([PHP_BINARY, $binary]))->toBe(11)
->and((new ProcessRunner)->run([PHP_BINARY, $binary]))->toBe(11);
Expand Down
Loading