diff --git a/app/config.php b/app/config.php
index 3e0b693..892782f 100644
--- a/app/config.php
+++ b/app/config.php
@@ -17,13 +17,13 @@
use PhpSchool\WorkshopManager\Command\UninstallWorkshop;
use PhpSchool\WorkshopManager\Command\UpdateWorkshop;
use PhpSchool\WorkshopManager\Command\VerifyInstall;
+use PhpSchool\WorkshopManager\ComposerInstaller;
use PhpSchool\WorkshopManager\ComposerInstallerFactory;
use PhpSchool\WorkshopManager\Downloader;
use PhpSchool\WorkshopManager\Filesystem;
use PhpSchool\WorkshopManager\Installer\Installer;
use PhpSchool\WorkshopManager\Installer\Uninstaller;
use PhpSchool\WorkshopManager\Installer\Updater;
-use PhpSchool\WorkshopManager\IOFactory;
use PhpSchool\WorkshopManager\Linker;
use PhpSchool\WorkshopManager\ManagerState;
use PhpSchool\WorkshopManager\Repository\InstalledWorkshopRepository;
@@ -125,14 +125,13 @@
);
}),
Installer::class => \DI\factory(function (ContainerInterface $c) {
- $io = $c->get(IOFactory::class)->getNullableIO($c->get(InputInterface::class), $c->get(OutputInterface::class));
return new Installer(
$c->get(InstalledWorkshopRepository::class),
$c->get(RemoteWorkshopRepository::class),
$c->get(Linker::class),
$c->get(Filesystem::class),
$c->get('appDir'),
- new ComposerInstallerFactory($c->get(Factory::class), $io),
+ $c->get(ComposerInstaller::class),
$c->get(Client::class),
$c->get(VersionChecker::class)
);
@@ -159,14 +158,14 @@
Client::class => \DI\factory(function (ContainerInterface $c) {
return new Client;
}),
- Factory::class => \DI\object(),
- IOFactory::class => \DI\object(),
- IOInterface::class => \DI\factory(function (ContainerInterface $c) {
- return $c->get(IOFactory::class)->getIO(
+ ComposerInstaller::class => function (ContainerInterface $c) {
+ return new ComposerInstaller(
$c->get(InputInterface::class),
- $c->get(OutputInterface::class)
+ $c->get(OutputInterface::class),
+ new Factory
);
- }),
+ },
+ Factory::class => \DI\object(),
InputInterface::class => \Di\factory(function () {
return new \Symfony\Component\Console\Input\ArgvInput($_SERVER['argv']);
}),
diff --git a/composer.json b/composer.json
index 663244c..60e249b 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,11 @@
"symfony/filesystem": "^3.1",
"tm/tooly-composer-script": "^1.0",
"padraic/phar-updater": "^1.0",
- "samsonasik/package-versions": "^1.0"
+ "samsonasik/package-versions": "^1.0",
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-zip": "*"
},
"require-dev": {
"phpunit/phpunit": "~5.4",
diff --git a/src/Command/InstallWorkshop.php b/src/Command/InstallWorkshop.php
index 1b5b1be..1c2b92d 100644
--- a/src/Command/InstallWorkshop.php
+++ b/src/Command/InstallWorkshop.php
@@ -68,10 +68,17 @@ public function __invoke(OutputInterface $output, $workshopName)
''
]);
} catch (ComposerFailureException $e) {
- $message = " There was a problem installing dependencies for \"%s\". Try running in verbose";
- $message .= " mode to see the composer error: %s \n";
+ $message = " There was a problem installing dependencies for \"%s\".%s Try running in verbose";
+ $message .= " mode to see more details: %s \n";
- $output->writeln(sprintf($message, $workshopName, $this->getCommand()));
+ $output->writeln(
+ sprintf(
+ $message,
+ $workshopName,
+ $e->getMessage() ? sprintf(' %s', $e->getMessage()) : '',
+ $this->getCommand()
+ )
+ );
} catch (\Exception $e) {
$output->writeln(
sprintf(" An unknown error occurred: \"%s\" \n", $e->getMessage())
diff --git a/src/Command/VerifyInstall.php b/src/Command/VerifyInstall.php
index 52dbfc4..c05f2b2 100644
--- a/src/Command/VerifyInstall.php
+++ b/src/Command/VerifyInstall.php
@@ -11,6 +11,11 @@
*/
class VerifyInstall
{
+ /**
+ * @var array
+ */
+ private static $requiredExtensions = ['json', 'zip', 'mbstring', 'curl'];
+
/**
* @var InputInterface
*/
@@ -26,6 +31,7 @@ class VerifyInstall
*/
private $workshopHomeDirectory;
+
/**
* @param InputInterface $input
* @param OutputInterface $output
@@ -44,9 +50,9 @@ public function __invoke()
$style->title("Verifying your installation");
-
+
if (strpos(getenv('PATH'), sprintf('%s/bin', $this->workshopHomeDirectory)) !== false) {
- $style->success('Your $PATH environment variable is configured correctly');
+ $style->success('Your $PATH environment variable is configured correctly.');
} else {
$style->error('The PHP School bin directory is not in your PATH variable.');
@@ -68,13 +74,33 @@ public function __invoke()
''
]);
}
-
+
if (version_compare(PHP_VERSION, '5.6')) {
- $message = 'Your PHP version is at least 5.6, which is required by this tool. Be aware that some ';
- $message .= 'workshops may require a higher version of PHP, so you may not be able to install them.';
- $style->success($message);
+ $message = 'Your PHP version is %s, PHP 5.6 is the minimum supported version for this tool. Please note ';
+ $message .= 'that some workshops may require a higher version of PHP, so you may not be able to install ';
+ $message .= 'them without upgrading PHP.';
+ $style->success(sprintf($message, PHP_VERSION));
} else {
$style->error('You need a PHP version of at least 5.6 to use PHP School.');
}
+
+ $missingExtensions = array_filter(static::$requiredExtensions, function ($extension) {
+ return !extension_loaded($extension);
+ });
+
+ array_walk($missingExtensions, function ($missingExtension) use ($style) {
+ $style->error(
+ sprintf(
+ 'The %s extension is missing - use your preferred package manager to install it.',
+ $missingExtension
+ )
+ );
+ });
+
+ if (empty($missingExtensions)) {
+ $message = 'All required PHP extensions are installed. Please note that some workshops may require ';
+ $message .= 'additional PHP extensions.';
+ $style->success($message);
+ }
}
}
diff --git a/src/ComposerInstaller.php b/src/ComposerInstaller.php
new file mode 100644
index 0000000..a0c99ae
--- /dev/null
+++ b/src/ComposerInstaller.php
@@ -0,0 +1,81 @@
+
+ */
+class ComposerInstaller
+{
+ /**
+ * @var InputInterface
+ */
+ private $input;
+
+ /**
+ * @var OutputInterface
+ */
+ private $output;
+
+ /**
+ * @var Factory
+ */
+ private $composerFactory;
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @param Factory $composerFactory
+ */
+ public function __construct(InputInterface $input, OutputInterface $output, Factory $composerFactory)
+ {
+ $this->input = $input;
+ $this->output = $output;
+ $this->composerFactory = $composerFactory;
+ }
+
+ /**
+ * @param string $pathToComposerProject
+ * @return InstallResult
+ */
+ public function install($pathToComposerProject)
+ {
+ if ($this->output->isVerbose()) {
+ $output = $this->output;
+ } else {
+ //write all output in verbose mode to a temp stream
+ //so we don't write it out when not in verbose mode
+ $output = new StreamOutput(
+ fopen('php://memory', 'w'),
+ OutputInterface::VERBOSITY_VERY_VERBOSE,
+ $this->output->isDecorated(),
+ $this->output->getFormatter()
+ );
+ }
+
+ $wrappedOutput = new RecordingOutput($output);
+ $io = new ConsoleIO($this->input, $wrappedOutput, new HelperSet);
+
+ $composer = $this->composerFactory->createComposer(
+ $io,
+ sprintf('%s/composer.json', rtrim($pathToComposerProject, '/')),
+ false,
+ $pathToComposerProject
+ );
+
+ return new InstallResult(
+ Installer::create($io, $composer)->run(),
+ $wrappedOutput->getOutput()
+ );
+ }
+}
diff --git a/src/ComposerInstallerFactory.php b/src/ComposerInstallerFactory.php
deleted file mode 100644
index 65cc21c..0000000
--- a/src/ComposerInstallerFactory.php
+++ /dev/null
@@ -1,51 +0,0 @@
-
- */
-class ComposerInstallerFactory
-{
- /**
- * @var ComposerFactory
- */
- private $composerFactory;
-
- /**
- * @var IOInterface
- */
- private $io;
-
- /**
- * @param ComposerFactory $composerFactory
- * @param IOInterface $io
- */
- public function __construct(ComposerFactory $composerFactory, IOInterface $io)
- {
- $this->io = $io;
- $this->composerFactory = $composerFactory;
- }
-
- /**
- * @param string $path
- * @return ComposerInstaller
- */
- public function create($path)
- {
- $composer = $this->composerFactory->createComposer(
- $this->io,
- sprintf('%s/composer.json', $path),
- false,
- $path
- );
-
- return ComposerInstaller::create($this->io, $composer);
- }
-}
diff --git a/src/Exception/ComposerFailureException.php b/src/Exception/ComposerFailureException.php
index f2787d2..baecb39 100644
--- a/src/Exception/ComposerFailureException.php
+++ b/src/Exception/ComposerFailureException.php
@@ -6,14 +6,26 @@
* Class ComposerFailureException
* @author Michael Woodward
*/
-final class ComposerFailureException extends \RuntimeException
+class ComposerFailureException extends \RuntimeException
{
/**
* @param \Exception $e
- * @return ComposerFailureException
+ * @return self
*/
public static function fromException(\Exception $e)
{
return new self($e->getMessage());
}
+
+ /**
+ * @param array $missingExtensions
+ * @return self
+ */
+ public static function fromMissingExtensions(array $missingExtensions)
+ {
+ $message = 'This workshop requires some extra PHP extensions. Please install them';
+ $message .= ' and try again. Required extensions are %s.';
+
+ return new self(sprintf($message, implode(', ', $missingExtensions)));
+ }
}
diff --git a/src/IOFactory.php b/src/IOFactory.php
deleted file mode 100644
index 98b58e4..0000000
--- a/src/IOFactory.php
+++ /dev/null
@@ -1,44 +0,0 @@
-
- */
-class IOFactory
-{
- /**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @return IOInterface
- */
- public function getIO(InputInterface $input, OutputInterface $output)
- {
- return new ConsoleIO($input, $output, new HelperSet);
- }
-
- /**
- * @param InputInterface $input
- * @param OutputInterface $output
- * @return IOInterface
- */
- public function getNullableIO(InputInterface $input, OutputInterface $output)
- {
- switch ($output->getVerbosity()) {
- case OutputInterface::VERBOSITY_VERBOSE:
- case OutputInterface::VERBOSITY_VERY_VERBOSE:
- case OutputInterface::VERBOSITY_DEBUG:
- return new ConsoleIO($input, $output, new HelperSet);
- default:
- return new NullIO;
- }
- }
-}
diff --git a/src/InstallResult.php b/src/InstallResult.php
new file mode 100644
index 0000000..fc463c3
--- /dev/null
+++ b/src/InstallResult.php
@@ -0,0 +1,91 @@
+
+ */
+class InstallResult
+{
+ /**
+ * @var int
+ */
+ private $exitCode;
+
+ /**
+ * @var string
+ */
+ private $output;
+
+ /**
+ * @var Collection
+ */
+ private $missingExtensions;
+
+ /**
+ * @param int $exitCode
+ * @param string $output
+ */
+ public function __construct($exitCode, $output)
+ {
+ $this->exitCode = $exitCode;
+ $this->output = $output;
+
+ $this->checkForMissingExtensions();
+ }
+
+ private function checkForMissingExtensions()
+ {
+ $this->missingExtensions = collect(explode(PHP_EOL, $this->output))
+ ->filter(function ($line) {
+ return preg_match(
+ '/the requested PHP extension [a-z-A-Z-_]+ is missing from your system/',
+ $line
+ );
+ })
+ ->map(function ($extError) {
+ preg_match(
+ '/the requested PHP extension ([a-z-A-Z-_]+) is missing from your system/',
+ $extError,
+ $match
+ );
+
+ return trim($match[1]);
+ })
+ ->unique();
+ }
+
+ /**
+ * @return int
+ */
+ public function getExitCode()
+ {
+ return $this->exitCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * @return bool
+ */
+ public function missingExtensions()
+ {
+ return !$this->missingExtensions->isEmpty();
+ }
+
+ /**
+ * @return array
+ */
+ public function getMissingExtensions()
+ {
+ return $this->missingExtensions->all();
+ }
+}
diff --git a/src/Installer/Installer.php b/src/Installer/Installer.php
index fff4425..0640a6d 100644
--- a/src/Installer/Installer.php
+++ b/src/Installer/Installer.php
@@ -5,6 +5,7 @@
use Exception;
use Github\Client;
use Github\Exception\ExceptionInterface;
+use PhpSchool\WorkshopManager\ComposerInstaller;
use PhpSchool\WorkshopManager\ComposerInstallerFactory;
use PhpSchool\WorkshopManager\Entity\InstalledWorkshop;
use PhpSchool\WorkshopManager\Entity\Workshop;
@@ -68,9 +69,9 @@ class Installer
private $versionChecker;
/**
- * @var ComposerInstallerFactory
+ * @var ComposerInstaller
*/
- private $composerFactory;
+ private $composerInstaller;
/**
* @param InstalledWorkshopRepository $installedWorkshops
@@ -78,7 +79,7 @@ class Installer
* @param Linker $linker
* @param Filesystem $filesystem
* @param string $workshopHomeDirectory
- * @param ComposerInstallerFactory $composerFactory
+ * @param ComposerInstaller $composerInstaller
* @param Client $gitHubClient
* @param VersionChecker $versionChecker
* @param string|null $notifyUrlFormat
@@ -89,7 +90,7 @@ public function __construct(
Linker $linker,
Filesystem $filesystem,
$workshopHomeDirectory,
- ComposerInstallerFactory $composerFactory,
+ ComposerInstaller $composerInstaller,
Client $gitHubClient,
VersionChecker $versionChecker,
$notifyUrlFormat = null
@@ -99,7 +100,7 @@ public function __construct(
$this->linker = $linker;
$this->filesystem = $filesystem;
$this->workshopHomeDirectory = $workshopHomeDirectory;
- $this->composerFactory = $composerFactory;
+ $this->composerInstaller = $composerInstaller;
$this->gitHubClient = $gitHubClient;
$this->versionChecker = $versionChecker;
$this->notifyFormat = $notifyUrlFormat ?: $this->notifyFormat;
@@ -163,17 +164,20 @@ public function installWorkshop($workshop)
$this->filesystem->executeInPath($destinationPath, function ($path) {
try {
- $res = $this->composerFactory->create($path)->run();
+ $result = $this->composerInstaller->install($path);
} catch (Exception $e) {
throw ComposerFailureException::fromException($e);
}
- if ($res > 0) {
+ if ($result->getExitCode() > 0) {
+ if ($result->missingExtensions()) {
+ throw ComposerFailureException::fromMissingExtensions($result->getMissingExtensions());
+ }
+
throw new ComposerFailureException();
}
});
-
$installedWorkshop = InstalledWorkshop::fromWorkshop($workshop, $release->getTag());
$this->installedWorkshopRepository->add($installedWorkshop);
$this->installedWorkshopRepository->save();
diff --git a/src/RecordingOutput.php b/src/RecordingOutput.php
new file mode 100644
index 0000000..8202701
--- /dev/null
+++ b/src/RecordingOutput.php
@@ -0,0 +1,175 @@
+
+ */
+class RecordingOutput implements OutputInterface
+{
+ /**
+ * @var OutputInterface
+ */
+ private $output;
+
+ /**
+ * @var string
+ */
+ private $buffer = '';
+
+ /**
+ * @param OutputInterface $output
+ */
+ public function __construct(OutputInterface $output)
+ {
+ $this->output = $output;
+ }
+
+ /**
+ * Returns whether verbosity is debug (-vvv).
+ *
+ * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
+ */
+ public function isDebug()
+ {
+ return $this->output->isDebug();
+ }
+
+ /**
+ * Sets output formatter.
+ *
+ * @param OutputFormatterInterface $formatter
+ */
+ public function setFormatter(OutputFormatterInterface $formatter)
+ {
+ $this->output->setFormatter($formatter);
+ }
+
+ /**
+ * Returns whether verbosity is verbose (-v).
+ *
+ * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
+ */
+ public function isVerbose()
+ {
+ return $this->output->isVerbose();
+ }
+
+ /**
+ * Returns whether verbosity is very verbose (-vv).
+ *
+ * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
+ */
+ public function isVeryVerbose()
+ {
+ return $this->output->isVeryVerbose();
+ }
+
+ /**
+ * Writes a message to the output.
+ *
+ * @param string|array $messages The message as an array of lines or a single string
+ * @param bool $newline Whether to add a newline
+ * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
+ * the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
+ */
+ public function write($messages, $newline = false, $options = 0)
+ {
+ $messages = (array) $messages;
+ $this->buffer .= sprintf('%s%s', implode($newline ? "\n" : '', $messages), $newline ? "\n" : '');
+ return $this->output->write($messages, $newline, $options);
+ }
+
+ /**
+ * Writes a message to the output and adds a newline at the end.
+ *
+ * @param string|array $messages The message as an array of lines of a single string
+ * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered
+ * the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
+ */
+ public function writeln($messages, $options = 0)
+ {
+ return $this->write($messages, true, $options);
+ }
+
+ /**
+ * Sets the verbosity of the output.
+ *
+ * @param int $level The level of verbosity (one of the VERBOSITY constants)
+ */
+ public function setVerbosity($level)
+ {
+ $this->output->setVerbosity($level);
+ }
+
+ /**
+ * Gets the current verbosity of the output.
+ *
+ * @return int The current level of verbosity (one of the VERBOSITY constants)
+ */
+ public function getVerbosity()
+ {
+ return $this->output->getVerbosity();
+ }
+
+ /**
+ * Sets the decorated flag.
+ *
+ * @param bool $decorated Whether to decorate the messages
+ */
+ public function setDecorated($decorated)
+ {
+ $this->output->setDecorated($decorated);
+ }
+
+ /**
+ * Gets the decorated flag.
+ *
+ * @return bool true if the output will decorate messages, false otherwise
+ */
+ public function isDecorated()
+ {
+ return $this->output->isDecorated();
+ }
+
+ /**
+ * Returns current output formatter instance.
+ *
+ * @return OutputFormatterInterface
+ */
+ public function getFormatter()
+ {
+ return $this->output->getFormatter();
+ }
+
+ /**
+ * Returns whether verbosity is quiet (-q).
+ *
+ * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
+ */
+ public function isQuiet()
+ {
+ return $this->output->isQuiet();
+ }
+
+ /**
+ * @return string
+ */
+ public function getOutput()
+ {
+ //see \Composer\IO\BufferIO
+ return preg_replace_callback("{(?<=^|\n|\x08)(.+?)(\x08+)}", function ($matches) {
+ $pre = strip_tags($matches[1]);
+
+ if (strlen($pre) === strlen($matches[2])) {
+ return '';
+ }
+
+ return rtrim($matches[1])."\n";
+ }, $this->buffer);
+ }
+}
diff --git a/test/Command/InstallWorkshopTest.php b/test/Command/InstallWorkshopTest.php
index 3de0988..04d9aa3 100644
--- a/test/Command/InstallWorkshopTest.php
+++ b/test/Command/InstallWorkshopTest.php
@@ -129,15 +129,18 @@ public function testWhenComposerInstallFails()
->expects($this->once())
->method('installWorkshop')
->with('learnyouphp')
- ->willThrowException(new ComposerFailureException('Some error'));
+ ->willThrowException(new ComposerFailureException('Some error.'));
- $msg = " There was a problem installing dependencies for \"learnyouphp\". Try running in verbose mode";
- $msg .= sprintf(" to see the composer error: %s install -v \n", $_SERVER['argv'][0]);
+ $msg = " There was a problem installing dependencies for \"learnyouphp\". Some error.";
+ $msg .= sprintf(
+ " Try running in verbose mode to see more details: %s install -v \n",
+ $_SERVER['argv'][0]
+ );
$this->output
->expects($this->exactly(2))
->method('writeln')
->withConsecutive(
- [""],
+ [''],
[$msg]
);
diff --git a/test/Command/VerifyInstallTest.php b/test/Command/VerifyInstallTest.php
index 395d744..0dbf35b 100644
--- a/test/Command/VerifyInstallTest.php
+++ b/test/Command/VerifyInstallTest.php
@@ -52,7 +52,7 @@ public function testErrorIsPrintedIfWorkshopDirNotInPath()
$output = $this->output->fetch();
$this->assertRegExp(
- sprintf('/%s/', preg_quote('The PHP School bin directory is not in your PATH variable.')),
+ sprintf('/%s/', preg_quote('[ERROR] The PHP School bin directory is not in your PATH variable.')),
$output
);
}
@@ -65,7 +65,42 @@ public function testSuccessIsPrintedIfWorkshopDirInPath()
$output = $this->output->fetch();
$this->assertRegExp(
- sprintf('/%s/', preg_quote('Your $PATH environment variable is configured correctly')),
+ sprintf('/%s/', preg_quote('[OK] Your $PATH environment variable is configured correctly')),
+ $output
+ );
+ }
+
+ public function testAllRequiredExtensions()
+ {
+ $this->command->__invoke();
+
+ $output = $this->output->fetch();
+ $this->assertRegExp(
+ sprintf('/%s/', preg_quote('[OK] All required PHP extensions are installed.')),
+ $output
+ );
+ }
+
+ public function testMissingExtensions()
+ {
+ $rc = new \ReflectionClass(VerifyInstall::class);
+ $rp = $rc->getProperty('requiredExtensions');
+ $rp->setAccessible(true);
+ $rp->setValue($this->command, ['some-ext']);
+
+ $this->command->__invoke();
+
+ $output = $this->output->fetch();
+ $this->assertRegExp(
+ sprintf(
+ '/%s/',
+ preg_quote('[ERROR] The some-ext extension is missing - use your preferred package manager to install it')
+ ),
+ $output
+ );
+
+ $this->assertNotRegExp(
+ sprintf('/%s/', preg_quote('[OK] All required PHP extensions are installed.')),
$output
);
}
diff --git a/test/ComposerInstallerFactoryTest.php b/test/ComposerInstallerFactoryTest.php
deleted file mode 100644
index 50bbfb8..0000000
--- a/test/ComposerInstallerFactoryTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- */
-class ComposerInstallerFactoryTest extends PHPUnit_Framework_TestCase
-{
- public function testCreate()
- {
- $tmpDir = sprintf('%s/%s', sys_get_temp_dir(), $this->getName());
- @mkdir($tmpDir);
- file_put_contents(sprintf('%s/composer.json', $tmpDir), json_encode(['name' => 'project']));
-
- $factory = new ComposerInstallerFactory(new Factory, new NullIO);
-
- $this->assertInstanceOf(Installer::class, $factory->create($tmpDir));
-
- unlink(sprintf('%s/composer.json', $tmpDir));
- rmdir($tmpDir);
- }
-}
diff --git a/test/ComposerInstallerTest.php b/test/ComposerInstallerTest.php
new file mode 100644
index 0000000..96e2b50
--- /dev/null
+++ b/test/ComposerInstallerTest.php
@@ -0,0 +1,117 @@
+
+ */
+class ComposerInstallerTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @var string
+ */
+ private $tempDir;
+
+ /**
+ * @var Filesystem
+ */
+ private $filesystem;
+
+ public function setUp()
+ {
+ $this->filesystem = new Filesystem;
+ $this->tempDir = sprintf('%s/%s', realpath(sys_get_temp_dir()), $this->getName());
+ @mkdir($this->tempDir, 0777, true);
+ }
+
+ public function tearDown()
+ {
+ $this->filesystem->remove($this->tempDir);
+ }
+
+ public function testComposerOutputIsWrittenIfInVerboseMode()
+ {
+ $input = new ArrayInput([]);
+ $output = new BufferedOutput();
+ $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
+
+ $installer = new ComposerInstaller($input, $output, new Factory);
+ file_put_contents(sprintf('%s/composer.json', $this->tempDir), '{"name" : "learnyouphp", "require" : { "php": ">=5.6"}}');
+ $res = $installer->install($this->tempDir);
+
+ $this->assertFileExists(sprintf('%s/vendor', $this->tempDir));
+ $this->assertFileExists(sprintf('%s/composer.lock', $this->tempDir));
+
+ $expectedOutput = "/Loading composer repositories with package information\n";
+ $expectedOutput .= "Updating dependencies\n";
+ $expectedOutput .= "Dependency resolution completed in \\d+\\.\\d+ seconds\n";
+ $expectedOutput .= "Analyzed \\d+ packages to resolve dependencies\n";
+ $expectedOutput .= "Analyzed \\d+ rules to resolve dependencies\n";
+ $expectedOutput .= "Nothing to install or update\n";
+ $expectedOutput .= "Writing lock file\n";
+ $expectedOutput .= "Generating autoload files\n/";
+ $this->assertRegExp($expectedOutput, $output->fetch());
+ $this->assertRegExp($expectedOutput, strip_tags($res->getOutput()));
+ $this->assertEquals(0, $res->getExitCode());
+ }
+
+ public function testComposerOutputIsNotWrittenIfNotInVerboseMode()
+ {
+ $input = new ArrayInput([]);
+ $output = new BufferedOutput;
+ $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
+
+ $installer = new ComposerInstaller($input, $output, new Factory);
+ file_put_contents(sprintf('%s/composer.json', $this->tempDir), '{"name" : "learnyouphp", "require" : { "php": ">=5.6"}}');
+ $res = $installer->install($this->tempDir);
+
+ $this->assertFileExists(sprintf('%s/vendor', $this->tempDir));
+ $this->assertFileExists(sprintf('%s/composer.lock', $this->tempDir));
+
+ $expectedOutput = "/Loading composer repositories with package information\n";
+ $expectedOutput .= "Updating dependencies\n";
+ $expectedOutput .= "Dependency resolution completed in \\d+\\.\\d+ seconds\n";
+ $expectedOutput .= "Analyzed \\d+ packages to resolve dependencies\n";
+ $expectedOutput .= "Analyzed \\d+ rules to resolve dependencies\n";
+ $expectedOutput .= "Nothing to install or update\n";
+ $expectedOutput .= "Writing lock file\n";
+ $expectedOutput .= "Generating autoload files\n/";
+ $this->assertEquals('', $output->fetch());
+ $this->assertRegExp($expectedOutput, strip_tags($res->getOutput()));
+ $this->assertEquals(0, $res->getExitCode());
+ }
+
+ public function testExceptionIsThrownIfNoComposerJson()
+ {
+ $input = new ArrayInput([]);
+ $output = new BufferedOutput;
+ $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
+
+ $this->expectException(\InvalidArgumentException::class);
+
+ $installer = new ComposerInstaller($input, $output, new Factory);
+ $installer->install($this->tempDir);
+ }
+
+ public function testExceptionIsThrownIfInvalidComposerJson()
+ {
+ $input = new ArrayInput([]);
+ $output = new BufferedOutput;
+ $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
+
+ $this->expectException(ParsingException::class);
+
+ $installer = new ComposerInstaller($input, $output, new Factory);
+ file_put_contents(sprintf('%s/composer.json', $this->tempDir), '{"name" : "learnyouphp"');
+ $installer->install($this->tempDir);
+ }
+}
diff --git a/test/Exception/ComposerFailureExceptionTest.php b/test/Exception/ComposerFailureExceptionTest.php
new file mode 100644
index 0000000..0e10cb9
--- /dev/null
+++ b/test/Exception/ComposerFailureExceptionTest.php
@@ -0,0 +1,30 @@
+
+ */
+class ComposerFailureExceptionTest extends PHPUnit_Framework_TestCase
+{
+ public function testFromException()
+ {
+ $e = new Exception('Some Error');
+
+ $composerException = ComposerFailureException::fromException($e);
+ $this->assertEquals('Some Error', $composerException->getMessage());
+ }
+
+ public function testFromMissingExtensions()
+ {
+ $message = 'This workshop requires some extra PHP extensions. Please install them';
+ $message .= ' and try again. Required extensions are mbstring, zip.';
+
+ $composerException = ComposerFailureException::fromMissingExtensions(['mbstring', 'zip']);
+ $this->assertEquals($message, $composerException->getMessage());
+ }
+}
diff --git a/test/IOFactoryTest.php b/test/IOFactoryTest.php
deleted file mode 100644
index 40a8dc0..0000000
--- a/test/IOFactoryTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
- */
-class IOFactoryTest extends PHPUnit_Framework_TestCase
-{
- public function testGetIO()
- {
- $factory = new IOFactory;
- $input = $this->createMock(InputInterface::class);
- $output = $this->createMock(OutputInterface::class);
-
- $this->assertInstanceOf(ConsoleIO::class, $factory->getIO($input, $output));
- }
-
- public function testGetNullableIOReturnsNullIOIfNotInVerboseMode()
- {
- $factory = new IOFactory;
- $input = $this->createMock(InputInterface::class);
- $output = $this->createMock(OutputInterface::class);
-
- $output
- ->expects($this->once())
- ->method('getVerbosity')
- ->willReturn(OutputInterface::VERBOSITY_NORMAL);
-
- $this->assertInstanceOf(NullIO::class, $factory->getNullableIO($input, $output));
- }
-
- public function testGetNullableIOReturnsNormalIOIfInVerboseMode()
- {
- $factory = new IOFactory;
- $input = $this->createMock(InputInterface::class);
- $output = $this->createMock(OutputInterface::class);
-
- $output
- ->expects($this->once())
- ->method('getVerbosity')
- ->willReturn(OutputInterface::VERBOSITY_VERBOSE);
-
- $this->assertInstanceOf(ConsoleIO::class, $factory->getNullableIO($input, $output));
- }
-}
diff --git a/test/InstallResultTest.php b/test/InstallResultTest.php
new file mode 100644
index 0000000..9580633
--- /dev/null
+++ b/test/InstallResultTest.php
@@ -0,0 +1,48 @@
+
+ */
+class InstallResultTest extends PHPUnit_Framework_TestCase
+{
+ public function testGetters()
+ {
+ $result = new InstallResult(0, '');
+
+ $this->assertEquals(0, $result->getExitCode());
+ $this->assertEquals('', $result->getOutput());
+ $this->assertFalse($result->missingExtensions());
+ $this->assertEmpty($result->getMissingExtensions());
+ }
+
+ public function testMissingExtensionsAreParsed()
+ {
+ $output = "the requested PHP extension mbstring is missing from your system\n";
+ $output .= "the requested PHP extension zip is missing from your system\n";
+
+ $result = new InstallResult(1, $output);
+
+ $this->assertEquals(1, $result->getExitCode());
+ $this->assertEquals($output, $result->getOutput());
+ $this->assertTrue($result->missingExtensions());
+ $this->assertSame(['mbstring', 'zip'], $result->getMissingExtensions());
+ }
+
+ public function testMissingExtensionsDupesAreRemoved()
+ {
+ $output = "the requested PHP extension mbstring is missing from your system\n";
+ $output .= "the requested PHP extension mbstring is missing from your system\n";
+
+ $result = new InstallResult(1, $output);
+
+ $this->assertEquals(1, $result->getExitCode());
+ $this->assertEquals($output, $result->getOutput());
+ $this->assertTrue($result->missingExtensions());
+ $this->assertSame(['mbstring'], $result->getMissingExtensions());
+ }
+}
diff --git a/test/Intstaller/InstallerTest.php b/test/Intstaller/InstallerTest.php
index 1719d3b..2206bda 100644
--- a/test/Intstaller/InstallerTest.php
+++ b/test/Intstaller/InstallerTest.php
@@ -11,6 +11,7 @@
use Github\Api\Repository\Contents;
use Github\Client;
use Github\Exception\RuntimeException;
+use PhpSchool\WorkshopManager\ComposerInstaller;
use PhpSchool\WorkshopManager\ComposerInstallerFactory;
use PhpSchool\WorkshopManager\Entity\InstalledWorkshop;
use PhpSchool\WorkshopManager\Entity\Workshop;
@@ -21,11 +22,14 @@
use PhpSchool\WorkshopManager\Exception\WorkshopNotFoundException;
use PhpSchool\WorkshopManager\Filesystem;
use PhpSchool\WorkshopManager\Installer\Installer;
+use PhpSchool\WorkshopManager\InstallResult;
use PhpSchool\WorkshopManager\Linker;
use PhpSchool\WorkshopManager\Repository\InstalledWorkshopRepository;
use PhpSchool\WorkshopManager\Repository\RemoteWorkshopRepository;
use PhpSchool\WorkshopManager\VersionChecker;
use PHPUnit_Framework_TestCase;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\BufferedOutput;
/**
* @author Aydin Hassan
@@ -38,7 +42,7 @@ class InstallerTest extends PHPUnit_Framework_TestCase
private $linker;
private $filesystem;
private $workshopHomeDir;
- private $composerFactory;
+ private $composerInstaller;
private $versionChecker;
private $ghClient;
private $installer;
@@ -56,7 +60,7 @@ public function setup()
$this->linker = $this->createMock(Linker::class);
$this->filesystem = new Filesystem;
$this->workshopHomeDir = sprintf('%s/%s', realpath(sys_get_temp_dir()), $this->getName());
- $this->composerFactory = new ComposerInstallerFactory(new Factory, new NullIO);
+ $this->composerInstaller = $this->createMock(ComposerInstaller::class);
@mkdir($this->workshopHomeDir);
$this->ghClient = $this->createMock(Client::class);
$this->versionChecker = new VersionChecker($this->ghClient);
@@ -66,7 +70,7 @@ public function setup()
$this->linker,
$this->filesystem,
$this->workshopHomeDir,
- $this->composerFactory,
+ $this->composerInstaller,
$this->ghClient,
$this->versionChecker,
'/dev/null/%s/%s'
@@ -222,15 +226,43 @@ public function testExceptionIsThrownIfCannotMoveWorkshopToInstallDir()
public function testExceptionIsThrownIfCannotRunComposerInstall()
{
+ $path = sprintf('%s/workshops/', $this->workshopHomeDir);
+ @mkdir($path);
+
$workshop = $this->configureRemoteRepository();
$this->configureTags($workshop);
- $this->configureDownload($workshop, false);
+ $this->configureDownload($workshop);
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%slearn-you-php', $path))
+ ->will($this->throwException(new \InvalidArgumentException('composer.json not found')));
+
+ $this->expectException(ComposerFailureException::class);
+ $this->expectExceptionMessage('composer.json not found');
+ $this->installer->installWorkshop($workshop->getCode());
+ }
+
+ public function testExceptionIsThrownIfCannotRunComposerInstallBecauseMissingExtensions()
+ {
$path = sprintf('%s/workshops/', $this->workshopHomeDir);
@mkdir($path);
+ $workshop = $this->configureRemoteRepository();
+ $this->configureTags($workshop);
+ $this->configureDownload($workshop);
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%slearn-you-php', $path))
+ ->willReturn(new InstallResult(1, "the requested PHP extension mbstring is missing from your system\n"));
+
+ $message = 'This workshop requires some extra PHP extensions. Please install them';
+ $message .= ' and try again. Required extensions are mbstring.';
+
$this->expectException(ComposerFailureException::class);
- $this->expectExceptionMessageRegExp('/^Composer could not find the config.*/');
+ $this->expectExceptionMessage($message);
$this->installer->installWorkshop($workshop->getCode());
}
@@ -244,6 +276,12 @@ public function testSuccessfulInstall()
$path = sprintf('%s/workshops/', $this->workshopHomeDir);
@mkdir($path);
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%slearn-you-php', $path))
+ ->willReturn(new InstallResult(0, ''));
+
$this->localJsonFile
->expects($this->once())
->method('write');
@@ -265,6 +303,12 @@ public function testWorkshopDirIsCreatedIfNotExists()
$this->configureTags($workshop);
$this->configureDownload($workshop);
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%s/workshops/learn-you-php', $this->workshopHomeDir))
+ ->willReturn(new InstallResult(0, ''));
+
$this->localJsonFile
->expects($this->once())
->method('write');
@@ -287,6 +331,12 @@ public function testWorkshopNameFolderIsRemovedIfExists()
$this->configureDownload($workshop);
mkdir(sprintf('%s/workshops/learn-you-php', $this->workshopHomeDir), 0775, true);
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%s/workshops/learn-you-php', $this->workshopHomeDir))
+ ->willReturn(new InstallResult(0, ''));
+
$this->localJsonFile
->expects($this->once())
->method('write');
@@ -310,6 +360,12 @@ public function testWorkshopTempDownloadIsRemovedIfExists()
mkdir(sprintf('%s/.temp', $this->workshopHomeDir), 0775, true);
touch(sprintf('%s/.temp/learn-you-php.zip', $this->workshopHomeDir));
+ $this->composerInstaller
+ ->expects($this->once())
+ ->method('install')
+ ->with(sprintf('%s/workshops/learn-you-php', $this->workshopHomeDir))
+ ->willReturn(new InstallResult(0, ''));
+
$this->localJsonFile
->expects($this->once())
->method('write');
@@ -371,7 +427,7 @@ private function configureTags(Workshop $workshop)
]);
}
- private function configureDownload(Workshop $workshop, $correctComposerJson = true)
+ private function configureDownload(Workshop $workshop)
{
$repo = $this->createMock(Repo::class);
$contents = $this->createMock(Contents::class);
@@ -391,10 +447,7 @@ private function configureDownload(Workshop $workshop, $correctComposerJson = tr
$zipArchive->open(sprintf('%s/temp.zip', $this->workshopHomeDir), \ZipArchive::CREATE);
$zipArchive->addEmptyDir('learnyouphp');
$zipArchive->addFromString('learnyouphp/file1.txt', 'data');
-
- if ($correctComposerJson) {
- $zipArchive->addFromString('learnyouphp/composer.json', '{"name" : "learnyouphp"}');
- }
+ $zipArchive->addFromString('learnyouphp/composer.json', '{"name" : "learnyouphp"}');
$zipArchive->close();
diff --git a/test/RecordingOutputTest.php b/test/RecordingOutputTest.php
new file mode 100644
index 0000000..6e40ef5
--- /dev/null
+++ b/test/RecordingOutputTest.php
@@ -0,0 +1,107 @@
+
+ */
+class RecordingOutputTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @var RecordingOutput
+ */
+ private $output;
+
+ /**
+ * @var BufferedOutput
+ */
+ private $wrappedOutput;
+
+ public function setup()
+ {
+ $this->wrappedOutput = new BufferedOutput;
+ $this->output = new RecordingOutput($this->wrappedOutput);
+ }
+
+ public function testMethodsDelegateToWrapped()
+ {
+ $this->assertSame($this->wrappedOutput->isDebug(), $this->output->isDebug());
+ $this->assertSame($this->wrappedOutput->isVerbose(), $this->output->isVerbose());
+ $this->assertSame($this->wrappedOutput->isVeryVerbose(), $this->output->isVeryVerbose());
+ $this->assertSame($this->wrappedOutput->getVerbosity(), $this->output->getVerbosity());
+ $this->assertSame($this->wrappedOutput->isDecorated(), $this->output->isDecorated());
+ $this->assertSame($this->wrappedOutput->getFormatter(), $this->output->getFormatter());
+ $this->assertSame($this->wrappedOutput->isQuiet(), $this->output->isQuiet());
+ }
+
+ public function testSettersDelegateToWrapped()
+ {
+ $formatter = new OutputFormatter;
+ $this->assertNotSame($formatter, $this->wrappedOutput->getFormatter());
+ $this->assertFalse($this->wrappedOutput->isDecorated());
+ $this->assertEquals(OutputInterface::VERBOSITY_NORMAL, $this->wrappedOutput->getVerbosity());
+
+ $this->output->setFormatter($formatter);
+ $this->assertSame($formatter, $this->wrappedOutput->getFormatter());
+
+ $this->output->setDecorated(true);
+ $this->assertTrue($this->output->isDecorated());
+
+ $this->output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
+ $this->assertEquals(OutputInterface::VERBOSITY_VERBOSE, $this->wrappedOutput->getVerbosity());
+ }
+
+ public function testWriteWithRecordsAndDelegates()
+ {
+ $this->output->write('Hello', false);
+
+ $this->assertEquals('Hello', $this->output->getOutput());
+ $this->assertEquals('Hello', $this->wrappedOutput->fetch());
+ }
+
+ public function testWriteWithArrayAndRecordsAndDelegates()
+ {
+ $this->output->write(['Hello', 'Aydin'], false);
+
+ $this->assertEquals('HelloAydin', $this->output->getOutput());
+ $this->assertEquals('HelloAydin', $this->wrappedOutput->fetch());
+ }
+
+ public function testWriteWithNewLineRecordsAndDelegates()
+ {
+ $this->output->write('Hello', true);
+
+ $this->assertEquals("Hello\n", $this->output->getOutput());
+ $this->assertEquals("Hello\n", $this->wrappedOutput->fetch());
+ }
+
+ public function testWriteWithArrayAndNewLineRecordsAndDelegates()
+ {
+ $this->output->write(['Hello', 'Aydin'], true);
+
+ $this->assertEquals("Hello\nAydin\n", $this->output->getOutput());
+ $this->assertEquals("Hello\nAydin\n", $this->wrappedOutput->fetch());
+ }
+
+ public function testWriteLineRecordsAndDelegates()
+ {
+ $this->output->writeln('Hello');
+
+ $this->assertEquals("Hello\n", $this->output->getOutput());
+ $this->assertEquals("Hello\n", $this->wrappedOutput->fetch());
+ }
+
+ public function testWriteLineWithArrayRecordsAndDelegates()
+ {
+ $this->output->writeln(['Hello', 'Aydin']);
+
+ $this->assertEquals("Hello\nAydin\n", $this->output->getOutput());
+ $this->assertEquals("Hello\nAydin\n", $this->wrappedOutput->fetch());
+ }
+}