Upgraded imagemagick and manually altered pdf to image module to handle changes....
[yaffs-website] / web / modules / contrib / imagemagick / tests / src / Functional / ToolkitImagemagickTest.php
index 2322581ffa91ff34b90bc1b2104278c94c70d54f..df6af8dab9e8c5fc299a49702ff801aa9ece2a31 100644 (file)
@@ -2,9 +2,12 @@
 
 namespace Drupal\Tests\imagemagick\Functional;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Image\ImageInterface;
 use Drupal\Tests\TestFileCreationTrait;
 use Drupal\Tests\BrowserTestBase;
+use Drupal\file_mdm\FileMetadataInterface;
+use Drupal\imagemagick\ImagemagickExecArguments;
 
 /**
  * Tests that core image manipulations work properly through Imagemagick.
@@ -57,6 +60,8 @@ class ToolkitImagemagickTest extends BrowserTestBase {
     'simpletest',
     'file_test',
     'imagemagick',
+    'file_mdm',
+    'file_mdm_exif',
   ];
 
   /**
@@ -79,51 +84,36 @@ class ToolkitImagemagickTest extends BrowserTestBase {
   }
 
   /**
-   * Provides data for testManipulations.
-   *
-   * @return array[]
-   *   A simple array of simple arrays, each having the following elements:
-   *   - binaries to use for testing.
-   */
-  public function providerManipulationTest() {
-    return [
-      ['imagemagick'],
-      ['graphicsmagick'],
-    ];
-  }
-
-  /**
-   * Test image toolkit operations.
-   *
-   * Since PHP can't visually check that our images have been manipulated
-   * properly, build a list of expected color values for each of the corners and
-   * the expected height and widths for the final images.
+   * Helper to setup the image toolkit.
    *
    * @param string $binaries
    *   The graphics package binaries to use for testing.
-   *
-   * @dataProvider providerManipulationTest
+   * @param bool $check_path
+   *   Whether the path to binaries should be tested.
    */
