Skip to content

Commit 7cf7182

Browse files
committed
ci: Add tests for document minification feature.
1 parent adec29d commit 7cf7182

7 files changed

Lines changed: 205 additions & 42 deletions

File tree

.gitlab-ci.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ build image:
4949
BUILDKIT_EXTRA_ARGS: "--opt build-arg:PHP_VERSION=${PHP_VERSION}"
5050
parallel:
5151
matrix:
52+
- PHP_VERSION: "8.3-alpine"
53+
- PHP_VERSION: "8.2-alpine"
5254
- PHP_VERSION: "8.1-alpine"
5355
- PHP_VERSION: "8.0-alpine"
5456
- PHP_VERSION: "7.4-alpine"
@@ -58,7 +60,7 @@ build image:
5860

5961
.code_sniffer_base:
6062
stage: check
61-
image: ${DOCKER_IMAGE_PREFIX}_8.1-alpine
63+
image: ${DOCKER_IMAGE_PREFIX}_8.3-alpine
6264
script:
6365
- vendor/bin/phpcs
6466

@@ -75,7 +77,7 @@ code_sniffer_manual:
7577

7678
.license_check_base:
7779
stage: check
78-
image: ${DOCKER_IMAGE_PREFIX}_8.1-alpine
80+
image: ${DOCKER_IMAGE_PREFIX}_8.3-alpine
7981
script:
8082
- ./license_checker.sh '*.php' | tee license_check_output.txt
8183
- '[ ! -s license_check_output.txt ]'
@@ -108,15 +110,18 @@ secret_detection:
108110
retry: 1
109111
parallel:
110112
matrix:
111-
- PHP_VERSION: "8.1-alpine"
113+
- PHP_VERSION: "8.3-alpine"
114+
- PHP_VERSION: "8.3-alpine"
115+
USE_MOCK_SERVER: "use mock server"
116+
- PHP_VERSION: "8.2-alpine"
117+
USE_MOCK_SERVER: "use mock server"
112118
- PHP_VERSION: "8.1-alpine"
113119
USE_MOCK_SERVER: "use mock server"
114120
- PHP_VERSION: "8.0-alpine"
115121
USE_MOCK_SERVER: "use mock server"
116122
- PHP_VERSION: "7.4-alpine"
117123
USE_MOCK_SERVER: "use mock server"
118124
- PHP_VERSION: "7.3-alpine"
119-
USE_MOCK_SERVER: "use mock server"
120125
image: ${DOCKER_IMAGE_PREFIX}_${PHP_VERSION}
121126
script:
122127
- >

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ARG PHP_VERSION
22
FROM php:${PHP_VERSION}
33
RUN apk update && apk upgrade --no-cache
4-
RUN apk add git -q
4+
RUN apk add git libpng-dev libzip-dev -q && docker-php-ext-install zip && docker-php-ext-install gd
55

66
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
77
RUN php composer-setup.php --install-dir /

phpunit.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
32
<phpunit colors="true" convertWarningsToExceptions="false">
43
<testsuites>
54
<testsuite name="deepl-php Test Suite">
@@ -18,4 +17,8 @@
1817
<logging>
1918
<junit outputFile="reports/junit.xml" />
2019
</logging>
20+
21+
<php>
22+
<ini name="memory_limit" value="256M" />
23+
</php>
2124
</phpunit>

src/DocumentMinifier.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ public function minifyDocument(string $inputFilePath, $cleanup = false): string
169169
if ($filesizeResponse !== false && $filesizeResponse) {
170170
if ($filesizeResponse > DocumentMinifier::MINIFIED_DOC_SIZE_LIMIT_WARNING) {
171171
trigger_error(
172-
'The input file could not be minified below 5 MB, likely a media type is missing. This might cause'
173-
.' translation to fail.',
172+
'The input file could not be minified below 5 MB, likely a media type is unsupported. This might '
173+
.'cause translation to fail.',
174174
E_USER_WARNING
175175
);
176176
}

tests/DeepLTestBase.php

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
namespace DeepL;
88

99
use PHPUnit\Framework\TestCase;
10-
use Psr\Http\Client\ClientInterface;
1110
use Ramsey\Uuid\Uuid;
1211

1312
class DeepLTestBase extends TestCase
@@ -68,11 +67,13 @@ class DeepLTestBase extends TestCase
6867
];
6968

7069
protected const DOC_MINIFICATION_TEST_FILES_MAPPING = [
71-
'example_document_template.docx' => '',
72-
'example_presentation_template.pptx' => '',
73-
'example_zip_template.zip' => '',
70+
'example_document_template.docx' => 'example_document.docx',
71+
'example_presentation_template.pptx' => 'example_presentation.pptx',
7472
];
7573

