Upstream composer/satis state of the touched files before f38603da21
This commit is contained in:
1 parent
0d162a13f2
commit
222a1d882b
2 files changed
+824
No files matched your search
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of composer/satis.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Satis\Console\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Satis\PackageSelection\PackageSelection;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
class PurgeCommand extends BaseCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->getName() ?? $this->setName('purge');
|
||||
$this
|
||||
->setDescription('Purge packages')
|
||||
->setDefinition([
|
||||
new InputArgument('file', InputArgument::OPTIONAL, 'Json file to use', './satis.json'),
|
||||
new InputArgument('output-dir', InputArgument::OPTIONAL, 'Location where to output built files', null),
|
||||
new InputArgument('dry-run', InputArgument::OPTIONAL, 'Dry run, allows to inspect what might be deleted', null),
|
||||
])
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>purge</info> command deletes useless archive files, depending
|
||||
on given json file (satis.json is used by default) and the
|
||||
newest json file in the include directory of the given output-dir.
|
||||
|
||||
In your satis.json (or other name you give), you must define
|
||||
"archive" argument. You also need to define "homepage" argument or "SATIS_HOMEPAGE" environment variable if you don't use archive "prefix-url" argument.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$configFile = $input->getArgument('file');
|
||||
$file = new JsonFile($configFile);
|
||||
if (!$file->exists()) {
|
||||
$output->writeln('<error>File not found: ' . $configFile . '</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
$config = $file->read();
|
||||
|
||||
/*
|
||||
* Check whether archive is defined
|
||||
*/
|
||||
if (!isset($config['archive']) || !isset($config['archive']['directory'])) {
|
||||
$output->writeln('<error>You must define "archive" parameter in your ' . $configFile . '</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$outputDir = $input->getArgument('output-dir') ?? $config['output-dir'] ?? null;
|
||||
if (null === $outputDir) {
|
||||
throw new \InvalidArgumentException('The output dir must be specified as second argument or be configured inside ' . $input->getArgument('file'));
|
||||
}
|
||||
|
||||
$dryRun = (bool) $input->getArgument('dry-run');
|
||||
if ($dryRun) {
|
||||
$output->writeln('<notice>Dry run enabled, no actual changes will be done.</notice>');
|
||||
}
|
||||
|
||||
$packageSelection = new PackageSelection($output, $outputDir, $config, false);
|
||||
$packages = $packageSelection->load();
|
||||
|
||||
$satis_homepage = getenv('SATIS_HOMEPAGE');
|
||||
$prefix = sprintf(
|
||||
'%s/%s/',
|
||||
$config['archive']['prefix-url'] ?? (false !== $satis_homepage ? $satis_homepage : $config['homepage']),
|
||||
$config['archive']['directory']
|
||||
);
|
||||
|
||||
$length = strlen($prefix);
|
||||
$needed = [];
|
||||
foreach ($packages as $package) {
|
||||
if (is_null($package->getDistType())) {
|
||||
continue;
|
||||
}
|
||||
$url = (string) $package->getDistUrl();
|
||||
if (substr($url, 0, $length) === $prefix) {
|
||||
$needed[] = substr($url, $length);
|
||||
}
|
||||
}
|
||||
|
||||
$distDirectory = sprintf('%s/%s', $outputDir, $config['archive']['directory']);
|
||||
|
||||
$finder = new Finder();
|
||||
$finder
|
||||
->files()
|
||||
->in($distDirectory)
|
||||
;
|
||||
|
||||
if (0 === $finder->count()) {
|
||||
$output->writeln('<warning>No archives found.</warning>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @var SplFileInfo[] $unreferenced */
|
||||
$unreferenced = [];
|
||||
foreach ($finder as $currentFile) {
|
||||
$filename = strtr($currentFile->getRelativePathname(), DIRECTORY_SEPARATOR, '/');
|
||||
if (!in_array($filename, $needed, true)) {
|
||||
$unreferenced[] = $currentFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === count($unreferenced)) {
|
||||
$output->writeln('<warning>No unreferenced archives found.</warning>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach ($unreferenced as $currentFile) {
|
||||
if (!$dryRun) {
|
||||
unlink($currentFile->getPathname());
|
||||
}
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'<info>Removed archive</info>: <comment>%s</comment>',
|
||||
$currentFile->getRelativePathname()
|
||||
));
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
$this->removeEmptyDirectories($output, $distDirectory);
|
||||
}
|
||||
|
||||
$output->writeln('<info>Done.</info>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function removeEmptyDirectories(OutputInterface $output, string $dir, int $depth = 2): bool
|
||||
{
|
||||
$empty = true;
|
||||
$children = @scandir($dir);
|
||||
|
||||
if (false === $children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($children as $child) {
|
||||
if ('.' === $child || '..' === $child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $dir . DIRECTORY_SEPARATOR . $child;
|
||||
|
||||
if (is_dir($path)
|
||||
&& $depth > 0
|
||||
&& $this->removeEmptyDirectories($output, $path, $depth - 1)
|
||||
&& rmdir($path)
|
||||
) {
|
||||
$output->writeln(sprintf('<info>Removed empty directory</info>: <comment>%s</comment>', $path));
|
||||
} else {
|
||||
$empty = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of composer/satis.
|
||||
*
|
||||
* (c) Composer <https://github.com/composer>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Satis\Console\Command;
|
||||
|
||||
use Composer\Console\Application;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
#[TestDox('PurgeCommand')]
|
||||
#[CoversClass(PurgeCommand::class)]
|
||||
class PurgeCommandTest extends TestCase
|
||||
{
|
||||
private string $configPath;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
vfsStream::setup('satis');
|
||||
$this->configPath = vfsStream::url('satis/satis.json');
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
private function createTester(): CommandTester
|
||||
{
|
||||
$app = new Application();
|
||||
$app->addCommand(new PurgeCommand());
|
||||
|
||||
return new CommandTester($app->find('purge'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
private function writeSatisConfig(array $config): void
|
||||
{
|
||||
file_put_contents($this->configPath, json_encode($config, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array<string, mixed>>> $packagesByName
|
||||
*/
|
||||
private function writeOutputRepository(string $outputDir, array $packagesByName, string $includeFile = 'include/packages.json'): void
|
||||
{
|
||||
$includePath = $outputDir . '/' . $includeFile;
|
||||
if (!is_dir(\dirname($includePath))) {
|
||||
mkdir(\dirname($includePath), 0777, true);
|
||||
}
|
||||
file_put_contents($includePath, json_encode(['packages' => $packagesByName], JSON_THROW_ON_ERROR));
|
||||
file_put_contents($outputDir . '/packages.json', json_encode([
|
||||
'includes' => [
|
||||
$includeFile => ['sha256' => 'dummy'],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array<string, mixed>>>
|
||||
*/
|
||||
private function packageEntry(string $name, string $version, string $distUrl, bool $withDist = true): array
|
||||
{
|
||||
$normalized = $version . '.0';
|
||||
if (1 === preg_match('/^\d+\.\d+\.\d+/', $version)) {
|
||||
$normalized = $version;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'version' => $version,
|
||||
'version_normalized' => $normalized,
|
||||
'type' => 'library',
|
||||
];
|
||||
if ($withDist) {
|
||||
$data['dist'] = [
|
||||
'type' => 'tar',
|
||||
'url' => $distUrl,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
$name => [
|
||||
$version => $data,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $parameters
|
||||
*/
|
||||
private function invokeMethod(object $object, string $methodName, array $parameters = []): mixed
|
||||
{
|
||||
$reflection = new \ReflectionClass($object::class);
|
||||
$method = $reflection->getMethod($methodName);
|
||||
|
||||
return $method->invokeArgs($object, $parameters);
|
||||
}
|
||||
|
||||
// --- Data providers ---
|
||||
|
||||
/**
|
||||
* Missing `archive` and `archive` without `directory` hit the same validation branch in PurgeCommand.
|
||||
*
|
||||
* @return iterable<string, array{array<string, mixed>}>
|
||||
*/
|
||||
public static function provideInvalidArchiveConfig(): iterable
|
||||
{
|
||||
yield 'archive key missing' => [[
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
]];
|
||||
|
||||
yield 'archive.directory missing' => [[
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'archive' => [],
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data providers run before setUp(), so vfs paths must be built inside the test.
|
||||
*
|
||||
* @return iterable<string, array{0: bool}>
|
||||
*/
|
||||
public static function provideOutputDirResolution(): iterable
|
||||
{
|
||||
yield 'from CLI argument' => [true];
|
||||
yield 'from config when argument omitted' => [false];
|
||||
}
|
||||
|
||||
// --- Validation ---
|
||||
|
||||
#[TestDox('execute() returns error when config file is not found')]
|
||||
public function testExecuteReturnsErrorWhenFileNotFound(): void
|
||||
{
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => vfsStream::url('satis/nonexistent.json'),
|
||||
'output-dir' => vfsStream::url('satis/output'),
|
||||
]);
|
||||
|
||||
self::assertSame(1, $tester->getStatusCode());
|
||||
self::assertStringContainsString('File not found', $tester->getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
#[DataProvider('provideInvalidArchiveConfig')]
|
||||
#[TestDox('execute() returns error when archive config is invalid')]
|
||||
public function testExecuteReturnsErrorWhenArchiveConfigInvalid(array $config): void
|
||||
{
|
||||
$this->writeSatisConfig($config);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => vfsStream::url('satis/output'),
|
||||
]);
|
||||
|
||||
self::assertSame(1, $tester->getStatusCode());
|
||||
self::assertStringContainsString('You must define "archive" parameter', $tester->getDisplay());
|
||||
}
|
||||
|
||||
#[TestDox('execute() throws when output dir is not in config or arguments')]
|
||||
public function testExecuteThrowsWhenOutputDirMissing(): void
|
||||
{
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The output dir must be specified as second argument or be configured inside');
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
]);
|
||||
}
|
||||
|
||||
// --- Output directory resolution ---
|
||||
|
||||
#[DataProvider('provideOutputDirResolution')]
|
||||
#[TestDox('execute() resolves output-dir from CLI or config')]
|
||||
public function testExecuteResolvesOutputDir(bool $outputDirFromCliArgument): void
|
||||
{
|
||||
$suffix = $outputDirFromCliArgument ? 'arg' : 'cfg';
|
||||
$outputDir = vfsStream::url('satis/output-' . $suffix);
|
||||
mkdir($outputDir);
|
||||
mkdir($outputDir . '/dist');
|
||||
|
||||
$baseConfig = [
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
];
|
||||
|
||||
if ($outputDirFromCliArgument) {
|
||||
$this->writeSatisConfig($baseConfig);
|
||||
$executeArgs = [
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
];
|
||||
} else {
|
||||
$this->writeSatisConfig(array_merge($baseConfig, ['output-dir' => $outputDir]));
|
||||
$executeArgs = [
|
||||
'file' => $this->configPath,
|
||||
];
|
||||
}
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute($executeArgs);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('No archives found', $tester->getDisplay());
|
||||
}
|
||||
|
||||
// --- Prefix / env / homepage ---
|
||||
|
||||
#[TestDox('execute() warns when no unreferenced archives are found')]
|
||||
public function testExecuteWarnsWhenNoUnreferencedArchivesFound(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-all-ref');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir, 0755, true);
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$relativePath = 'vendor/kept.tar.gz';
|
||||
file_put_contents($archiveDir . '/' . $relativePath, 'content');
|
||||
|
||||
$distUrl = 'https://example.com/dist/' . $relativePath;
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry('vendor/kept', '1.0.0', $distUrl));
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('No unreferenced archives found', $tester->getDisplay());
|
||||
self::assertFileExists($archiveDir . '/' . $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrong prefix-url would mark the on-disk file as unreferenced and change the outcome (purge vs this message).
|
||||
*/
|
||||
#[TestDox('execute() uses archive prefix-url when set')]
|
||||
public function testExecuteUsesArchivePrefixUrl(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-prefix');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/p', 0755, true);
|
||||
|
||||
$relativePath = 'p/archive.tar.gz';
|
||||
file_put_contents($archiveDir . '/' . $relativePath, 'x');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'p/pkg',
|
||||
'1.0.0',
|
||||
'https://cdn.example.com/dist/' . $relativePath
|
||||
));
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://cdn.example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('No unreferenced archives found', $tester->getDisplay());
|
||||
}
|
||||
|
||||
#[TestDox('execute() uses SATIS_HOMEPAGE when prefix-url is not defined')]
|
||||
public function testExecuteUsesSatisHomepageEnv(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-env');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$relativePath = 'vendor/file.tar.gz';
|
||||
file_put_contents($archiveDir . '/' . $relativePath, 'c');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'vendor/pkg',
|
||||
'1.0.0',
|
||||
'https://custom.example.com/dist/' . $relativePath
|
||||
));
|
||||
|
||||
$original = getenv('SATIS_HOMEPAGE');
|
||||
putenv('SATIS_HOMEPAGE=https://custom.example.com');
|
||||
|
||||
try {
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('No unreferenced archives found', $tester->getDisplay());
|
||||
} finally {
|
||||
if (false === $original) {
|
||||
putenv('SATIS_HOMEPAGE');
|
||||
} else {
|
||||
putenv('SATIS_HOMEPAGE=' . $original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[TestDox('execute() uses homepage from config when SATIS_HOMEPAGE is unset and prefix-url is missing')]
|
||||
public function testExecuteUsesHomepageFromConfig(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-home');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$relativePath = 'vendor/file.tar.gz';
|
||||
file_put_contents($archiveDir . '/' . $relativePath, 'c');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'vendor/pkg',
|
||||
'1.0.0',
|
||||
'https://config.example.com/dist/' . $relativePath
|
||||
));
|
||||
|
||||
$original = getenv('SATIS_HOMEPAGE');
|
||||
putenv('SATIS_HOMEPAGE');
|
||||
|
||||
try {
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://config.example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('No unreferenced archives found', $tester->getDisplay());
|
||||
} finally {
|
||||
if (false === $original) {
|
||||
putenv('SATIS_HOMEPAGE');
|
||||
} else {
|
||||
putenv('SATIS_HOMEPAGE=' . $original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Purge behavior ---
|
||||
|
||||
#[TestDox('execute() removes unreferenced archives when dry-run is disabled')]
|
||||
public function testExecuteRemovesUnreferencedArchives(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-purge');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$referencedFile = $archiveDir . '/vendor/referenced.tar.gz';
|
||||
$unreferencedFile = $archiveDir . '/vendor/unreferenced.tar.gz';
|
||||
file_put_contents($referencedFile, 'a');
|
||||
file_put_contents($unreferencedFile, 'b');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'vendor/ref',
|
||||
'1.0.0',
|
||||
'https://example.com/dist/vendor/referenced.tar.gz'
|
||||
));
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('Removed archive', $tester->getDisplay());
|
||||
self::assertStringContainsString('unreferenced.tar.gz', $tester->getDisplay());
|
||||
self::assertFileDoesNotExist($unreferencedFile);
|
||||
self::assertFileExists($referencedFile);
|
||||
}
|
||||
|
||||
#[TestDox('execute() does not count packages without dist type as needed')]
|
||||
public function testExecuteTreatsPackagesWithoutDistTypeAsNotNeeded(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-nodist');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$relativePath = 'vendor/file.tar.gz';
|
||||
file_put_contents($archiveDir . '/' . $relativePath, 'c');
|
||||
|
||||
$this->writeOutputRepository($outputDir, [
|
||||
'vendor/nodist' => [
|
||||
'1.0.0' => [
|
||||
'name' => 'vendor/nodist',
|
||||
'version' => '1.0.0',
|
||||
'version_normalized' => '1.0.0.0',
|
||||
'type' => 'library',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('Removed archive', $tester->getDisplay());
|
||||
self::assertFileDoesNotExist($archiveDir . '/' . $relativePath);
|
||||
}
|
||||
|
||||
#[TestDox('execute() removes empty directories after purging archives')]
|
||||
public function testExecuteRemovesEmptyDirectoriesAfterPurge(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-rmdir');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
$subdir = $archiveDir . '/vendor/branch';
|
||||
mkdir($subdir, 0755, true);
|
||||
|
||||
file_put_contents($subdir . '/orphan.tar.gz', 'x');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'vendor/keep',
|
||||
'1.0.0',
|
||||
'https://example.com/dist/vendor/branch/kept.tar.gz'
|
||||
));
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertStringContainsString('Removed archive', $tester->getDisplay());
|
||||
self::assertStringContainsString('Removed empty directory', $tester->getDisplay());
|
||||
}
|
||||
|
||||
// --- Dry-run ---
|
||||
|
||||
#[TestDox('execute() does not remove archives or empty dirs when dry-run is enabled')]
|
||||
public function testExecuteDoesNotRemoveArchivesInDryRun(): void
|
||||
{
|
||||
$outputDir = vfsStream::url('satis/output-dry');
|
||||
mkdir($outputDir);
|
||||
$archiveDir = $outputDir . '/dist';
|
||||
mkdir($archiveDir . '/vendor', 0755, true);
|
||||
|
||||
$unreferencedFile = $archiveDir . '/vendor/unreferenced.tar.gz';
|
||||
file_put_contents($unreferencedFile, 'content');
|
||||
|
||||
$this->writeOutputRepository($outputDir, $this->packageEntry(
|
||||
'vendor/other',
|
||||
'1.0.0',
|
||||
'https://example.com/dist/vendor/other.tar.gz'
|
||||
));
|
||||
|
||||
$this->writeSatisConfig([
|
||||
'name' => 'test/satis-repo',
|
||||
'homepage' => 'https://example.com',
|
||||
'output-dir' => $outputDir,
|
||||
'archive' => [
|
||||
'directory' => 'dist',
|
||||
'prefix-url' => 'https://example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$tester = $this->createTester();
|
||||
$tester->execute([
|
||||
'file' => $this->configPath,
|
||||
'output-dir' => $outputDir,
|
||||
'dry-run' => '1',
|
||||
]);
|
||||
|
||||
self::assertSame(0, $tester->getStatusCode());
|
||||
self::assertFileExists($unreferencedFile);
|
||||
self::assertStringContainsString('Dry run enabled', $tester->getDisplay());
|
||||
self::assertStringContainsString('Removed archive', $tester->getDisplay());
|
||||
self::assertStringNotContainsString('Removed empty directory', $tester->getDisplay());
|
||||
}
|
||||
|
||||
// --- removeEmptyDirectories() ---
|
||||
|
||||
#[TestDox('removeEmptyDirectories() returns false for non-existent directory')]
|
||||
public function testRemoveEmptyDirectoriesReturnsFalseForNonExistentDirectory(): void
|
||||
{
|
||||
$command = new PurgeCommand();
|
||||
$result = $this->invokeMethod($command, 'removeEmptyDirectories', [
|
||||
new NullOutput(),
|
||||
vfsStream::url('satis/nonexistent'),
|
||||
]);
|
||||
|
||||
self::assertFalse($result);
|
||||
}
|
||||
|
||||
#[TestDox('removeEmptyDirectories() does not remove the root directory passed in')]
|
||||
public function testRemoveEmptyDirectoriesLeavesRootDirectoryInPlace(): void
|
||||
{
|
||||
$root = vfsStream::url('satis/empty-root');
|
||||
mkdir($root);
|
||||
|
||||
$command = new PurgeCommand();
|
||||
$result = $this->invokeMethod($command, 'removeEmptyDirectories', [
|
||||
new NullOutput(),
|
||||
$root,
|
||||
]);
|
||||
|
||||
self::assertTrue($result);
|
||||
self::assertDirectoryExists($root);
|
||||
}
|
||||
|
||||
#[TestDox('removeEmptyDirectories() removes nested empty child directories')]
|
||||
public function testRemoveEmptyDirectoriesRemovesNestedEmptyDirectories(): void
|
||||
{
|
||||
$base = vfsStream::url('satis/nested-empty');
|
||||
mkdir($base);
|
||||
mkdir($base . '/a');
|
||||
mkdir($base . '/a/b');
|
||||
|
||||
$out = new BufferedOutput();
|
||||
$command = new PurgeCommand();
|
||||
$this->invokeMethod($command, 'removeEmptyDirectories', [$out, $base, 2]);
|
||||
|
||||
self::assertDirectoryDoesNotExist($base . '/a/b');
|
||||
self::assertDirectoryDoesNotExist($base . '/a');
|
||||
self::assertStringContainsString('Removed empty directory', $out->fetch());
|
||||
}
|
||||
|
||||
#[TestDox('removeEmptyDirectories() does not remove non-empty directories')]
|
||||
public function testRemoveEmptyDirectoriesDoesNotRemoveNonEmptyDirectory(): void
|
||||
{
|
||||
$base = vfsStream::url('satis/non-empty');
|
||||
mkdir($base);
|
||||
$dir = $base . '/keep';
|
||||
mkdir($dir);
|
||||
file_put_contents($dir . '/file.txt', 'content');
|
||||
|
||||
$command = new PurgeCommand();
|
||||
$result = $this->invokeMethod($command, 'removeEmptyDirectories', [
|
||||
new NullOutput(),
|
||||
$dir,
|
||||
]);
|
||||
|
||||
self::assertFalse($result);
|
||||
self::assertDirectoryExists($dir);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user