Skip to content

Commit

Permalink
Added support for the base-less tests and symlinks.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexSkrypnyk committed Jan 21, 2025
1 parent 8822a5e commit 0bbf794
Show file tree
Hide file tree
Showing 30 changed files with 254 additions and 17 deletions.
24 changes: 14 additions & 10 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Ignore files for distribution archives.

/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/logo.png export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
/phpstan.neon export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/logo.png export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/tests/phpunit/Fixtures export-ignore
/tests/phpunit/Functional/CreateProjectTest.php export-ignore
/tests/phpunit/Unit export-ignore
Empty file removed tests/.gitkeep
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ dir2_flat/*

# Ignore all files and subdirectories in the directory.
dir3_subdirs/*
dir3_subdirs_symlink/*
# But include sub-sub-directory.
!dir3_subdirs/dir32-unignored/
!dir3_subdirs_symlink/dir32-unignored/

# Ignore directory and all subdirectories.
dir4_full_ignore/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea/
.coverage-html/
.phpunit.cache/
vendor/
^composer.lock
64 changes: 64 additions & 0 deletions tests/phpunit/Fixtures/install_no_base/post_install/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "testorg/testpackage",
"description": "Test description",
"license": "MIT",
"type": "composer-plugin",
"authors": [
{
"name": "Alex Skrypnyk",
"email": "[email protected]",
"homepage": "https://alexskrypnyk.com",
"role": "Maintainer"
}
],
"homepage": "https://github.com/alexskrypnyk/customizer",
"support": {
"issues": "https://github.com/alexskrypnyk/customizer/issues",
"source": "https://github.com/alexskrypnyk/customizer"
},
"require": {
"php": ">=8.2",
"composer-plugin-api": "^2.0"
},
"require-dev": {
"composer/composer": "^2.7",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"drupal/coder": "^8.3",
"ergebnis/composer-normalize": "^2.42",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^11.1",
"rector/rector": "^2"
},
"prefer-stable": true,
"autoload": {
"psr-4": {
"AlexSkrypnyk\\Customizer\\": ""
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true
},
"sort-packages": true
},
"extra": {
"class": "AlexSkrypnyk\\Customizer\\Plugin",
"customizer": true
},
"scripts": {
"lint": [
"phpcs",
"phpstan",
"rector --clear-cache --dry-run"
],
"lint-fix": [
"rector --clear-cache",
"phpcbf"
],
"reset": "rm -Rf vendor vendor-bin composer.lock",
"test": "phpunit --no-coverage",
"test-coverage": "phpunit"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions tests/phpunit/Functional/CreateProjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ public function testInstall(): void {
$this->assertComposerLockUpToDate();
}

/**
* Assert that fixtures without 'base' use the current directory as the base.
*/
#[Group('install')]
public function testInstallNoBase(): void {
static::customizerSetAnswers([
'testorg/testpackage',
'Test description',
'MIT',
self::TUI_ANSWER_NOTHING,
]);

$this->runComposerCreateProject();

$this->assertComposerCommandSuccessOutputContains('Welcome to the "alexskrypnyk/customizer" project customizer');
$this->assertComposerCommandSuccessOutputContains('Name');
$this->assertComposerCommandSuccessOutputContains('testorg/testpackage');
$this->assertComposerCommandSuccessOutputContains('Description');
$this->assertComposerCommandSuccessOutputContains('Test description');
$this->assertComposerCommandSuccessOutputContains('License');
$this->assertComposerCommandSuccessOutputContains('MIT');
$this->assertComposerCommandSuccessOutputContains('Project was customized');

$this->assertFixtureDirectoryEqualsSut('post_install');
$this->assertComposerLockUpToDate();
}