74+
protected const DOC_MINIFICATION_UNSUPPORTED_TEST_TEMPLATE = 'example_zip_template.zip';
75+
protected const DOC_MINIFICATION_UNSUPPORTED_TEST_FILE = 'example_zip.zip';
76+
7677
protected const EXAMPLE_DOCUMENT_INPUT = DeepLTestBase::EXAMPLE_TEXT['en'];
7778
protected const EXAMPLE_DOCUMENT_OUTPUT = DeepLTestBase::EXAMPLE_TEXT['de'];
7879
protected $EXAMPLE_LARGE_DOCUMENT_INPUT;
@@ -248,48 +249,45 @@ public function assertExceptionClass($class, callable $function): \Exception
248249
$this->fail("Expected exception of class '$class' but nothing was thrown");
249250
}
250251

251-
public function createDocumentMinificationTestFiles(): void
252+
public static function getFullPathForTestFile(string $testFileName): string
253+
{
254+
return __DIR__ . '/../resources/' . $testFileName;
255+
}
256+
257+
public static function createDocumentMinificationTestFiles(): void
252258
{
253259
foreach (DeepLTestBase::DOC_MINIFICATION_TEST_FILES_MAPPING as $templateFilename => $inflatedFilename) {
254-
$testDocTemplateFile = __DIR__ . '/../resources/' . $templateFilename;
255-
$testDocInflatedFile = __DIR__ . '/../resources/' . $inflatedFilename;
256-
$this->inflateTestFileWithLargeImage($testDocTemplateFile, $testDocInflatedFile);
260+
$testDocTemplateFile = self::getFullPathForTestFile($templateFilename);
261+
$testDocInflatedFile = self::getFullPathForTestFile($inflatedFilename);
262+
self::inflateTestFileWithLargeImage($testDocTemplateFile, $testDocInflatedFile);
257263
}
258264
}
259265

260-
public function removeDocumentMinificationTestFiles()
266+
public static function removeDocumentMinificationTestFiles()
261267
{
262268
foreach (DeepLTestBase::DOC_MINIFICATION_TEST_FILES_MAPPING as $inflatedFileToDelete) {
263269
unlink($inflatedFileToDelete);
264270
}
265271
}
266272

