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
6 changes: 6 additions & 0 deletions .changelog/autoload-files-testability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: fixed
type: patch
---

Detect PHP sources declared in Composer `autoload.files` so function-only packages run tests and coverage reporting instead of silently skipping them, without changing API documentation discovery.
2 changes: 1 addition & 1 deletion src/Project/ProjectCapabilitiesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private function resolveHasPhpSourceFiles(array $apiDirectories): bool
}
}

foreach (self::API_AUTOLOAD_TYPES as $autoloadType) {
foreach ([...self::API_AUTOLOAD_TYPES, 'files'] as $autoloadType) {
foreach ($this->normalizeAutoloadPaths($this->composer->getAutoload($autoloadType)) as $path) {
$absolutePath = $this->filesystem->getAbsolutePath($path);

Expand Down
56 changes: 56 additions & 0 deletions tests/Console/Command/ReportsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
namespace FastForward\DevTools\Tests\Console\Command;

use Symfony\Component\Console\Output\BufferedOutput;
use FastForward\DevTools\Composer\Json\ComposerJsonInterface;
use FastForward\DevTools\Filesystem\FilesystemInterface;
use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
use FastForward\DevTools\Console\Command\ReportsCommand;
use FastForward\DevTools\Console\Output\GithubActionOutput;
Expand Down Expand Up @@ -49,6 +51,10 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use function Safe\file_put_contents;
use function Safe\mkdir;
use function Safe\rmdir;
use function Safe\unlink;

#[UsesClass(ContainerFactory::class)]
#[UsesClass(DevToolsServiceProvider::class)]
Expand Down Expand Up @@ -253,6 +259,56 @@ public function executeWithNoCacheWillForwardNoCacheOnlyToDocsAndTests(): void
self::assertSame(ReportsCommand::SUCCESS, $this->executeCommand());
}

/**
* @return void
*/
#[Test]
public function executeWillGenerateCoverageForFilesOnlyPackage(): void
{
$workspace = sys_get_temp_dir() . '/dev-tools-files-reports-' . uniqid('', true);
mkdir($workspace);
mkdir($workspace . '/tests');
file_put_contents($workspace . '/functions.php', "<?php\nfunction fixtureFunction(): bool { return true; }\n");

try {
$composer = $this->prophesize(ComposerJsonInterface::class);
$filesystem = $this->prophesize(FilesystemInterface::class);
foreach (['psr-4', 'psr-0', 'classmap'] as $autoloadType) {
$composer->getAutoload($autoloadType)->willReturn([]);
}
$composer->getAutoload('files')->willReturn(['functions.php']);
$filesystem->getAbsolutePath('functions.php')->willReturn($workspace . '/functions.php');
$filesystem->exists('./tests')->willReturn(true);
$filesystem->exists('docs')->willReturn(false);
$filesystem->exists('.github/wiki')->willReturn(false);
$resolver = new ProjectCapabilitiesResolver($composer->reveal(), $filesystem->reveal());
$capabilities = $resolver->resolve();
self::assertSame([], $capabilities->getApiDirectories());
self::assertTrue($capabilities->canRunTests());
$this->projectCapabilitiesResolver->resolve()->willReturn($capabilities);
$this->processBuilder->build([DevToolsPathResolver::getBinaryPath(), 'tests'])
->willReturn($this->process->reveal())->shouldBeCalledOnce();
$this->processQueue->add($this->process->reveal(), false, false, 'Generating Coverage Report')
->shouldBeCalledOnce();
$this->processQueue->add($this->process->reveal(), false, true, 'Generating API Docs Report')
->shouldBeCalledOnce();
$this->processQueue->add($this->process->reveal(), false, false, 'Generating Metrics Report')
->shouldBeCalledOnce();
$this->processQueue->run($this->output->reveal())
->willReturn(ReportsCommand::SUCCESS)->shouldBeCalledOnce();
$this->logger->log('info', 'Generating frontpage for Fast Forward documentation...', Argument::type('array'))
->shouldBeCalledOnce();
$this->logger->log('info', 'Documentation reports generated successfully.', Argument::type('array'))
->shouldBeCalledOnce();

self::assertSame(ReportsCommand::SUCCESS, $this->executeCommand());
} finally {
unlink($workspace . '/functions.php');
rmdir($workspace . '/tests');
rmdir($workspace);
}
}

/**
* @return void
*/
Expand Down
55 changes: 55 additions & 0 deletions tests/Console/Command/TestsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use FastForward\DevTools\Path\DevToolsPathResolver;
use FastForward\DevTools\Project\ProjectCapabilities;
use FastForward\DevTools\Project\ProjectCapabilitiesResolverInterface;
use FastForward\DevTools\Project\ProjectCapabilitiesResolver;
use FastForward\DevTools\Path\WorkingProjectPathResolver;
use FastForward\DevTools\Environment\Environment as DevToolsEnvironment;
use FastForward\DevTools\Environment\RuntimeEnvironment;
Expand All @@ -54,6 +55,8 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use function Safe\file_put_contents;
use function Safe\unlink;
use function Safe\mkdir;
use function Safe\rmdir;
use function Safe\getcwd;
Expand All @@ -69,6 +72,7 @@
#[UsesClass(ProcessBuilder::class)]
#[UsesClass(ManagedWorkspace::class)]
#[UsesClass(ProjectCapabilities::class)]
#[UsesClass(ProjectCapabilitiesResolver::class)]
#[UsesClass(WorkingProjectPathResolver::class)]
#[UsesTrait(LogsCommandResults::class)]
final class TestsCommandTest extends TestCase
Expand Down Expand Up @@ -572,6 +576,57 @@ public function executeWithInvalidMinCoverageWillReturnFailure(): void
self::assertSame(TestsCommand::FAILURE, $this->invokeExecute());
}

/**
* @return void
*/
#[Test]
public function executeWillPropagateTestFailureForFilesOnlyPackage(): void
{
$workspace = sys_get_temp_dir() . '/dev-tools-files-tests-' . uniqid('', true);
mkdir($workspace);
mkdir($workspace . '/tests');
file_put_contents($workspace . '/functions.php', "<?php\nfunction fixtureFunction(): bool { return true; }\n");

try {
$composer = $this->prophesize(ComposerJsonInterface::class);
$filesystem = $this->prophesize(FilesystemInterface::class);
foreach (['psr-4', 'psr-0', 'classmap'] as $autoloadType) {
$composer->getAutoload($autoloadType)->willReturn([]);
}
$composer->getAutoload('files')->willReturn(['functions.php']);
$filesystem->getAbsolutePath('functions.php')->willReturn($workspace . '/functions.php');
$filesystem->exists('./tests')->willReturn(true);
$filesystem->exists('docs')->willReturn(false);
$filesystem->exists('.github/wiki')->willReturn(false);
$resolver = new ProjectCapabilitiesResolver($composer->reveal(), $filesystem->reveal());
$capabilities = $resolver->resolve();
self::assertSame([], $capabilities->getApiDirectories());
self::assertTrue($capabilities->canRunTests());
$this->projectCapabilitiesResolver->resolve(Argument::any())->willReturn($capabilities);
$this->processQueue->add(
Argument::that(static fn(Process $process): bool => str_contains(
$process->getCommandLine(),
DevToolsPathResolver::getPreferredToolBinaryPath('phpunit'),
)),
false,
false,
'Running PHPUnit Tests',
)->shouldBeCalledOnce();
$this->processQueue->run($this->output->reveal())
->willReturn(TestsCommand::FAILURE)->shouldBeCalledOnce();
$this->logger->log('info', 'Running PHPUnit tests...', Argument::type('array'))
->shouldBeCalledOnce();
$this->logger->error('PHPUnit tests failed.', Argument::type('array'))
->shouldBeCalledOnce();

self::assertSame(TestsCommand::FAILURE, $this->invokeExecute());
} finally {
unlink($workspace . '/functions.php');
rmdir($workspace . '/tests');
rmdir($workspace);
}
}

/**
* @return void
*/
Expand Down
43 changes: 43 additions & 0 deletions tests/Project/ProjectCapabilitiesResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ final class ProjectCapabilitiesResolverTest extends TestCase
protected function setUp(): void
{
$this->composer = $this->prophesize(ComposerJsonInterface::class);
$this->composer->getAutoload('files')->willReturn([]);
$this->filesystem = new SymfonyFilesystem();
$this->workingDirectory = getcwd();
$this->workspace = sys_get_temp_dir() . '/dev-tools-project-capabilities-' . uniqid('', true);
Expand Down Expand Up @@ -178,6 +179,48 @@ public function resolveWillDetectPhpSourceFromFileBasedClassmapEntries(): void
self::assertFalse($capabilities->canGenerateApiDocumentation());
}

/**
* @return void
*/
#[Test]
public function resolveWillDetectFilesOnlySourceWithoutInventingApiDirectories(): void
{
mkdir($this->workspace . '/tests');
file_put_contents($this->workspace . '/functions.php', "<?php\nfunction example(): void {}\n");

foreach (['psr-4', 'psr-0', 'classmap'] as $type) {
$this->composer->getAutoload($type)->willReturn([]);
}

$this->composer->getAutoload('files')->willReturn(['missing.php', 'functions.php']);

$capabilities = $this->resolver->resolve();

self::assertTrue($capabilities->hasTestsPath());
self::assertTrue($capabilities->hasPhpSourceFiles());
self::assertTrue($capabilities->canRunTests());
self::assertSame([], $capabilities->getApiDirectories());
self::assertFalse($capabilities->canGenerateApiDocumentation());
self::assertFalse($capabilities->canGenerateWiki());
}

/**
* @return void
*/
#[Test]
public function resolveWillIgnoreMissingAndNonPhpAutoloadFiles(): void
{
file_put_contents($this->workspace . '/README.md', 'Guide');

foreach (['psr-4', 'psr-0', 'classmap'] as $type) {
$this->composer->getAutoload($type)->willReturn([]);
}

$this->composer->getAutoload('files')->willReturn(['missing.php', 'README.md']);

self::assertFalse($this->resolver->resolve()->canRunTests());
}

/**
* @return void
*/
Expand Down
Loading