-  public function testManipulations($binaries) {
+  protected function setUpToolkit($binaries, $check_path = TRUE) {
     // Change the toolkit.
     \Drupal::configFactory()->getEditable('system.image')
       ->set('toolkit', 'imagemagick')
       ->save();
 
     // Execute tests with selected binaries.
-    // The test can only be executed if binaries are available on the shell
-    // path.
     \Drupal::configFactory()->getEditable('imagemagick.settings')
       ->set('debug', TRUE)
       ->set('binaries', $binaries)
       ->set('quality', 100)
       ->save();
-    $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->checkPath('');
-    if (!empty($status['errors'])) {
-      // Bots running automated test on d.o. do not have binaries installed,
-      // so the test will be skipped; it can be run locally where binaries are
-      // installed.
-      $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+
+    if ($check_path) {
+      // The test can only be executed if binaries are available on the shell
+      // path.
+      $status = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick')->getExecManager()->checkPath('');
+      if (!empty($status['errors'])) {
+        // Bots running automated test on d.o. do not have binaries installed,
+        // so the test will be skipped; it can be run locally where binaries
+        // are installed.
+        $this->markTestSkipped("Tests for '{$binaries}' cannot run because the binaries are not available on the shell path.");
+      }
     }
 
     // Set the toolkit on the image factory.
@@ -135,6 +125,36 @@ class ToolkitImagemagickTest extends BrowserTestBase {
     // Prepare directory.
     file_unmanaged_delete_recursive($this->testDirectory);
     file_prepare_directory($this->testDirectory, FILE_CREATE_DIRECTORY);
+  }
+
+  /**
+   * Provides data for testManipulations.
+   *
+   * @return array[]
+   *   A simple array of simple arrays, each having the following elements:
+   *   - binaries to use for testing.
+   */
+  public function providerManipulationTest() {
+    return [
+      ['imagemagick'],
+      ['graphicsmagick'],
+    ];
+  }
+
+  /**
+   * Test image toolkit operations.
+   *
+   * Since PHP can't visually check that our images have been manipulated
+   * properly, build a list of expected color values for each of the corners and
+   * the expected height and widths for the final images.
+   *
+   * @param string $binaries
+   *   The graphics package binaries to use for testing.
+   *
+   * @dataProvider providerManipulationTest
+   */
+  public function testManipulations($binaries) {
+    $this->setUpToolkit($binaries);
 
     // Typically the corner colors will be unchanged. These colors are in the
     // order of top-left, top-right, bottom-right, bottom-left.
@@ -309,31 +329,36 @@ class ToolkitImagemagickTest extends BrowserTestBase {
     $this->getTestFiles('image');
 
     foreach ($files as $file) {
+      $image_uri = 'public://' . $file;
       foreach ($operations as $op => $values) {
         // Load up a fresh image.
-        $image = $this->imageFactory->get('public://' . $file);
+        $image = $this->imageFactory->get($image_uri);
         if (!$image->isValid()) {
           $this->fail("Could not load image $file.");
           continue 2;
         }
 
         // Check that no multi-frame information is set.
-        $this->assertNull($image->getToolkit()->getFrames());
+        $this->assertIdentical(1, $image->getToolkit()->getFrames());
 
         // Perform our operation.
         $image->apply($values['function'], $values['arguments']);
 
         // Save and reload image.
         $file_path = $this->testDirectory . '/' . $op . substr($file, -4);
-        $image->save($file_path);
+        $this->assertTrue($image->save($file_path));
         $image = $this->imageFactory->get($file_path);
         $this->assertTrue($image->isValid());
 
-        // @todo GraphicsMagick specifics, temporarily adjust tests.
-        $package = $image->getToolkit()->getPackage();
+        // @todo Suite specifics, temporarily adjust tests.
+        $package = $image->getToolkit()->getExecManager()->getPackage();
         if ($package === 'graphicsmagick') {
-          // @todo Issues with crop on GIF files, investigate.
-          if (in_array($file, ['image-test.gif', 'image-test-no-transparency.gif']) && in_array($op, ['crop', 'scale_and_crop'])) {
+          // @todo Issues with crop and convert on GIF files, investigate.
+          if (in_array($file, [
+            'image-test.gif', 'image-test-no-transparency.gif',
+          ]) && in_array($op, [
+            'crop', 'scale_and_crop', 'convert_png',
+          ])) {
             continue;
           }
         }
@@ -409,7 +434,7 @@ class ToolkitImagemagickTest extends BrowserTestBase {
 
             }
             $color = $this->getPixelColor($image, $x, $y);
-            $correct_colors = $this->colorsAreClose($color, $corner, $values['tolerance']);
+            $this->colorsAreClose($color, $corner, $values['tolerance'], $file, $op);
           }
         }
       }
@@ -470,10 +495,14 @@ class ToolkitImagemagickTest extends BrowserTestBase {
       'viet "with double quotes" hình ảnh thử nghiệm.png',
     ];
     foreach ($file_names as $file) {
+      // On Windows, skip filenames with non-allowed characters.
+      if (substr(PHP_OS, 0, 3) === 'WIN' && preg_match('/[:*?"<>|]/', $file)) {
+        continue;
+      }
       $image = $this->imageFactory->get();
-      $image->createNew(50, 20, 'png');
+      $this->assertTrue($image->createNew(50, 20, 'png'));
       $file_path = $this->testDirectory . '/' . $file;
-      $image->save($file_path);
+      $this->assertTrue($image->save($file_path), $file);
       $image_reloaded = $this->imageFactory->get($file_path, 'gd');
       $this->assertTrue($image_reloaded->isValid(), "Image file '$file' loaded successfully.");
     }
@@ -481,21 +510,31 @@ class ToolkitImagemagickTest extends BrowserTestBase {
     // Test handling a file stored through a remote stream wrapper.
     $image = $this->imageFactory->get('dummy-remote://image-test.png');
     // Source file should be equal to the copied local temp source file.
-    $this->assertEqual(filesize('dummy-remote://image-test.png'), filesize($image->getToolkit()->getSourceLocalPath()));
+    $this->assertEqual(filesize('dummy-remote://image-test.png'), filesize($image->getToolkit()->arguments()->getSourceLocalPath()));
     $image->desaturate();
-    $image->save('dummy-remote://remote-image-test.png');
+    $this->assertTrue($image->save('dummy-remote://remote-image-test.png'));
     // Destination file should exists, and destination local temp file should
     // have been reset.
-    $this->assertTrue(file_exists($image->getToolkit()->getDestination()));
-    $this->assertEqual('dummy-remote://remote-image-test.png', $image->getToolkit()->getDestination());
-    $this->assertIdentical('', $image->getToolkit()->getDestinationLocalPath());
+    $this->assertTrue(file_exists($image->getToolkit()->arguments()->getDestination()));
+    $this->assertEqual('dummy-remote://remote-image-test.png', $image->getToolkit()->arguments()->getDestination());
+    $this->assertIdentical('', $image->getToolkit()->arguments()->getDestinationLocalPath());
 
     // Test retrieval of EXIF information.
+    file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FILE_EXISTS_REPLACE);
+    // The image files that will be tested.
     $image_files = [
       [
         'path' => drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg',
         'orientation' => 8,
       ],
+      [
+        'path' => 'public://test-exif.jpeg',
+        'orientation' => 8,
+      ],
+      [
+        'path' => 'dummy-remote://test-exif.jpeg',
+        'orientation' => 8,
+      ],
       [
         'path' => 'public://image-test.jpg',
         'orientation' => NULL,
@@ -513,7 +552,6 @@ class ToolkitImagemagickTest extends BrowserTestBase {
         'orientation' => NULL,
       ],
     ];
-
     foreach ($image_files as $image_file) {
       // Get image using 'identify'.
       \Drupal::configFactory()->getEditable('imagemagick.settings')
@@ -521,13 +559,6 @@ class ToolkitImagemagickTest extends BrowserTestBase {
         ->save();
       $image = $this->imageFactory->get($image_file['path']);
       $this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
-
-      // Get image using 'getimagesize'.
-      \Drupal::configFactory()->getEditable('imagemagick.settings')
-        ->set('use_identify', FALSE)
-        ->save();
-      $image = $this->imageFactory->get($image_file['path']);
-      $this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
     }
 
     // Test multi-frame GIF image.
@@ -544,7 +575,6 @@ class ToolkitImagemagickTest extends BrowserTestBase {
         'rotated_height' => 26,
       ],
     ];
-
     // Get images using 'identify'.
     \Drupal::configFactory()->getEditable('imagemagick.settings')
       ->set('use_identify', TRUE)
@@ -557,7 +587,7 @@ class ToolkitImagemagickTest extends BrowserTestBase {
 
       // Scaling should preserve frames.
       $image->scale(30);
-      $image->save($image_file['destination']);
+      $this->assertTrue($image->save($image_file['destination']));
       $image = $this->imageFactory->get($image_file['destination']);
       $this->assertIdentical($image_file['scaled_width'], $image->getWidth());
       $this->assertIdentical($image_file['scaled_height'], $image->getHeight());
@@ -565,7 +595,7 @@ class ToolkitImagemagickTest extends BrowserTestBase {
 
       // Rotating should preserve frames.
       $image->rotate(24);
-      $image->save($image_file['destination']);
+      $this->assertTrue($image->save($image_file['destination']));
       $image = $this->imageFactory->get($image_file['destination']);
       $this->assertIdentical($image_file['rotated_width'], $image->getWidth());
       $this->assertIdentical($image_file['rotated_height'], $image->getHeight());
@@ -573,12 +603,301 @@ class ToolkitImagemagickTest extends BrowserTestBase {
 
       // Converting to PNG should drop frames.
       $image->convert('png');
-      $this->assertNull($image->getToolkit()->getFrames());
-      $image->save($image_file['destination']);
+      $this->assertTrue($image->save($image_file['destination']));
       $image = $this->imageFactory->get($image_file['destination']);
+      $this->assertIdentical(1, $image->getToolkit()->getFrames());
       $this->assertIdentical($image_file['rotated_width'], $image->getWidth());
       $this->assertIdentical($image_file['rotated_height'], $image->getHeight());
-      $this->assertNull($image->getToolkit()->getFrames());
+      $this->assertIdentical(1, $image->getToolkit()->getFrames());
+    }
+  }
+
+  /**
+   * Legacy methods tests.
+   *
+   * @param string $binaries
+   *   The graphics package binaries to use for testing.
+   *
+   * @dataProvider providerManipulationTest
+   *
+   * @todo remove in 8.x-3.0.
+   *
+   * @group legacy
+   */
+  public function testManipulationsLegacy($binaries) {
+    $this->setUpToolkit($binaries);
+
+    // Check package.
+    $toolkit = \Drupal::service('image.toolkit.manager')->createInstance('imagemagick');
+    $this->assertSame($binaries, $toolkit->getPackage());
+    $this->assertNotNull($toolkit->getPackageLabel());
+    $this->assertSame([], $toolkit->checkPath('')['errors']);
+
+    // Typically the corner colors will be unchanged. These colors are in the
+    // order of top-left, top-right, bottom-right, bottom-left.
+    $default_corners = [
+      $this->red,
+      $this->green,
+      $this->blue,
+      $this->transparent,
+    ];
+
+    // A list of files that will be tested.
+    $files = [
+      'image-test.png',
+      'image-test.gif',
+      'image-test-no-transparency.gif',
+      'image-test.jpg',
+    ];
+
+    // Setup a list of tests to perform on each type.
+    $operations = [
+      'resize' => [
+        'function' => 'resize',
+        'arguments' => ['width' => 20, 'height' => 10],
+        'width' => 20,
+        'height' => 10,
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'scale_x' => [
+        'function' => 'scale',
+        'arguments' => ['width' => 20],
+        'width' => 20,
+        'height' => 10,
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'scale_y' => [
+        'function' => 'scale',
+        'arguments' => ['height' => 10],
+        'width' => 20,
+        'height' => 10,
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'upscale_x' => [
+        'function' => 'scale',
+        'arguments' => ['width' => 80, 'upscale' => TRUE],
+        'width' => 80,
+        'height' => 40,
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'upscale_y' => [
+        'function' => 'scale',
+        'arguments' => ['height' => 40, 'upscale' => TRUE],
+        'width' => 80,
+        'height' => 40,
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'crop' => [
+        'function' => 'crop',
+        'arguments' => ['x' => 12, 'y' => 4, 'width' => 16, 'height' => 12],
+        'width' => 16,
+        'height' => 12,
+        'corners' => array_fill(0, 4, $this->white),
+        'tolerance' => 0,
+      ],
+      'scale_and_crop' => [
+        'function' => 'scale_and_crop',
+        'arguments' => ['width' => 10, 'height' => 8],
+        'width' => 10,
+        'height' => 8,
+        'corners' => array_fill(0, 4, $this->black),
+        'tolerance' => 100,
+      ],
+      'convert_jpg' => [
+        'function' => 'convert',
+        'width' => 40,
+        'height' => 20,
+        'arguments' => ['extension' => 'jpeg'],
+        'mimetype' => 'image/jpeg',
+        'corners' => $default_corners,
+        'tolerance' => 0,
+      ],
+      'convert_gif' => [
+        'function' => 'convert',
+        'width' => 40,
+        'height' => 20,
+        'arguments' => ['extension' => 'gif'],
+        'mimetype' => 'image/gif',
+        'corners' => $default_corners,
+        'tolerance' => 15,
+      ],
+      'convert_png' => [
+        'function' => 'convert',
+        'width' => 40,
+        'height' => 20,
+        'arguments' => ['extension' => 'png'],
+        'mimetype' => 'image/png',
+        'corners' => $default_corners,
+        'tolerance' => 5,
+      ],
+      'rotate_5' => [
+        'function' => 'rotate',
+        'arguments' => [
+          'degrees' => 5,
+          'background' => '#FF00FF',
+          'resize_filter' => 'Box',
+        ],
+        'width' => 41,
+        'height' => 23,
+        'corners' => array_fill(0, 4, $this->fuchsia),
+        'tolerance' => 5,
+      ],
+      'rotate_minus_10' => [
+        'function' => 'rotate',
+        'arguments' => [
+          'degrees' => -10,
+          'background' => '#FF00FF',
+          'resize_filter' => 'Box',
+        ],
+        'width' => 41,
+        'height' => 26,
+        'corners' => array_fill(0, 4, $this->fuchsia),
+        'tolerance' => 15,
+      ],
+      'rotate_90' => [
+        'function' => 'rotate',
+        'arguments' => ['degrees' => 90, 'background' => '#FF00FF'],
+        'width' => 20,
+        'height' => 40,
+        'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
+        'tolerance' => 0,
+      ],
+      'rotate_transparent_5' => [
+        'function' => 'rotate',
+        'arguments' => ['degrees' => 5, 'resize_filter' => 'Box'],
+        'width' => 41,
+        'height' => 23,
+        'corners' => array_fill(0, 4, $this->transparent),
+        'tolerance' => 0,
+      ],
+      'rotate_transparent_90' => [
+        'function' => 'rotate',
+        'arguments' => ['degrees' => 90],
+        'width' => 20,
+        'height' => 40,
+        'corners' => [$this->transparent, $this->red, $this->green, $this->blue],
+        'tolerance' => 0,
+      ],
+      'desaturate' => [
+        'function' => 'desaturate',
+        'arguments' => [],
+        'height' => 20,
+        'width' => 40,
+        // Grayscale corners are a bit funky. Each of the corners are a shade of
+        // gray. The values of these were determined simply by looking at the
+        // final image to see what desaturated colors end up being.
+        'corners' => [
+          array_fill(0, 3, 76) + [3 => 0],
+          array_fill(0, 3, 149) + [3 => 0],
+          array_fill(0, 3, 29) + [3 => 0],
+          array_fill(0, 3, 225) + [3 => 127],
+        ],
+        // @todo tolerance here is too high. Check reasons.
+        'tolerance' => 17000,
+      ],
+    ];
+
+    // Prepare a copy of test files.
+    $this->getTestFiles('image');
+
+    foreach ($files as $file) {
+      $image_uri = 'public://' . $file;
+      foreach ($operations as $op => $values) {
+        // Load up a fresh image.
+        $image = $this->imageFactory->get($image_uri);
+        if (!$image->isValid()) {
+          $this->fail("Could not load image $file.");
+          continue 2;
+        }
+
+        // Check that no multi-frame information is set.
+        $this->assertIdentical(1, $image->getToolkit()->getFrames());
+
+        // Legacy source tests.
+        $this->assertSame($image_uri, $image->getToolkit()->getSource());
+        $this->assertSame($image->getToolkit()->arguments()->getSourceLocalPath(), $image->getToolkit()->getSourceLocalPath());
+        $this->assertSame($image->getToolkit()->arguments()->getSourceFormat(), $image->getToolkit()->getSourceFormat());
+
+        // Perform our operation.
+        $image->apply($values['function'], $values['arguments']);
+
+        // Save image.
+        $file_path = $this->testDirectory . '/' . $op . substr($file, -4);
+        $this->assertTrue($image->save($file_path));
+
+        // Legacy destination tests.
+        $this->assertSame($file_path, $image->getToolkit()->getDestination());
+        $this->assertSame('', $image->getToolkit()->getDestinationLocalPath());
+        $this->assertNotNull($image->getToolkit()->arguments()->getSourceFormat(), $image->getToolkit()->getDestinationFormat());
+
+        // Reload image.
+        $image = $this->imageFactory->get($file_path);
+        $this->assertTrue($image->isValid());
+
+        // Legacy set methods.
+        $image->getToolkit()->setSourceLocalPath('bar');
+        $image->getToolkit()->setSourceFormat('PNG');
+        $image->getToolkit()->setDestination('foo');
+        $image->getToolkit()->setDestinationLocalPath('baz');
+        $image->getToolkit()->setDestinationFormat('GIF');
+        $this->assertSame('bar', $image->getToolkit()->arguments()->getSourceLocalPath());
+        $this->assertSame('PNG', $image->getToolkit()->arguments()->getSourceFormat());
+        $this->assertSame('foo', $image->getToolkit()->arguments()->getDestination());
+        $this->assertSame('baz', $image->getToolkit()->arguments()->getDestinationLocalPath());
+        $this->assertSame('GIF', $image->getToolkit()->arguments()->getDestinationFormat());
+        $image->getToolkit()->setSourceFormatFromExtension('jpg');
+        $image->getToolkit()->setDestinationFormatFromExtension('jpg');
+        $this->assertSame('JPEG', $image->getToolkit()->arguments()->getSourceFormat());
+        $this->assertSame('JPEG', $image->getToolkit()->arguments()->getDestinationFormat());
+      }
+    }
+
+    // Test retrieval of EXIF information.
+    file_unmanaged_copy(drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg', 'public://', FILE_EXISTS_REPLACE);
+    // The image files that will be tested.
+    $image_files = [
+      [
+        'path' => drupal_get_path('module', 'imagemagick') . '/misc/test-exif.jpeg',
+        'orientation' => 8,
+      ],
+      [
+        'path' => 'public://test-exif.jpeg',
+        'orientation' => 8,
+      ],
+      [
+        'path' => 'dummy-remote://test-exif.jpeg',
+        'orientation' => 8,
+      ],
+      [
+        'path' => 'public://image-test.jpg',
+        'orientation' => NULL,
+      ],
+      [
+        'path' => 'public://image-test.png',
+        'orientation' => NULL,
+      ],
+      [
+        'path' => 'public://image-test.gif',
+        'orientation' => NULL,
+      ],
+      [
+        'path' => NULL,
+        'orientation' => NULL,
+      ],
+    ];
+
+    foreach ($image_files as $image_file) {
+      // Get image using 'getimagesize'.
+      \Drupal::configFactory()->getEditable('imagemagick.settings')
+        ->set('use_identify', FALSE)
+        ->save();
+      $image = $this->imageFactory->get($image_file['path']);
+      $this->assertIdentical($image_file['orientation'], $image->getToolkit()->getExifOrientation());
     }
   }
 
@@ -596,7 +915,7 @@ class ToolkitImagemagickTest extends BrowserTestBase {
     $this->assertFieldByName('image_toolkit', 'imagemagick');
     $edit = [
       'image_toolkit' => 'gd',
-      'imagemagick[suite][path_to_binaries]' => '/foo/bar',
+      'imagemagick[suite][path_to_binaries]' => '/foo/bar/',
     ];
     $this->drupalPostForm(NULL, $edit, 'Save configuration');
     $this->assertFieldByName('image_toolkit', 'gd');
@@ -642,7 +961,7 @@ class ToolkitImagemagickTest extends BrowserTestBase {
 
     $transparent_index = imagecolortransparent($toolkit->getResource());
     if ($color_index == $transparent_index) {
-      return array(0, 0, 0, 127);
+      return [0, 0, 0, 127];
     }
 
     return array_values(imagecolorsforindex($toolkit->getResource(), $color_index));
@@ -660,18 +979,294 @@ class ToolkitImagemagickTest extends BrowserTestBase {
    *   The expected RGBA array.
    * @param int $tolerance
    *   The acceptable difference between the colors.
+   * @param string $file
+   *   The image file being tested.
+   * @param string $op
+   *   The image operation being tested.
    *
    * @return bool
    *   TRUE if the colors differences are within tolerance, FALSE otherwise.
    */
-  protected function colorsAreClose(array $actual, array $expected, $tolerance) {
+  protected function colorsAreClose(array $actual, array $expected, $tolerance, $file, $op) {
     // Fully transparent colors are equal, regardless of RGB.
     if ($actual[3] == 127 && $expected[3] == 127) {
       return TRUE;
     }
     $distance = pow(($actual[0] - $expected[0]), 2) + pow(($actual[1] - $expected[1]), 2) + pow(($actual[2] - $expected[2]), 2) + pow(($actual[3] - $expected[3]), 2);
-    $this->assertLessThanOrEqual($tolerance, $distance, "Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance);
+    $this->assertLessThanOrEqual($tolerance, $distance, "Actual: {" . implode(',', $actual) . "}, Expected: {" . implode(',', $expected) . "}, Distance: " . $distance . ", Tolerance: " . $tolerance . ", File: " . $file . ", Operation: " . $op);
     return TRUE;
   }
 
+  /**
+   * Test legacy arguments handling.
+   *
+   * @todo remove in 8.x-3.0.
+   *
+   * @group legacy
+   */
+  public function testArgumentsLegacy() {
+    $this->setUpToolkit('imagemagick');
+
+    // Prepare a copy of test files.
+    $this->getTestFiles('image');
+
+    $image_uri = "public://image-test.png";
+    $image = $this->imageFactory->get($image_uri);
+    if (!$image->isValid()) {
+      $this->fail("Could not load image $image_uri.");
+    }
+
+    // Setup a list of arguments.
+    $image->getToolkit()->addArgument("-resize 100x75!");
+    // Internal argument.
+    $image->getToolkit()->addArgument(">!>INTERNAL");
+    $image->getToolkit()->addArgument("-quality 75");
+    $image->getToolkit()->prependArgument("-hoxi 76");
+
+    // Use methods introduced in 8.x-2.3.
+    $image->getToolkit()->arguments()
+      // Pre source argument.
+      ->add("-density 25", ImagemagickExecArguments::PRE_SOURCE)
+      // Another internal argument.
+      ->add("GATEAU", ImagemagickExecArguments::INTERNAL)
+      // Another pre source argument.
+      ->add("-auchocolat 90", ImagemagickExecArguments::PRE_SOURCE)
+      // Add two arguments with additional info.
+      ->add(
+        "-addz 150",
+        ImagemagickExecArguments::POST_SOURCE,
+        ImagemagickExecArguments::APPEND,
+        [
+          'foo' => 'bar',
+          'qux' => 'der',
+        ]
+      )
+      ->add(
+        "-addz 200",
+        ImagemagickExecArguments::POST_SOURCE,
+        ImagemagickExecArguments::APPEND,
+        [
+          'wey' => 'lod',
+          'foo' => 'bar',
+        ]
+      );
+
+    // Test find arguments skipping identifiers.
+    $this->assertSame([
+      0 => '-hoxi 76',
+      1 => '-resize 100x75!',
+      2 => '>!>INTERNAL',
+      3 => '-quality 75',
+      5 => '>!>GATEAU',
+      7 => '-addz 150',
+      8 => '-addz 200',
+    ], $image->getToolkit()->getArguments());
+    $this->assertSame([2], array_keys($image->getToolkit()->arguments()->find('/^INTERNAL/')));
+    $this->assertSame([5], array_keys($image->getToolkit()->arguments()->find('/^GATEAU/')));
+    $this->assertSame([6], array_keys($image->getToolkit()->arguments()->find('/^\-auchocolat/')));
+    $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/^\-addz/')));
+    $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/.*/', NULL, ['foo' => 'bar'])));
+    $this->assertSame([], $image->getToolkit()->arguments()->find('/.*/', NULL, ['arw' => 'moo']));
+    $this->assertSame(2, $image->getToolkit()->findArgument('>!>INTERNAL'));
+    $this->assertSame(5, $image->getToolkit()->findArgument('>!>GATEAU'));
+    $this->assertFalse($image->getToolkit()->findArgument('-auchocolat'));
+
+    // Check resulting command line strings.
+    $this->assertSame('-density 25 -auchocolat 90', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+    $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+    $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->getStringForBinary());
+  }
+
+  /**
+   * Test arguments handling.
+   */
+  public function testArguments() {
+    $this->setUpToolkit('imagemagick');
+
+    // Prepare a copy of test files.
+    $this->getTestFiles('image');
+
+    $image_uri = "public://image-test.png";
+    $image = $this->imageFactory->get($image_uri);
+    if (!$image->isValid()) {
+      $this->fail("Could not load image $image_uri.");
+    }
+
+    // Setup a list of arguments.
+    $image->getToolkit()->arguments()
+      ->add("-resize 100x75!")
+      // Internal argument.
+      ->add("INTERNAL", ImagemagickExecArguments::INTERNAL)
+      ->add("-quality 75")
+      // Prepend argument.
+      ->add("-hoxi 76", ImagemagickExecArguments::POST_SOURCE, 0)
+      // Pre source argument.
+      ->add("-density 25", ImagemagickExecArguments::PRE_SOURCE)
+      // Another internal argument.
+      ->add("GATEAU", ImagemagickExecArguments::INTERNAL)
+      // Another pre source argument.
+      ->add("-auchocolat 90", ImagemagickExecArguments::PRE_SOURCE)
+      // Add two arguments with additional info.
+      ->add(
+        "-addz 150",
+        ImagemagickExecArguments::POST_SOURCE,
+        ImagemagickExecArguments::APPEND,
+        [
+          'foo' => 'bar',
+          'qux' => 'der',
+        ]
+      )
+      ->add(
+        "-addz 200",
+        ImagemagickExecArguments::POST_SOURCE,
+        ImagemagickExecArguments::APPEND,
+        [
+          'wey' => 'lod',
+          'foo' => 'bar',
+        ]
+      );
+
+    // Test find arguments skipping identifiers.
+    $this->assertSame([2], array_keys($image->getToolkit()->arguments()->find('/^INTERNAL/')));
+    $this->assertSame([5], array_keys($image->getToolkit()->arguments()->find('/^GATEAU/')));
+    $this->assertSame([6], array_keys($image->getToolkit()->arguments()->find('/^\-auchocolat/')));
+    $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/^\-addz/')));
+    $this->assertSame([7, 8], array_keys($image->getToolkit()->arguments()->find('/.*/', NULL, ['foo' => 'bar'])));
+    $this->assertSame([], $image->getToolkit()->arguments()->find('/.*/', NULL, ['arw' => 'moo']));
+
+    // Check resulting command line strings.
+    $this->assertSame('-density 25 -auchocolat 90', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+    $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+    // Add arguments with a specific index.
+    $image->getToolkit()->arguments()
+      ->add("-ix aa", ImagemagickExecArguments::POST_SOURCE, 4)
+      ->add("-ix bb", ImagemagickExecArguments::POST_SOURCE, 4);
+    $this->assertSame([4, 5], array_keys($image->getToolkit()->arguments()->find('/^\-ix/')));
+    $this->assertSame("-hoxi 76 -resize 100x75! -quality 75 -ix bb -ix aa -addz 150 -addz 200", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+    // Create a new image and inspect the arguments.
+    $image->createNew(100, 200);
+    $this->assertSame([0], array_keys($image->getToolkit()->arguments()->find('/^./', NULL, ['image_toolkit_operation' => 'create_new'])));
+    $this->assertSame([0], array_keys($image->getToolkit()->arguments()->find('/^./', NULL, ['image_toolkit_operation_plugin_id' => 'imagemagick_create_new'])));
+    $this->assertSame("-size 100x200 xc:transparent", $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+  }
+
+  /**
+   * Test module arguments alter hook.
+   */
+  public function testArgumentsAlterHook() {
+    $this->setUpToolkit('imagemagick');
+
+    $fmdm = $this->container->get('file_metadata_manager');
+
+    // Change the Advanced Colorspace setting, must be included in the command
+    // line.
+    \Drupal::configFactory()->getEditable('imagemagick.settings')
+      ->set('advanced.colorspace', 'GRAY')
+      ->save();
+
+    // Prepare a copy of test files.
+    $this->getTestFiles('image');
+    $image_uri = "public://image-test.png";
+    $image = $this->imageFactory->get($image_uri);
+    if (!$image->isValid()) {
+      $this->fail("Could not load image $image_uri.");
+    }
+
+    // Check the source colorspace.
+    $this->assertSame('SRGB', $image->getToolkit()->getColorspace());
+
+    // Setup a list of arguments.
+    $image->getToolkit()->arguments()
+      ->add("-resize 100x75!")
+      ->add("-quality 75");
+
+    // Save the derived image.
+    $image->save($image_uri . '.derived');
+
+    // Check expected command line.
+    if (substr(PHP_OS, 0, 3) === 'WIN') {
+      $expected = "-resize 100x75! -quality 75 -colorspace \"GRAY\"";
+    }
+    else {
+      $expected = "-resize 100x75! -quality 75 -colorspace 'GRAY'";
+    }
+    $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+
+    // Check that the colorspace has been actually changed in the file.
+    Cache::InvalidateTags([
+      'config:imagemagick.file_metadata_plugin.imagemagick_identify',
+    ]);
+    $fmdm->release($image_uri . '.derived');
+    $image_md = $fmdm->uri($image_uri . '.derived');
+    $image = $this->imageFactory->get($image_uri . '.derived');
+    $this->assertIdentical(FileMetadataInterface::LOADED_FROM_FILE, $image_md->isMetadataLoaded('imagemagick_identify'));
+    $this->assertSame('GRAY', $image->getToolkit()->getColorspace());
+
+    // Change the Prepend settings, must be included in the command line.
+    // Once before the source image.
+    \Drupal::configFactory()->getEditable('imagemagick.settings')
+      ->set('prepend', '-debug All')
+      ->set('prepend_pre_source', TRUE)
+      ->save();
+    $image = $this->imageFactory->get($image_uri);
+    $image->getToolkit()->arguments()
+      ->add("-resize 100x75!")
+      ->add("-quality 75");
+    $image->save($image_uri . '.derived');
+    if (substr(PHP_OS, 0, 3) === 'WIN') {
+      $expected = "-resize 100x75! -quality 75 -colorspace \"GRAY\"";
+    }
+    else {
+      $expected = "-resize 100x75! -quality 75 -colorspace 'GRAY'";
+    }
+    $this->assertSame('-debug All', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+    $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+    // Then after the source image.
+    \Drupal::configFactory()->getEditable('imagemagick.settings')
+      ->set('prepend_pre_source', FALSE)
+      ->save();
+    $image = $this->imageFactory->get($image_uri);
+    $image->getToolkit()->arguments()
+      ->add("-resize 100x75!")
+      ->add("-quality 75");
+    $image->save($image_uri . '.derived');
+    if (substr(PHP_OS, 0, 3) === 'WIN') {
+      $expected = "-debug All -resize 100x75! -quality 75 -colorspace \"GRAY\"";
+    }
+    else {
+      $expected = "-debug All -resize 100x75! -quality 75 -colorspace 'GRAY'";
+    }
+    $this->assertSame('', $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::PRE_SOURCE));
+    $this->assertSame($expected, $image->getToolkit()->arguments()->toString(ImagemagickExecArguments::POST_SOURCE));
+  }
+
+  /**
+   * Test missing command on ExecManager.
+   */
+  public function testExecManagerCommandNotFound() {
+    $exec_manager = \Drupal::service('imagemagick.exec_manager');
+    $output = '';
+    $error = '';
+    $expected = substr(PHP_OS, 0, 3) !== 'WIN' ? 127 : 1;
+    $ret = $exec_manager->runOsShell('pinkpanther', '-inspector Clouseau', 'blake', $output, $error);
+    $this->assertEquals($expected, $ret, $error);
+  }
+
+  /**
+   * Test timeout on ExecManager.
+   */
+  public function testExecManagerTimeout() {
+    $exec_manager = \Drupal::service('imagemagick.exec_manager');
+    $output = '';
+    $error = '';
+    $expected = substr(PHP_OS, 0, 3) !== 'WIN' ? 143 : 1;
+    // Set a short timeout (1 sec.) and run a process that is expected to last
+    // longer (10 secs.). Should return a 'terminate' exit code.
+    $exec_manager->setTimeout(1);
+    $ret = $exec_manager->runOsShell('sleep', '10', 'sleep', $output, $error);
+    $this->assertEquals($expected, $ret, $error);
+  }
+
 }