267-
private function inflateTestFileWithLargeImage(string $inputFile, string $outputFile)
273+
protected static function inflateTestFileWithLargeImage(string $inputFile, string $outputFile)
268274
{
269-
$extractionDir = __DIR__ . '/../resources/';
275+
$extractionDir = self::getFullPathForTestFile('inflation_tmp_dir');
270276
if (!mkdir($extractionDir)) {
271-
throw new \RuntimeException('Failed creating dir for test files for doc minification');
277+
throw new \RuntimeException("Failed creating dir $extractionDir for test files for doc minification");
272278
}
273-
$zip = new \ZipArchive();
274-
if ($zip->open($inputFile) === true) {
275-
$zip->extractTo($extractionDir);
276-
$zip->close();
277-
} else {
278-
throw new \RuntimeException('Failed inflating test file for doc minification');
279-
}
280-
281-
$inflatedImage = imagecreatetruecolor(18384, 18384);
282-
$white = imagecolorallocate($inflatedImage, 255, 255, 255);
283-
imagefill($inflatedImage, 0, 0, $white);
284-
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($extractionDir));
285-
foreach ($iterator as $file) {
286-
if ($file->getExtension() == 'png') {
287-
imagepng($inflatedImage, $file->getPathname());
288-
}
289-
}
290-
279+
self::extractZipFileTo($inputFile, $extractionDir);
291280
$zip = new \ZipArchive();
292281
if ($zip->open($outputFile, \ZipArchive::CREATE) === true) {
282+
$fakeImageName = 'placeholder_image.png';
283+
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+=-<,>.?:';
284+
$length = 90000000;
285+
# str_shuffle is not cryptographically secure, but this is just test data
286+
$randomString = substr(str_shuffle(
287+
str_repeat($characters, ceil($length/strlen($characters)))
288+
), 1, $length);
289+
$zip->addFromString($fakeImageName, $randomString);
290+
293291
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($extractionDir));
294292
foreach ($iterator as $file) {
295293
if (substr($file, -2) === '/.' || substr($file, -3) === '/..') {
@@ -298,7 +296,6 @@ private function inflateTestFileWithLargeImage(string $inputFile, string $output
298296
$file->isDir() ?
299297
$zip->addEmptyDir(str_replace($extractionDir . '/', '', $file . '/'))
300298
: $zip->addFile($file, str_replace($extractionDir . '/', '', $file));
301-
$zip->setCompressionName($file, \ZipArchive::CM_STORE);
302299
}
303300
$zip->close();
304301
} else {
@@ -308,6 +305,61 @@ private function inflateTestFileWithLargeImage(string $inputFile, string $output
308305
DocumentMinifier::recursivelyDeleteDirectory($extractionDir);
309306
}
310307

308+
protected static function extractZipFileTo(string $zipFilePath, string $extractionDirPath): void
309+
{
310+
if (!is_dir($extractionDirPath)) {
311+
mkdir($extractionDirPath);
312+
}
313+
$zip = new \ZipArchive();
314+
if ($zip->open($zipFilePath) === false) {
315+
throw new \RuntimeException("Failed opening zip file $zipFilePath");
316+
}
317+
$zip->extractTo($extractionDirPath);
318+
$zip->close();
319+
}
320+
321+
public function assertDirectoriesAreEqual(string $dir1, string $dir2, string $message = ''): void
322+
{
323+
$dir1Hashes = $this->getDirectoryContentsToHashes($dir1);
324+
$dir2Hashes = $this->getDirectoryContentsToHashes($dir2);
325+
326+
$this->assertAssociativeArraysAreValueEqual($dir1Hashes, $dir2Hashes, $message);
327+
}
328+
329+
protected function assertAssociativeArraysAreValueEqual(array $array1, array $array2, string $message = ''): void
330+
{
331+
$this->assertEquals(count($array1), count($array2));
332+
foreach ($array1 as $key1 => $value1) {
333+
if (is_string($value1)) {
334+
$this->assertEquals($value1, $array2[$key1], $message);
335+
} else {
336+
$this->assertAssociativeArraysAreValueEqual($array1[$key1], $array2[$key1], $message);
337+
}
338+
}
339+
}
340+
341+
protected function getDirectoryContentsToHashes(string $dir): array
342+
{
343+
$hashes = array();
344+
if (is_dir($dir)) {
345+
$objects = scandir($dir);
346+
foreach ($objects as $path) {
347+
if ($path !== '.' && $path !== '..') {
348+
$absolutePath = $dir . '/' . $path;
349+
if (is_dir($path)) {
350+
$hashes[$path] = $this->getDirectoryContentsToHashes($absolutePath);
351+
} else {
352+
$hashes[$path] = hash_file('md5', $absolutePath);
353+
if ($hashes[$path] === false) {
354+
throw new \RuntimeException("Failed hashing $absolutePath");
355+
}
356+
}
357+
}
358+
}
359+
}
360+
return $hashes;
361+
}
362+
311363
/**
312364
* This is necessary due to https://github.com/php-mock/php-mock-phpunit#restrictions
313365
* In short, as these methods can be called by other tests before UserAgentTest and other
@@ -324,7 +376,7 @@ public static function setUpBeforeClass(): void
324376
self::defineFunctionMock(__NAMESPACE__, 'curl_setopt_array');
325377
}
326378

327-
public function provideHttpClient()
379+
public static function provideHttpClient()
328380
{
329381
return [[null], [new \GuzzleHttp\Client()]];
330382
}

tests/DocumentMinificationTest.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
// Copyright 2024 DeepL SE (https://www.deepl.com)
4+
// Use of this source code is governed by an MIT
5+
// license that can be found in the LICENSE file.
6+
7+
namespace DeepL;
8+
9+
use \Psr\Http\Client\ClientInterface;
10+
11+
class DocumentMinificationTest extends DeepLTestBase
12+
{
13+
protected static $unsupportedTestFile;
14+
protected static $supportedTestFile;
15+
protected static $tmpDir;
16+
17+
public static function setUpBeforeClass(): void
18+
{
19+
parent::setUpBeforeClass();
20+
self::$unsupportedTestFile = self::getFullPathForTestFile(
21+
DeepLTestBase::DOC_MINIFICATION_UNSUPPORTED_TEST_FILE
22+
);
23+
self::inflateTestFileWithLargeImage(
24+
self::getFullPathForTestFile(DeepLTestBase::DOC_MINIFICATION_UNSUPPORTED_TEST_TEMPLATE),
25+
self::$unsupportedTestFile
26+
);
27+
self::createDocumentMinificationTestFiles();
28+
self::$supportedTestFile = self::getFullPathForTestFile(DeepLTestBase::DOC_MINIFICATION_TEST_FILES_MAPPING[
29+
array_key_first(DeepLTestBase::DOC_MINIFICATION_TEST_FILES_MAPPING)
30+
]);
31+
self::$tmpDir = self::getFullPathForTestFile('tmp_dir');
32+
mkdir(self::$tmpDir);
33+
}
34+
35+
public static function tearDownAfterClass(): void
36+
{
37+
parent::tearDownAfterClass();
38+
self::removeDocumentMinificationTestFiles();
39+
unlink(self::getFullPathForTestFile(DeepLTestBase::DOC_MINIFICATION_UNSUPPORTED_TEST_FILE));
40+
if (is_dir(self::$tmpDir)) {
41+
rmdir(self::$tmpDir);
42+
}
43+
}
44+
45+
public function testMinifyDocumentHappyPath()
46+
{
47+
$minifier = new DocumentMinifier(self::$tmpDir);
48+
$minifiedDoc = $minifier->minifyDocument(self::$supportedTestFile, false);
49+
$fileSize = filesize($minifiedDoc);
50+
$this->assertLessThan(50000, $fileSize, 'Did not properly minify document (resulting file too large)');
51+
$this->assertGreaterThan(100, $fileSize, 'Did not properly minify document (resulting file too small)');
52+
53+
$minifier->recursivelyDeleteDirectory($minifier->getExtractedDocDirectory());
54+
$minifier->recursivelyDeleteDirectory($minifier->getOriginalMediaDirectory());
55+
unlink($minifier->getMinifiedDocFile($minifiedDoc));
56+
}
57+
58+
public function testDeminifyDocumentHappyPath()
59+
{
60+
$outputFile = self::getFullPathForTestFile('example_zip_transformed.zip');
61+
$minifier = new DocumentMinifier(self::$tmpDir);
62+
$minifiedFile = $minifier->minifyDocument(self::$unsupportedTestFile, true);
63+
$minifier->deminifyDocument($minifiedFile, $outputFile, false);
64+
65+
$inputExtractionDir = self::$tmpDir . '/input_dir';
66+
$outputExtractionDir = self::$tmpDir . '/output_dir';
67+
$this->extractZipFileTo(self::$unsupportedTestFile, $inputExtractionDir);
68+
$this->extractZipFileTo($outputFile, $outputExtractionDir);
69+
$this->assertDirectoriesAreEqual(
70+
$inputExtractionDir,
71+
$outputExtractionDir,
72+
'Minified + deminified file are not identical! This could happen if a different compression algorithm '
73+
. 'was used to create the input, but indicates an issue.'
74+
);
75+
76+
$minifier->recursivelyDeleteDirectory(self::$tmpDir);
77+
unlink($outputFile);
78+
}
79+
80+
/**
81+
* @dataProvider provideHttpClient
82+
*/
83+
public function testMinifyAndTranslateDocuments(?ClientInterface $httpClient)
84+
{
85+
$this->needsRealServer();
86+
$translator = $this->makeTranslator([TranslatorOptions::HTTP_CLIENT => $httpClient]);
87+
list(, , , $outputDocumentPath) = $this->tempFiles();
88+
foreach (DeepLTestBase::DOC_MINIFICATION_TEST_FILES_MAPPING as $inflated) {
89+
$curDoc = self::getFullPathForTestFile($inflated);
90+
$status = $translator->translateDocument(
91+
$curDoc,
92+
$outputDocumentPath,
93+
'en',
94+
'de',
95+
array(TranslateDocumentOptions::ENABLE_DOCUMENT_MINIFICATION => true)
96+
);
97+
$this->assertEquals(50000, $status->billedCharacters);
98+
$this->assertEquals('done', $status->status);
99+
$this->assertTrue($status->done());
100+
unlink($outputDocumentPath);
101+
}
102+
}
103+
}

tests/TranslateTextTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function testLangCodeMixedCase(?ClientInterface $httpClient)
9292
$this->assertEquals('en', $result->detectedSourceLang);
9393
}
9494

95-
public function deprecatedTargetLang(): array
95+
public static function deprecatedTargetLang(): array
9696
{
9797
return [['en'], ['pt']];
9898
}
@@ -127,7 +127,7 @@ public function testInvalidTargetLanguage(?ClientInterface $httpClient)
127127
$translator->translateText(DeepLTestBase::EXAMPLE_TEXT['de'], null, 'xx');
128128
}
129129

130-
public function invalidTextParameters(): array
130+
public static function invalidTextParameters(): array
131131
{
132132
return [[42], [[42]]];
133133
}

0 commit comments

Comments
 (0)