#[RunInSeparateProcess]
#[Group('install')]
public function testInstallAdditionalCleanup(): void {
Expand Down
111 changes: 106 additions & 5 deletions tests/phpunit/Functional/CustomizerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* Base class for functional tests.
*
* This class is intended to be distributed with the Customizer package and
* used in consumer site's tests to allow easy testing of the integrated
* used in consumer package's tests to allow easy testing of the integrated
* Customizer command.
*
* Extend this class in your test case to get access to the Customizer command
Expand Down Expand Up @@ -193,10 +193,29 @@ protected function initLocations(string $cwd, ?callable $cb = NULL): void {
static::$fixtures .= DIRECTORY_SEPARATOR . $this->dataName();
}

// Copy the 'base' fixture files to the repository if they were provided for
// this test.
// Copy the 'base' fixture to the 'local' fixture.
if (is_dir(static::$fixtures)) {
$this->fs->mirror(static::$fixtures . DIRECTORY_SEPARATOR . 'base', static::$repo);
$base_dir = static::$fixtures . DIRECTORY_SEPARATOR . 'base';

// Use this project's root directory as a base directory if the 'base'
// fixture was not provided. This allows to use the current project's
// files as a 'base' for the test.
//
// @note Composer uses .gitattributes to determine which files to include
// in the package when running `create-project`, so add the files that are
// not intended to be used in the consumer to the .gitattributes file
// of this project.
$allowed_files = [];
if (!is_dir($base_dir)) {
$base_dir = static::$root;
// Only use the git-tracked files to replicate a "clean" project as it
// would be seen by Composer at the code repository.
// Make sure to commit the changes locally before running
// the tests (even as a temporary commit).
$allowed_files = $this->getTrackedFiles($base_dir);
}

$this->mirrorFiltered($base_dir, static::$repo, $allowed_files);
}

if ($cb !== NULL && $cb instanceof \Closure) {
Expand Down Expand Up @@ -302,6 +321,87 @@ protected function runComposerCreateProject(array $options = []): void {
$this->tester->run($options + $defaults);
}

/**
* Get the tracked files in a Git repository.
*
* @param string $dir
* The directory to check.
*
* @return array<string>
* The list of tracked files.
*
* @throws \RuntimeException
* If the directory is not a Git repository.
*/
protected function getTrackedFiles(string $dir): array {
if (!is_dir($dir . '/.git')) {
throw new \RuntimeException("The directory is not a Git repository.");
}

$tracked_files = [];
$output = [];
$code = 0;
exec(sprintf('git --git-dir=%s --work-tree=%s ls-files', escapeshellarg($dir . '/.git'), escapeshellarg($dir)), $output, $code);
if ($code !== 0) {
throw new \RuntimeException("Failed to retrieve tracked files using git ls-files.");
}

foreach ($output as $file) {
$tracked_files[] = $dir . DIRECTORY_SEPARATOR . $file;
}

return $tracked_files;
}

/**
* Mirror a directory with filtering.
*
* @param string $src
* The source directory.
* @param string $dst
* The destination directory.
* @param array<string> $allowed_files
* The list of allowed files.
*/
protected function mirrorFiltered(string $src, string $dst, array $allowed_files = []): void {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($src, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
);

foreach ($files as $file) {
if (!$file instanceof \SplFileInfo) {
continue;
}

if (!empty($allowed_files) && !in_array($file->getPathname(), $allowed_files, TRUE)) {
continue;
}

$relative_path = substr($file->getPathname(), strlen($src) + 1);
$target_path = $dst . DIRECTORY_SEPARATOR . $relative_path;

if (!is_dir(dirname($target_path))) {
mkdir(dirname($target_path), 0755, TRUE);
}

// Always copy the contents of the file as a regular file.
if (is_link($file->getPathname())) {
// Resolve the symlink to the actual file content.
$resolved_path = realpath($file->getPathname());
if ($resolved_path !== FALSE) {
copy($resolved_path, $target_path);
}
else {
throw new \RuntimeException("Failed to resolve symlink for: " . $file->getPathname());
}
}
else {
// Copy regular files.
copy($file->getPathname(), $target_path);
}
}
}

/**
* Assert that the Composer lock file is up to date.
*/
Expand Down Expand Up @@ -500,6 +600,7 @@ protected function assertDirectoriesEqual(string $dir1, string $dir2, ?callable
if (str_ends_with($pattern, DIRECTORY_SEPARATOR)) {
return str_starts_with($path, rtrim($pattern, DIRECTORY_SEPARATOR));
}

// Match direct children (e.g., "dir/*").
if (str_contains($pattern, '/*')) {
$parent_dir = rtrim($pattern, '/*') . DIRECTORY_SEPARATOR;
Expand All @@ -514,7 +615,7 @@ protected function assertDirectoriesEqual(string $dir1, string $dir2, ?callable
// Get the files in the directories.
$get_files = static function (string $dir, array $rules, callable $match_path, ?callable $match_content): array {
$files = [];
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS));
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS));
foreach ($iterator as $file) {
if (!$file instanceof \SplFileInfo) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/Unit/FilesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public static function dataProviderReplaceInPath(): array {
}

#[DataProvider('dataProviderreplaceInPathBetweenMarkers')]
public function testreplaceInPathBetweenMarkers(string $path, array $before, string $search, string $replace, string $start, string $end, array $after): void {
public function testReplaceInPathBetweenMarkers(string $path, array $before, string $search, string $replace, string $start, string $end, array $after): void {
$this->createFileTree(static::$sut, $before);

CustomizeCommand::replaceInPathBetweenMarkers(
Expand All @@ -235,7 +235,7 @@ public function testreplaceInPathBetweenMarkers(string $path, array $before, str
}

/**
* Data provider for testreplaceInPathBetweenMarkers.
* Data provider for testReplaceInPathBetweenMarkers.
*
* @return array
* The data.
Expand Down
11 changes: 11 additions & 0 deletions tests/phpunit/Unit/SelfTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ public static function dataProviderAssertDirectoriesEqual(): array {
'files_not_equal' => [
[
'dir1' => [
'd32f2_symlink_deep.txt',
'dir1_flat/d1f1_symlink.txt',
'dir1_flat/d1f3-only-src.txt',
'dir3_subdirs/dir32-unignored/d32f1_symlink.txt',
'dir3_subdirs_symlink/d3f1-ignored.txt',
'dir3_subdirs_symlink/d3f2-ignored.txt',
'dir3_subdirs_symlink/dir31/d31f1-ignored.txt',
'dir3_subdirs_symlink/dir31/d31f2-ignored.txt',
'dir3_subdirs_symlink/dir32-unignored/d32f1.txt',
'dir3_subdirs_symlink/dir32-unignored/d32f1_symlink.txt',
'dir3_subdirs_symlink/dir32-unignored/d32f2.txt',
'f2_symlink.txt',
],
'dir2' => [
'dir2_flat-present-dst/d2f1.txt',
Expand Down

0 comments on commit 0bbf794

Please sign in to comment.