Browse Source

fix: Allow using cache when running in Docker distribution (#7769)

Greg Korba 1 year ago
parent
commit
8056c295ab

+ 5 - 7
doc/usage.rst

@@ -194,13 +194,11 @@ To visualize all the rules that belong to a ruleset:
 Caching
 -------
 
-The caching mechanism is enabled by default. This will speed up further runs by
-fixing only files that were modified since the last run. The tool will fix all
-files if the tool version has changed or the list of rules has changed.
-The cache is supported only when the tool was downloaded as a phar file or
-installed via Composer. The cache is written to the drive progressively, so do
-not be afraid of interruption - rerun the command and start where you left.
-The cache mechanism also supports executing the command in parallel.
+The caching mechanism is enabled by default. This will speed up further runs by fixing only files that were modified
+since the last run. The tool will fix all files if the tool version has changed or the list of rules has changed.
+The cache is supported only when the tool was downloaded as a PHAR file, executed within pre-built Docker image
+or installed via Composer. The cache is written to the drive progressively, so do not be afraid of interruption -
+rerun the command and start where you left. The cache mechanism also supports executing the command in parallel.
 
 Cache can be disabled via ``--using-cache`` option or config file:
 

+ 8 - 1
src/Console/ConfigurationResolver.php

@@ -479,7 +479,7 @@ final class ConfigurationResolver
             }
         }
 
-        $this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer());
+        $this->usingCache = $this->usingCache && $this->isCachingAllowedForRuntime();
 
         return $this->usingCache;
     }
@@ -949,4 +949,11 @@ final class ConfigurationResolver
 
         return $config;
     }
+
+    private function isCachingAllowedForRuntime(): bool
+    {
+        return $this->toolInfo->isInstalledAsPhar()
+            || $this->toolInfo->isInstalledByComposer()
+            || $this->toolInfo->isRunInsideDocker();
+    }
 }

+ 9 - 0
src/ToolInfo.php

@@ -98,6 +98,15 @@ final class ToolInfo implements ToolInfoInterface
         return $this->isInstalledByComposer;
     }
 
+    /**
+     * Determines if the tool is run inside our pre-built Docker image.
+     * The `/fixer/` path comes from our Dockerfile, tool is installed there and added to global PATH via symlinked binary.
+     */
+    public function isRunInsideDocker(): bool
+    {
+        return is_file('/.dockerenv') && str_starts_with(__FILE__, '/fixer/');
+    }
+
     public function getPharDownloadUri(string $version): string
     {
         return sprintf(

+ 2 - 0
src/ToolInfoInterface.php

@@ -32,5 +32,7 @@ interface ToolInfoInterface
 
     public function isInstalledByComposer(): bool;
 
+    public function isRunInsideDocker(): bool;
+
     public function getPharDownloadUri(string $version): string;
 }

+ 5 - 0
tests/Console/Command/SelfUpdateCommandTest.php

@@ -431,6 +431,11 @@ final class SelfUpdateCommandTest extends TestCase
                 throw new \LogicException('Not implemented.');
             }
 
+            public function isRunInsideDocker(): bool
+            {
+                return false;
+            }
+
             public function getPharDownloadUri(string $version): string
             {
                 return sprintf('%s/%s.phar', $this->directory->url(), $version);

+ 91 - 8
tests/Console/ConfigurationResolverTest.php

@@ -817,6 +817,84 @@ final class ConfigurationResolverTest extends TestCase
         self::assertFalse($resolver->getUsingCache());
     }
 
+    /**
+     * @dataProvider provideResolveUsingCacheForRuntimesCases
+     */
+    public function testResolveUsingCacheForRuntimes(bool $cacheAllowed, bool $installedWithComposer, bool $asPhar, bool $inDocker): void
+    {
+        $config = new Config();
+        $config->setUsingCache(true);
+
+        $resolver = $this->createConfigurationResolver(
+            [],
+            $config,
+            '',
+            new class($installedWithComposer, $asPhar, $inDocker) implements ToolInfoInterface {
+                private bool $installedWithComposer;
+                private bool $asPhar;
+                private bool $inDocker;
+
+                public function __construct(bool $installedWithComposer, bool $asPhar, bool $inDocker)
+                {
+                    $this->installedWithComposer = $installedWithComposer;
+                    $this->asPhar = $asPhar;
+                    $this->inDocker = $inDocker;
+                }
+
+                public function getComposerInstallationDetails(): array
+                {
+                    throw new \BadMethodCallException();
+                }
+
+                public function getComposerVersion(): string
+                {
+                    throw new \BadMethodCallException();
+                }
+
+                public function getVersion(): string
+                {
+                    throw new \BadMethodCallException();
+                }
+
+                public function isInstalledAsPhar(): bool
+                {
+                    return $this->asPhar;
+                }
+
+                public function isInstalledByComposer(): bool
+                {
+                    return $this->installedWithComposer;
+                }
+
+                public function isRunInsideDocker(): bool
+                {
+                    return $this->inDocker;
+                }
+
+                public function getPharDownloadUri(string $version): string
+                {
+                    throw new \BadMethodCallException();
+                }
+            }
+        );
+
+        self::assertSame($cacheAllowed, $resolver->getUsingCache());
+    }
+
+    /**
+     * @return iterable<array{0: bool, 1: bool, 2: bool, 3: bool}>
+     */
+    public static function provideResolveUsingCacheForRuntimesCases(): iterable
+    {
+        yield 'none of the allowed runtimes' => [false, false, false, false];
+
+        yield 'composer installation' => [true, true, false, false];
+
+        yield 'PHAR distribution' => [true, false, true, false];
+
+        yield 'Docker runtime' => [true, false, false, true];
+    }
+
     public function testResolveCacheFileWithoutConfigAndOption(): void
     {
         $config = new Config();
@@ -1303,17 +1381,17 @@ For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/b
     /**
      * @param array<string, mixed> $options
      */
-    private function createConfigurationResolver(array $options, Config $config = null, string $cwdPath = ''): ConfigurationResolver
-    {
-        if (null === $config) {
-            $config = new Config();
-        }
-
+    private function createConfigurationResolver(
+        array $options,
+        Config $config = null,
+        string $cwdPath = '',
+        ToolInfoInterface $toolInfo = null
+    ): ConfigurationResolver {
         return new ConfigurationResolver(
-            $config,
+            $config ?? new Config(),
             $options,
             $cwdPath,
-            $this->createToolInfoDouble()
+            $toolInfo ?? $this->createToolInfoDouble()
         );
     }
 
@@ -1379,6 +1457,11 @@ For more info about updating see: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/b
                 throw new \BadMethodCallException();
             }
 
+            public function isRunInsideDocker(): bool
+            {
+                return false;
+            }
+
             public function getPharDownloadUri(string $version): string
             {
                 throw new \BadMethodCallException();

+ 5 - 0
tests/Console/WarningsDetectorTest.php

@@ -108,6 +108,11 @@ final class WarningsDetectorTest extends TestCase
                 return $this->isInstalledByComposer;
             }
 
+            public function isRunInsideDocker(): bool
+            {
+                return false;
+            }
+
             public function getPharDownloadUri(string $version): string
             {
                 throw new \LogicException('Not implemented.');