tcpdf/tests/compare_runs.php

472 lines
16 KiB
PHP

#!/usr/bin/env php
<?php
/**
* This script compares two runs of the test suite.
*
* The comparison is not bit by bit, as most of the generated
* files have some time-induced differences.
*
* This script will however point out gross discrepancies.
*
* PDF documents will be compared as PNG images.
*
* @author Philippe Jausions
* @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
*/
namespace Tecnickcom\TCPDF\Tests;
use LocateBinaries\LocateBinaries;
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
echo 'Run `composer install` in the tests/ directory first.' . PHP_EOL;
exit(-1);
}
require __DIR__ . '/vendor/autoload.php';
$options = getopt('o:vhr:', array(
'group:',
'output-dir:',
'reference-dir:',
'stop-on-defect',
'verbose',
'help'
));
function printCompareRunsHelp()
{
echo 'Usage:' . PHP_EOL;
echo ' php compare_runs.php [-chv] [-o <path>] -r referenceDir compareDir' . PHP_EOL;
echo 'Description:' . PHP_EOL;
echo ' Compares 2 runs of the TCPDF test suite.' . PHP_EOL;
echo " PDF files are first converted to PNG images using XpdfReader's `pdftopng` tool" . PHP_EOL;
echo " or using Poppler's `pdftoppm`." . PHP_EOL;
echo " PNG images are compared using ImageMagick's `magick` tool." . PHP_EOL;
echo 'Supported environment variables:' . PHP_EOL;
echo ' PDFINFO_BINARY Path to pdfinfo executable to use.' . PHP_EOL;
echo ' For more information on pdfinfo, visit https://www.xpdfreader.com/' . PHP_EOL;
echo ' or https://poppler.freedesktop.org/' . PHP_EOL;
echo ' PDFTOPNG_BINARY Path to pdftopng executable to use.' . PHP_EOL;
echo ' For more information on pdftopng, visit https://www.xpdfreader.com/' . PHP_EOL;
echo ' PDFTOPPM_BINARY Path to pdftoppm executable to use.' . PHP_EOL;
echo ' For more information on pdftoppm, visit https://poppler.freedesktop.org/' . PHP_EOL;
echo ' MAGICK_BINARY Path to magick executable to use.' . PHP_EOL;
echo ' For more information on magick, visit https://imagemagick.org/' . PHP_EOL;
echo 'Arguments:' . PHP_EOL;
echo ' compareDir' . PHP_EOL;
echo ' Path to the folder containing the results of test run to compare' . PHP_EOL;
echo ' with the reference.' . PHP_EOL;
echo 'Options:' . PHP_EOL;
echo ' -c, --clean-up' . PHP_EOL;
echo ' Clean up generated files. NOT IMPLEMENTED YET.' . PHP_EOL;
echo ' -o <path>, --output-dir=<path>' . PHP_EOL;
echo ' The folder in which files should be generated.' . PHP_EOL;
echo ' Default is to create a folder in the system\'s temporary folder.' . PHP_EOL;
echo ' -r <path>, --reference-dir=<path>' . PHP_EOL;
echo ' Path to the folder containing the results of the test run of reference.' . PHP_EOL;
echo ' If no reference folder is provided, a set will be automatically downloaded.' . PHP_EOL;
echo ' --group=<name>' . PHP_EOL;
echo ' Filter the tests to compare based on the @group annotation present in the file.' . PHP_EOL;
echo ' --stop-on-defect' . PHP_EOL;
echo ' Stop execution upon first difference.' . PHP_EOL;
echo ' -v, --verbose' . PHP_EOL;
echo ' Outputs more information.' . PHP_EOL;
echo ' -h, --help' . PHP_EOL;
echo ' Prints this message.' . PHP_EOL;
}
if (false === $options
|| array_key_exists('h', $options)
|| array_key_exists('help', $options)) {
printCompareRunsHelp();
exit(false === $options ? -1 : 0);
}
if (!empty($options['o'])) {
$outputDir = $options['o'];
}
if (!empty($options['output-dir'])) {
$outputDir = $options['output-dir'];
}
if (!empty($options['r'])) {
$referenceDirArg = $options['r'];
}
if (!empty($options['reference-dir'])) {
$referenceDirArg = $options['reference-dir'];
}
if (!isset($referenceDirArg)) {
echo "Please provide the reference folder using the -r or --reference-dir argument." . PHP_EOL;
exit(-1);
}
// TODO Automate the cleanup of generated files (PNG images from PDF conversion)
if (array_key_exists('c', $options) || array_key_exists('clean-up', $options)) {
$preserveOutputFiles = false;
} elseif (isset($outputDir)) {
$preserveOutputFiles = true;
} else {
$preserveOutputFiles = false;
}
$stopOnDefect = array_key_exists('stop-on-defect', $options);
$verbose = array_key_exists('v', $options) || array_key_exists('verbose', $options);
$groups = array();
if (!empty($options['group'])) {
if (is_array($options['group'])) {
$groups = $options['group'];
} else {
$groups = explode(',', $options['group']);
}
}
// Yes, this is a very basic check.
if ($argc < 2) {
echo "Please provide folder path" . PHP_EOL;
exit(-1);
}
$compareDirArg = $argv[$argc - 1];
if (!is_dir(realpath($compareDirArg))) {
echo "Could not find folder to compare: $compareDirArg" . PHP_EOL;
exit(-1);
}
// Normalize the folder path to end with a slash
$compareDir = rtrim(realpath($compareDirArg), '/\\') . DIRECTORY_SEPARATOR;
if (!is_dir(realpath($referenceDirArg))) {
echo "Could not find reference folder: $referenceDirArg" . PHP_EOL;
exit(-1);
}
// Normalize the folder path to end with a slash
$referenceDir = trim(realpath($referenceDirArg), '/\\') . DIRECTORY_SEPARATOR;
$rootDir = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$exampleDir = $rootDir . 'examples' . DIRECTORY_SEPARATOR;
$isBinaryLocatorAvailable = class_exists('\LocateBinaries\LocateBinaries');
$pdfinfo = getenv('PDFINFO_BINARY');
if (empty($pdfinfo)) {
$paths = ($isBinaryLocatorAvailable)
? LocateBinaries::locateInstalledBinaries('pdfinfo')
: array();
if (empty($paths)) {
echo 'pdfinfo could not be located.' . PHP_EOL;
echo 'Please set the PDFINFO_BINARY environment variable.' . PHP_EOL;
if (!$isBinaryLocatorAvailable) {
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
}
exit(-1);
}
$pdfinfo = reset($paths);
}
$pdftopng = getenv('PDFTOPNG_BINARY');
if (empty($pdftopng)) {
$paths = ($isBinaryLocatorAvailable)
? LocateBinaries::locateInstalledBinaries('pdftopng')
: array();
if (!empty($paths)) {
$pdftopng = reset($paths);
}
}
$pdftoppm = getenv('PDFTOPPM_BINARY');
if (empty($pdftoppm)) {
$paths = ($isBinaryLocatorAvailable)
? LocateBinaries::locateInstalledBinaries('pdftoppm')
: array();
if (!empty($paths)) {
$pdftoppm = reset($paths);
}
}
if (empty($pdftopng) && empty($pdftoppm)) {
echo 'pdftopng nor pdftoppm could not be located.' . PHP_EOL;
echo 'Please set the PDFTOPNG_BINARY or PDFTOPPM_BINARY environment variable.' . PHP_EOL;
if (!$isBinaryLocatorAvailable) {
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
}
exit(-1);
}
$tools = array(
'pdfinfo' => $pdfinfo,
'pdftopng' => $pdftopng,
'pdftoppm' => $pdftoppm,
);
$pdfTools = new PdfTools($tools, $verbose);
echo 'pdfinfo: ' . $pdfinfo . PHP_EOL;
echo 'pdfinfo version: ' . $pdfTools->getPdfinfoVersionInfo() . PHP_EOL;
echo PHP_EOL;
echo 'pdftopng: ' . $pdftopng . PHP_EOL;
echo 'pdftopng version: ' . $pdfTools->getPdftopngVersionInfo() . PHP_EOL;
echo PHP_EOL;
$magick = getenv('MAGICK_BINARY');
if (empty($magick)) {
$paths = ($isBinaryLocatorAvailable)
? LocateBinaries::locateInstalledBinaries('magick')
: array();
if (empty($paths)) {
echo 'magick could not be located.' . PHP_EOL;
echo 'Please set the MAGICK_BINARY environment variable.' . PHP_EOL;
if (!$isBinaryLocatorAvailable) {
echo 'You could install rosell-dk/locate-binaries via composer to detect binaries.' . PHP_EOL;
}
exit(-1);
}
$magick = reset($paths);
}
$imagemagick = new ImageMagick($magick, $verbose);
echo 'magick: ' . $magick . PHP_EOL;
echo 'magick version: ' . $imagemagick->getMagickVersionInfo() . PHP_EOL;
echo PHP_EOL;
$isGeneratedTempDir = false;
if (!isset($outputDir)) {
echo PHP_EOL;
echo "The --output-dir option was not used, a temporary folder will be necessary." . PHP_EOL;
try {
$outputDir = \Cs278\Mktemp\temporaryDir('TCPDF-tests.XXXXXXXXX') . DIRECTORY_SEPARATOR;
} catch (\Exception $e) {
echo $e->getMessage();
exit(-1);
}
$isGeneratedTempDir = true;
}
if (!is_dir(realpath($outputDir))) {
echo "Could not find output folder: $outputDir" . PHP_EOL;
exit(-1);
}
echo "Example folder: $exampleDir" . PHP_EOL;
$outputDir = realpath($outputDir);
$outputDir = rtrim($outputDir, '/\\') . DIRECTORY_SEPARATOR;
echo "Output folder: $outputDir" . PHP_EOL;
echo PHP_EOL;
echo 'Comparing folders:' . PHP_EOL;
echo "< $referenceDir" . PHP_EOL;
echo "> $compareDir" . PHP_EOL;
$separator = '----------------------------------';
$testRunner = new TestRunner($exampleDir);
$differences = array();
$ignored = array();
$comparisons = 0;
$bitByBitComparisons = 0;
$testFiles = $testRunner
->filterByGroup($groups)
->getTestFiles()
;
foreach ($testFiles as $file => $type) {
++$comparisons;
$outputFile = basename($file, '.php') . '.output.' . strtolower($type);
$outputFile1 = $referenceDir . $outputFile;
$outputFile2 = $compareDir . $outputFile;
$exists1 = file_exists($outputFile1);
$exists2 = file_exists($outputFile2);
if (!$exists1 && !$exists2) {
$ignored[] = $file;
continue;
}
if ($exists1 && !$exists2) {
$differences[] = $file . ' (FILE PRESENCE)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< PRESENT' . PHP_EOL;
echo '> MISSING' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
if (!$exists1 && $exists2) {
$differences[] = $file . ' (FILE PRESENCE)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< ABSENT' . PHP_EOL;
echo '> PRESENT' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
// Exact size comparison is not workable for most of the files
$size1 = filesize($outputFile1);
$size2 = filesize($outputFile2);
if ($size1 === 0 && $size2 > 0) {
$differences[] = $file . ' (FILE SIZE)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< EMPTY' . PHP_EOL;
echo '> HAS CONTENT' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
if ($size1 > 0 && $size2 === 0) {
$differences[] = $file . ' (FILE SIZE)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< HAS CONTENT' . PHP_EOL;
echo '> EMPTY' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
if ($size1 === 0 && $size2 === 0) {
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< EMPTY' . PHP_EOL;
echo '> EMPTY' . PHP_EOL;
continue;
}
// Can we compare files?
// For now, just some example files are easily comparable.
// (See the @group comparable docBlock annotation)
$examplePHPSource = file_get_contents($exampleDir . $file);
if (strpos($examplePHPSource, '* @group comparable') !== false) {
++$bitByBitComparisons;
$pngFiles1 = null;
$pngFiles2 = null;
if ('PDF' !== $type) {
$hash1 = sha1_file($outputFile1);
$hash2 = sha1_file($outputFile2);
if ($hash1 !== $hash2) {
$differences[] = $file . ' (CONTENT)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< ORIGIN' . PHP_EOL;
echo '> DIFFERENT' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
}
if ('PNG' === $type) {
$pngFiles1 = array($outputFile1);
$pngFiles2 = array($outputFile2);
}
// For PDF files, we generate PNG files (one per page) with pdftopng
if ('PDF' === $type) {
// Let's first check there are both valid PDF files
$isPdf1 = $pdfTools->isPdf($outputFile1);
$isPdf2 = $pdfTools->isPdf($outputFile2);
if ($isPdf1 && !$isPdf2) {
$differences[] = $file . ' (CONTENT)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< PDF' . PHP_EOL;
echo '> NOT PDF' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
if (!$isPdf1 && $isPdf2) {
$differences[] = $file . ' (CONTENT)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< NOT PDF' . PHP_EOL;
echo '> PDF' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
if (!$isPdf1 && !$isPdf2) {
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< NOT PDF' . PHP_EOL;
echo '> NOT PDF' . PHP_EOL;
continue;
}
// Now we convert each page of the PDF into PNG
$conversionDir = $outputDir . basename($file, '.php') . DIRECTORY_SEPARATOR;
$pngFiles1 = $pdfTools->convertToPng($outputFile1, $conversionDir . 'ref');
$pngFiles2 = $pdfTools->convertToPng($outputFile2, $conversionDir . 'cmp');
if (count($pngFiles1) !== count($pngFiles2)) {
$differences[] = $file . ' (CONTENT)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< ' . count($pngFiles1) . ' page(s)' . PHP_EOL;
echo '> ' . count($pngFiles2) . ' page(s)' . PHP_EOL;
if ($stopOnDefect) {
break;
}
continue;
}
}
if (isset($pngFiles1, $pngFiles2)) {
$pngCount = count($pngFiles1);
for ($i = 0; $i < $pngCount; ++$i) {
if (!$imagemagick->areSimilar($pngFiles1[$i], $pngFiles2[$i])) {
$differences[] = $file . ' (CONTENT)';
echo $separator . PHP_EOL;
echo $outputFile . PHP_EOL;
echo '< ORIGIN' . PHP_EOL;
echo '> DIFFERENT' . PHP_EOL;
if ($stopOnDefect) {
break 2;
}
continue 2;
}
}
}
}
}
$passed = empty($differences);
echo PHP_EOL;
if ($passed) {
echo 'Comparison: IDENTICAL' . PHP_EOL;
} else {
echo 'Comparison: DIFFERENCES EXIST' . PHP_EOL;
}
echo " Total tests: $comparisons" . PHP_EOL;
$ignoredCount = count($ignored);
if ($ignoredCount > 0) {
echo ' Probably skipped tests: ' . $ignoredCount . PHP_EOL;
foreach ($ignored as $ignoredFile) {
echo ' ' . $ignoredFile . PHP_EOL;
}
}
echo " Exact content comparisons: " . $bitByBitComparisons . PHP_EOL;
if ($differences) {
echo ' Differences: ' . count($differences) . PHP_EOL;
echo ' Files:' . PHP_EOL;
foreach ($differences as $file) {
echo " $file" . PHP_EOL;
}
}
$exitCode = (!$passed) ? 1 : 0;
echo 'Exit code: ' . $exitCode . PHP_EOL;
exit($exitCode);