From 08b37553cc81a24ab72c7ea658218356315be43c Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Sun, 9 Aug 2015 17:55:58 -0500 Subject: [PATCH 01/15] Import the entire puphpet-release-composer-installer source. --- .gitignore | 9 +- composer.json | 40 ++++ phpunit.xml.dist | 26 ++ .../Composer/PuphpetReleaseInstaller.php | 128 ++++++++++ src/bootstrap.php | 15 ++ .../Test/PuphpetReleaseInstallerTest.php | 55 +++++ tests/bootstrap.php | 3 + tests/integration/composer.json | 25 ++ tests/integration/puphpet.yaml | 36 +++ .../integration/simulate-composer-install.sh | 222 ++++++++++++++++++ 10 files changed, 555 insertions(+), 4 deletions(-) create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Loadsys/Composer/PuphpetReleaseInstaller.php create mode 100644 src/bootstrap.php create mode 100644 tests/Loadsys/Composer/Test/PuphpetReleaseInstallerTest.php create mode 100644 tests/bootstrap.php create mode 100755 tests/integration/composer.json create mode 100755 tests/integration/puphpet.yaml create mode 100755 tests/integration/simulate-composer-install.sh diff --git a/.gitignore b/.gitignore index 3a693c9..b9e67eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +# Ignore composer deps. +/vendor/ +/composer.lock composer.phar -vendor/ -# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +# Ignore the folder we use when testing the installers. +/build/* diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e70d9ee --- /dev/null +++ b/composer.json @@ -0,0 +1,40 @@ +{ + "name": "loadsys/composer-plugins", + "description": "Provides composer custom installers and hook scripts.", + "keywords": [ + "composer", + "plugins", + "loadsys", + "codesniffer", + "puphpet", + "vagrant", + "dangerous", + "do-not-use", + "overwrites-files" + ], + "type": "composer-plugin", + "license": "MIT", + "authors": [ + { + "name": "Brian Porter", + "email": "beporter@users.sourceforge.net" + } + ], + "autoload": { + "psr-0": { + "Loadsys\\Composer": "src/" + } + }, + "extra": { + "class": "Loadsys\\Composer\\LoadsysComposerPlugin" + }, + "require": { + "composer-plugin-api": "~1.0.0", + "symfony/filesystem": "~2.6" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "~4.1", + "squizlabs/php_codesniffer": "~2.0" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..040070d --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + tests/Loadsys/Composer + + + + + + src/Loadsys/Composer + + + \ No newline at end of file diff --git a/src/Loadsys/Composer/PuphpetReleaseInstaller.php b/src/Loadsys/Composer/PuphpetReleaseInstaller.php new file mode 100644 index 0000000..d7d6a3c --- /dev/null +++ b/src/Loadsys/Composer/PuphpetReleaseInstaller.php @@ -0,0 +1,128 @@ +supports($package->getType())) { + return; + } + $this->mirrorReleaseItems($package); + $this->copyConfigFile($package); + $this->checkGitignore($package); + } + + /** + * Mirror (copy or delete, only as necessary) items from the installed + * package's release/ folder into the target directory. + * + */ + protected function mirrorReleaseItems($package) { + // Copy everything from the release/ subfolder to the project root. + $releaseDir = $this->getInstallPath($package) . DS . 'release'; + $targetDir = getcwd(); + $acceptList = [ + 'Vagrantfile', + 'puphpet', + ]; + + // Return true if the first part of the subpath for the current file exists in the accept array. + $acceptFunc = function ($current, $key, $iterator) use ($acceptList) { + $pathComponents = explode(DS, $iterator->getSubPathname()); + return in_array($pathComponents[0], $acceptList); + }; + $dirIterator = new RecursiveDirectoryIterator($releaseDir, RecursiveDirectoryIterator::SKIP_DOTS); + $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, $acceptFunc); + $releaseItems = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::SELF_FIRST); + + $filesystem = new Filesystem(); + $filesystem->mirror($releaseDir, $targetDir, $releaseItems, ['override' => true]); + } + + /** + * Search for a config file in the consuming project and copy it into + * place if present. + * + */ + protected function copyConfigFile($package) { + $configFilePath = getcwd() . DS . 'puphpet.yaml'; + $targetPath = getcwd() . DS . 'puphpet' . DS . 'config.yaml'; + if (is_readable($configFilePath)) { + copy($configFilePath, $targetPath); + } + } + + /** + * Check that release items copied into the consuming project are + * properly ignored in source control (very, VERY crudely.) + * + */ + protected function checkGitIgnore($package) { + $gitFolder = getcwd() . DS . '.git' . DS; + + if (!file_exists($gitFolder)) { + return; + } + + $gitignoreFile = getcwd() . DS . '.gitignore'; + $required = [ + '/Vagrantfile', + '/puphpet/', + '/.vagrant/', + ]; + + touch($gitignoreFile); + $lines = file($gitignoreFile, FILE_IGNORE_NEW_LINES); + + foreach ($required as $entry) { + if (!in_array($entry, $lines)) { + $lines[] = $entry; + } + } + + file_put_contents($gitignoreFile, implode(PHP_EOL, $lines)); + } +} diff --git a/src/bootstrap.php b/src/bootstrap.php new file mode 100644 index 0000000..7a9060b --- /dev/null +++ b/src/bootstrap.php @@ -0,0 +1,15 @@ +package = new Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('Composer\IO\PackageInterface'); + $this->composer = new Composer(); + $this->composer->setConfig(new Config(false)); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + + parent::tearDown(); + } + + /** + * testNothing + * + * @return void + */ + public function testNothing() { + $this->markTestIncomplete('@TODO: No tests written for PuphpetReleaseInstaller.'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..b053da6 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ +add('Loadsys\Composer\Test', __DIR__); diff --git a/tests/integration/composer.json b/tests/integration/composer.json new file mode 100755 index 0000000..5d9eb5d --- /dev/null +++ b/tests/integration/composer.json @@ -0,0 +1,25 @@ +{ + "name": "me/test-composer-installer", + "description": "Template file for testing the puphpet-release-composer-installer. Written to ../tmp/ with proper branch names.", + "require": { + "loadsys/puphpet-release-composer-installer": "dev-PRCI_BRANCH_NAME", + "loadsys/puphpet-release": "dev-PR_BRANCH_NAME" + }, + "repositories": [ + { + "packagist": false + }, + { + "type": "vcs", + "url": "https://github.com/symfony/Filesystem.git" + }, + { + "type": "vcs", + "url": "PR_DIRECTORY" + }, + { + "type": "vcs", + "url": "../" + } + ] +} diff --git a/tests/integration/puphpet.yaml b/tests/integration/puphpet.yaml new file mode 100755 index 0000000..8a0d652 --- /dev/null +++ b/tests/integration/puphpet.yaml @@ -0,0 +1,36 @@ +--- +readme: + - 'Minimal config file to test the puphpet-release-composer-installer.' + - 'Not having this file screws up `vagrant global-status`.' + - 'Just have to define enough keys for the Vagrantfile to load successfully.' + - 'Do not change the next line. It is used during the test suite to confirm' + - 'that this file was copied into place correctly.' +canary: "foo" +vagrantfile-local: + vm: + box: puphpet/debian75-x64 + box_url: puphpet/debian75-x64 + hostname: 'dummy-entry' + memory: '128' + cpus: '1' + chosen_provider: virtualbox + network: + private_network: '0.0.0.0' + forwarded_port: { } + post_up_message: '' + provider: + virtualbox: + modifyvm: { } + vmware: { } + provision: + puppet: + manifests_path: puphpet/puppet + manifest_file: site.pp + module_path: puphpet/puppet/modules + options: { } + synced_folder: { } + usable_port_range: + start: 10200 + stop: 10500 + ssh: { } + vagrant: { } diff --git a/tests/integration/simulate-composer-install.sh b/tests/integration/simulate-composer-install.sh new file mode 100755 index 0000000..649b4c8 --- /dev/null +++ b/tests/integration/simulate-composer-install.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash + +#--------------------------------------------------------------------- +usage () { + cat </dev/null 2>&1 && pwd )" +TEST_DIR="${BASE_DIR}/tests/integration" +BUILD_DIR="${BASE_DIR}/build" + + +# Set testing mode. +TEST_MODE= +if [ "$1" = '-t' ]; then + echo "## Setting test mode ON." + TEST_MODE="yes" + shift +fi + + +# Make sure the build dir exists. +if [ ! -d "${BUILD_DIR}" ]; then + echo "## Creating build directory." + mkdir -p "${BUILD_DIR}" +fi + + +# Make sure the build dir contains a symlink to your working copy of `loadsys/puphpet-release`. +echo "## Checking the symlink to the release project." +RELEASE_PROJECT_SYMLINK="${BUILD_DIR}/release-project" + +if [ -h "${RELEASE_PROJECT_SYMLINK}" ]; then + RELEASE_PROJECT_PATH=$(readlink "${RELEASE_PROJECT_SYMLINK}") +elif [ -d "${RELEASE_PROJECT_SYMLINK}" ]; then + RELEASE_PROJECT_PATH="${RELEASE_PROJECT_SYMLINK}" +elif [ "${TEST_MODE}" ]; then + echo "!! No symlink to the release project working copy" + echo "!! is present at \`${RELEASE_PROJECT_SYMLINK}\`." + echo "!! Please create it." + exit 1 +else + read -p " Please provide the path to the release project working copy > " RELEASE_PROJECT_PATH + ln -s "${RELEASE_PROJECT_PATH}" "${RELEASE_PROJECT_SYMLINK}" +fi + + +# Set the release project's branch name to use. +if [ "${TEST_MODE}" ]; then + # In testing mode, just default to master when no arg provided. + RELEASE_PROJECT_BRANCH=${1:-master} + shift +elif [ -n "$1" ]; then + RELEASE_PROJECT_BRANCH=$1 +else + read -p " Please provide the branch name from the release project to use > " RELEASE_PROJECT_BRANCH +fi +echo "## Release project branch name is \`${RELEASE_PROJECT_BRANCH}\`." + + +# Get the name of the branch that is currently checked out in ../ to use. +# echo " !! DEBUG VALUES !!" +# echo " TRAVIS_TAG= $TRAVIS_TAG" +# echo " TRAVIS_PULL_REQUEST= $TRAVIS_PULL_REQUEST" +# echo " TRAVIS_COMMIT= $TRAVIS_COMMIT" +# echo " TRAVIS_BRANCH= $TRAVIS_BRANCH" + +if [ -n "${TRAVIS_TAG}" ]; then + echo "" + echo "!! Unable to perform integration tests against git tags in Travis !!" + echo "" + echo " Travis does not identify the originating branch name when " + echo " executing a build for a git tag (TRAVIS_BRANCH is unhelpfully " + echo " the same as TRAVIS_TAG), and Composer does not provide a " + echo " way to target a non-version-number tag name, so we can not " + echo " write a custom composer.json to complete this build. " + echo "" + echo " Exiting 0" + exit 0 +elif [[ -n "${TRAVIS_PULL_REQUEST}" && "${TRAVIS_PULL_REQUEST}" -ne "false" ]]; then + INSTALLER_PROJECT_BRANCH="pull/${TRAVIS_PULL_REQUEST}/merge" + ( + cd "${BASE_DIR}" >/dev/null 2>&1 + git checkout -qb $INSTALLER_PROJECT_BRANCH + ) +elif [ -n "${TRAVIS_COMMIT}" ]; then + INSTALLER_PROJECT_BRANCH="${TRAVIS_BRANCH}#${TRAVIS_COMMIT}" +else + INSTALLER_PROJECT_BRANCH=$( cd "${BASE_DIR}" >/dev/null 2>&1; git rev-parse --quiet --abbrev-ref HEAD 2>/dev/null ) +fi +echo "## Installer project branch name is \`${INSTALLER_PROJECT_BRANCH}\`." + + +# Delete all contents from the build dir, except the .gitkeep file and release-project symlink. +echo "## Purging old files from build directory." +shopt -s dotglob extglob +( + cd "${BUILD_DIR}" + rm -rf !(.|..|.gitkeep|release-project|${SHUNIT_DOWNLOAD_VERSION}) +) + + +# Copy the testing files from tests/integration/ to build/. +echo "## Populating the build directory." +shopt -s dotglob +( + cd "${TEST_DIR}" + cp -R * "${BUILD_DIR}/" + mkdir "${BUILD_DIR}/.git/" +) + +shopt -u dotglob extglob + + +# Write the composer.json file in this test dir to the build/ dir, adding branch names obtained earlier. +echo "## Writing customized composer.json file." +sed \ + -e "s|PRCI_BRANCH_NAME|${INSTALLER_PROJECT_BRANCH}|" \ + -e "s|PR_BRANCH_NAME|${RELEASE_PROJECT_BRANCH}|" \ + -e "s|PR_DIRECTORY|${RELEASE_PROJECT_PATH}|" \ + <"${TEST_DIR}/composer.json" \ + >"${BUILD_DIR}/composer.json" + + +# Execute the `composer install` command itself. +echo "## Executing \`composer install\` in the build directory." +COMPOSER_OUTPUT=$( + cd "${BUILD_DIR}/"; + composer install --no-interaction --ignore-platform-reqs +) +COMPOSER_EXIT_CODE=$? + +# End the script if test mode is OFF. +if [ -z "${TEST_MODE}" ]; then + if [ "${COMPOSER_EXIT_CODE}" ]; then + echo "!! Composer installation failed. Examine the results in \`${BUILD_DIR}\`." + echo '' + echo "${COMPOSER_OUTPUT}" + exit $COMPOSER_EXIT_CODE + else + echo "## Done simulating \`composer install\`. Examine the results in \`${BUILD_DIR}\`." + exit 0 + fi +fi + + +# In test mode, run assertions using the shunit2 test framework. +# (We run this script via travis as an integration test suite.) + + +# Make sure we have shunit2 available. +SHUNIT_TMP_DOWNLOAD="${BUILD_DIR}/${SHUNIT_DOWNLOAD_VERSION}.tgz" +SHUNIT_EXTRACT_PATH="${BUILD_DIR}" +SHUNIT_EXECUTABLE="${SHUNIT_EXTRACT_PATH}/${SHUNIT_DOWNLOAD_VERSION}/src/shunit2" +if [ ! -x "${SHUNIT_EXECUTABLE}" ]; then + if [ ! -f "${SHUNIT_TMP_DOWNLOAD}" ]; then + echo "## Fetching shunit2." + curl -L \ + --silent \ + --output "${SHUNIT_TMP_DOWNLOAD}" \ + $SHUNIT_FETCH_URL + fi + echo "## Unpacking shunit2." + tar zxf "${SHUNIT_TMP_DOWNLOAD}" -C "${SHUNIT_EXTRACT_PATH}" +fi + + +# Define the tests to execute. +echo "## Defining tests." +testComposerExitCode () { + assertTrue "composer must not error during install. + +${COMPOSER_OUTPUT} + " "$COMPOSER_EXIT_CODE" +} + +testGitignore () { + grep -qe '^/Vagrantfile$' "${BUILD_DIR}/.gitignore" + assertTrue ".gitignore must have a '/Vagrantfile' entry." "$?" + + grep -qe '^/puphpet/$' "${BUILD_DIR}/.gitignore" + assertTrue ".gitignore must have a '/puphpet/' entry." "$?" + + grep -qe '^/.vagrant/$' "${BUILD_DIR}/.gitignore" + assertTrue ".gitignore must have a '/.vagrant/' entry." "$?" +} + +testPuphpetDir () { + [ -d "${BUILD_DIR}/puphpet" ] + assertTrue "puphpet/ directory must be present." "$?" || return + + grep -qe '^canary: "foo"$' "${BUILD_DIR}/puphpet/config.yaml" + assertTrue "puphpet.yaml file must be copied into puphpet/ directory." "$?" +} + +# Load and run shUnit2 +echo "## Executing tests:" +. "${SHUNIT_EXECUTABLE}" + From fa6f9f522b8afe49267621871d0c8a15d4be932f Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Sun, 9 Aug 2015 17:57:14 -0500 Subject: [PATCH 02/15] WIP PHPCS Coding Standard Installer Building the start of an installer that copies coding standards into the proper squizlabs/ subfolder. --- .../Composer/LoadsysComposerPlugin.php | 32 +++ .../Composer/PhpcsCodingStandardInstaller.php | 245 ++++++++++++++++++ .../Test/LoadsysComposerPluginTest.php | 75 ++++++ .../Test/PhpcsCodingStandardInstallerTest.php | 106 ++++++++ tests/samples/CodingStandardOne/ruleset.xml | 4 + tests/samples/NotACodingStandard/README.md | 5 + tests/samples/SecondStandard/ruleset.xml | 3 + 7 files changed, 470 insertions(+) create mode 100644 src/Loadsys/Composer/LoadsysComposerPlugin.php create mode 100644 src/Loadsys/Composer/PhpcsCodingStandardInstaller.php create mode 100644 tests/Loadsys/Composer/Test/LoadsysComposerPluginTest.php create mode 100644 tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php create mode 100644 tests/samples/CodingStandardOne/ruleset.xml create mode 100644 tests/samples/NotACodingStandard/README.md create mode 100644 tests/samples/SecondStandard/ruleset.xml diff --git a/src/Loadsys/Composer/LoadsysComposerPlugin.php b/src/Loadsys/Composer/LoadsysComposerPlugin.php new file mode 100644 index 0000000..2420ab5 --- /dev/null +++ b/src/Loadsys/Composer/LoadsysComposerPlugin.php @@ -0,0 +1,32 @@ +getInstallationManager()->addInstaller($puphpetReleaseInstaller); + + $phpcsCodingStandardInstaller = new PhpcsCodingStandardInstaller($io, $composer); + $composer->getInstallationManager()->addInstaller($phpcsCodingStandardInstaller); + } +} diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php new file mode 100644 index 0000000..42590fd --- /dev/null +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -0,0 +1,245 @@ +io = $io; + $this->composer = $composer; + } + + /** + * Defines the `type`s of composer packages to which this installer applies. + * + * A project's composer.json file must specify `"type": "phpcs-coding-standard"` + * in order to trigger this installer. + * + * @param string $packageType The `type` specified in the consuming project's composer.json. + * @return bool True if this installer should be activated for the package in question, false if not. + */ + public function supports($packageType) { + return ('phpcs-coding-standard' === $packageType); + } + + /** + * Return the install path based on package type. + * + * @param PackageInterface $package + * @param string $frameworkType + * @return string + */ +// public function getInstallPath(PackageInterface $package, $frameworkType = '') { +// $type = $this->package->getType(); +// +// $prettyName = $this->package->getPrettyName(); +// if (strpos($prettyName, '/') !== false) { +// list($vendor, $name) = explode('/', $prettyName); +// } else { +// $vendor = ''; +// $name = $prettyName; +// } +// +// $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type')); +// +// $extra = $package->getExtra(); +// if (!empty($extra['installer-name'])) { +// $availableVars['name'] = $extra['installer-name']; +// } +// +// if ($this->composer->getPackage()) { +// $extra = $this->composer->getPackage()->getExtra(); +// if (!empty($extra['installer-paths'])) { +// $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type); +// if ($customPath !== false) { +// return $this->templatePath($customPath, $availableVars); +// } +// } +// } +// +// $packageType = substr($type, strlen($frameworkType) + 1); +// $locations = $this->getLocations(); +// if (!isset($locations[$packageType])) { +// throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type)); +// } +// +// return $this->templatePath($locations[$packageType], $availableVars); +// } + + /** + * Scan $basePath for folders that contain `ruleset.xml` files. + * + * Return an array of partial paths (from $basePath) for all matching folders found. + * + * @param string $basePath A filesystem path without trailing slash to scan for folders with `ruleset.xml` files. + * @return array Array of partial file paths (from $basePath) to folders containing a ruleset.xml, no leading or trailing slashes. Empty array if none found. + */ + protected function findRulesetFolders($basePath) { + $rulesetFolders = array_map(function ($v) use ($basePath){ + return dirname(str_replace("{$basePath}/", '', $v)); + }, glob("{$basePath}/*/ruleset.xml")); + + return $rulesetFolders; + } + + /** + * Attempt to locate the squizlabs/php_codesniffer/standards folder. + * + * Return the full system path if found, no trailing slash. + * + * @return string Full system path to the PHP CodeSniffer's "standards/" folder. + */ + protected function findCodesnifferRoot() { + // approach 1 +// $vendorDir = $this->composer->getConfig()->get('vendor-dir'); +// return $vendorDir . DS . 'squizlabs/php_codesniffer/CodeSniffer/Standards'; + + + // approach 2 + $phpcsPackage = new \Composer\Package\Package('squizlabs/php_codesniffer', '~2.0', '2.0'); + return $this->composer->getInstallationManager()->getInstallPath($phpcsPackage). DS . 'CodeSniffer/Standards'; + +//@TODO: verify the folder exists. + + // approach 3 + // wet get ALL installed packages +// $packages = $event->getComposer()->getRepositoryManager() +// ->getLocalRepository()->getPackages(); +// $installationManager = $event->getComposer()->getInstallationManager(); +// +// foreach ($packages as $package) { +// $installPath = $installationManager->getInstallPath($package); +// //do my process here +// } + + + // approach 4 +// $repositoryManager = $composer->getRepositoryManager(); +// $installationManager = $composer->getInstallationManager(); +// $localRepository = $repositoryManager->getLocalRepository(); +// +// $packages = $localRepository->getPackages(); +// foreach ($packages as $package) { +// if ($package->getName() === 'willdurand/geocoder') { +// $installPath = $installationManager->getInstallPath($package); +// break; +// } +// } + } + + /** + * Override LibraryInstaller::installCode() to hook in additional post-download steps. + * + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + */ + protected function installCode(PackageInterface $package) { + parent::installCode($package); + + if (!$this->supports($package->getType())) { + return; + } + + $this->mirrorCodingStandardFolders($package); + } + + /** + * Mirror (copy or delete, only as necessary) items from the installed + * package's release/ folder into the target directory. + * + */ + protected function mirrorCodingStandardFolders($package) { + $packageBasePath = $this->getInstallPath($package); + $rulesets = $this->findRulesetFolders($packageBasePath); + $destDir = $this->findCodesnifferRoot(); + + + // Return true if the first part of the subpath for the current file exists in the accept array. + $acceptFunc = function ($current, $key, $iterator) use ($rulesets) { + $pathComponents = explode(DS, $iterator->getSubPathname()); + return in_array($pathComponents[0], $rulesets); + }; + $dirIterator = new RecursiveDirectoryIterator($packageBasePath, RecursiveDirectoryIterator::SKIP_DOTS); + $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, $acceptFunc); + $codingStandardsFolders = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::SELF_FIRST); + + $filesystem = new Filesystem(); + $filesystem->mirror($packageBasePath, $destDir, $codingStandardsFolders, ['override' => true]); + } + + /** + * Search for a config file in the consuming project and copy it into + * place if present. + * + */ +// protected function copyConfigFile($package) { +// $configFilePath = getcwd() . DS . 'puphpet.yaml'; +// $targetPath = getcwd() . DS . 'puphpet' . DS . 'config.yaml'; +// if (is_readable($configFilePath)) { +// copy($configFilePath, $targetPath); +// } +// } + + /** + * Check that release items copied into the consuming project are + * properly ignored in source control (very, VERY crudely.) + * + */ +// protected function checkGitIgnore($package) { +// $gitFolder = getcwd() . DS . '.git' . DS; +// +// if (!file_exists($gitFolder)) { +// return; +// } +// +// $gitignoreFile = getcwd() . DS . '.gitignore'; +// $required = [ +// '/Vagrantfile', +// '/puphpet/', +// '/.vagrant/', +// ]; +// +// touch($gitignoreFile); +// $lines = file($gitignoreFile, FILE_IGNORE_NEW_LINES); +// +// foreach ($required as $entry) { +// if (!in_array($entry, $lines)) { +// $lines[] = $entry; +// } +// } +// +// file_put_contents($gitignoreFile, implode(PHP_EOL, $lines)); +// } +} diff --git a/tests/Loadsys/Composer/Test/LoadsysComposerPluginTest.php b/tests/Loadsys/Composer/Test/LoadsysComposerPluginTest.php new file mode 100644 index 0000000..6634b84 --- /dev/null +++ b/tests/Loadsys/Composer/Test/LoadsysComposerPluginTest.php @@ -0,0 +1,75 @@ +package = new Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->composer = new Composer(); + $this->plugin = new LoadsysComposerPlugin(); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + unset($this->plugin); + + parent::tearDown(); + } + + /** + * All we can do is confirm that the plugin tried to register the + * correct installer class during ::activate(). + * + * @return void + */ + public function testActivate() { + $this->composer = $this->getMock('Composer\Composer', [ + 'getInstallationManager', + 'addInstaller' + ]); + $this->composer->setConfig(new Config(false)); + + $this->composer->expects($this->any()) + ->method('getInstallationManager') + ->will($this->returnSelf()); + + $this->composer->expects($this->at(1)) + ->method('addInstaller') + ->with($this->isInstanceOf('Loadsys\Composer\PuphpetReleaseInstaller')); + $this->composer->expects($this->at(3)) + ->method('addInstaller') + ->with($this->isInstanceOf('Loadsys\Composer\PhpcsCodingStandardInstaller')); + + $this->plugin->activate($this->composer, $this->io); + } +} diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php new file mode 100644 index 0000000..085f954 --- /dev/null +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php @@ -0,0 +1,106 @@ +baseDir = getcwd(); + $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('\Composer\IO\IOInterface'); + $this->composer = new \Composer\Composer(); + $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); + $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); + $this->Installer = new TestPhpcsCodingStandardInstaller($this->io, $this->composer); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + unset($this->composer); + + parent::tearDown(); + } + + /** + * testNothing + * + * @return void + */ + public function testNothing() { + $this->markTestIncomplete('@TODO: No tests written for PhpcsCodingStandardInstaller.'); + } + + /** + * test supports() + * + * @return void + */ + public function testSupports() { + $this->assertTrue( + $this->Installer->supports('phpcs-coding-standard'), + 'Coding standard installer should activate for `type=phpcs-coding-standard`.' + ); + $this->assertFalse( + $this->Installer->supports('anything-else'), + 'Coding standard installer should not activate for unrecognized package types.' + ); + } + + /** + * test findRulesetFolders() + * + * @return void + */ + public function testFindRulesetFolders() { + $sampleDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/samples'; + $expected = array( + 'CodingStandardOne', + 'SecondStandard', + ); + $result = $this->Installer->findRulesetFolders($sampleDir); + + $this->assertEquals( + $expected, + $result, + 'Only folders containing a ruleset.xml should be returned.' + ); + } +} diff --git a/tests/samples/CodingStandardOne/ruleset.xml b/tests/samples/CodingStandardOne/ruleset.xml new file mode 100644 index 0000000..30ca014 --- /dev/null +++ b/tests/samples/CodingStandardOne/ruleset.xml @@ -0,0 +1,4 @@ + + This is a dummy ruleset.xml file that identifies the folder it + lives in as being a PHP Codesniffer "coding standard". + diff --git a/tests/samples/NotACodingStandard/README.md b/tests/samples/NotACodingStandard/README.md new file mode 100644 index 0000000..60b4edb --- /dev/null +++ b/tests/samples/NotACodingStandard/README.md @@ -0,0 +1,5 @@ +# Not A Coding Standard + +This folder does **not** contain a `ruleset.xml` file, and should +therefore be excluded from the list of folders to install into +the `CodeSniffer/Standards/` folder. diff --git a/tests/samples/SecondStandard/ruleset.xml b/tests/samples/SecondStandard/ruleset.xml new file mode 100644 index 0000000..70a9e67 --- /dev/null +++ b/tests/samples/SecondStandard/ruleset.xml @@ -0,0 +1,3 @@ + + This folder contains a ruleset.xml file, and should be included. + From fa8208791bf19d0373a97b590ef7e91fbaf54612 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Sun, 9 Aug 2015 18:18:50 -0500 Subject: [PATCH 03/15] Update code. --- .../Composer/PhpcsCodingStandardInstaller.php | 98 +------------------ 1 file changed, 3 insertions(+), 95 deletions(-) diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index 42590fd..016efa1 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -7,15 +7,12 @@ namespace Loadsys\Composer; -// Needed for LibraryInstaller: use Composer\Composer; -use Composer\Installer\LibraryInstaller; use Composer\IO\IOInterface; +use Composer\Installer\LibraryInstaller; use Composer\Package\PackageInterface; - -// Needed for copying the release folder to the root. -use \DirectoryIterator; use \RecursiveCallbackFilterIterator; +use \RecursiveDirectoryIterator; use \RecursiveIteratorIterator; use Symfony\Component\Filesystem\Filesystem; @@ -54,50 +51,6 @@ public function supports($packageType) { return ('phpcs-coding-standard' === $packageType); } - /** - * Return the install path based on package type. - * - * @param PackageInterface $package - * @param string $frameworkType - * @return string - */ -// public function getInstallPath(PackageInterface $package, $frameworkType = '') { -// $type = $this->package->getType(); -// -// $prettyName = $this->package->getPrettyName(); -// if (strpos($prettyName, '/') !== false) { -// list($vendor, $name) = explode('/', $prettyName); -// } else { -// $vendor = ''; -// $name = $prettyName; -// } -// -// $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type')); -// -// $extra = $package->getExtra(); -// if (!empty($extra['installer-name'])) { -// $availableVars['name'] = $extra['installer-name']; -// } -// -// if ($this->composer->getPackage()) { -// $extra = $this->composer->getPackage()->getExtra(); -// if (!empty($extra['installer-paths'])) { -// $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type); -// if ($customPath !== false) { -// return $this->templatePath($customPath, $availableVars); -// } -// } -// } -// -// $packageType = substr($type, strlen($frameworkType) + 1); -// $locations = $this->getLocations(); -// if (!isset($locations[$packageType])) { -// throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type)); -// } -// -// return $this->templatePath($locations[$packageType], $availableVars); -// } - /** * Scan $basePath for folders that contain `ruleset.xml` files. * @@ -165,7 +118,7 @@ protected function findCodesnifferRoot() { * @param InstalledRepositoryInterface $repo repository in which to check * @param PackageInterface $package package instance */ - protected function installCode(PackageInterface $package) { + protected function installCode(\Composer\Package\PackageInterface $package) { parent::installCode($package); if (!$this->supports($package->getType())) { @@ -185,7 +138,6 @@ protected function mirrorCodingStandardFolders($package) { $rulesets = $this->findRulesetFolders($packageBasePath); $destDir = $this->findCodesnifferRoot(); - // Return true if the first part of the subpath for the current file exists in the accept array. $acceptFunc = function ($current, $key, $iterator) use ($rulesets) { $pathComponents = explode(DS, $iterator->getSubPathname()); @@ -198,48 +150,4 @@ protected function mirrorCodingStandardFolders($package) { $filesystem = new Filesystem(); $filesystem->mirror($packageBasePath, $destDir, $codingStandardsFolders, ['override' => true]); } - - /** - * Search for a config file in the consuming project and copy it into - * place if present. - * - */ -// protected function copyConfigFile($package) { -// $configFilePath = getcwd() . DS . 'puphpet.yaml'; -// $targetPath = getcwd() . DS . 'puphpet' . DS . 'config.yaml'; -// if (is_readable($configFilePath)) { -// copy($configFilePath, $targetPath); -// } -// } - - /** - * Check that release items copied into the consuming project are - * properly ignored in source control (very, VERY crudely.) - * - */ -// protected function checkGitIgnore($package) { -// $gitFolder = getcwd() . DS . '.git' . DS; -// -// if (!file_exists($gitFolder)) { -// return; -// } -// -// $gitignoreFile = getcwd() . DS . '.gitignore'; -// $required = [ -// '/Vagrantfile', -// '/puphpet/', -// '/.vagrant/', -// ]; -// -// touch($gitignoreFile); -// $lines = file($gitignoreFile, FILE_IGNORE_NEW_LINES); -// -// foreach ($required as $entry) { -// if (!in_array($entry, $lines)) { -// $lines[] = $entry; -// } -// } -// -// file_put_contents($gitignoreFile, implode(PHP_EOL, $lines)); -// } } From 5299cd53ce704cf147082feb4732602725092660 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Sun, 9 Aug 2015 18:23:09 -0500 Subject: [PATCH 04/15] Try avoiding NS conflict. --- src/Loadsys/Composer/PhpcsCodingStandardInstaller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index 016efa1..3fbfd90 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -14,7 +14,7 @@ use \RecursiveCallbackFilterIterator; use \RecursiveDirectoryIterator; use \RecursiveIteratorIterator; -use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesytem; if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); @@ -147,7 +147,7 @@ protected function mirrorCodingStandardFolders($package) { $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, $acceptFunc); $codingStandardsFolders = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::SELF_FIRST); - $filesystem = new Filesystem(); + $filesystem = new SymfonyFilesytem(); $filesystem->mirror($packageBasePath, $destDir, $codingStandardsFolders, ['override' => true]); } } From 5c207b579357c3db45b1d8f01287c0e73919cf36 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Sun, 9 Aug 2015 18:28:13 -0500 Subject: [PATCH 05/15] Remove unnecessary override constructor. --- .../Composer/PhpcsCodingStandardInstaller.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index 3fbfd90..61963d4 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -11,6 +11,7 @@ use Composer\IO\IOInterface; use Composer\Installer\LibraryInstaller; use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; use \RecursiveCallbackFilterIterator; use \RecursiveDirectoryIterator; use \RecursiveIteratorIterator; @@ -27,17 +28,6 @@ */ class PhpcsCodingStandardInstaller extends LibraryInstaller { - /** - * Initializes base installer. - * - * @param IOInterface $io - * @param Composer $composer - */ - public function __construct(IOInterface $io, Composer $composer) { - $this->io = $io; - $this->composer = $composer; - } - /** * Defines the `type`s of composer packages to which this installer applies. * @@ -76,8 +66,7 @@ protected function findRulesetFolders($basePath) { */ protected function findCodesnifferRoot() { // approach 1 -// $vendorDir = $this->composer->getConfig()->get('vendor-dir'); -// return $vendorDir . DS . 'squizlabs/php_codesniffer/CodeSniffer/Standards'; +// return $this->vendorDir . DS . 'squizlabs/php_codesniffer/' . 'CodeSniffer/Standards'; // approach 2 From 0f8d82acf86e87e008952586f1417275af19c17c Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 10 Aug 2015 11:37:10 -0500 Subject: [PATCH 06/15] WIP extracting common code into a reusable class. --- .../Composer/PhpcsCodingStandardHook.php | 221 +++++++++++++++ .../Composer/PhpcsCodingStandardInstaller.php | 145 ++++------ .../Test/PhpcsCodingStandardHookTest.php | 251 ++++++++++++++++++ .../Test/PhpcsCodingStandardInstallerTest.php | 8 +- 4 files changed, 529 insertions(+), 96 deletions(-) create mode 100644 src/Loadsys/Composer/PhpcsCodingStandardHook.php create mode 100644 tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php diff --git a/src/Loadsys/Composer/PhpcsCodingStandardHook.php b/src/Loadsys/Composer/PhpcsCodingStandardHook.php new file mode 100644 index 0000000..d367a36 --- /dev/null +++ b/src/Loadsys/Composer/PhpcsCodingStandardHook.php @@ -0,0 +1,221 @@ +getOperation()->getPackage(); + + // If the package defines the correct type, + // it will have already been copied by the Installer. + if ($installedPackage->getType() === self::PHPCS_PACKAGE_TYPE) { + return; + } + + // Otherwise, check for Coding Standard folders and copy them. + // (This is a relatively quick no-op if there are no + // `ruleset.xml` files in the package.) + self::mirrorCodingStandardFolders($event); + +//@TODO: Experiment with alternate approach of writing the "current" packages path into CodeSniffer.conf using new self::configInstalledPathAdd() method below. + } + + /** + * Mirror (copy or delete, only as necessary) items from the installed + * package's release/ folder into the target directory. + * + * @param \Composer\Installer\PackageEvent $event The composer Package event being fired. + * @return void + */ + public static function mirrorCodingStandardFolders(PackageEvent $event) { + $package = $event->getOperation()->getPackage(); + $packageBasePath = $package->getComposer()->getInstallationManager()->getInstallPath($package); + $rulesets = self::findRulesetFolders($packageBasePath); + $destDir = self::findCodesnifferRoot($event); + + // No-op if no ruleset.xml's found or squizlabs/php_codesniffer not installed. + if (empty($rulesets) || !$destDir) { + return; + } + + // Return true if the first part of the subpath for + // the current file exists in the accept array. + $acceptFunc = function ($current, $key, $iterator) use ($rulesets) { + $pathComponents = explode(DS, $iterator->getSubPathname()); + return in_array($pathComponents[0], $rulesets); + }; + + // Build up an iterator that will only select files + // within folders containing `ruleset.xml files. + $dirIterator = new RecursiveDirectoryIterator( + $packageBasePath, + RecursiveDirectoryIterator::SKIP_DOTS + ); + $filterIterator = new RecursiveCallbackFilterIterator( + $dirIterator, + $acceptFunc + ); + $codingStandardsFolders = new RecursiveIteratorIterator( + $filterIterator, + RecursiveIteratorIterator::SELF_FIRST + ); + + // Iterate over all of the select files, + // copying them to the CodeSniffer/Standards/ folder. + $filesystem = new SymfonyFilesytem(); + $filesystem->mirror( + $packageBasePath, + $destDir, + $codingStandardsFolders, + array('override' => true) + ); + } + + //@TODO: Write the removeStandards() method. Must take a single PackageEvent, scan that package for ruleset.xml folders, then remove the same ones from CodeSniffer/Standards/ if present. (Alternate approach is to remove the path to "this" package from CodeSniffer.conf using the new self::configInstalledPathRemove() method below. + public function removeStandards(PackageEvent $event) { + } + + //@TODO: doc block + public static function configInstalledPathAdd($path) { + //@TODO: write and test this: + $installedPaths = self::readInstalledPaths(); + $installedPaths[] = $path; + return self::saveInstalledPaths($installedPaths); + } + + //@TODO: doc block + public static function configInstalledPathRemove($path) { + //@TODO: write and test this: + $installedPaths = self::readInstalledPaths(); + if ($key = array_search($path, $installedPaths)) { + unset($installedPaths[$key]); + } + return self::saveInstalledPaths($installedPaths); + } + + /** + * Scan $basePath for folders that contain `ruleset.xml` files. + * + * Return an array of partial paths (from $basePath) for all + * matching folders found. + * + * @param string $basePath A filesystem path without trailing slash to scan for folders with `ruleset.xml` files. + * @return array Array of partial file paths (from $basePath) to folders containing a ruleset.xml, no leading or trailing slashes. Empty array if none found. + */ + protected static function findRulesetFolders($basePath) { + $rulesetFolders = array_map(function ($v) use ($basePath){ + return dirname(str_replace($basePath . DS, '', $v)); + }, glob($basePath . DS . '*' . DS . 'ruleset.xml')); + + return $rulesetFolders; + } + + /** + * Attempt to locate the squizlabs/php_codesniffer/standards folder. + * + * Return the full system path if found, no trailing slash. + * + * @param Composer\Installer\PackageEvent $event Current composer event. Used to get access to the InstallationManager. + * @return string|false Full system path to the PHP CodeSniffer's "standards/" folder, false if not found. + */ + protected static function findCodesnifferRoot(PackageEvent $event) { + $phpcsPackage = new Package('squizlabs/php_codesniffer', '2.0', ''); + $path = $event->getComposer()->getInstallationManager()->getInstallPath($phpcsPackage); + + $path .= DS . 'CodeSniffer' . DS . 'Standards'; + if (!is_readable($path)) { + return false; + } + + return $path; + } + + //@TODO: doc block + protected static function readInstalledPaths() { + self::codeSnifferInit(); + $pathsString = PHP_CodeSniffer::getInstalledStandardPaths(); + if (is_null($pathsString)) { + return array(); + } + return explode(',', $pathsString); + } + + //@TODO: doc block + protected static function saveInstalledPaths(array $paths) { + return PHP_CodeSniffer::setConfigData('installed_paths', implode(',', array_unique($paths))); + } + + //@TODO: doc block + protected static function codeSnifferInit() { + if (!class_exists('PHP_CodeSniffer')) { + $composerInstall = dirname(dirname(dirname(__FILE__))) . '/vendor/squizlabs/php_codesniffer/CodeSniffer.php'; + if (file_exists($composerInstall)) { + require_once $composerInstall; + } else { + require_once 'PHP/CodeSniffer.php'; + } + } + } +} diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index 61963d4..a30ae6b 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -2,141 +2,100 @@ /** * PhpcsCodingStandardInstaller * - * Ensures that a composer package with `type=phpcs-coding-standard` in its composer.json file will have any subfolders that contain a `ruleset.xml` file copied into the `VENDOR/squizlabs/php_codesniffer/Standards/` folder. + * Ensures that a composer package with `type=phpcs-coding-standard` + * in its composer.json file will have any subfolders that contain + * a `ruleset.xml` file copied into the + * `VENDOR/squizlabs/php_codesniffer/CodeSniffer/Standards/` folder. */ namespace Loadsys\Composer; -use Composer\Composer; -use Composer\IO\IOInterface; use Composer\Installer\LibraryInstaller; use Composer\Package\PackageInterface; -use Composer\Util\Filesystem; -use \RecursiveCallbackFilterIterator; -use \RecursiveDirectoryIterator; -use \RecursiveIteratorIterator; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesytem; +use Loadsys\Composer\PhpcsCodingStandardHook; if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); } /** - * Installer and event handler. - * + * PHP CodeSniffer Coding Standard Installer * + * Hooks the ::installCode() step to perform additional detection + * of Coding Standard folders in the current package, and copies + * them to the CodeSniffer/Standards folder. */ class PhpcsCodingStandardInstaller extends LibraryInstaller { /** * Defines the `type`s of composer packages to which this installer applies. * - * A project's composer.json file must specify `"type": "phpcs-coding-standard"` - * in order to trigger this installer. + * A project's composer.json file must specify + * `"type": "phpcs-coding-standard"` in order to trigger this + * installer. * * @param string $packageType The `type` specified in the consuming project's composer.json. * @return bool True if this installer should be activated for the package in question, false if not. */ public function supports($packageType) { - return ('phpcs-coding-standard' === $packageType); - } - - /** - * Scan $basePath for folders that contain `ruleset.xml` files. - * - * Return an array of partial paths (from $basePath) for all matching folders found. - * - * @param string $basePath A filesystem path without trailing slash to scan for folders with `ruleset.xml` files. - * @return array Array of partial file paths (from $basePath) to folders containing a ruleset.xml, no leading or trailing slashes. Empty array if none found. - */ - protected function findRulesetFolders($basePath) { - $rulesetFolders = array_map(function ($v) use ($basePath){ - return dirname(str_replace("{$basePath}/", '', $v)); - }, glob("{$basePath}/*/ruleset.xml")); - - return $rulesetFolders; - } - - /** - * Attempt to locate the squizlabs/php_codesniffer/standards folder. - * - * Return the full system path if found, no trailing slash. - * - * @return string Full system path to the PHP CodeSniffer's "standards/" folder. - */ - protected function findCodesnifferRoot() { - // approach 1 -// return $this->vendorDir . DS . 'squizlabs/php_codesniffer/' . 'CodeSniffer/Standards'; - - - // approach 2 - $phpcsPackage = new \Composer\Package\Package('squizlabs/php_codesniffer', '~2.0', '2.0'); - return $this->composer->getInstallationManager()->getInstallPath($phpcsPackage). DS . 'CodeSniffer/Standards'; - -//@TODO: verify the folder exists. - - // approach 3 - // wet get ALL installed packages -// $packages = $event->getComposer()->getRepositoryManager() -// ->getLocalRepository()->getPackages(); -// $installationManager = $event->getComposer()->getInstallationManager(); -// -// foreach ($packages as $package) { -// $installPath = $installationManager->getInstallPath($package); -// //do my process here -// } - - - // approach 4 -// $repositoryManager = $composer->getRepositoryManager(); -// $installationManager = $composer->getInstallationManager(); -// $localRepository = $repositoryManager->getLocalRepository(); -// -// $packages = $localRepository->getPackages(); -// foreach ($packages as $package) { -// if ($package->getName() === 'willdurand/geocoder') { -// $installPath = $installationManager->getInstallPath($package); -// break; -// } -// } + return ($packageType === PhpcsCodingStandardHook::PHPCS_PACKAGE_TYPE); } /** * Override LibraryInstaller::installCode() to hook in additional post-download steps. * - * @param InstalledRepositoryInterface $repo repository in which to check - * @param PackageInterface $package package instance + * @param \Composer\Package\PackageInterface $package Package instance. + * @return void */ - protected function installCode(\Composer\Package\PackageInterface $package) { + protected function installCode(PackageInterface $package) { parent::installCode($package); if (!$this->supports($package->getType())) { return; } - $this->mirrorCodingStandardFolders($package); + PhpcsCodingStandardHook::mirrorCodingStandardFolders($package); } /** - * Mirror (copy or delete, only as necessary) items from the installed - * package's release/ folder into the target directory. + * Override LibraryInstaller::updateCode() to hook in additional post-update steps. * + * @param \Composer\Package\PackageInterface $initial Existing Package instance. + * @param \Composer\Package\PackageInterface $target New Package instance. + * @return void */ - protected function mirrorCodingStandardFolders($package) { - $packageBasePath = $this->getInstallPath($package); - $rulesets = $this->findRulesetFolders($packageBasePath); - $destDir = $this->findCodesnifferRoot(); + protected function updateCode(PackageInterface $initial, PackageInterface $target) { + //@TODO: Adapt to re-copy the Standards folders. + //parent::updateCode($initial, $target); + + $initialDownloadPath = $this->getInstallPath($initial); + $targetDownloadPath = $this->getInstallPath($target); + if ($targetDownloadPath !== $initialDownloadPath) { + // if the target and initial dirs intersect, we force a remove + install + // to avoid the rename wiping the target dir as part of the initial dir cleanup + if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath + || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath + ) { + $this->removeCode($initial); + $this->installCode($target); + return; + } + $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); + } + $this->downloadManager->update($initial, $target, $targetDownloadPath); + } - // Return true if the first part of the subpath for the current file exists in the accept array. - $acceptFunc = function ($current, $key, $iterator) use ($rulesets) { - $pathComponents = explode(DS, $iterator->getSubPathname()); - return in_array($pathComponents[0], $rulesets); - }; - $dirIterator = new RecursiveDirectoryIterator($packageBasePath, RecursiveDirectoryIterator::SKIP_DOTS); - $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, $acceptFunc); - $codingStandardsFolders = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::SELF_FIRST); + /** + * Override LibraryInstaller::removeCode() to hook in additional post-update steps. + * + * @param \Composer\Package\PackageInterface $package Package instance. + * @return void + */ + protected function removeCode(PackageInterface $package) { + //@TODO: Adapt to remove the copied Standards folders. + //parent::removeCode($package); - $filesystem = new SymfonyFilesytem(); - $filesystem->mirror($packageBasePath, $destDir, $codingStandardsFolders, ['override' => true]); + $downloadPath = $this->getPackageBasePath($package); + $this->downloadManager->remove($package, $downloadPath); } } diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php new file mode 100644 index 0000000..4cd6114 --- /dev/null +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php @@ -0,0 +1,251 @@ +baseDir = getcwd(); + $this->phpcsInstallDir = sys_get_temp_dir() . md5(__FILE__ . time()); + $this->standardsInstallDir = $this->phpcsInstallDir . '/CodeSniffer/Standards'; + mkdir($this->standardsInstallDir, 0777, true); + + $this->sampleDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/samples'; + + $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('\Composer\IO\IOInterface'); + $this->composer = new \Composer\Composer(); + $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); + $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + $this->removeDir($this->phpcsInstallDir); + + parent::tearDown(); + } + + /** + * Helper function to recursively delete temporary directories created for tests. + * + * @return void + */ + protected function removeDir($d) { + $i = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($d, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ($i as $path) { + $path->isDir() && !$path->isLink() ? rmdir($path->getPathname()) : unlink($path->getPathname()); + } + rmdir($d); + } + + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostPackageInstallMatchingType() { + $this->marktestIncomplete('@TODO: Write a test where the package type is already phpcs-coding-standard'); + } + + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostPackageInstallNoStandards() { + $this->marktestIncomplete('@TODO: Write a test where the package does not have any stanrds to install.'); + } + + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostPackageInstallSuccessful() { + $this->marktestIncomplete('@TODO: Write a test where the package type is not phpcs-coding-standard and has standards to install.'); + } + + /** + * test mirrorCodingStandardFolders() + * + * @return void + */ + public function testMirrorCodingStandardFoldersSuccessful() { + $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') + ->disableOriginalConstructor() + ->setMethods(array('getOperation', 'getPackage', 'getComposer', 'getInstallationManager', 'getInstallPath')) + ->getMock(); + $event->method('getOperation')->will($this->returnSelf()); + $event->method('getPackage')->will($this->returnSelf()); + $event->method('getComposer')->will($this->returnSelf()); + $event->method('getInstallationManager')->will($this->returnSelf()); + $event->expects($this->at(4)) + ->method('getInstallPath') + ->will($this->returnValue($this->sampleDir)); + $event->expects($this->at(7)) + ->method('getInstallPath') + ->will($this->returnValue($this->phpcsInstallDir)); + + $expected = array( + 'CodingStandardOne', + 'SecondStandard', + ); + + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($event); + + foreach ($expected as $standard) { + $this->assertTrue( + is_readable($this->standardsInstallDir . DS . $standard . DS . 'ruleset.xml'), + "Folder `$standard` containing ruleset.xml should be copied to Standards/ folder." + ); + } + } + + /** + * test mirrorCodingStandardFolders() + * + * @return void + */ + public function testMirrorCodingStandardFoldersNoDest() { + $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') + ->disableOriginalConstructor() + ->setMethods(array('getOperation', 'getPackage', 'getComposer', 'getInstallationManager', 'getInstallPath')) + ->getMock(); + $event->method('getOperation')->will($this->returnSelf()); + $event->method('getPackage')->will($this->returnSelf()); + $event->method('getComposer')->will($this->returnSelf()); + $event->method('getInstallationManager')->will($this->returnSelf()); + $event->expects($this->at(4)) + ->method('getInstallPath') + ->will($this->returnValue($this->sampleDir)); + $event->expects($this->at(7)) + ->method('getInstallPath') + ->will($this->returnValue(false)); + + $expected = array( + 'CodingStandardOne', + 'SecondStandard', + ); + + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($event); + + foreach ($expected as $standard) { + $this->assertFalse( + is_readable($this->standardsInstallDir . DS . $standard . DS . 'ruleset.xml'), + 'No folders should be copied when destination dir is not found.' + ); + } + } + + /** + * test findRulesetFolders() + * + * @return void + */ + public function testFindRulesetFolders() { + $expected = array( + 'CodingStandardOne', + 'SecondStandard', + ); + $result = TestPhpcsCodingStandardHook::findRulesetFolders($this->sampleDir); + + $this->assertEquals( + $expected, + $result, + 'Only folders containing a ruleset.xml should be returned.' + ); + } + + /** + * test findCodesnifferRoot() + * + * @return void + */ + public function testFindCodesnifferRootExists() { + $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') + ->disableOriginalConstructor() + ->setMethods(array('getComposer', 'getInstallationManager', 'getInstallPath')) + ->getMock(); + $event->method('getComposer')->will($this->returnSelf()); + $event->method('getInstallationManager')->will($this->returnSelf()); + $event->expects($this->once()) + ->method('getInstallPath') + ->will($this->returnValue($this->phpcsInstallDir)); + + $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($event); + + $this->assertEquals( + $this->standardsInstallDir, + $result, + 'Full path to the existing Standards/ folder should be returned.' + ); + } + + /** + * test findCodesnifferRoot() + * + * @return void + */ + public function testFindCodesnifferRootDoesNotExist() { + $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') + ->disableOriginalConstructor() + ->setMethods(array('getComposer', 'getInstallationManager', 'getInstallPath')) + ->getMock(); + $event->method('getComposer')->will($this->returnSelf()); + $event->method('getInstallationManager')->will($this->returnSelf()); + $event->expects($this->once()) + ->method('getInstallPath') + ->will($this->returnValue('does-not-exist')); + + $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($event); + + $this->assertFalse( + $result, + 'False shouldbe returned for a non-existent path.' + ); + } +} diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php index 085f954..53754d0 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php @@ -85,17 +85,19 @@ public function testSupports() { } /** - * test findRulesetFolders() + * test installCode() * * @return void */ - public function testFindRulesetFolders() { + public function testInstallCode() { $sampleDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/samples'; $expected = array( 'CodingStandardOne', 'SecondStandard', ); - $result = $this->Installer->findRulesetFolders($sampleDir); + $package = $this->getMock('@TODO'); + + $result = $this->Installer->installCode($package); $this->assertEquals( $expected, From ecb56876506ed9376e632e207e91a5c66d48293c Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 11 Aug 2015 12:24:08 -0500 Subject: [PATCH 07/15] WIP tests. --- .gitignore | 1 + README.md | 113 ++++++++++++++ .../Composer/PhpcsCodingStandardHook.php | 142 +++++++++++++----- .../Composer/PhpcsCodingStandardInstaller.php | 62 ++++---- .../Test/PhpcsCodingStandardHookTest.php | 111 +++++++------- .../Test/PhpcsCodingStandardInstallerTest.php | 121 ++++++++++++--- 6 files changed, 403 insertions(+), 147 deletions(-) diff --git a/.gitignore b/.gitignore index b9e67eb..159aba5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ composer.phar # Ignore the folder we use when testing the installers. /build/* +/tmp diff --git a/README.md b/README.md index 8344bee..d815eef 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,115 @@ # composer-plugins Including this package in your composer.json makes a number of installers and project types available. + + +## PHP CodeSniffer, Coding Standard installer type + +When you develop your own Coding Standard, you can package it for installation via Composer, but `phpcs` won't know about your standard unless you either manually specify it on the command line: + +```shell +$ vendor/bin/phpcs --standard=vendor/yourname/your-codesniffer/YourStandard . +``` + +Or you must use the `--config-set` switch to write your path into `phpcs`'s config file: + +```shell +$ vendor/bin/phpcs --config-set installed_paths vendor/yourname/your-codesniffer +``` + +Neither of these is convenient, and defeats the purpose of using Composer to make your dependencies "automatically" available to your project. + +This plugin provide a custom installer that engages for the Composer `type` of `phpcs-coding-standard`. + +When you create your coding standard package, use this `type` in your composer.json file, and `require` this package of composer plugins in order to gain access to the Installer for that type: + +```json +{ + "name": "yourname/your-codesniffer", + "type": "phpcs-coding-standard", + "require": { + "squizlabs/php_codesniffer": "~2.3", + "loadsys/composer-plugins": "dev-master" + } +} +``` + +It also usually makes sense for your coding standard to include the required version of php_codesniffer itself, so that your projects that use this standard don't have to require it themselves, or accidentally require the wrong version. + +With this setup, when your standard is included in another project, the installer in this package will search its `vendor/your-name/your-codesniffer/` folder for folders that contain `ruleset.xml` files, indicating that those sub-folders contain Coding Standards. This installer will then copy those folders into the `vendor/squizlabs/CodeSniffer/Standards/` folder for you, making your Coding Standard immediately available to `phpcs --standard YourStandard .` without any additional configuration. + + + +## PHP CodeSniffer, post-install hook (copying folders approach) + +Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. + +The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then copies those folders into the `CodeSniffer/Standards/` directory, making them available to `phpcs`. + +To use this hook script, add the following to your root project's `composer.json`: + +```json +{ + "require": { + "squizlabs/php_codesniffer": "~2.3", + "loadsys/composer-plugins": "dev-master" + }, + "scripts": { + "post-install-cmd": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + ], + "post-update-cmd": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + ], + "pre-package-uninstall": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::prePackageUninstall" + ] + } +} +``` + +The `postInstall` command checks every installed package for Coding Standard folders, and copies those folders into the `CodeSniffer/Standards/` folder directly. This should also be run post-update, in order to copy updates to a Coding Standard into the correct place for use. + +The `prePackageUninstall` removes any Coding Standard folders from a package that is being removed from the phpcs `CodeSniffer/Standards/` folder. + + + + + + + + + + +# Config File Approach + +## PHP CodeSniffer, post-install hook + +Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. + +The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then adds the vendor paths for these folders into the CodeSniffer.conf file, making them available to `phpcs` in their natural install location. + +To use this hook script, add the following to your root project's `composer.json`: + +```json +{ + "require": { + "squizlabs/php_codesniffer": "~2.3", + "loadsys/composer-plugins": "dev-master" + }, + "scripts": { + "post-install-cmd": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + ], + "post-update-cmd": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + ], + "pre-package-uninstall": [ + "Loadsys\\Composer\\PhpcsCodingStandardHook::prePackageUninstall" + ] + } +} +``` + +The `postInstall` command checks every installed package for Coding Standard folders, and adds the path for any packaging containing Standards into `phpcs`'s `installed_paths` config setting. This should also be run `post-update` in case the package's installed_path has changed. + +The `postPackageUninstall` removes the path for a package being removed from the phpcs config file. diff --git a/src/Loadsys/Composer/PhpcsCodingStandardHook.php b/src/Loadsys/Composer/PhpcsCodingStandardHook.php index d367a36..4c0649d 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardHook.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardHook.php @@ -51,7 +51,43 @@ class PhpcsCodingStandardHook { const PHPCS_PACKAGE_TYPE = 'phpcs-coding-standard'; /** - * Intended for use as a post-install-cmd script. + * Intended for use as a post-install-cmd/post-update-cmd script. + * + * Scans all packages just installed for subfolders containing + * `ruleset.xml` files, and mirrors those folders into the + * CodeSniffer/Standards/ folder of the squizlabs/php_codesniffer + * package, if present. + * + * No-op if there are no `ruleset.xml` files or the PHP CodeSniffer + * package is not installed, making it safe to run on every package. + * + * @param \Composer\Installer\PackageEvent $event The composer Package event being fired. + * @return void + */ + public static function postInstall(PackageEvent $event) { + $packages = $event + ->getComposer() + ->getRepositoryManager() + ->getLocalRepository() + ->getPackages() + ; + + foreach ($packages as $package) { + // If the package defines the correct type, + // it will have already been handled by the Installer. + if ($package->getType() === self::PHPCS_PACKAGE_TYPE) { + return; + } + + // Otherwise, check for Coding Standard folders and copy them. + // (This is a relatively quick no-op if there are no + // `ruleset.xml` files in the package.) + self::mirrorCodingStandardFolders($package); + } + } + + /** + * Intended for use as a pre-package-uninstall script. * * Scans each package as it is installed for subfolders containing * `ruleset.xml` files, and mirrors those folders into the @@ -64,36 +100,32 @@ class PhpcsCodingStandardHook { * @param \Composer\Installer\PackageEvent $event The composer Package event being fired. * @return void */ -//@TODO: This should really be set to trigger on post-install and post-update, and loop over ALL locally installed packages, instead of firing repeatedly as each package goes in. That would make it more likely that the squizlabs/php_codesniffer folder will already be present. - public static function postPackageInstall(PackageEvent $event) { - $installedPackage = $event->getOperation()->getPackage(); + public static function prePackageUninstall(PackageEvent $event) { + $package = $event->getOperation()->getPackage(); - // If the package defines the correct type, - // it will have already been copied by the Installer. - if ($installedPackage->getType() === self::PHPCS_PACKAGE_TYPE) { + // If the package defines the correct type, it's coding standard + // folders will have already been removed by the Installer. + if ($package->getType() === self::PHPCS_PACKAGE_TYPE) { return; } - // Otherwise, check for Coding Standard folders and copy them. - // (This is a relatively quick no-op if there are no - // `ruleset.xml` files in the package.) - self::mirrorCodingStandardFolders($event); - -//@TODO: Experiment with alternate approach of writing the "current" packages path into CodeSniffer.conf using new self::configInstalledPathAdd() method below. + // Otherwise, check for Coding Standard folders in the package + // about to be removed, and remove them from the + // CodeSniffer/Standards/ first. + self::deleteCodingStandardFolders($package); } /** * Mirror (copy or delete, only as necessary) items from the installed * package's release/ folder into the target directory. * - * @param \Composer\Installer\PackageEvent $event The composer Package event being fired. + * @param \Composer\Package\PackageInterface $package The composer Package being installed. * @return void */ - public static function mirrorCodingStandardFolders(PackageEvent $event) { - $package = $event->getOperation()->getPackage(); + public static function mirrorCodingStandardFolders(PackageInterface $package) { $packageBasePath = $package->getComposer()->getInstallationManager()->getInstallPath($package); $rulesets = self::findRulesetFolders($packageBasePath); - $destDir = self::findCodesnifferRoot($event); + $destDir = self::findCodesnifferRoot($package->getComposer()); // No-op if no ruleset.xml's found or squizlabs/php_codesniffer not installed. if (empty($rulesets) || !$destDir) { @@ -133,26 +165,28 @@ public static function mirrorCodingStandardFolders(PackageEvent $event) { ); } - //@TODO: Write the removeStandards() method. Must take a single PackageEvent, scan that package for ruleset.xml folders, then remove the same ones from CodeSniffer/Standards/ if present. (Alternate approach is to remove the path to "this" package from CodeSniffer.conf using the new self::configInstalledPathRemove() method below. - public function removeStandards(PackageEvent $event) { - } + /** + * Remove Coding Standards folders from phpcs. + * + * Check the to-be-removed package for Coding Standard folders, remove those folders from the CodeSniffer/Standards/ dir. + * + * @param \Composer\Package\PackageInterface $package The composer Package about to be removed. + * @return void + */ + public function deleteCodingStandardFolders(PackageInterface $package) { + $packageBasePath = $package->getComposer()->getInstallationManager()->getInstallPath($package); + $rulesets = self::findRulesetFolders($packageBasePath); + $destDir = self::findCodesnifferRoot($package->getComposer()); - //@TODO: doc block - public static function configInstalledPathAdd($path) { - //@TODO: write and test this: - $installedPaths = self::readInstalledPaths(); - $installedPaths[] = $path; - return self::saveInstalledPaths($installedPaths); - } + // No-op if no ruleset.xml's found. + if (empty($rulesets) || !$destDir) { + return; + } - //@TODO: doc block - public static function configInstalledPathRemove($path) { - //@TODO: write and test this: - $installedPaths = self::readInstalledPaths(); - if ($key = array_search($path, $installedPaths)) { - unset($installedPaths[$key]); + $filesystem = new Filesystem(); + foreach ($rulesets as $ruleset) { + $filesystem->removeDirectory($destDir . DS . $ruleset); } - return self::saveInstalledPaths($installedPaths); } /** @@ -177,12 +211,12 @@ protected static function findRulesetFolders($basePath) { * * Return the full system path if found, no trailing slash. * - * @param Composer\Installer\PackageEvent $event Current composer event. Used to get access to the InstallationManager. + * @param Composer\Composer $composer Used to get access to the InstallationManager. * @return string|false Full system path to the PHP CodeSniffer's "standards/" folder, false if not found. */ - protected static function findCodesnifferRoot(PackageEvent $event) { + protected static function findCodesnifferRoot(Composer $composer) { $phpcsPackage = new Package('squizlabs/php_codesniffer', '2.0', ''); - $path = $event->getComposer()->getInstallationManager()->getInstallPath($phpcsPackage); + $path = $composer->getInstallationManager()->getInstallPath($phpcsPackage); $path .= DS . 'CodeSniffer' . DS . 'Standards'; if (!is_readable($path)) { @@ -192,6 +226,40 @@ protected static function findCodesnifferRoot(PackageEvent $event) { return $path; } + + + + + + + + + + + + + + +// @TODO: Break this stuff out into a separate Hook class. + + + //@TODO: doc block + public static function configInstalledPathAdd($path) { + //@TODO: write and test this: + $installedPaths = self::readInstalledPaths(); + $installedPaths[] = $path; + return self::saveInstalledPaths($installedPaths); + } + + //@TODO: doc block + public static function configInstalledPathRemove($path) { + //@TODO: write and test this: + $installedPaths = self::readInstalledPaths(); + if ($key = array_search($path, $installedPaths)) { + unset($installedPaths[$key]); + } + return self::saveInstalledPaths($installedPaths); + } //@TODO: doc block protected static function readInstalledPaths() { self::codeSnifferInit(); diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index a30ae6b..5e00235 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -5,28 +5,41 @@ * Ensures that a composer package with `type=phpcs-coding-standard` * in its composer.json file will have any subfolders that contain * a `ruleset.xml` file copied into the - * `VENDOR/squizlabs/php_codesniffer/CodeSniffer/Standards/` folder. + * `VENDOR/squizlabs/php_codesniffer/CodeSniffer/Standards/` folder + * during installation, and removed during uninstallation. */ namespace Loadsys\Composer; +use Composer\Composer; use Composer\Installer\LibraryInstaller; +use Composer\IO\IOInterface; use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; use Loadsys\Composer\PhpcsCodingStandardHook; -if (!defined('DS')) { - define('DS', DIRECTORY_SEPARATOR); -} - /** - * PHP CodeSniffer Coding Standard Installer + * PHP CodeSniffer Coding Standard "Copying" Installer * - * Hooks the ::installCode() step to perform additional detection - * of Coding Standard folders in the current package, and copies - * them to the CodeSniffer/Standards folder. + * Hooks the ::installCode(), ::updateCode() and ::removeCode() steps + * to perform additional detection of Coding Standard folders in the + * current package, and copies them to the CodeSniffer/Standards folder. */ class PhpcsCodingStandardInstaller extends LibraryInstaller { + /** + * Initializes library installer. + * + * @param IOInterface $io + * @param Composer $composer + * @param string $type + * @param Filesystem $filesystem + */ + public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, $hook = null) { + parent::__construct($io, $composer, $type, $filesystem); + $this->hook = (!is_null($hook) ? $hook : new PhpcsCodingStandardHook()); + } + /** * Defines the `type`s of composer packages to which this installer applies. * @@ -54,7 +67,7 @@ protected function installCode(PackageInterface $package) { return; } - PhpcsCodingStandardHook::mirrorCodingStandardFolders($package); + $this->hook->mirrorCodingStandardFolders($package); } /** @@ -65,24 +78,13 @@ protected function installCode(PackageInterface $package) { * @return void */ protected function updateCode(PackageInterface $initial, PackageInterface $target) { - //@TODO: Adapt to re-copy the Standards folders. - //parent::updateCode($initial, $target); + parent::updateCode($initial, $target); - $initialDownloadPath = $this->getInstallPath($initial); - $targetDownloadPath = $this->getInstallPath($target); - if ($targetDownloadPath !== $initialDownloadPath) { - // if the target and initial dirs intersect, we force a remove + install - // to avoid the rename wiping the target dir as part of the initial dir cleanup - if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath - || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath - ) { - $this->removeCode($initial); - $this->installCode($target); - return; - } - $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); + if (!$this->supports($package->getType())) { + return; } - $this->downloadManager->update($initial, $target, $targetDownloadPath); + + $this->hook->mirrorCodingStandardFolders($target); } /** @@ -92,10 +94,10 @@ protected function updateCode(PackageInterface $initial, PackageInterface $targe * @return void */ protected function removeCode(PackageInterface $package) { - //@TODO: Adapt to remove the copied Standards folders. - //parent::removeCode($package); + if ($this->supports($package->getType())) { + $this->hook->deleteCodingStandardFolders($package); + } - $downloadPath = $this->getPackageBasePath($package); - $this->downloadManager->remove($package, $downloadPath); + parent::removeCode($package); } } diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php index 4cd6114..d7fde67 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php @@ -7,6 +7,7 @@ namespace Loadsys\Composer\Test; +use Composer\Composer; use Composer\Installer\PackageEvent; use Loadsys\Composer\PhpcsCodingStandardHook; @@ -17,8 +18,8 @@ class TestPhpcsCodingStandardHook extends PhpcsCodingStandardHook { public static function findRulesetFolders($basePath) { return parent::findRulesetFolders($basePath); } - public static function findCodesnifferRoot(PackageEvent $event) { - return parent::findCodesnifferRoot($event); + public static function findCodesnifferRoot(Composer $composer) { + return parent::findCodesnifferRoot($composer); } } @@ -67,7 +68,7 @@ public function tearDown() { } /** - * Helper function to recursively delete temporary directories created for tests. + * Helper method to recursively delete temporary directories created for tests. * * @return void */ @@ -82,12 +83,42 @@ protected function removeDir($d) { rmdir($d); } + + /** + * Helper method to set up the proper paths for the + * CodeSniffer/Standards/ and currently-being-installed-package + * directories. + * + * The returned $composer mock will return each path for the + * matching method call to the mock. Remember that PHPUnit counts + * ALL mocked methods in sequence! + * + * @param array $installedPaths Numeric keys are the `at()` calls to `getInstallPath` where the matching string value is returned as the path. + * @return array [mocked \Composer\Composer, mocked \Composer\Package\Package] + */ + protected function mockComposerAndPackage($getInstallPaths) { + $composer = $this->getMock('\Composer\Composer', array('getInstallationManager', 'getInstallPath')); + $composer->method('getInstallationManager')->will($this->returnSelf()); + foreach ($getInstallPaths as $at => $path) { + $composer->expects($this->at($at)) + ->method('getInstallPath') + ->willReturn($path); + } + + $package = $this->getMockBuilder('\Composer\Package\Package') + ->disableOriginalConstructor() + ->setMethods(array('getComposer')) + ->getMock(); + $package->method('getComposer')->willReturn($composer); + + return array($composer, $package); + } /** * test postPackageInstall() * * @return void */ - public function testPostPackageInstallMatchingType() { + public function testPostInstallMatchingType() { $this->marktestIncomplete('@TODO: Write a test where the package type is already phpcs-coding-standard'); } @@ -96,7 +127,7 @@ public function testPostPackageInstallMatchingType() { * * @return void */ - public function testPostPackageInstallNoStandards() { + public function testPostInstallNoStandards() { $this->marktestIncomplete('@TODO: Write a test where the package does not have any stanrds to install.'); } @@ -105,7 +136,7 @@ public function testPostPackageInstallNoStandards() { * * @return void */ - public function testPostPackageInstallSuccessful() { + public function testPostInstallSuccessful() { $this->marktestIncomplete('@TODO: Write a test where the package type is not phpcs-coding-standard and has standards to install.'); } @@ -115,27 +146,17 @@ public function testPostPackageInstallSuccessful() { * @return void */ public function testMirrorCodingStandardFoldersSuccessful() { - $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') - ->disableOriginalConstructor() - ->setMethods(array('getOperation', 'getPackage', 'getComposer', 'getInstallationManager', 'getInstallPath')) - ->getMock(); - $event->method('getOperation')->will($this->returnSelf()); - $event->method('getPackage')->will($this->returnSelf()); - $event->method('getComposer')->will($this->returnSelf()); - $event->method('getInstallationManager')->will($this->returnSelf()); - $event->expects($this->at(4)) - ->method('getInstallPath') - ->will($this->returnValue($this->sampleDir)); - $event->expects($this->at(7)) - ->method('getInstallPath') - ->will($this->returnValue($this->phpcsInstallDir)); + list($composer, $package) = $this->mockComposerAndPackage(array( + 1 => $this->sampleDir, + 3 => $this->phpcsInstallDir, + )); $expected = array( 'CodingStandardOne', 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($event); + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($package); foreach ($expected as $standard) { $this->assertTrue( @@ -151,27 +172,17 @@ public function testMirrorCodingStandardFoldersSuccessful() { * @return void */ public function testMirrorCodingStandardFoldersNoDest() { - $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') - ->disableOriginalConstructor() - ->setMethods(array('getOperation', 'getPackage', 'getComposer', 'getInstallationManager', 'getInstallPath')) - ->getMock(); - $event->method('getOperation')->will($this->returnSelf()); - $event->method('getPackage')->will($this->returnSelf()); - $event->method('getComposer')->will($this->returnSelf()); - $event->method('getInstallationManager')->will($this->returnSelf()); - $event->expects($this->at(4)) - ->method('getInstallPath') - ->will($this->returnValue($this->sampleDir)); - $event->expects($this->at(7)) - ->method('getInstallPath') - ->will($this->returnValue(false)); + list($composer, $package) = $this->mockComposerAndPackage(array( + 1 => $this->sampleDir, + 3 => false, + )); $expected = array( 'CodingStandardOne', 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($event); + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($package); foreach ($expected as $standard) { $this->assertFalse( @@ -206,17 +217,11 @@ public function testFindRulesetFolders() { * @return void */ public function testFindCodesnifferRootExists() { - $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') - ->disableOriginalConstructor() - ->setMethods(array('getComposer', 'getInstallationManager', 'getInstallPath')) - ->getMock(); - $event->method('getComposer')->will($this->returnSelf()); - $event->method('getInstallationManager')->will($this->returnSelf()); - $event->expects($this->once()) - ->method('getInstallPath') - ->will($this->returnValue($this->phpcsInstallDir)); + list($composer, $package) = $this->mockComposerAndPackage(array( + 1 => $this->phpcsInstallDir, + )); - $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($event); + $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($composer); $this->assertEquals( $this->standardsInstallDir, @@ -231,17 +236,11 @@ public function testFindCodesnifferRootExists() { * @return void */ public function testFindCodesnifferRootDoesNotExist() { - $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') - ->disableOriginalConstructor() - ->setMethods(array('getComposer', 'getInstallationManager', 'getInstallPath')) - ->getMock(); - $event->method('getComposer')->will($this->returnSelf()); - $event->method('getInstallationManager')->will($this->returnSelf()); - $event->expects($this->once()) - ->method('getInstallPath') - ->will($this->returnValue('does-not-exist')); + list($composer, $package) = $this->mockComposerAndPackage(array( + 1 => 'does-not-exist', + )); - $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($event); + $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($composer); $this->assertFalse( $result, diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php index 53754d0..dcf7d1b 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php @@ -9,14 +9,42 @@ namespace Loadsys\Composer\Test; +use Composer\Package\PackageInterface; +use Loadsys\Composer\PhpcsCodingStandardHook; use Loadsys\Composer\PhpcsCodingStandardInstaller; /** - * Child class to expose protected methods for direct testing. + * Test stub of the PhpcsCodingStandardHook class. + * + * Instead of `use Loadsys\Composer\PhpcsCodingStandardHook;`, define + * a class with known operations to isolate the installer from the + * filesystem side-effects for testing. Methods set internal + * properties for test inspection. + * + * This class **MUST** be declared before the "real" class would be + * autoloaded by our SUT. + */ +class StubPhpcsCodingStandardHook { + public $calls = array(); + public function __call($name, $arguments) { + $this->calls[$name] = $arguments; + return $name; + } +} + +/** + * Expose protected methods for direct testing, since this class doesn't + * otherwise expose public interfaces to us. */ class TestPhpcsCodingStandardInstaller extends PhpcsCodingStandardInstaller { - public function findRulesetFolders($basePath) { - return parent::findRulesetFolders($basePath); + public function installCode(PackageInterface $package) { + return parent::installCode($package); + } + public function updateCode(PackageInterface $initial, PackageInterface $target) { + return parent::updateCode($initial, $target); + } + public function removeCode(PackageInterface $package) { + return parent::removeCode($package); } } @@ -37,12 +65,23 @@ public function setUp() { parent::setUp(); $this->baseDir = getcwd(); - $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); $this->io = $this->getMock('\Composer\IO\IOInterface'); + $this->package = $this->getMock('\Composer\Package\Package', + array('getType'), + array('CamelCased', '1.0', '1.0') + ); + $downloadManager = $this->getMock('\Composer\Downloader\DownloadManager', array(), array($this->io)); + $this->hook = new StubPhpcsCodingStandardHook(); $this->composer = new \Composer\Composer(); $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); - $this->Installer = new TestPhpcsCodingStandardInstaller($this->io, $this->composer); + $this->composer->setDownloadManager($downloadManager); + $this->Installer = new TestPhpcsCodingStandardInstaller( + $this->io, + $this->composer, + 'library', + new \Composer\Util\Filesystem(), $this->hook + ); } /** @@ -54,20 +93,12 @@ public function tearDown() { unset($this->package); unset($this->io); unset($this->composer); - unset($this->composer); + unset($this->hook); + unset($this->Installer); parent::tearDown(); } - /** - * testNothing - * - * @return void - */ - public function testNothing() { - $this->markTestIncomplete('@TODO: No tests written for PhpcsCodingStandardInstaller.'); - } - /** * test supports() * @@ -89,20 +120,62 @@ public function testSupports() { * * @return void */ - public function testInstallCode() { - $sampleDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/samples'; - $expected = array( - 'CodingStandardOne', - 'SecondStandard', + public function testInstallCodeCorrectType() { + $this->package->expects($this->any()) + ->method('getType') + ->willReturn(PhpcsCodingStandardHook::PHPCS_PACKAGE_TYPE); + + $result = $this->Installer->installCode($this->package); + + $this->assertEquals( + null, + $result, + 'Return value should always be null.' ); - $package = $this->getMock('@TODO'); + $this->assertEquals( + array('mirrorCodingStandardFolders' => array($this->package)), + $this->Installer->hook->calls, + 'Our mocked static class should have registered a single call to mirrorCodingStandardFolders().' + ); + } - $result = $this->Installer->installCode($package); + /** + * test installCode() + * + * @return void + */ + public function testInstallCodeIncorrectType() { + $this->package->expects($this->any()) + ->method('getType') + ->willReturn('not-the-correct-type'); + + $result = $this->Installer->installCode($this->package); $this->assertEquals( - $expected, + null, $result, - 'Only folders containing a ruleset.xml should be returned.' + 'Return value should always be null.' ); + $this->assertEquals( + array(), + $this->Installer->hook->calls, + 'There should be no call to our stubbed static class.' + ); + } + + /** + * test updateCode() + * + * @return void + */ + public function testUpdateCode() { + } + + /** + * test removeCode() + * + * @return void + */ + public function testRemoveCode() { } } From 27401189f5a454c5aae4a504b1dedce160bb919e Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 11 Aug 2015 13:02:02 -0500 Subject: [PATCH 08/15] WIP Crazy Composer interface issues. --- .../Composer/PhpcsCodingStandardHook.php | 30 +++++++++++-------- .../Composer/PhpcsCodingStandardInstaller.php | 9 ++++-- .../Test/PhpcsCodingStandardHookTest.php | 28 ++++++++--------- .../Test/PhpcsCodingStandardInstallerTest.php | 5 ++-- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/Loadsys/Composer/PhpcsCodingStandardHook.php b/src/Loadsys/Composer/PhpcsCodingStandardHook.php index 4c0649d..d4b32d7 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardHook.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardHook.php @@ -65,8 +65,8 @@ class PhpcsCodingStandardHook { * @return void */ public static function postInstall(PackageEvent $event) { - $packages = $event - ->getComposer() + $composer = $event->getComposer(); + $packages = $composer ->getRepositoryManager() ->getLocalRepository() ->getPackages() @@ -82,7 +82,8 @@ public static function postInstall(PackageEvent $event) { // Otherwise, check for Coding Standard folders and copy them. // (This is a relatively quick no-op if there are no // `ruleset.xml` files in the package.) - self::mirrorCodingStandardFolders($package); + $installPath = $composer->getInstallationManager()->getInstallPath($package); + self::mirrorCodingStandardFolders($composer, $installPath); } } @@ -112,20 +113,22 @@ public static function prePackageUninstall(PackageEvent $event) { // Otherwise, check for Coding Standard folders in the package // about to be removed, and remove them from the // CodeSniffer/Standards/ first. - self::deleteCodingStandardFolders($package); + $composer = $event->getComposer(); + $installPath = $composer->getInstallationManager()->getInstallPath($package); + self::deleteCodingStandardFolders($composer, $installPath); } /** * Mirror (copy or delete, only as necessary) items from the installed * package's release/ folder into the target directory. * - * @param \Composer\Package\PackageInterface $package The composer Package being installed. + * @param \Composer\Composer $composer Active composer instance. + * @param \Composer\Package\PackageInterface $package The installation path for the Package being installed. * @return void */ - public static function mirrorCodingStandardFolders(PackageInterface $package) { - $packageBasePath = $package->getComposer()->getInstallationManager()->getInstallPath($package); + public static function mirrorCodingStandardFolders(Composer $composer, $packageBasePath) { $rulesets = self::findRulesetFolders($packageBasePath); - $destDir = self::findCodesnifferRoot($package->getComposer()); + $destDir = self::findCodesnifferRoot($composer); // No-op if no ruleset.xml's found or squizlabs/php_codesniffer not installed. if (empty($rulesets) || !$destDir) { @@ -168,15 +171,16 @@ public static function mirrorCodingStandardFolders(PackageInterface $package) { /** * Remove Coding Standards folders from phpcs. * - * Check the to-be-removed package for Coding Standard folders, remove those folders from the CodeSniffer/Standards/ dir. + * Check the to-be-removed package for Coding Standard folders, + * remove those folders from the CodeSniffer/Standards/ dir. * - * @param \Composer\Package\PackageInterface $package The composer Package about to be removed. + * @param \Composer\Composer $composer Active composer instance. + * @param \Composer\Package\PackageInterface $package The installation path for the Package about to be removed. * @return void */ - public function deleteCodingStandardFolders(PackageInterface $package) { - $packageBasePath = $package->getComposer()->getInstallationManager()->getInstallPath($package); + public static function deleteCodingStandardFolders(Composer $composer, $packageBasePath) { $rulesets = self::findRulesetFolders($packageBasePath); - $destDir = self::findCodesnifferRoot($package->getComposer()); + $destDir = self::findCodesnifferRoot($composer); // No-op if no ruleset.xml's found. if (empty($rulesets) || !$destDir) { diff --git a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php index 5e00235..082e78b 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php +++ b/src/Loadsys/Composer/PhpcsCodingStandardInstaller.php @@ -67,7 +67,8 @@ protected function installCode(PackageInterface $package) { return; } - $this->hook->mirrorCodingStandardFolders($package); + $installPath = $this->composer->getInstallationManager()->getInstallPath($package); + $this->hook->mirrorCodingStandardFolders($this->composer, $installPath); } /** @@ -84,7 +85,8 @@ protected function updateCode(PackageInterface $initial, PackageInterface $targe return; } - $this->hook->mirrorCodingStandardFolders($target); + $installPath = $this->composer->getInstallationManager()->getInstallPath($target); + $this->hook->mirrorCodingStandardFolders($this->composer, $installPath); } /** @@ -95,7 +97,8 @@ protected function updateCode(PackageInterface $initial, PackageInterface $targe */ protected function removeCode(PackageInterface $package) { if ($this->supports($package->getType())) { - $this->hook->deleteCodingStandardFolders($package); + $installPath = $this->composer->getInstallationManager()->getInstallPath($package); + $this->hook->deleteCodingStandardFolders($this->composer, $installPath); } parent::removeCode($package); diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php index d7fde67..ea59fc6 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php @@ -94,9 +94,9 @@ protected function removeDir($d) { * ALL mocked methods in sequence! * * @param array $installedPaths Numeric keys are the `at()` calls to `getInstallPath` where the matching string value is returned as the path. - * @return array [mocked \Composer\Composer, mocked \Composer\Package\Package] + * @return array [mocked \Composer\Composer, mocked \Composer\Installer\PackageEvent] */ - protected function mockComposerAndPackage($getInstallPaths) { + protected function mockComposerAndEvent($getInstallPaths) { $composer = $this->getMock('\Composer\Composer', array('getInstallationManager', 'getInstallPath')); $composer->method('getInstallationManager')->will($this->returnSelf()); foreach ($getInstallPaths as $at => $path) { @@ -105,13 +105,13 @@ protected function mockComposerAndPackage($getInstallPaths) { ->willReturn($path); } - $package = $this->getMockBuilder('\Composer\Package\Package') + $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') ->disableOriginalConstructor() ->setMethods(array('getComposer')) ->getMock(); - $package->method('getComposer')->willReturn($composer); + $event->method('getComposer')->willReturn($composer); - return array($composer, $package); + return array($composer, $event); } /** * test postPackageInstall() @@ -146,9 +146,8 @@ public function testPostInstallSuccessful() { * @return void */ public function testMirrorCodingStandardFoldersSuccessful() { - list($composer, $package) = $this->mockComposerAndPackage(array( - 1 => $this->sampleDir, - 3 => $this->phpcsInstallDir, + list($composer, $event) = $this->mockComposerAndEvent(array( + 1 => $this->phpcsInstallDir, )); $expected = array( @@ -156,7 +155,7 @@ public function testMirrorCodingStandardFoldersSuccessful() { 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($package); + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); foreach ($expected as $standard) { $this->assertTrue( @@ -172,9 +171,8 @@ public function testMirrorCodingStandardFoldersSuccessful() { * @return void */ public function testMirrorCodingStandardFoldersNoDest() { - list($composer, $package) = $this->mockComposerAndPackage(array( - 1 => $this->sampleDir, - 3 => false, + list($composer, $event) = $this->mockComposerAndEvent(array( + 1 => false, )); $expected = array( @@ -182,7 +180,7 @@ public function testMirrorCodingStandardFoldersNoDest() { 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($package); + $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); foreach ($expected as $standard) { $this->assertFalse( @@ -217,7 +215,7 @@ public function testFindRulesetFolders() { * @return void */ public function testFindCodesnifferRootExists() { - list($composer, $package) = $this->mockComposerAndPackage(array( + list($composer, $event) = $this->mockComposerAndEvent(array( 1 => $this->phpcsInstallDir, )); @@ -236,7 +234,7 @@ public function testFindCodesnifferRootExists() { * @return void */ public function testFindCodesnifferRootDoesNotExist() { - list($composer, $package) = $this->mockComposerAndPackage(array( + list($composer, $package) = $this->mockComposerAndEvent(array( 1 => 'does-not-exist', )); diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php index dcf7d1b..1f240e6 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php +++ b/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php @@ -71,10 +71,11 @@ public function setUp() { array('CamelCased', '1.0', '1.0') ); $downloadManager = $this->getMock('\Composer\Downloader\DownloadManager', array(), array($this->io)); + $installationManager = $this->getMock('\Composer\Installer\InstallationManager', array(), array()); $this->hook = new StubPhpcsCodingStandardHook(); $this->composer = new \Composer\Composer(); $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); - $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); + $this->composer->setInstallationManager($installationManager); $this->composer->setDownloadManager($downloadManager); $this->Installer = new TestPhpcsCodingStandardInstaller( $this->io, @@ -133,7 +134,7 @@ public function testInstallCodeCorrectType() { 'Return value should always be null.' ); $this->assertEquals( - array('mirrorCodingStandardFolders' => array($this->package)), + array('mirrorCodingStandardFolders' => array($this->composer, null)), $this->Installer->hook->calls, 'Our mocked static class should have registered a single call to mirrorCodingStandardFolders().' ); From 5cb5128364ca1191ccabdae20526072a0dd650eb Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 12 Aug 2015 09:20:23 -0500 Subject: [PATCH 09/15] Re-namespace all the things. --- README.md | 12 +++---- composer.json | 9 +++-- phpunit.xml.dist | 6 ++-- .../Composer => }/LoadsysComposerPlugin.php | 4 +-- .../CodingStandardHook.php} | 8 ++--- .../CodingStandardInstaller.php} | 12 +++---- .../ReleaseInstaller.php} | 4 +-- src/bootstrap.php | 15 -------- .../LoadsysComposerPluginTest.php | 6 ++-- .../CodingStandardHookTest.php} | 24 ++++++------- .../CodingStandardInstallerTest.php} | 34 ++++++++----------- .../Puphpet/ReleaseInstallerTest.php} | 8 ++--- tests/bootstrap.php | 4 +-- 13 files changed, 66 insertions(+), 80 deletions(-) rename src/{Loadsys/Composer => }/LoadsysComposerPlugin.php (82%) rename src/{Loadsys/Composer/PhpcsCodingStandardHook.php => PhpCodesniffer/CodingStandardHook.php} (98%) rename src/{Loadsys/Composer/PhpcsCodingStandardInstaller.php => PhpCodesniffer/CodingStandardInstaller.php} (90%) rename src/{Loadsys/Composer/PuphpetReleaseInstaller.php => Puphpet/ReleaseInstaller.php} (97%) delete mode 100644 src/bootstrap.php rename tests/{Loadsys/Composer/Test => TestCase}/LoadsysComposerPluginTest.php (92%) rename tests/{Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php => TestCase/PhpCodesniffer/CodingStandardHookTest.php} (88%) rename tests/{Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php => TestCase/PhpCodesniffer/CodingStandardInstallerTest.php} (80%) rename tests/{Loadsys/Composer/Test/PuphpetReleaseInstallerTest.php => TestCase/Puphpet/ReleaseInstallerTest.php} (85%) diff --git a/README.md b/README.md index d815eef..0406b7c 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,13 @@ To use this hook script, add the following to your root project's `composer.json }, "scripts": { "post-install-cmd": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::postInstall" ], "post-update-cmd": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::postInstall" ], "pre-package-uninstall": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::prePackageUninstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::prePackageUninstall" ] } } @@ -98,13 +98,13 @@ To use this hook script, add the following to your root project's `composer.json }, "scripts": { "post-install-cmd": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::postInstall" ], "post-update-cmd": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::postInstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::postInstall" ], "pre-package-uninstall": [ - "Loadsys\\Composer\\PhpcsCodingStandardHook::prePackageUninstall" + "Loadsys\\Composer\\PhpCodesniffer\\CodingStandardHook::prePackageUninstall" ] } } diff --git a/composer.json b/composer.json index e70d9ee..4bc6daf 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,13 @@ } ], "autoload": { - "psr-0": { - "Loadsys\\Composer": "src/" + "psr-4": { + "Loadsys\\Composer\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Loadsys\\Composer\\Test\\": "tests" } }, "extra": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 040070d..1b50fb6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,14 +13,14 @@ bootstrap="tests/bootstrap.php" > - - tests/Loadsys/Composer + + tests/TestCase - src/Loadsys/Composer + src \ No newline at end of file diff --git a/src/Loadsys/Composer/LoadsysComposerPlugin.php b/src/LoadsysComposerPlugin.php similarity index 82% rename from src/Loadsys/Composer/LoadsysComposerPlugin.php rename to src/LoadsysComposerPlugin.php index 2420ab5..7c8b836 100644 --- a/src/Loadsys/Composer/LoadsysComposerPlugin.php +++ b/src/LoadsysComposerPlugin.php @@ -23,10 +23,10 @@ class LoadsysComposerPlugin implements PluginInterface { * @return void */ public function activate(Composer $composer, IOInterface $io) { - $puphpetReleaseInstaller = new PuphpetReleaseInstaller($io, $composer); + $puphpetReleaseInstaller = new Puphpet\ReleaseInstaller($io, $composer); $composer->getInstallationManager()->addInstaller($puphpetReleaseInstaller); - $phpcsCodingStandardInstaller = new PhpcsCodingStandardInstaller($io, $composer); + $phpcsCodingStandardInstaller = new PhpCodesniffer\CodingStandardInstaller($io, $composer); $composer->getInstallationManager()->addInstaller($phpcsCodingStandardInstaller); } } diff --git a/src/Loadsys/Composer/PhpcsCodingStandardHook.php b/src/PhpCodesniffer/CodingStandardHook.php similarity index 98% rename from src/Loadsys/Composer/PhpcsCodingStandardHook.php rename to src/PhpCodesniffer/CodingStandardHook.php index d4b32d7..e875544 100644 --- a/src/Loadsys/Composer/PhpcsCodingStandardHook.php +++ b/src/PhpCodesniffer/CodingStandardHook.php @@ -1,16 +1,16 @@ hook = (!is_null($hook) ? $hook : new PhpcsCodingStandardHook()); + $this->hook = (!is_null($hook) ? $hook : new CodingStandardHook()); } /** @@ -51,7 +51,7 @@ public function __construct(IOInterface $io, Composer $composer, $type = 'librar * @return bool True if this installer should be activated for the package in question, false if not. */ public function supports($packageType) { - return ($packageType === PhpcsCodingStandardHook::PHPCS_PACKAGE_TYPE); + return ($packageType === CodingStandardHook::PHPCS_PACKAGE_TYPE); } /** diff --git a/src/Loadsys/Composer/PuphpetReleaseInstaller.php b/src/Puphpet/ReleaseInstaller.php similarity index 97% rename from src/Loadsys/Composer/PuphpetReleaseInstaller.php rename to src/Puphpet/ReleaseInstaller.php index d7d6a3c..008280b 100644 --- a/src/Loadsys/Composer/PuphpetReleaseInstaller.php +++ b/src/Puphpet/ReleaseInstaller.php @@ -1,6 +1,6 @@ composer->expects($this->at(1)) ->method('addInstaller') - ->with($this->isInstanceOf('Loadsys\Composer\PuphpetReleaseInstaller')); + ->with($this->isInstanceOf('Loadsys\Composer\Puphpet\ReleaseInstaller')); $this->composer->expects($this->at(3)) ->method('addInstaller') - ->with($this->isInstanceOf('Loadsys\Composer\PhpcsCodingStandardInstaller')); + ->with($this->isInstanceOf('Loadsys\Composer\PhpCodesniffer\CodingStandardInstaller')); $this->plugin->activate($this->composer, $this->io); } diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php similarity index 88% rename from tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php rename to tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php index ea59fc6..ad065d0 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardHookTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php @@ -1,20 +1,20 @@ standardsInstallDir = $this->phpcsInstallDir . '/CodeSniffer/Standards'; mkdir($this->standardsInstallDir, 0777, true); - $this->sampleDir = dirname(dirname(dirname(dirname(__FILE__)))) . '/samples'; + $this->sampleDir = $this->baseDir . '/tests/samples'; $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); $this->io = $this->getMock('\Composer\IO\IOInterface'); @@ -155,7 +155,7 @@ public function testMirrorCodingStandardFoldersSuccessful() { 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); + $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); foreach ($expected as $standard) { $this->assertTrue( @@ -180,7 +180,7 @@ public function testMirrorCodingStandardFoldersNoDest() { 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); + $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); foreach ($expected as $standard) { $this->assertFalse( @@ -200,7 +200,7 @@ public function testFindRulesetFolders() { 'CodingStandardOne', 'SecondStandard', ); - $result = TestPhpcsCodingStandardHook::findRulesetFolders($this->sampleDir); + $result = TestCodingStandardHook::findRulesetFolders($this->sampleDir); $this->assertEquals( $expected, @@ -219,7 +219,7 @@ public function testFindCodesnifferRootExists() { 1 => $this->phpcsInstallDir, )); - $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($composer); + $result = TestCodingStandardHook::findCodesnifferRoot($composer); $this->assertEquals( $this->standardsInstallDir, @@ -238,7 +238,7 @@ public function testFindCodesnifferRootDoesNotExist() { 1 => 'does-not-exist', )); - $result = TestPhpcsCodingStandardHook::findCodesnifferRoot($composer); + $result = TestCodingStandardHook::findCodesnifferRoot($composer); $this->assertFalse( $result, diff --git a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php similarity index 80% rename from tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php rename to tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php index 1f240e6..22e71f5 100644 --- a/tests/Loadsys/Composer/Test/PhpcsCodingStandardInstallerTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php @@ -1,30 +1,26 @@ calls[$name] = $arguments; @@ -36,7 +32,7 @@ public function __call($name, $arguments) { * Expose protected methods for direct testing, since this class doesn't * otherwise expose public interfaces to us. */ -class TestPhpcsCodingStandardInstaller extends PhpcsCodingStandardInstaller { +class TestCodingStandardInstaller extends CodingStandardInstaller { public function installCode(PackageInterface $package) { return parent::installCode($package); } @@ -49,9 +45,9 @@ public function removeCode(PackageInterface $package) { } /** - * PhpcsCodingStandardInstaller Test + * PhpCodesniffer\CodingStandardInstaller Test */ -class PhpcsCodingStandardInstallerTest extends \PHPUnit_Framework_TestCase { +class CodingStandardInstallerTest extends \PHPUnit_Framework_TestCase { private $package; private $composer; private $io; @@ -72,12 +68,12 @@ public function setUp() { ); $downloadManager = $this->getMock('\Composer\Downloader\DownloadManager', array(), array($this->io)); $installationManager = $this->getMock('\Composer\Installer\InstallationManager', array(), array()); - $this->hook = new StubPhpcsCodingStandardHook(); + $this->hook = new StubCodingStandardHook(); $this->composer = new \Composer\Composer(); $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); $this->composer->setInstallationManager($installationManager); $this->composer->setDownloadManager($downloadManager); - $this->Installer = new TestPhpcsCodingStandardInstaller( + $this->Installer = new TestCodingStandardInstaller( $this->io, $this->composer, 'library', @@ -124,7 +120,7 @@ public function testSupports() { public function testInstallCodeCorrectType() { $this->package->expects($this->any()) ->method('getType') - ->willReturn(PhpcsCodingStandardHook::PHPCS_PACKAGE_TYPE); + ->willReturn(CodingStandardHook::PHPCS_PACKAGE_TYPE); $result = $this->Installer->installCode($this->package); diff --git a/tests/Loadsys/Composer/Test/PuphpetReleaseInstallerTest.php b/tests/TestCase/Puphpet/ReleaseInstallerTest.php similarity index 85% rename from tests/Loadsys/Composer/Test/PuphpetReleaseInstallerTest.php rename to tests/TestCase/Puphpet/ReleaseInstallerTest.php index a4d10fe..f76af3c 100644 --- a/tests/Loadsys/Composer/Test/PuphpetReleaseInstallerTest.php +++ b/tests/TestCase/Puphpet/ReleaseInstallerTest.php @@ -1,7 +1,7 @@ markTestIncomplete('@TODO: No tests written for PuphpetReleaseInstaller.'); + $this->markTestIncomplete('@TODO: No tests written for Puphpet\ReleaseInstaller.'); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b053da6..31d0f65 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,3 +1,3 @@ add('Loadsys\Composer\Test', __DIR__); + +include 'vendor/autoload.php'; From fec31d78cc63bf28ef20849517da575b534cce5a Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 12 Aug 2015 09:30:21 -0500 Subject: [PATCH 10/15] Composer api docs lie. --- src/PhpCodesniffer/CodingStandardHook.php | 8 ++++---- tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpCodesniffer/CodingStandardHook.php b/src/PhpCodesniffer/CodingStandardHook.php index e875544..29caf5b 100644 --- a/src/PhpCodesniffer/CodingStandardHook.php +++ b/src/PhpCodesniffer/CodingStandardHook.php @@ -12,14 +12,14 @@ namespace Loadsys\Composer\PhpCodesniffer; -use Composer\Script\Event; -use Composer\Installer\PackageEvent; use Composer\Composer; use Composer\IO\IOInterface; +use Composer\Installer\PackageEvent; use Composer\Package\Package; use Composer\Package\PackageInterface; +use Composer\Script\Event; use Composer\Util\Filesystem; use \RecursiveCallbackFilterIterator; use \RecursiveDirectoryIterator; @@ -61,10 +61,10 @@ class CodingStandardHook { * No-op if there are no `ruleset.xml` files or the PHP CodeSniffer * package is not installed, making it safe to run on every package. * - * @param \Composer\Installer\PackageEvent $event The composer Package event being fired. + * @param \Composer\Script\Event $event The composer event being fired. * @return void */ - public static function postInstall(PackageEvent $event) { + public static function postInstall(Event $event) { $composer = $event->getComposer(); $packages = $composer ->getRepositoryManager() diff --git a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php index ad065d0..1c359cd 100644 --- a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php @@ -105,7 +105,7 @@ protected function mockComposerAndEvent($getInstallPaths) { ->willReturn($path); } - $event = $this->getMockBuilder('\Composer\Installer\PackageEvent') + $event = $this->getMockBuilder('\Composer\Script\Event') ->disableOriginalConstructor() ->setMethods(array('getComposer')) ->getMock(); From 00f334aa6e494c56a1fc39d759b0a36f9162678b Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 12 Aug 2015 10:32:01 -0500 Subject: [PATCH 11/15] Codesniff and doc cleanup. --- src/PhpCodesniffer/CodingStandardHook.php | 83 ++-- .../CodingStandardInstaller.php | 23 +- src/Puphpet/ReleaseInstaller.php | 33 +- tests/TestCase/LoadsysComposerPluginTest.php | 114 ++--- .../PhpCodesniffer/CodingStandardHookTest.php | 419 ++++++++++-------- .../CodingStandardInstallerTest.php | 270 +++++------ .../TestCase/Puphpet/ReleaseInstallerTest.php | 80 ++-- 7 files changed, 548 insertions(+), 474 deletions(-) diff --git a/src/PhpCodesniffer/CodingStandardHook.php b/src/PhpCodesniffer/CodingStandardHook.php index 29caf5b..f14c7f6 100644 --- a/src/PhpCodesniffer/CodingStandardHook.php +++ b/src/PhpCodesniffer/CodingStandardHook.php @@ -12,22 +12,17 @@ namespace Loadsys\Composer\PhpCodesniffer; - - use Composer\Composer; -use Composer\IO\IOInterface; use Composer\Installer\PackageEvent; +use Composer\IO\IOInterface; use Composer\Package\Package; use Composer\Package\PackageInterface; use Composer\Script\Event; use Composer\Util\Filesystem; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesytem; use \RecursiveCallbackFilterIterator; use \RecursiveDirectoryIterator; use \RecursiveIteratorIterator; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesytem; - - - if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); @@ -69,8 +64,7 @@ public static function postInstall(Event $event) { $packages = $composer ->getRepositoryManager() ->getLocalRepository() - ->getPackages() - ; + ->getPackages(); foreach ($packages as $package) { // If the package defines the correct type, @@ -123,7 +117,7 @@ public static function prePackageUninstall(PackageEvent $event) { * package's release/ folder into the target directory. * * @param \Composer\Composer $composer Active composer instance. - * @param \Composer\Package\PackageInterface $package The installation path for the Package being installed. + * @param string $packageBasePath The installation path for the Package being installed. * @return void */ public static function mirrorCodingStandardFolders(Composer $composer, $packageBasePath) { @@ -164,7 +158,7 @@ public static function mirrorCodingStandardFolders(Composer $composer, $packageB $packageBasePath, $destDir, $codingStandardsFolders, - array('override' => true) + ['override' => true] ); } @@ -175,7 +169,7 @@ public static function mirrorCodingStandardFolders(Composer $composer, $packageB * remove those folders from the CodeSniffer/Standards/ dir. * * @param \Composer\Composer $composer Active composer instance. - * @param \Composer\Package\PackageInterface $package The installation path for the Package about to be removed. + * @param string $packageBasePath The installation path for the Package about to be removed. * @return void */ public static function deleteCodingStandardFolders(Composer $composer, $packageBasePath) { @@ -203,7 +197,7 @@ public static function deleteCodingStandardFolders(Composer $composer, $packageB * @return array Array of partial file paths (from $basePath) to folders containing a ruleset.xml, no leading or trailing slashes. Empty array if none found. */ protected static function findRulesetFolders($basePath) { - $rulesetFolders = array_map(function ($v) use ($basePath){ + $rulesetFolders = array_map(function ($v) use ($basePath) { return dirname(str_replace($basePath . DS, '', $v)); }, glob($basePath . DS . '*' . DS . 'ruleset.xml')); @@ -231,56 +225,73 @@ protected static function findCodesnifferRoot(Composer $composer) { } +// @TODO: Break this stuff out into a separate Hook class? - - - - - - - - - - - -// @TODO: Break this stuff out into a separate Hook class. - - - //@TODO: doc block + /** + * Inject the provided filesystem path into phpcs's CodeSniffer.conf's [installed_paths] key. + * + * @param string $path The relative path to the coding standard folder to add. + * @return bool True if the path was successfully added to the config file, false on failure. + */ public static function configInstalledPathAdd($path) { - //@TODO: write and test this: + //@TODO: test configInstalledPathAdd() $installedPaths = self::readInstalledPaths(); $installedPaths[] = $path; return self::saveInstalledPaths($installedPaths); } - //@TODO: doc block + /** + * Remove the provided filesystem path into phpcs's CodeSniffer.conf's [installed_paths] key. + * + * @param string $path The relative path to the coding standard folder to remove. + * @return bool True if the path was successfully removed to the config file, false on failure. + */ public static function configInstalledPathRemove($path) { - //@TODO: write and test this: + //@TODO: test configInstalledPathRemove() $installedPaths = self::readInstalledPaths(); - if ($key = array_search($path, $installedPaths)) { + $key = array_search($path, $installedPaths); + if ($key) { unset($installedPaths[$key]); } + return self::saveInstalledPaths($installedPaths); } - //@TODO: doc block + + /** + * Fetch the entire array of [installed_paths] from the phpcs config file. + * + * @return array Empty array if the config file load fails at all, otherwise the list of paths in the config. + */ protected static function readInstalledPaths() { + //@TODO: test readInstalledPaths() self::codeSnifferInit(); $pathsString = PHP_CodeSniffer::getInstalledStandardPaths(); if (is_null($pathsString)) { - return array(); + return []; } + return explode(',', $pathsString); } - //@TODO: doc block + /** + * Write an array of paths back into phpcs's config file's [installed_paths]. + * + * @param array $paths Array of relative paths. Any duplicates will be stripped before saving. + * @return bool True if the config file was written successfully, false on failure. + */ protected static function saveInstalledPaths(array $paths) { + //@TODO: test saveInstalledPaths() return PHP_CodeSniffer::setConfigData('installed_paths', implode(',', array_unique($paths))); } - //@TODO: doc block + /** + * Ensures that the PHP_CodeSniffer class is available for static access. + * + * @return void + */ protected static function codeSnifferInit() { + //@TODO: test codeSnifferInit() if (!class_exists('PHP_CodeSniffer')) { $composerInstall = dirname(dirname(dirname(__FILE__))) . '/vendor/squizlabs/php_codesniffer/CodeSniffer.php'; if (file_exists($composerInstall)) { diff --git a/src/PhpCodesniffer/CodingStandardInstaller.php b/src/PhpCodesniffer/CodingStandardInstaller.php index 6cb2e5d..7881c73 100644 --- a/src/PhpCodesniffer/CodingStandardInstaller.php +++ b/src/PhpCodesniffer/CodingStandardInstaller.php @@ -27,17 +27,18 @@ */ class CodingStandardInstaller extends LibraryInstaller { - /** - * Initializes library installer. - * - * @param IOInterface $io - * @param Composer $composer - * @param string $type - * @param Filesystem $filesystem - */ - public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, $hook = null) { - parent::__construct($io, $composer, $type, $filesystem); - $this->hook = (!is_null($hook) ? $hook : new CodingStandardHook()); + /** + * Initializes library installer. + * + * @param \Composer\IO\IOInterface $io Active I/O interface. + * @param \Composer\Composer $composer Active Composer instance. + * @param string $type Current package `type`. + * @param \Composer\Util\Filesystem $filesystem Utility filesystem instance. + * @param \Loadsys\Composer\PhpCodesniffer\CodingStandardHook $hook Instance of the hook class that does the heavy lifting for us. + */ + public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, $hook = null) { + parent::__construct($io, $composer, $type, $filesystem); + $this->hook = (!is_null($hook) ? $hook : new CodingStandardHook()); } /** diff --git a/src/Puphpet/ReleaseInstaller.php b/src/Puphpet/ReleaseInstaller.php index 008280b..0477f33 100644 --- a/src/Puphpet/ReleaseInstaller.php +++ b/src/Puphpet/ReleaseInstaller.php @@ -1,27 +1,25 @@ supports($package->getType())) { return; } + $this->mirrorReleaseItems($package); $this->copyConfigFile($package); $this->checkGitignore($package); @@ -59,6 +58,8 @@ protected function installCode(PackageInterface $package) { * Mirror (copy or delete, only as necessary) items from the installed * package's release/ folder into the target directory. * + * @param PackageInterface $package package instance + * @return void */ protected function mirrorReleaseItems($package) { // Copy everything from the release/ subfolder to the project root. @@ -86,8 +87,9 @@ protected function mirrorReleaseItems($package) { * Search for a config file in the consuming project and copy it into * place if present. * + * @return void */ - protected function copyConfigFile($package) { + protected function copyConfigFile() { $configFilePath = getcwd() . DS . 'puphpet.yaml'; $targetPath = getcwd() . DS . 'puphpet' . DS . 'config.yaml'; if (is_readable($configFilePath)) { @@ -99,8 +101,9 @@ protected function copyConfigFile($package) { * Check that release items copied into the consuming project are * properly ignored in source control (very, VERY crudely.) * + * @return void */ - protected function checkGitIgnore($package) { + protected function checkGitIgnore() { $gitFolder = getcwd() . DS . '.git' . DS; if (!file_exists($gitFolder)) { diff --git a/tests/TestCase/LoadsysComposerPluginTest.php b/tests/TestCase/LoadsysComposerPluginTest.php index ff712d3..6559517 100644 --- a/tests/TestCase/LoadsysComposerPluginTest.php +++ b/tests/TestCase/LoadsysComposerPluginTest.php @@ -1,75 +1,75 @@ package = new Package('CamelCased', '1.0', '1.0'); - $this->io = $this->getMock('Composer\IO\IOInterface'); - $this->composer = new Composer(); - $this->plugin = new LoadsysComposerPlugin(); - } + $this->package = new Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('Composer\IO\IOInterface'); + $this->composer = new Composer(); + $this->plugin = new LoadsysComposerPlugin(); + } - /** - * tearDown - * - * @return void - */ - public function tearDown() { - unset($this->package); - unset($this->io); - unset($this->composer); - unset($this->plugin); + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + unset($this->plugin); - parent::tearDown(); - } + parent::tearDown(); + } - /** - * All we can do is confirm that the plugin tried to register the - * correct installer class during ::activate(). - * - * @return void - */ - public function testActivate() { - $this->composer = $this->getMock('Composer\Composer', [ - 'getInstallationManager', - 'addInstaller' - ]); - $this->composer->setConfig(new Config(false)); + /** + * All we can do is confirm that the plugin tried to register the + * correct installer class during ::activate(). + * + * @return void + */ + public function testActivate() { + $this->composer = $this->getMock('Composer\Composer', [ + 'getInstallationManager', + 'addInstaller' + ]); + $this->composer->setConfig(new Config(false)); - $this->composer->expects($this->any()) - ->method('getInstallationManager') - ->will($this->returnSelf()); + $this->composer->expects($this->any()) + ->method('getInstallationManager') + ->will($this->returnSelf()); - $this->composer->expects($this->at(1)) - ->method('addInstaller') - ->with($this->isInstanceOf('Loadsys\Composer\Puphpet\ReleaseInstaller')); - $this->composer->expects($this->at(3)) - ->method('addInstaller') - ->with($this->isInstanceOf('Loadsys\Composer\PhpCodesniffer\CodingStandardInstaller')); + $this->composer->expects($this->at(1)) + ->method('addInstaller') + ->with($this->isInstanceOf('Loadsys\Composer\Puphpet\ReleaseInstaller')); + $this->composer->expects($this->at(3)) + ->method('addInstaller') + ->with($this->isInstanceOf('Loadsys\Composer\PhpCodesniffer\CodingStandardInstaller')); - $this->plugin->activate($this->composer, $this->io); - } + $this->plugin->activate($this->composer, $this->io); + } } diff --git a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php index 1c359cd..7155fa1 100644 --- a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php @@ -27,51 +27,51 @@ public static function findCodesnifferRoot(Composer $composer) { * CodingStandardHook Test */ class CodingStandardHookTest extends \PHPUnit_Framework_TestCase { - private $package; - private $composer; - private $io; - - /** - * setUp - * - * @return void - */ - public function setUp() { - parent::setUp(); - - $this->baseDir = getcwd(); - $this->phpcsInstallDir = sys_get_temp_dir() . md5(__FILE__ . time()); - $this->standardsInstallDir = $this->phpcsInstallDir . '/CodeSniffer/Standards'; - mkdir($this->standardsInstallDir, 0777, true); - - $this->sampleDir = $this->baseDir . '/tests/samples'; - - $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); - $this->io = $this->getMock('\Composer\IO\IOInterface'); - $this->composer = new \Composer\Composer(); - $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); - $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); - } - - /** - * tearDown - * - * @return void - */ - public function tearDown() { - unset($this->package); - unset($this->io); - unset($this->composer); - $this->removeDir($this->phpcsInstallDir); - - parent::tearDown(); - } - - /** - * Helper method to recursively delete temporary directories created for tests. - * - * @return void - */ + private $package; + private $composer; + private $io; + + /** + * setUp + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $this->baseDir = getcwd(); + $this->phpcsInstallDir = sys_get_temp_dir() . md5(__FILE__ . time()); + $this->standardsInstallDir = $this->phpcsInstallDir . '/CodeSniffer/Standards'; + mkdir($this->standardsInstallDir, 0777, true); + + $this->sampleDir = $this->baseDir . '/tests/samples'; + + $this->package = new \Composer\Package\Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('\Composer\IO\IOInterface'); + $this->composer = new \Composer\Composer(); + $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); + $this->composer->setInstallationManager(new \Composer\Installer\InstallationManager()); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + $this->removeDir($this->phpcsInstallDir); + + parent::tearDown(); + } + + /** + * Helper method to recursively delete temporary directories created for tests. + * + * @return void + */ protected function removeDir($d) { $i = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($d, \FilesystemIterator::SKIP_DOTS), @@ -80,24 +80,24 @@ protected function removeDir($d) { foreach ($i as $path) { $path->isDir() && !$path->isLink() ? rmdir($path->getPathname()) : unlink($path->getPathname()); } + rmdir($d); } - - /** - * Helper method to set up the proper paths for the - * CodeSniffer/Standards/ and currently-being-installed-package - * directories. - * - * The returned $composer mock will return each path for the - * matching method call to the mock. Remember that PHPUnit counts - * ALL mocked methods in sequence! - * - * @param array $installedPaths Numeric keys are the `at()` calls to `getInstallPath` where the matching string value is returned as the path. - * @return array [mocked \Composer\Composer, mocked \Composer\Installer\PackageEvent] - */ + /** + * Helper method to set up the proper paths for the + * CodeSniffer/Standards/ and currently-being-installed-package + * directories. + * + * The returned $composer mock will return each path for the + * matching method call to the mock. Remember that PHPUnit counts + * ALL mocked methods in sequence! + * + * @param array $installedPaths Numeric keys are the `at()` calls to `getInstallPath` where the matching string value is returned as the path. + * @return array [mocked \Composer\Composer, mocked \Composer\Installer\PackageEvent] + */ protected function mockComposerAndEvent($getInstallPaths) { - $composer = $this->getMock('\Composer\Composer', array('getInstallationManager', 'getInstallPath')); + $composer = $this->getMock('\Composer\Composer', ['getInstallationManager', 'getInstallPath']); $composer->method('getInstallationManager')->will($this->returnSelf()); foreach ($getInstallPaths as $at => $path) { $composer->expects($this->at($at)) @@ -105,144 +105,189 @@ protected function mockComposerAndEvent($getInstallPaths) { ->willReturn($path); } - $event = $this->getMockBuilder('\Composer\Script\Event') + $event = $this->getMockBuilder('\Composer\Script\Event') ->disableOriginalConstructor() - ->setMethods(array('getComposer')) + ->setMethods(['getComposer']) ->getMock(); $event->method('getComposer')->willReturn($composer); - return array($composer, $event); - } - /** - * test postPackageInstall() - * - * @return void - */ - public function testPostInstallMatchingType() { - $this->marktestIncomplete('@TODO: Write a test where the package type is already phpcs-coding-standard'); - } - - /** - * test postPackageInstall() - * - * @return void - */ - public function testPostInstallNoStandards() { - $this->marktestIncomplete('@TODO: Write a test where the package does not have any stanrds to install.'); - } - - /** - * test postPackageInstall() - * - * @return void - */ - public function testPostInstallSuccessful() { - $this->marktestIncomplete('@TODO: Write a test where the package type is not phpcs-coding-standard and has standards to install.'); - } - - /** - * test mirrorCodingStandardFolders() - * - * @return void - */ - public function testMirrorCodingStandardFoldersSuccessful() { - list($composer, $event) = $this->mockComposerAndEvent(array( - 1 => $this->phpcsInstallDir, - )); - - $expected = array( - 'CodingStandardOne', - 'SecondStandard', - ); - - $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); - - foreach ($expected as $standard) { + return [$composer, $event]; + } + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostInstallMatchingType() { + $this->marktestIncomplete('@TODO: Write a test where the package type is already phpcs-coding-standard'); + } + + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostInstallNoStandards() { + $this->marktestIncomplete('@TODO: Write a test where the package does not have any stanrds to install.'); + } + + /** + * test postPackageInstall() + * + * @return void + */ + public function testPostInstallSuccessful() { + $this->marktestIncomplete('@TODO: Write a test where the package type is not phpcs-coding-standard and has standards to install.'); + } + + /** + * test mirrorCodingStandardFolders() + * + * @return void + */ + public function testMirrorCodingStandardFoldersSuccessful() { + list($composer, $event) = $this->mockComposerAndEvent([ + 1 => $this->phpcsInstallDir, + ]); + + $expected = [ + 'CodingStandardOne', + 'SecondStandard', + ]; + + $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); + + foreach ($expected as $standard) { $this->assertTrue( is_readable($this->standardsInstallDir . DS . $standard . DS . 'ruleset.xml'), "Folder `$standard` containing ruleset.xml should be copied to Standards/ folder." ); - } - } - - /** - * test mirrorCodingStandardFolders() - * - * @return void - */ - public function testMirrorCodingStandardFoldersNoDest() { - list($composer, $event) = $this->mockComposerAndEvent(array( - 1 => false, - )); - - $expected = array( - 'CodingStandardOne', - 'SecondStandard', - ); - - $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); - - foreach ($expected as $standard) { + } + } + + /** + * test mirrorCodingStandardFolders() + * + * @return void + */ + public function testMirrorCodingStandardFoldersNoDest() { + list($composer, $event) = $this->mockComposerAndEvent([ + 1 => false, + ]); + + $expected = [ + 'CodingStandardOne', + 'SecondStandard', + ]; + + $result = TestCodingStandardHook::mirrorCodingStandardFolders($composer, $this->sampleDir); + + foreach ($expected as $standard) { $this->assertFalse( is_readable($this->standardsInstallDir . DS . $standard . DS . 'ruleset.xml'), 'No folders should be copied when destination dir is not found.' ); - } - } - - /** - * test findRulesetFolders() - * - * @return void - */ - public function testFindRulesetFolders() { - $expected = array( - 'CodingStandardOne', - 'SecondStandard', - ); - $result = TestCodingStandardHook::findRulesetFolders($this->sampleDir); - - $this->assertEquals( - $expected, - $result, - 'Only folders containing a ruleset.xml should be returned.' - ); - } - - /** - * test findCodesnifferRoot() - * - * @return void - */ - public function testFindCodesnifferRootExists() { - list($composer, $event) = $this->mockComposerAndEvent(array( - 1 => $this->phpcsInstallDir, - )); - - $result = TestCodingStandardHook::findCodesnifferRoot($composer); - - $this->assertEquals( - $this->standardsInstallDir, - $result, - 'Full path to the existing Standards/ folder should be returned.' - ); - } - - /** - * test findCodesnifferRoot() - * - * @return void - */ - public function testFindCodesnifferRootDoesNotExist() { - list($composer, $package) = $this->mockComposerAndEvent(array( - 1 => 'does-not-exist', - )); - - $result = TestCodingStandardHook::findCodesnifferRoot($composer); - - $this->assertFalse( - $result, - 'False shouldbe returned for a non-existent path.' - ); - } + } + } + + /** + * test findRulesetFolders() + * + * @return void + */ + public function testFindRulesetFolders() { + $expected = [ + 'CodingStandardOne', + 'SecondStandard', + ]; + $result = TestCodingStandardHook::findRulesetFolders($this->sampleDir); + + $this->assertEquals( + $expected, + $result, + 'Only folders containing a ruleset.xml should be returned.' + ); + } + + /** + * test findCodesnifferRoot() + * + * @return void + */ + public function testFindCodesnifferRootExists() { + list($composer, $event) = $this->mockComposerAndEvent([ + 1 => $this->phpcsInstallDir, + ]); + + $result = TestCodingStandardHook::findCodesnifferRoot($composer); + + $this->assertEquals( + $this->standardsInstallDir, + $result, + 'Full path to the existing Standards/ folder should be returned.' + ); + } + + /** + * test findCodesnifferRoot() + * + * @return void + */ + public function testFindCodesnifferRootDoesNotExist() { + list($composer, $package) = $this->mockComposerAndEvent([ + 1 => 'does-not-exist', + ]); + + $result = TestCodingStandardHook::findCodesnifferRoot($composer); + + $this->assertFalse( + $result, + 'False shouldbe returned for a non-existent path.' + ); + } + + /** + * test configInstalledPathAdd() + * + * @return void + */ + public function testConfigInstalledPathAdd() { + $this->marktestIncomplete('@TODO: Write tests for configInstalledPathAdd()'); + } + + /** + * test configInstalledPathRemove() + * + * @return void + */ + public function testConfigInstalledPathRemove() { + $this->marktestIncomplete('@TODO: Write tests for configInstalledPathRemove()'); + } + + /** + * test readInstalledPaths() + * + * @return void + */ + public function testReadInstalledPaths() { + $this->marktestIncomplete('@TODO: Write tests for readInstalledPaths()'); + } + + /** + * test saveInstalledPaths() + * + * @return void + */ + public function testSaveInstalledPaths() { + $this->marktestIncomplete('@TODO: Write tests for saveInstalledPaths()'); + } + + /** + * test codeSnifferInit() + * + * @return void + */ + public function testcodeSnifferInit() { + $this->marktestIncomplete('@TODO: Write tests for codeSnifferInit()'); + } } diff --git a/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php index 22e71f5..a336897 100644 --- a/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardInstallerTest.php @@ -21,7 +21,7 @@ * Methods set internal properties for test inspection. */ class StubCodingStandardHook { - public $calls = array(); + public $calls = []; public function __call($name, $arguments) { $this->calls[$name] = $arguments; return $name; @@ -48,131 +48,145 @@ public function removeCode(PackageInterface $package) { * PhpCodesniffer\CodingStandardInstaller Test */ class CodingStandardInstallerTest extends \PHPUnit_Framework_TestCase { - private $package; - private $composer; - private $io; - - /** - * setUp - * - * @return void - */ - public function setUp() { - parent::setUp(); - - $this->baseDir = getcwd(); - $this->io = $this->getMock('\Composer\IO\IOInterface'); - $this->package = $this->getMock('\Composer\Package\Package', - array('getType'), - array('CamelCased', '1.0', '1.0') - ); - $downloadManager = $this->getMock('\Composer\Downloader\DownloadManager', array(), array($this->io)); - $installationManager = $this->getMock('\Composer\Installer\InstallationManager', array(), array()); - $this->hook = new StubCodingStandardHook(); - $this->composer = new \Composer\Composer(); - $this->composer->setConfig(new \Composer\Config(false, $this->baseDir)); - $this->composer->setInstallationManager($installationManager); - $this->composer->setDownloadManager($downloadManager); - $this->Installer = new TestCodingStandardInstaller( - $this->io, - $this->composer, - 'library', - new \Composer\Util\Filesystem(), $this->hook - ); - } - - /** - * tearDown - * - * @return void - */ - public function tearDown() { - unset($this->package); - unset($this->io); - unset($this->composer); - unset($this->hook); - unset($this->Installer); - - parent::tearDown(); - } - - /** - * test supports() - * - * @return void - */ - public function testSupports() { - $this->assertTrue( - $this->Installer->supports('phpcs-coding-standard'), - 'Coding standard installer should activate for `type=phpcs-coding-standard`.' - ); - $this->assertFalse( - $this->Installer->supports('anything-else'), - 'Coding standard installer should not activate for unrecognized package types.' - ); - } - - /** - * test installCode() - * - * @return void - */ - public function testInstallCodeCorrectType() { - $this->package->expects($this->any()) - ->method('getType') - ->willReturn(CodingStandardHook::PHPCS_PACKAGE_TYPE); - - $result = $this->Installer->installCode($this->package); - - $this->assertEquals( - null, - $result, - 'Return value should always be null.' - ); - $this->assertEquals( - array('mirrorCodingStandardFolders' => array($this->composer, null)), - $this->Installer->hook->calls, - 'Our mocked static class should have registered a single call to mirrorCodingStandardFolders().' - ); - } - - /** - * test installCode() - * - * @return void - */ - public function testInstallCodeIncorrectType() { - $this->package->expects($this->any()) - ->method('getType') - ->willReturn('not-the-correct-type'); - - $result = $this->Installer->installCode($this->package); - - $this->assertEquals( - null, - $result, - 'Return value should always be null.' - ); - $this->assertEquals( - array(), - $this->Installer->hook->calls, - 'There should be no call to our stubbed static class.' - ); - } - - /** - * test updateCode() - * - * @return void - */ - public function testUpdateCode() { - } - - /** - * test removeCode() - * - * @return void - */ - public function testRemoveCode() { - } + private $package; + private $composer; + private $io; + + /** + * setUp + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $this->baseDir = getcwd(); + $this->io = $this->getMock('\Composer\IO\IOInterface'); + $this->package = $this->getMock( + '\Composer\Package\Package', + ['getType'], + ['CamelCased', '1.0', '1.0'] + ); + $downloadManager = $this->getMock( + '\Composer\Downloader\DownloadManager', + [], + [$this->io] + ); + $installationManager = $this->getMock( + '\Composer\Installer\InstallationManager', + [], + [] + ); + $this->hook = new StubCodingStandardHook(); + $this->composer = new \Composer\Composer(); + $this->composer->setConfig( + new \Composer\Config(false, $this->baseDir) + ); + $this->composer->setInstallationManager($installationManager); + $this->composer->setDownloadManager($downloadManager); + $this->Installer = new TestCodingStandardInstaller( + $this->io, + $this->composer, + 'library', + new \Composer\Util\Filesystem(), + $this->hook + ); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); + unset($this->hook); + unset($this->Installer); + + parent::tearDown(); + } + + /** + * test supports() + * + * @return void + */ + public function testSupports() { + $this->assertTrue( + $this->Installer->supports('phpcs-coding-standard'), + 'Coding standard installer should activate for `type=phpcs-coding-standard`.' + ); + $this->assertFalse( + $this->Installer->supports('anything-else'), + 'Coding standard installer should not activate for unrecognized package types.' + ); + } + + /** + * test installCode() + * + * @return void + */ + public function testInstallCodeCorrectType() { + $this->package->expects($this->any()) + ->method('getType') + ->willReturn(CodingStandardHook::PHPCS_PACKAGE_TYPE); + + $result = $this->Installer->installCode($this->package); + + $this->assertEquals( + null, + $result, + 'Return value should always be null.' + ); + $this->assertEquals( + ['mirrorCodingStandardFolders' => [$this->composer, null]], + $this->Installer->hook->calls, + 'Our mocked static class should have registered a single call to mirrorCodingStandardFolders().' + ); + } + + /** + * test installCode() + * + * @return void + */ + public function testInstallCodeIncorrectType() { + $this->package->expects($this->any()) + ->method('getType') + ->willReturn('not-the-correct-type'); + + $result = $this->Installer->installCode($this->package); + + $this->assertEquals( + null, + $result, + 'Return value should always be null.' + ); + $this->assertEquals( + [], + $this->Installer->hook->calls, + 'There should be no call to our stubbed static class.' + ); + } + + /** + * test updateCode() + * + * @return void + */ + public function testUpdateCode() { + $this->markTestIncomplete('@TODO: Write updateCode() tests.'); + } + + /** + * test removeCode() + * + * @return void + */ + public function testRemoveCode() { + $this->markTestIncomplete('@TODO: Write removeCode() tests.'); + } } diff --git a/tests/TestCase/Puphpet/ReleaseInstallerTest.php b/tests/TestCase/Puphpet/ReleaseInstallerTest.php index f76af3c..c86f26e 100644 --- a/tests/TestCase/Puphpet/ReleaseInstallerTest.php +++ b/tests/TestCase/Puphpet/ReleaseInstallerTest.php @@ -1,55 +1,55 @@ package = new Package('CamelCased', '1.0', '1.0'); - $this->io = $this->getMock('Composer\IO\PackageInterface'); - $this->composer = new Composer(); - $this->composer->setConfig(new Config(false)); - } + $this->package = new Package('CamelCased', '1.0', '1.0'); + $this->io = $this->getMock('Composer\IO\PackageInterface'); + $this->composer = new Composer(); + $this->composer->setConfig(new Config(false)); + } - /** - * tearDown - * - * @return void - */ - public function tearDown() { - unset($this->package); - unset($this->io); - unset($this->composer); + /** + * tearDown + * + * @return void + */ + public function tearDown() { + unset($this->package); + unset($this->io); + unset($this->composer); - parent::tearDown(); - } + parent::tearDown(); + } - /** - * testNothing - * - * @return void - */ - public function testNothing() { - $this->markTestIncomplete('@TODO: No tests written for Puphpet\ReleaseInstaller.'); - } + /** + * testNothing + * + * @return void + */ + public function testNothing() { + $this->markTestIncomplete('@TODO: No tests written for Puphpet\ReleaseInstaller.'); + } } From baa4819f1b3688d64a232e2df80d9baa8e63bc74 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 12 Aug 2015 13:36:08 -0500 Subject: [PATCH 12/15] Import remaining bits from puphpet-installer project. Once merged, this should make the `loadsys/puphpet-release-composer-installer` package obsolete. Note: The puphpet-release project also needs to have its composer.json updated to switch from `loadsys/puphpet-release-composer-installer` to `loadsys/composer-plugins`. --- .travis.yml | 27 ++++ README.md | 131 +++++++++++++++++- phpunit.xml.dist | 5 +- tests/integration/composer.json | 2 +- .../integration/simulate-composer-install.sh | 2 +- 5 files changed, 156 insertions(+), 11 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..51c9486 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: php +sudo: false +php: + - '5.6' +# - '5.5' +# - '5.4' +#matrix: +# fast_finish: true +env: + global: + # Contains a $GITHUB_TOKEN env var for use with composer to avoid API limits. + - secure: "hsmni1Mi9zCjYaGaCpRnXPYzdBywqPyhKQdrcS4cxJpCIPROd1Txtj8KKa2hdHp6f/xObd3zAqQeJgKDIzkj3ht0D1Za7BCuh+fY21F76k1u/SXIATgq7kk+vFg83EfcuojW4WI94FRhCfJ2bUOzZOpTzpsteo8vadyCJHHztXjcEnvmd9WrTI7OyeMTO/C51dq1yUJyJ1X/XdgwC2VrsUJLAIQzlvEenW7jQzmLp6F2T7b9sh7QrheSmqjX6A8SiN2PBe9YHQgpg0s9Rck3phiG+Th7L+Kpudc+M83a8izI4djyITevZc8l84dHYyonkh68jTNgCjOEz7gRmhuUUAXEaGLeZOJSiwbQKJ8juqPWA9eqTu4x8AR3b56ONb5TVATKNqpNSkzsYu43vtxqsaVsH8GLA3ic3KewRRM9awiLZyuZ1Npk5riD1UhgXv0CdR5geAQmgUh50PGwVcYNFm5/wz9hKaTBevbv1a1fXPzjQqB6aiV/qa7KQppSuYI3h1APOhxpFofQbKy+2plFTPjvWwpowJ3HjULuhSx5Nd7Gg4I8plCfo8yhI8VODOW4KYyh2617hBq2nMvEyS9/DgZ5Bix/mOuOoJMJ/fKnmXS49QLUMadcLSBMlKTUBgEuDtXuK3Q6RgYAY8KUMw6i5Dpu2mPAocoGR1T6ZaDr7Qs=" +branches: + except: + - gh-pages +install: + - composer self-update --no-interaction + - composer config -g github-oauth.github.com $GITHUB_TOKEN + - composer install --no-interaction + - git clone https://github.com/loadsys/puphpet-release.git build/release-project +script: + # Run unit tests. + - vendor/bin/phpunit + # Run integration tests. + - tests/integration/simulate-composer-install.sh -t master +notifications: + email: false diff --git a/README.md b/README.md index 0406b7c..d58e1ff 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ -# composer-plugins +# Loadsys Composer Plugins + Including this package in your composer.json makes a number of installers and project types available. -## PHP CodeSniffer, Coding Standard installer type +## PHP CodeSniffer, `phpcs-coding-standard` type -When you develop your own Coding Standard, you can package it for installation via Composer, but `phpcs` won't know about your standard unless you either manually specify it on the command line: +When you develop your own Coding Standard for the [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), you can package it for installation via Composer, but `phpcs` won't know about your standard unless you either manually specify it on the command line every time: ```shell $ vendor/bin/phpcs --standard=vendor/yourname/your-codesniffer/YourStandard . ``` -Or you must use the `--config-set` switch to write your path into `phpcs`'s config file: +Or you must use the `--config-set` switch to write your Standard's path into the `phpcs` config file: ```shell $ vendor/bin/phpcs --config-set installed_paths vendor/yourname/your-codesniffer @@ -33,7 +34,9 @@ When you create your coding standard package, use this `type` in your composer.j } ``` -It also usually makes sense for your coding standard to include the required version of php_codesniffer itself, so that your projects that use this standard don't have to require it themselves, or accidentally require the wrong version. +You must also make sure your coding standard lives in a subfolder of the package, usually with a proper name. For example: `GIT_ROOT/YourStandard/ruleset.xml`. This allows for multiple standards to be installed from a single package. + +It also usually makes sense for your coding standard to include the required version of `squizlabs/php_codesniffer` itself, so that your projects that use this standard don't have to require it themselves, or accidentally require the wrong version. With this setup, when your standard is included in another project, the installer in this package will search its `vendor/your-name/your-codesniffer/` folder for folders that contain `ruleset.xml` files, indicating that those sub-folders contain Coding Standards. This installer will then copy those folders into the `vendor/squizlabs/CodeSniffer/Standards/` folder for you, making your Coding Standard immediately available to `phpcs --standard YourStandard .` without any additional configuration. @@ -43,7 +46,7 @@ With this setup, when your standard is included in another project, the installe Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. -The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then copies those folders into the `CodeSniffer/Standards/` directory, making them available to `phpcs`. +The script works by scanning each installed package for any immediate sub-folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then copies those folders into the `CodeSniffer/Standards/` directory, making them available to `phpcs`. To use this hook script, add the following to your root project's `composer.json`: @@ -113,3 +116,119 @@ To use this hook script, add the following to your root project's `composer.json The `postInstall` command checks every installed package for Coding Standard folders, and adds the path for any packaging containing Standards into `phpcs`'s `installed_paths` config setting. This should also be run `post-update` in case the package's installed_path has changed. The `postPackageUninstall` removes the path for a package being removed from the phpcs config file. + + + + + + + + + + +# puphpet-release-composer-installer + +Imported docs from the `loadsys/puphpet-release-composer-installer` project. + +@TODO: Integrate these docs into the master README above. + + +[![Build Status](https://travis-ci.org/loadsys/puphpet-release-composer-installer.svg?branch=master)](https://travis-ci.org/loadsys/puphpet-release-composer-installer) + +Provides a composer custom installer that works with `loadsys/puphpet-release` to add a PuPHPet.com vagrant box to a project via composer. + +You probably will never need to use this project yourself directly. We use it for our [loadsys/puphpet-release](https://github.com/loadsys/puphpet-release) package to copy parts of the PuPHPet package into the necessary locations for the consuming project. + + +## :warning: Big Important Warning + +It's critically important to point out that this installer does things that composer [very explicitly](https://github.com/composer/installers#should-we-allow-dynamic-package-types-or-paths-no) **should not be doing.** We break this very good and wise rule only because the tools we're working with (vagrant and puphpet) leave us with no other practical choice. Again: You should **NOT** do what this installer does. In all likelihood there is a better way. + +If you use this installer, it will overwrite existing (important!) files in your project. If you have customized your Vagrantfile, then `composer require` a project that uses this installer, _your `Vagrantfile` file and `puphpet/` folder will be unceremoniously overwritten without notice._ Do not complain about this. This is what this installer is designed to do and you've been duly warned of its danger. + + +## Usage + +To use this installer with another composer package, add the following block to your package's `composer.json` file: + +```json + "type": "puphpet-release", + "require": { + "loadsys/puphpet-release-composer-installer": "*" + }, +``` + + +### Composer Post Install Actions + +This installer is responsible for performing post-`composer install` actions for the `loadsys/puphpet-release` package. + +When this package is included in another project via composer, the installer fires a number of additional actions in order to address some of the incompatibilities between puphpet's default setup and the requirements for Vagrant (such as the `Vagrantfile` living in the project's root directory instead of the composer-installed `/vendors/loadsys/puphpet-release/release/` folder.) + +* Copies a Vagrantfile into the consuming project's root folder. +* Copies a puphpet/ folder into the consuming project's root folder. +* Copies the consuming project's `/puphpet.yaml` into the correct place as `/puphpet/config.yaml`. +* Tries to ensure that the consuming project's `/.gitignore` file contains the proper entries to ignore `/Vagrantfile` and `/puphpet/`, if it is present. + +Unresolved Questions: + +* Do we always overwrite the Vagrantfile and puphpet/ folders? +* What if there are customizations to files/ or exec-*/ folders? Should we even try to detect those? (diff the contents of the package's release/ folder with the versions in project root?) +* Should we try to validate that the target project's config.yaml file has all expected (mandatory) keys as the spec changes upstream. Can we write/maintain a "unit test" and/or diffing tool for it? It's just YAML after all. +* What should we do if there isn't a `/puphpet.yaml` for us to copy? The VM will surely not work correctly with completely "default" options. Maybe prompt the user to go generate one? + + +## Contributing + + +### Running Unit Tests + +* `composer install` +* `vendor/bin/phpunit` + + +### Manually Testing Installer Output + +Testing this composer plugin is difficult because it involves at least 2 other projects: the loadsys/puphpet-release, and the project from which you want to consume it. This project contains a `tests/integration/` directory that is set up to exercise this installer and test the result of including the `loadsys/puphpet-release` package in a consumer project. To use it: + +1. Check out this project: `git clone git@github.com:loadsys/puphpet-release-composer-installer.git` + +1. Check out a copy of the puphpet-release project somewhere to work on it. `git clone git@github.com:loadsys/puphpet-release.git` (Make a note of this path.) + +1. Create a feature branch in either project, and **commit** your changes to the branch. (Committing the changes is very important to the process: Any changes you wish to test must exist in the git index already, not just in your working copy.) + +1. Run `./tests/integration/simulate-composer-install.sh` + + The script will prompt you for any necessary information, reset the build/ dir for use, write the appropriate "composer.json" changes for you, and execute a `composer install` command for you in the build/ dir where you can review the results. + + * The `build/` folder should end up with a `Vagrantfile` and `puphpet/` folder in it. + * The sample `build/puphpet.yaml` file should have been copied to `build/puphpet/config.yaml`. + * The sample `.gitignore` file should have been "safely" updated to include the new additions to the "root" project folder (`build/`). + +1. From here, the process loops through the following steps: + * Make changes to the puphpet-release or puphpet-release-composer-installer projects. + * **Commit** the changes to your working branch. + * Run `./tests/integration/simulate-composer-install.sh` again. + * Check the results in the `build/` directory. + * Repeat. + +1. Once you're satisfied with the results, push your branch and submit a PR. + + +### Running Integration Tests + +The simulation script also includes a number of functional tests for verifying the results of the installer's operation. Use the `-t` flag to enable them. + +* `./tests/integration/simulate-composer-install.sh -t [puphpet-release-branchname]` # Release project branch name defaults to `master`. + +The script will report any errors and exit non-zero on failure. + + +## License + +[MIT](https://github.com/loadsys/puphpet-release/blob/master/LICENSE). In particular, all [PuPHPet](http://puphpet.com) work belongs to the original authors. This project is strictly for our own convenience. + + +## Copyright + +© [Loadsys Web Strategies](http://loadsys.com) 2015 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1b50fb6..55083e7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,8 @@ - @@ -23,4 +22,4 @@ src - \ No newline at end of file + diff --git a/tests/integration/composer.json b/tests/integration/composer.json index 5d9eb5d..490e080 100755 --- a/tests/integration/composer.json +++ b/tests/integration/composer.json @@ -2,7 +2,7 @@ "name": "me/test-composer-installer", "description": "Template file for testing the puphpet-release-composer-installer. Written to ../tmp/ with proper branch names.", "require": { - "loadsys/puphpet-release-composer-installer": "dev-PRCI_BRANCH_NAME", + "loadsys/composer-plugins": "dev-PRCI_BRANCH_NAME", "loadsys/puphpet-release": "dev-PR_BRANCH_NAME" }, "repositories": [ diff --git a/tests/integration/simulate-composer-install.sh b/tests/integration/simulate-composer-install.sh index 649b4c8..0dcce9b 100755 --- a/tests/integration/simulate-composer-install.sh +++ b/tests/integration/simulate-composer-install.sh @@ -155,7 +155,7 @@ COMPOSER_EXIT_CODE=$? # End the script if test mode is OFF. if [ -z "${TEST_MODE}" ]; then - if [ "${COMPOSER_EXIT_CODE}" ]; then + if [ ! "${COMPOSER_EXIT_CODE}" ]; then echo "!! Composer installation failed. Examine the results in \`${BUILD_DIR}\`." echo '' echo "${COMPOSER_OUTPUT}" From e123e0afac4bf6d95988eb2fe3dd6cf4a7a8f543 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Thu, 13 Aug 2015 09:54:48 -0500 Subject: [PATCH 13/15] Anticipate phpcs v3 paths. --- src/PhpCodesniffer/CodingStandardHook.php | 30 ++++++++++++------- .../PhpCodesniffer/CodingStandardHookTest.php | 16 +++++----- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/PhpCodesniffer/CodingStandardHook.php b/src/PhpCodesniffer/CodingStandardHook.php index f14c7f6..01531b0 100644 --- a/src/PhpCodesniffer/CodingStandardHook.php +++ b/src/PhpCodesniffer/CodingStandardHook.php @@ -122,7 +122,7 @@ public static function prePackageUninstall(PackageEvent $event) { */ public static function mirrorCodingStandardFolders(Composer $composer, $packageBasePath) { $rulesets = self::findRulesetFolders($packageBasePath); - $destDir = self::findCodesnifferRoot($composer); + $destDir = self::findStandardsFolder($composer); // No-op if no ruleset.xml's found or squizlabs/php_codesniffer not installed. if (empty($rulesets) || !$destDir) { @@ -174,7 +174,7 @@ public static function mirrorCodingStandardFolders(Composer $composer, $packageB */ public static function deleteCodingStandardFolders(Composer $composer, $packageBasePath) { $rulesets = self::findRulesetFolders($packageBasePath); - $destDir = self::findCodesnifferRoot($composer); + $destDir = self::findStandardsFolder($composer); // No-op if no ruleset.xml's found. if (empty($rulesets) || !$destDir) { @@ -205,23 +205,31 @@ protected static function findRulesetFolders($basePath) { } /** - * Attempt to locate the squizlabs/php_codesniffer/standards folder. + * Attempt to locate the squizlabs/php_codesniffer/[...]/Standards folder. * - * Return the full system path if found, no trailing slash. + * Return the full system path if found, no trailing slash. Attempts to + * be compatible with both PHP_CodeSniffer v2.x and v3.x. * * @param Composer\Composer $composer Used to get access to the InstallationManager. - * @return string|false Full system path to the PHP CodeSniffer's "standards/" folder, false if not found. + * @return string|false Full system path to the PHP CodeSniffer's "Standards/" folder, false if not found. */ - protected static function findCodesnifferRoot(Composer $composer) { + protected static function findStandardsFolder(Composer $composer) { $phpcsPackage = new Package('squizlabs/php_codesniffer', '2.0', ''); - $path = $composer->getInstallationManager()->getInstallPath($phpcsPackage); + $base = $composer->getInstallationManager()->getInstallPath($phpcsPackage); - $path .= DS . 'CodeSniffer' . DS . 'Standards'; - if (!is_readable($path)) { - return false; + $subPaths = [ + 'src' . DS . 'Standards', // PHPCS v3 + 'CodeSniffer' . DS . 'Standards', // v2 + ]; + + foreach ($subPaths as $subPath) { + $path = $base . DS . $subPath; + if (is_dir($path) && is_readable($path)) { + return $path; + } } - return $path; + return false; } diff --git a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php index 7155fa1..706f2bb 100644 --- a/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php +++ b/tests/TestCase/PhpCodesniffer/CodingStandardHookTest.php @@ -18,8 +18,8 @@ class TestCodingStandardHook extends CodingStandardHook { public static function findRulesetFolders($basePath) { return parent::findRulesetFolders($basePath); } - public static function findCodesnifferRoot(Composer $composer) { - return parent::findCodesnifferRoot($composer); + public static function findStandardsFolder(Composer $composer) { + return parent::findStandardsFolder($composer); } } @@ -210,16 +210,16 @@ public function testFindRulesetFolders() { } /** - * test findCodesnifferRoot() + * test findStandardsFolder() * * @return void */ - public function testFindCodesnifferRootExists() { + public function testFindStandardsFolderExists() { list($composer, $event) = $this->mockComposerAndEvent([ 1 => $this->phpcsInstallDir, ]); - $result = TestCodingStandardHook::findCodesnifferRoot($composer); + $result = TestCodingStandardHook::findStandardsFolder($composer); $this->assertEquals( $this->standardsInstallDir, @@ -229,16 +229,16 @@ public function testFindCodesnifferRootExists() { } /** - * test findCodesnifferRoot() + * test findStandardsFolder() * * @return void */ - public function testFindCodesnifferRootDoesNotExist() { + public function testFindStandardsFolderDoesNotExist() { list($composer, $package) = $this->mockComposerAndEvent([ 1 => 'does-not-exist', ]); - $result = TestCodingStandardHook::findCodesnifferRoot($composer); + $result = TestCodingStandardHook::findStandardsFolder($composer); $this->assertFalse( $result, From ebf51628cb16ce3d9a1fa4cc89665bbe85ec0371 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 19 Oct 2015 16:02:57 -0500 Subject: [PATCH 14/15] Strip out the now-abandoned Puphpet Release Installer. --- .travis.yml | 4 - README.md | 119 +--------- composer.json | 5 +- src/LoadsysComposerPlugin.php | 3 - src/Puphpet/ReleaseInstaller.php | 131 ----------- tests/TestCase/LoadsysComposerPluginTest.php | 3 - .../TestCase/Puphpet/ReleaseInstallerTest.php | 55 ----- tests/integration/composer.json | 25 -- tests/integration/puphpet.yaml | 36 --- .../integration/simulate-composer-install.sh | 222 ------------------ 10 files changed, 13 insertions(+), 590 deletions(-) delete mode 100644 src/Puphpet/ReleaseInstaller.php delete mode 100644 tests/TestCase/Puphpet/ReleaseInstallerTest.php delete mode 100755 tests/integration/composer.json delete mode 100755 tests/integration/puphpet.yaml delete mode 100755 tests/integration/simulate-composer-install.sh diff --git a/.travis.yml b/.travis.yml index 51c9486..60ed138 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,7 @@ install: - composer self-update --no-interaction - composer config -g github-oauth.github.com $GITHUB_TOKEN - composer install --no-interaction - - git clone https://github.com/loadsys/puphpet-release.git build/release-project script: - # Run unit tests. - vendor/bin/phpunit - # Run integration tests. - - tests/integration/simulate-composer-install.sh -t master notifications: email: false diff --git a/README.md b/README.md index d58e1ff..82a5df3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # Loadsys Composer Plugins +[![Build Status](https://travis-ci.org/loadsys/composer-plugins.svg?branch=master)](https://travis-ci.org/loadsys/composer-plugins) + Including this package in your composer.json makes a number of installers and project types available. -## PHP CodeSniffer, `phpcs-coding-standard` type + + +## PHP CodeSniffer, `phpcs-coding-standard` type (config editing approach) When you develop your own Coding Standard for the [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), you can package it for installation via Composer, but `phpcs` won't know about your standard unless you either manually specify it on the command line every time: @@ -42,6 +46,7 @@ With this setup, when your standard is included in another project, the installe + ## PHP CodeSniffer, post-install hook (copying folders approach) Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. @@ -77,19 +82,11 @@ The `prePackageUninstall` removes any Coding Standard folders from a package tha - - - - - - -# Config File Approach - -## PHP CodeSniffer, post-install hook +## PHP CodeSniffer, post-install hook (config editing approach) Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. -The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then adds the vendor paths for these folders into the CodeSniffer.conf file, making them available to `phpcs` in their natural install location. +The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then adds the vendor paths for these folders into the `CodeSniffer.conf` file, making them available to `phpcs` in their natural install location. To use this hook script, add the following to your root project's `composer.json`: @@ -113,115 +110,21 @@ To use this hook script, add the following to your root project's `composer.json } ``` -The `postInstall` command checks every installed package for Coding Standard folders, and adds the path for any packaging containing Standards into `phpcs`'s `installed_paths` config setting. This should also be run `post-update` in case the package's installed_path has changed. - -The `postPackageUninstall` removes the path for a package being removed from the phpcs config file. - - - - - - - - - - -# puphpet-release-composer-installer - -Imported docs from the `loadsys/puphpet-release-composer-installer` project. - -@TODO: Integrate these docs into the master README above. - - -[![Build Status](https://travis-ci.org/loadsys/puphpet-release-composer-installer.svg?branch=master)](https://travis-ci.org/loadsys/puphpet-release-composer-installer) - -Provides a composer custom installer that works with `loadsys/puphpet-release` to add a PuPHPet.com vagrant box to a project via composer. - -You probably will never need to use this project yourself directly. We use it for our [loadsys/puphpet-release](https://github.com/loadsys/puphpet-release) package to copy parts of the PuPHPet package into the necessary locations for the consuming project. - - -## :warning: Big Important Warning - -It's critically important to point out that this installer does things that composer [very explicitly](https://github.com/composer/installers#should-we-allow-dynamic-package-types-or-paths-no) **should not be doing.** We break this very good and wise rule only because the tools we're working with (vagrant and puphpet) leave us with no other practical choice. Again: You should **NOT** do what this installer does. In all likelihood there is a better way. +The `postInstall` command checks every installed package for Coding Standard folders, and adds the path for any packages containing Standards into `phpcs`'s `installed_paths` config setting. This should also be run `post-update` in case the package's installed_path has changed. -If you use this installer, it will overwrite existing (important!) files in your project. If you have customized your Vagrantfile, then `composer require` a project that uses this installer, _your `Vagrantfile` file and `puphpet/` folder will be unceremoniously overwritten without notice._ Do not complain about this. This is what this installer is designed to do and you've been duly warned of its danger. +The `postPackageUninstall` removes the path for a package that is being removed from the phpcs config file. -## Usage - -To use this installer with another composer package, add the following block to your package's `composer.json` file: - -```json - "type": "puphpet-release", - "require": { - "loadsys/puphpet-release-composer-installer": "*" - }, -``` - - -### Composer Post Install Actions - -This installer is responsible for performing post-`composer install` actions for the `loadsys/puphpet-release` package. - -When this package is included in another project via composer, the installer fires a number of additional actions in order to address some of the incompatibilities between puphpet's default setup and the requirements for Vagrant (such as the `Vagrantfile` living in the project's root directory instead of the composer-installed `/vendors/loadsys/puphpet-release/release/` folder.) - -* Copies a Vagrantfile into the consuming project's root folder. -* Copies a puphpet/ folder into the consuming project's root folder. -* Copies the consuming project's `/puphpet.yaml` into the correct place as `/puphpet/config.yaml`. -* Tries to ensure that the consuming project's `/.gitignore` file contains the proper entries to ignore `/Vagrantfile` and `/puphpet/`, if it is present. - -Unresolved Questions: - -* Do we always overwrite the Vagrantfile and puphpet/ folders? -* What if there are customizations to files/ or exec-*/ folders? Should we even try to detect those? (diff the contents of the package's release/ folder with the versions in project root?) -* Should we try to validate that the target project's config.yaml file has all expected (mandatory) keys as the spec changes upstream. Can we write/maintain a "unit test" and/or diffing tool for it? It's just YAML after all. -* What should we do if there isn't a `/puphpet.yaml` for us to copy? The VM will surely not work correctly with completely "default" options. Maybe prompt the user to go generate one? ## Contributing - ### Running Unit Tests * `composer install` * `vendor/bin/phpunit` -### Manually Testing Installer Output - -Testing this composer plugin is difficult because it involves at least 2 other projects: the loadsys/puphpet-release, and the project from which you want to consume it. This project contains a `tests/integration/` directory that is set up to exercise this installer and test the result of including the `loadsys/puphpet-release` package in a consumer project. To use it: - -1. Check out this project: `git clone git@github.com:loadsys/puphpet-release-composer-installer.git` - -1. Check out a copy of the puphpet-release project somewhere to work on it. `git clone git@github.com:loadsys/puphpet-release.git` (Make a note of this path.) - -1. Create a feature branch in either project, and **commit** your changes to the branch. (Committing the changes is very important to the process: Any changes you wish to test must exist in the git index already, not just in your working copy.) - -1. Run `./tests/integration/simulate-composer-install.sh` - - The script will prompt you for any necessary information, reset the build/ dir for use, write the appropriate "composer.json" changes for you, and execute a `composer install` command for you in the build/ dir where you can review the results. - - * The `build/` folder should end up with a `Vagrantfile` and `puphpet/` folder in it. - * The sample `build/puphpet.yaml` file should have been copied to `build/puphpet/config.yaml`. - * The sample `.gitignore` file should have been "safely" updated to include the new additions to the "root" project folder (`build/`). - -1. From here, the process loops through the following steps: - * Make changes to the puphpet-release or puphpet-release-composer-installer projects. - * **Commit** the changes to your working branch. - * Run `./tests/integration/simulate-composer-install.sh` again. - * Check the results in the `build/` directory. - * Repeat. - -1. Once you're satisfied with the results, push your branch and submit a PR. - - -### Running Integration Tests - -The simulation script also includes a number of functional tests for verifying the results of the installer's operation. Use the `-t` flag to enable them. - -* `./tests/integration/simulate-composer-install.sh -t [puphpet-release-branchname]` # Release project branch name defaults to `master`. - -The script will report any errors and exit non-zero on failure. ## License @@ -229,6 +132,8 @@ The script will report any errors and exit non-zero on failure. [MIT](https://github.com/loadsys/puphpet-release/blob/master/LICENSE). In particular, all [PuPHPet](http://puphpet.com) work belongs to the original authors. This project is strictly for our own convenience. + + ## Copyright © [Loadsys Web Strategies](http://loadsys.com) 2015 diff --git a/composer.json b/composer.json index 4bc6daf..6f18b7e 100644 --- a/composer.json +++ b/composer.json @@ -7,10 +7,7 @@ "loadsys", "codesniffer", "puphpet", - "vagrant", - "dangerous", - "do-not-use", - "overwrites-files" + "vagrant" ], "type": "composer-plugin", "license": "MIT", diff --git a/src/LoadsysComposerPlugin.php b/src/LoadsysComposerPlugin.php index 7c8b836..82d4361 100644 --- a/src/LoadsysComposerPlugin.php +++ b/src/LoadsysComposerPlugin.php @@ -23,9 +23,6 @@ class LoadsysComposerPlugin implements PluginInterface { * @return void */ public function activate(Composer $composer, IOInterface $io) { - $puphpetReleaseInstaller = new Puphpet\ReleaseInstaller($io, $composer); - $composer->getInstallationManager()->addInstaller($puphpetReleaseInstaller); - $phpcsCodingStandardInstaller = new PhpCodesniffer\CodingStandardInstaller($io, $composer); $composer->getInstallationManager()->addInstaller($phpcsCodingStandardInstaller); } diff --git a/src/Puphpet/ReleaseInstaller.php b/src/Puphpet/ReleaseInstaller.php deleted file mode 100644 index 0477f33..0000000 --- a/src/Puphpet/ReleaseInstaller.php +++ /dev/null @@ -1,131 +0,0 @@ -supports($package->getType())) { - return; - } - - $this->mirrorReleaseItems($package); - $this->copyConfigFile($package); - $this->checkGitignore($package); - } - - /** - * Mirror (copy or delete, only as necessary) items from the installed - * package's release/ folder into the target directory. - * - * @param PackageInterface $package package instance - * @return void - */ - protected function mirrorReleaseItems($package) { - // Copy everything from the release/ subfolder to the project root. - $releaseDir = $this->getInstallPath($package) . DS . 'release'; - $targetDir = getcwd(); - $acceptList = [ - 'Vagrantfile', - 'puphpet', - ]; - - // Return true if the first part of the subpath for the current file exists in the accept array. - $acceptFunc = function ($current, $key, $iterator) use ($acceptList) { - $pathComponents = explode(DS, $iterator->getSubPathname()); - return in_array($pathComponents[0], $acceptList); - }; - $dirIterator = new RecursiveDirectoryIterator($releaseDir, RecursiveDirectoryIterator::SKIP_DOTS); - $filterIterator = new RecursiveCallbackFilterIterator($dirIterator, $acceptFunc); - $releaseItems = new RecursiveIteratorIterator($filterIterator, RecursiveIteratorIterator::SELF_FIRST); - - $filesystem = new Filesystem(); - $filesystem->mirror($releaseDir, $targetDir, $releaseItems, ['override' => true]); - } - - /** - * Search for a config file in the consuming project and copy it into - * place if present. - * - * @return void - */ - protected function copyConfigFile() { - $configFilePath = getcwd() . DS . 'puphpet.yaml'; - $targetPath = getcwd() . DS . 'puphpet' . DS . 'config.yaml'; - if (is_readable($configFilePath)) { - copy($configFilePath, $targetPath); - } - } - - /** - * Check that release items copied into the consuming project are - * properly ignored in source control (very, VERY crudely.) - * - * @return void - */ - protected function checkGitIgnore() { - $gitFolder = getcwd() . DS . '.git' . DS; - - if (!file_exists($gitFolder)) { - return; - } - - $gitignoreFile = getcwd() . DS . '.gitignore'; - $required = [ - '/Vagrantfile', - '/puphpet/', - '/.vagrant/', - ]; - - touch($gitignoreFile); - $lines = file($gitignoreFile, FILE_IGNORE_NEW_LINES); - - foreach ($required as $entry) { - if (!in_array($entry, $lines)) { - $lines[] = $entry; - } - } - - file_put_contents($gitignoreFile, implode(PHP_EOL, $lines)); - } -} diff --git a/tests/TestCase/LoadsysComposerPluginTest.php b/tests/TestCase/LoadsysComposerPluginTest.php index 6559517..f5bc0e3 100644 --- a/tests/TestCase/LoadsysComposerPluginTest.php +++ b/tests/TestCase/LoadsysComposerPluginTest.php @@ -64,9 +64,6 @@ public function testActivate() { ->will($this->returnSelf()); $this->composer->expects($this->at(1)) - ->method('addInstaller') - ->with($this->isInstanceOf('Loadsys\Composer\Puphpet\ReleaseInstaller')); - $this->composer->expects($this->at(3)) ->method('addInstaller') ->with($this->isInstanceOf('Loadsys\Composer\PhpCodesniffer\CodingStandardInstaller')); diff --git a/tests/TestCase/Puphpet/ReleaseInstallerTest.php b/tests/TestCase/Puphpet/ReleaseInstallerTest.php deleted file mode 100644 index c86f26e..0000000 --- a/tests/TestCase/Puphpet/ReleaseInstallerTest.php +++ /dev/null @@ -1,55 +0,0 @@ -package = new Package('CamelCased', '1.0', '1.0'); - $this->io = $this->getMock('Composer\IO\PackageInterface'); - $this->composer = new Composer(); - $this->composer->setConfig(new Config(false)); - } - - /** - * tearDown - * - * @return void - */ - public function tearDown() { - unset($this->package); - unset($this->io); - unset($this->composer); - - parent::tearDown(); - } - - /** - * testNothing - * - * @return void - */ - public function testNothing() { - $this->markTestIncomplete('@TODO: No tests written for Puphpet\ReleaseInstaller.'); - } -} diff --git a/tests/integration/composer.json b/tests/integration/composer.json deleted file mode 100755 index 490e080..0000000 --- a/tests/integration/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "me/test-composer-installer", - "description": "Template file for testing the puphpet-release-composer-installer. Written to ../tmp/ with proper branch names.", - "require": { - "loadsys/composer-plugins": "dev-PRCI_BRANCH_NAME", - "loadsys/puphpet-release": "dev-PR_BRANCH_NAME" - }, - "repositories": [ - { - "packagist": false - }, - { - "type": "vcs", - "url": "https://github.com/symfony/Filesystem.git" - }, - { - "type": "vcs", - "url": "PR_DIRECTORY" - }, - { - "type": "vcs", - "url": "../" - } - ] -} diff --git a/tests/integration/puphpet.yaml b/tests/integration/puphpet.yaml deleted file mode 100755 index 8a0d652..0000000 --- a/tests/integration/puphpet.yaml +++ /dev/null @@ -1,36 +0,0 @@ ---- -readme: - - 'Minimal config file to test the puphpet-release-composer-installer.' - - 'Not having this file screws up `vagrant global-status`.' - - 'Just have to define enough keys for the Vagrantfile to load successfully.' - - 'Do not change the next line. It is used during the test suite to confirm' - - 'that this file was copied into place correctly.' -canary: "foo" -vagrantfile-local: - vm: - box: puphpet/debian75-x64 - box_url: puphpet/debian75-x64 - hostname: 'dummy-entry' - memory: '128' - cpus: '1' - chosen_provider: virtualbox - network: - private_network: '0.0.0.0' - forwarded_port: { } - post_up_message: '' - provider: - virtualbox: - modifyvm: { } - vmware: { } - provision: - puppet: - manifests_path: puphpet/puppet - manifest_file: site.pp - module_path: puphpet/puppet/modules - options: { } - synced_folder: { } - usable_port_range: - start: 10200 - stop: 10500 - ssh: { } - vagrant: { } diff --git a/tests/integration/simulate-composer-install.sh b/tests/integration/simulate-composer-install.sh deleted file mode 100755 index 0dcce9b..0000000 --- a/tests/integration/simulate-composer-install.sh +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env bash - -#--------------------------------------------------------------------- -usage () { - cat </dev/null 2>&1 && pwd )" -TEST_DIR="${BASE_DIR}/tests/integration" -BUILD_DIR="${BASE_DIR}/build" - - -# Set testing mode. -TEST_MODE= -if [ "$1" = '-t' ]; then - echo "## Setting test mode ON." - TEST_MODE="yes" - shift -fi - - -# Make sure the build dir exists. -if [ ! -d "${BUILD_DIR}" ]; then - echo "## Creating build directory." - mkdir -p "${BUILD_DIR}" -fi - - -# Make sure the build dir contains a symlink to your working copy of `loadsys/puphpet-release`. -echo "## Checking the symlink to the release project." -RELEASE_PROJECT_SYMLINK="${BUILD_DIR}/release-project" - -if [ -h "${RELEASE_PROJECT_SYMLINK}" ]; then - RELEASE_PROJECT_PATH=$(readlink "${RELEASE_PROJECT_SYMLINK}") -elif [ -d "${RELEASE_PROJECT_SYMLINK}" ]; then - RELEASE_PROJECT_PATH="${RELEASE_PROJECT_SYMLINK}" -elif [ "${TEST_MODE}" ]; then - echo "!! No symlink to the release project working copy" - echo "!! is present at \`${RELEASE_PROJECT_SYMLINK}\`." - echo "!! Please create it." - exit 1 -else - read -p " Please provide the path to the release project working copy > " RELEASE_PROJECT_PATH - ln -s "${RELEASE_PROJECT_PATH}" "${RELEASE_PROJECT_SYMLINK}" -fi - - -# Set the release project's branch name to use. -if [ "${TEST_MODE}" ]; then - # In testing mode, just default to master when no arg provided. - RELEASE_PROJECT_BRANCH=${1:-master} - shift -elif [ -n "$1" ]; then - RELEASE_PROJECT_BRANCH=$1 -else - read -p " Please provide the branch name from the release project to use > " RELEASE_PROJECT_BRANCH -fi -echo "## Release project branch name is \`${RELEASE_PROJECT_BRANCH}\`." - - -# Get the name of the branch that is currently checked out in ../ to use. -# echo " !! DEBUG VALUES !!" -# echo " TRAVIS_TAG= $TRAVIS_TAG" -# echo " TRAVIS_PULL_REQUEST= $TRAVIS_PULL_REQUEST" -# echo " TRAVIS_COMMIT= $TRAVIS_COMMIT" -# echo " TRAVIS_BRANCH= $TRAVIS_BRANCH" - -if [ -n "${TRAVIS_TAG}" ]; then - echo "" - echo "!! Unable to perform integration tests against git tags in Travis !!" - echo "" - echo " Travis does not identify the originating branch name when " - echo " executing a build for a git tag (TRAVIS_BRANCH is unhelpfully " - echo " the same as TRAVIS_TAG), and Composer does not provide a " - echo " way to target a non-version-number tag name, so we can not " - echo " write a custom composer.json to complete this build. " - echo "" - echo " Exiting 0" - exit 0 -elif [[ -n "${TRAVIS_PULL_REQUEST}" && "${TRAVIS_PULL_REQUEST}" -ne "false" ]]; then - INSTALLER_PROJECT_BRANCH="pull/${TRAVIS_PULL_REQUEST}/merge" - ( - cd "${BASE_DIR}" >/dev/null 2>&1 - git checkout -qb $INSTALLER_PROJECT_BRANCH - ) -elif [ -n "${TRAVIS_COMMIT}" ]; then - INSTALLER_PROJECT_BRANCH="${TRAVIS_BRANCH}#${TRAVIS_COMMIT}" -else - INSTALLER_PROJECT_BRANCH=$( cd "${BASE_DIR}" >/dev/null 2>&1; git rev-parse --quiet --abbrev-ref HEAD 2>/dev/null ) -fi -echo "## Installer project branch name is \`${INSTALLER_PROJECT_BRANCH}\`." - - -# Delete all contents from the build dir, except the .gitkeep file and release-project symlink. -echo "## Purging old files from build directory." -shopt -s dotglob extglob -( - cd "${BUILD_DIR}" - rm -rf !(.|..|.gitkeep|release-project|${SHUNIT_DOWNLOAD_VERSION}) -) - - -# Copy the testing files from tests/integration/ to build/. -echo "## Populating the build directory." -shopt -s dotglob -( - cd "${TEST_DIR}" - cp -R * "${BUILD_DIR}/" - mkdir "${BUILD_DIR}/.git/" -) - -shopt -u dotglob extglob - - -# Write the composer.json file in this test dir to the build/ dir, adding branch names obtained earlier. -echo "## Writing customized composer.json file." -sed \ - -e "s|PRCI_BRANCH_NAME|${INSTALLER_PROJECT_BRANCH}|" \ - -e "s|PR_BRANCH_NAME|${RELEASE_PROJECT_BRANCH}|" \ - -e "s|PR_DIRECTORY|${RELEASE_PROJECT_PATH}|" \ - <"${TEST_DIR}/composer.json" \ - >"${BUILD_DIR}/composer.json" - - -# Execute the `composer install` command itself. -echo "## Executing \`composer install\` in the build directory." -COMPOSER_OUTPUT=$( - cd "${BUILD_DIR}/"; - composer install --no-interaction --ignore-platform-reqs -) -COMPOSER_EXIT_CODE=$? - -# End the script if test mode is OFF. -if [ -z "${TEST_MODE}" ]; then - if [ ! "${COMPOSER_EXIT_CODE}" ]; then - echo "!! Composer installation failed. Examine the results in \`${BUILD_DIR}\`." - echo '' - echo "${COMPOSER_OUTPUT}" - exit $COMPOSER_EXIT_CODE - else - echo "## Done simulating \`composer install\`. Examine the results in \`${BUILD_DIR}\`." - exit 0 - fi -fi - - -# In test mode, run assertions using the shunit2 test framework. -# (We run this script via travis as an integration test suite.) - - -# Make sure we have shunit2 available. -SHUNIT_TMP_DOWNLOAD="${BUILD_DIR}/${SHUNIT_DOWNLOAD_VERSION}.tgz" -SHUNIT_EXTRACT_PATH="${BUILD_DIR}" -SHUNIT_EXECUTABLE="${SHUNIT_EXTRACT_PATH}/${SHUNIT_DOWNLOAD_VERSION}/src/shunit2" -if [ ! -x "${SHUNIT_EXECUTABLE}" ]; then - if [ ! -f "${SHUNIT_TMP_DOWNLOAD}" ]; then - echo "## Fetching shunit2." - curl -L \ - --silent \ - --output "${SHUNIT_TMP_DOWNLOAD}" \ - $SHUNIT_FETCH_URL - fi - echo "## Unpacking shunit2." - tar zxf "${SHUNIT_TMP_DOWNLOAD}" -C "${SHUNIT_EXTRACT_PATH}" -fi - - -# Define the tests to execute. -echo "## Defining tests." -testComposerExitCode () { - assertTrue "composer must not error during install. - -${COMPOSER_OUTPUT} - " "$COMPOSER_EXIT_CODE" -} - -testGitignore () { - grep -qe '^/Vagrantfile$' "${BUILD_DIR}/.gitignore" - assertTrue ".gitignore must have a '/Vagrantfile' entry." "$?" - - grep -qe '^/puphpet/$' "${BUILD_DIR}/.gitignore" - assertTrue ".gitignore must have a '/puphpet/' entry." "$?" - - grep -qe '^/.vagrant/$' "${BUILD_DIR}/.gitignore" - assertTrue ".gitignore must have a '/.vagrant/' entry." "$?" -} - -testPuphpetDir () { - [ -d "${BUILD_DIR}/puphpet" ] - assertTrue "puphpet/ directory must be present." "$?" || return - - grep -qe '^canary: "foo"$' "${BUILD_DIR}/puphpet/config.yaml" - assertTrue "puphpet.yaml file must be copied into puphpet/ directory." "$?" -} - -# Load and run shUnit2 -echo "## Executing tests:" -. "${SHUNIT_EXECUTABLE}" - From 54c4b58fe1f53a30dc0031ee2778e5e9752441db Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 20 Oct 2015 08:50:06 -0500 Subject: [PATCH 15/15] Minor updates. --- README.md | 14 +++++++++++--- composer.json | 2 +- phpunit.xml.dist | 2 ++ .../PhpCodesniffer/CodingStandardHookTest.php | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82a5df3..376e6a9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Including this package in your composer.json makes a number of installers and pr -## PHP CodeSniffer, `phpcs-coding-standard` type (config editing approach) +## PHP CodeSniffer, `phpcs-coding-standard` type (copying folders approach) When you develop your own Coding Standard for the [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), you can package it for installation via Composer, but `phpcs` won't know about your standard unless you either manually specify it on the command line every time: @@ -84,6 +84,12 @@ The `prePackageUninstall` removes any Coding Standard folders from a package tha ## PHP CodeSniffer, post-install hook (config editing approach) +:warning: Speculative! :warning: + +@TODO: _This approach is partially coded already, but the plan is to eventually use an `extra` flag in the composer.json file to determine whether to copy standards folders or manage entries in phpcs's config file. This approach could be used both by the `phpcs-coding-standard` type as well as the postInstall hooks._ + + + Sometimes you want to use somebody else's coding standard package where you can't set the `type` explicitly. In cases like this, this package provides composer hook scripts that can be used to accomplish the same effect. The script works by scanning each installed package for any folders containing a `ruleset.xml` file (which indicates that folder contains a Coding Standard.) It then adds the vendor paths for these folders into the `CodeSniffer.conf` file, making them available to `phpcs` in their natural install location. @@ -119,6 +125,8 @@ The `postPackageUninstall` removes the path for a package that is being removed ## Contributing +Create an issue or submit a pull request. + ### Running Unit Tests * `composer install` @@ -129,11 +137,11 @@ The `postPackageUninstall` removes the path for a package that is being removed ## License -[MIT](https://github.com/loadsys/puphpet-release/blob/master/LICENSE). In particular, all [PuPHPet](http://puphpet.com) work belongs to the original authors. This project is strictly for our own convenience. +[MIT](https://github.com/loadsys/puphpet-release/blob/master/LICENSE). ## Copyright -© [Loadsys Web Strategies](http://loadsys.com) 2015 +© 2015 [Loadsys Web Strategies](http://loadsys.com) diff --git a/composer.json b/composer.json index 6f18b7e..c12f401 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "require-dev": { "composer/composer": "1.0.*@dev", - "phpunit/phpunit": "~4.1", + "phpunit/phpunit": "~4.8", "squizlabs/php_codesniffer": "~2.0" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 55083e7..2009bc3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,7 @@ marktestIncomplete('@TODO: Write a test where the package does not have any stanrds to install.'); + $this->marktestIncomplete('@TODO: Write a test where the package does not have any standards to install.'); } /** @@ -242,7 +242,7 @@ public function testFindStandardsFolderDoesNotExist() { $this->assertFalse( $result, - 'False shouldbe returned for a non-existent path.' + 'False should be returned for a non-existent path.' ); }