da203d4fa30650e6e293eba5aed1d6c372a107a3
[moodle.git] / lib / filestorage / tests / zip_packer_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for /lib/filestorage/zip_packer.php and zip_archive.php
19  *
20  * @package   core_files
21  * @category  phpunit
22  * @copyright 2012 Petr Skoda
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/filestorage/file_progress.php');
31 class core_files_zip_packer_testcase extends advanced_testcase implements file_progress {
32     protected $testfile;
33     protected $files;
35     /**
36      * @var array Progress information passed to the progress reporter
37      */
38     protected $progress;
40     protected function setUp() {
41         parent::setUp();
43         $this->testfile = __DIR__.'/fixtures/test.txt';
45         $fs = get_file_storage();
46         $context = context_system::instance();
47         if (!$file = $fs->get_file($context->id, 'phpunit', 'data', 0, '/', 'test.txt')) {
48             $file = $fs->create_file_from_pathname(
49                 array('contextid'=>$context->id, 'component'=>'phpunit', 'filearea'=>'data', 'itemid'=>0, 'filepath'=>'/', 'filename'=>'test.txt'),
50                 $this->testfile);
51         }
53         $this->files = array(
54             'test.test' => $this->testfile,
55             'testíček.txt' => $this->testfile,
56             'Prüfung.txt' => $this->testfile,
57             '测试.txt' => $this->testfile,
58             '試験.txt' => $this->testfile,
59             'Žluťoučký/Koníček.txt' => $file,
60         );
61     }
63     public function test_get_packer() {
64         $this->resetAfterTest(false);
65         $packer = get_file_packer();
66         $this->assertInstanceOf('zip_packer', $packer);
68         $packer = get_file_packer('application/zip');
69         $this->assertInstanceOf('zip_packer', $packer);
70     }
72     /**
73      * @depends test_get_packer
74      */
75     public function test_list_files() {
76         $this->resetAfterTest(false);
78         $files = array(
79             __DIR__.'/fixtures/test_moodle_22.zip',
80             __DIR__.'/fixtures/test_moodle.zip',
81             __DIR__.'/fixtures/test_tc_8.zip',
82             __DIR__.'/fixtures/test_7zip_927.zip',
83             __DIR__.'/fixtures/test_winzip_165.zip',
84             __DIR__.'/fixtures/test_winrar_421.zip',
85             __DIR__.'/fixtures/test_thumbsdb.zip',
86         );
88         if (function_exists('normalizer_normalize')) {
89             // Unfortunately there is no way to standardise UTF-8 strings without INTL extension.
90             $files[] = __DIR__.'/fixtures/test_infozip_3.zip';
91             $files[] = __DIR__.'/fixtures/test_osx_1074.zip';
92             $files[] = __DIR__.'/fixtures/test_osx_compress.zip';
93         }
95         $packer = get_file_packer('application/zip');
97         foreach ($files as $archive) {
98             $archivefiles = $packer->list_files($archive);
99             $this->assertTrue(is_array($archivefiles), "Archive not extracted properly: ".basename($archive).' ');
100             $this->assertTrue(count($this->files) === count($archivefiles) or count($this->files) === count($archivefiles) - 1); // Some zippers create empty dirs.
101             foreach ($archivefiles as $file) {
102                 if ($file->pathname === 'Žluťoučký/') {
103                     // Some zippers create empty dirs.
104                     continue;
105                 }
106                 $this->assertArrayHasKey($file->pathname, $this->files, "File $file->pathname not extracted properly: ".basename($archive).' ');
107             }
108         }
110         // Windows packer supports only DOS encoding.
111         $archive = __DIR__.'/fixtures/test_win8_de.zip';
112         $archivefiles = $packer->list_files($archive);
113         $this->assertTrue(is_array($archivefiles), "Archive not extracted properly: ".basename($archive).' ');
114         $this->assertEquals(2, count($archivefiles));
115         foreach ($archivefiles as $file) {
116             $this->assertTrue($file->pathname === 'Prüfung.txt' or $file->pathname === 'test.test');
117         }
119         $zip_archive = new zip_archive();
120         $zip_archive->open(__DIR__.'/fixtures/test_win8_cz.zip', file_archive::OPEN, 'cp852');
121         $archivefiles = $zip_archive->list_files();
122         $this->assertTrue(is_array($archivefiles), "Archive not extracted properly: ".basename($archive).' ');
123         $this->assertEquals(3, count($archivefiles));
124         foreach ($archivefiles as $file) {
125             $this->assertTrue($file->pathname === 'Žluťoučký/Koníček.txt' or $file->pathname === 'testíček.txt' or $file->pathname === 'test.test');
126         }
127         $zip_archive->close();
129         // Empty archive extraction.
130         $archive = __DIR__.'/fixtures/empty.zip';
131         $archivefiles = $packer->list_files($archive);
132         $this->assertSame(array(), $archivefiles);
133     }
135     /**
136      * @depends test_list_files
137      */
138     public function test_archive_to_pathname() {
139         global $CFG;
141         $this->resetAfterTest(false);
143         $packer = get_file_packer('application/zip');
144         $archive = "$CFG->tempdir/archive.zip";
146         $this->assertFileNotExists($archive);
147         $result = $packer->archive_to_pathname($this->files, $archive);
148         $this->assertTrue($result);
149         $this->assertFileExists($archive);
151         $archivefiles = $packer->list_files($archive);
152         $this->assertTrue(is_array($archivefiles));
153         $this->assertEquals(count($this->files), count($archivefiles));
154         foreach ($archivefiles as $file) {
155             $this->assertArrayHasKey($file->pathname, $this->files);
156         }
158         // Test invalid files parameter.
159         $archive = "$CFG->tempdir/archive2.zip";
160         $this->assertFileNotExists($archive);
162         $this->assertFileNotExists(__DIR__.'/xx/yy/ee.txt');
163         $files = array('xtest.txt'=>__DIR__.'/xx/yy/ee.txt');
165         $result = $packer->archive_to_pathname($files, $archive, false);
166         $this->assertFalse($result);
167         $this->assertDebuggingCalled();
168         $this->assertFileNotExists($archive);
170         $result = $packer->archive_to_pathname($files, $archive);
171         $this->assertTrue($result);
172         $this->assertFileExists($archive);
173         $this->assertDebuggingCalled();
174         $archivefiles = $packer->list_files($archive);
175         $this->assertSame(array(), $archivefiles);
176         unlink($archive);
178         $this->assertFileNotExists(__DIR__.'/xx/yy/ee.txt');
179         $this->assertFileExists(__DIR__.'/fixtures/test.txt');
180         $files = array('xtest.txt'=>__DIR__.'/xx/yy/ee.txt', 'test.txt'=>__DIR__.'/fixtures/test.txt', 'ytest.txt'=>__DIR__.'/xx/yy/yy.txt');
181         $result = $packer->archive_to_pathname($files, $archive);
182         $this->assertTrue($result);
183         $this->assertFileExists($archive);
184         $archivefiles = $packer->list_files($archive);
185         $this->assertCount(1, $archivefiles);
186         $this->assertEquals('test.txt', $archivefiles[0]->pathname);
187         $dms = $this->getDebuggingMessages();
188         $this->assertCount(2, $dms);
189         $this->resetDebugging();
190         unlink($archive);
191     }
193     /**
194      * @depends test_archive_to_pathname
195      */
196     public function test_archive_to_storage() {
197         $this->resetAfterTest(false);
199         $packer = get_file_packer('application/zip');
200         $fs = get_file_storage();
201         $context = context_system::instance();
203         $this->assertFalse($fs->file_exists($context->id, 'phpunit', 'test', 0, '/', 'archive.zip'));
204         $result = $packer->archive_to_storage($this->files, $context->id, 'phpunit', 'test', 0, '/', 'archive.zip');
205         $this->assertInstanceOf('stored_file', $result);
206         $this->assertTrue($fs->file_exists($context->id, 'phpunit', 'test', 0, '/', 'archive.zip'));
208         $archivefiles = $result->list_files($packer);
209         $this->assertTrue(is_array($archivefiles));
210         $this->assertEquals(count($this->files), count($archivefiles));
211         foreach ($archivefiles as $file) {
212             $this->assertArrayHasKey($file->pathname, $this->files);
213         }
214     }
216     /**
217      * @depends test_archive_to_storage
218      */
219     public function test_extract_to_pathname() {
220         global $CFG;
222         $this->resetAfterTest(false);
224         $packer = get_file_packer('application/zip');
225         $fs = get_file_storage();
226         $context = context_system::instance();
228         $target = "$CFG->tempdir/test/";
229         $testcontent = file_get_contents($this->testfile);
231         @mkdir($target, $CFG->directorypermissions);
232         $this->assertTrue(is_dir($target));
234         $archive = "$CFG->tempdir/archive.zip";
235         $this->assertFileExists($archive);
236         $result = $packer->extract_to_pathname($archive, $target);
237         $this->assertTrue(is_array($result));
238         $this->assertEquals(count($this->files), count($result));
239         foreach ($this->files as $file => $unused) {
240             $this->assertTrue($result[$file]);
241             $this->assertFileExists($target.$file);
242             $this->assertSame($testcontent, file_get_contents($target.$file));
243         }
245         $archive = $fs->get_file($context->id, 'phpunit', 'test', 0, '/', 'archive.zip');
246         $this->assertNotEmpty($archive);
247         $result = $packer->extract_to_pathname($archive, $target);
248         $this->assertTrue(is_array($result));
249         $this->assertEquals(count($this->files), count($result));
250         foreach ($this->files as $file => $unused) {
251             $this->assertTrue($result[$file]);
252             $this->assertFileExists($target.$file);
253             $this->assertSame($testcontent, file_get_contents($target.$file));
254         }
255     }
257     /**
258      * @depends test_archive_to_storage
259      */
260     public function test_extract_to_pathname_onlyfiles() {
261         global $CFG;
263         $this->resetAfterTest(false);
265         $packer = get_file_packer('application/zip');
266         $fs = get_file_storage();
267         $context = context_system::instance();
269         $target = "$CFG->tempdir/onlyfiles/";
270         $testcontent = file_get_contents($this->testfile);
272         @mkdir($target, $CFG->directorypermissions);
273         $this->assertTrue(is_dir($target));
275         $onlyfiles = array('test', 'test.test', 'Žluťoučký/Koníček.txt', 'Idontexist');
276         $willbeextracted = array_intersect(array_keys($this->files), $onlyfiles);
277         $donotextract = array_diff(array_keys($this->files), $onlyfiles);
279         $archive = "$CFG->tempdir/archive.zip";
280         $this->assertFileExists($archive);
281         $result = $packer->extract_to_pathname($archive, $target, $onlyfiles);
282         $this->assertTrue(is_array($result));
283         $this->assertEquals(count($willbeextracted), count($result));
285         foreach ($willbeextracted as $file) {
286             $this->assertTrue($result[$file]);
287             $this->assertFileExists($target.$file);
288             $this->assertSame($testcontent, file_get_contents($target.$file));
289         }
290         foreach ($donotextract as $file) {
291             $this->assertFalse(isset($result[$file]));
292             $this->assertFileNotExists($target.$file);
293         }
295     }
297     /**
298      * @depends test_archive_to_storage
299      */
300     public function test_extract_to_storage() {
301         global $CFG;
303         $this->resetAfterTest(false);
305         $packer = get_file_packer('application/zip');
306         $fs = get_file_storage();
307         $context = context_system::instance();
309         $testcontent = file_get_contents($this->testfile);
311         $archive = $fs->get_file($context->id, 'phpunit', 'test', 0, '/', 'archive.zip');
312         $this->assertNotEmpty($archive);
313         $result = $packer->extract_to_storage($archive, $context->id, 'phpunit', 'target', 0, '/');
314         $this->assertTrue(is_array($result));
315         $this->assertEquals(count($this->files), count($result));
316         foreach ($this->files as $file => $unused) {
317             $this->assertTrue($result[$file]);
318             $stored_file = $fs->get_file_by_hash(sha1("/$context->id/phpunit/target/0/$file"));
319             $this->assertInstanceOf('stored_file', $stored_file);
320             $this->assertSame($testcontent, $stored_file->get_content());
321         }
323         $archive = "$CFG->tempdir/archive.zip";
324         $this->assertFileExists($archive);
325         $result = $packer->extract_to_storage($archive, $context->id, 'phpunit', 'target', 0, '/');
326         $this->assertTrue(is_array($result));
327         $this->assertEquals(count($this->files), count($result));
328         foreach ($this->files as $file => $unused) {
329             $this->assertTrue($result[$file]);
330             $stored_file = $fs->get_file_by_hash(sha1("/$context->id/phpunit/target/0/$file"));
331             $this->assertInstanceOf('stored_file', $stored_file);
332             $this->assertSame($testcontent, $stored_file->get_content());
333         }
334         unlink($archive);
335     }
337     /**
338      * @depends test_extract_to_storage
339      */
340     public function test_add_files() {
341         global $CFG;
343         $this->resetAfterTest(false);
345         $packer = get_file_packer('application/zip');
346         $archive = "$CFG->tempdir/archive.zip";
348         $this->assertFileNotExists($archive);
349         $packer->archive_to_pathname(array(), $archive);
350         $this->assertFileExists($archive);
352         $zip_archive = new zip_archive();
353         $zip_archive->open($archive, file_archive::OPEN);
354         $this->assertEquals(0, $zip_archive->count());
356         $zip_archive->add_file_from_string('test.txt', 'test');
357         $zip_archive->close();
358         $zip_archive->open($archive, file_archive::OPEN);
359         $this->assertEquals(1, $zip_archive->count());
361         $zip_archive->add_directory('test2');
362         $zip_archive->close();
363         $zip_archive->open($archive, file_archive::OPEN);
364         $files = $zip_archive->list_files();
365         $this->assertCount(2, $files);
366         $this->assertEquals('test.txt', $files[0]->pathname);
367         $this->assertEquals('test2/', $files[1]->pathname);
369         $result = $zip_archive->add_file_from_pathname('test.txt', __DIR__.'/nonexistent/file.txt');
370         $this->assertFalse($result);
371         $zip_archive->close();
372         $zip_archive->open($archive, file_archive::OPEN);
373         $this->assertEquals(2, $zip_archive->count());
374         $zip_archive->close();
376         unlink($archive);
377     }
379     /**
380      * @depends test_add_files
381      */
382     public function test_open_archive() {
383         global $CFG;
385         $this->resetAfterTest(true);
387         $archive = "$CFG->tempdir/archive.zip";
389         $this->assertFileNotExists($archive);
391         $zip_archive = new zip_archive();
392         $result = $zip_archive->open($archive, file_archive::OPEN);
393         $this->assertFalse($result);
394         $this->assertDebuggingCalled();
396         $zip_archive = new zip_archive();
397         $result = $zip_archive->open($archive, file_archive::CREATE);
398         $this->assertTrue($result);
399         $zip_archive->add_file_from_string('test.txt', 'test');
400         $zip_archive->close();
401         $zip_archive->open($archive, file_archive::OPEN);
402         $this->assertEquals(1, $zip_archive->count());
404         $zip_archive = new zip_archive();
405         $result = $zip_archive->open($archive, file_archive::OVERWRITE);
406         $this->assertTrue($result);
407         $zip_archive->add_file_from_string('test2.txt', 'test');
408         $zip_archive->close();
409         $zip_archive->open($archive, file_archive::OPEN);
410         $this->assertEquals(1, $zip_archive->count());
411         $zip_archive->close();
413         unlink($archive);
414         $zip_archive = new zip_archive();
415         $result = $zip_archive->open($archive, file_archive::OVERWRITE);
416         $this->assertTrue($result);
417         $zip_archive->add_file_from_string('test2.txt', 'test');
418         $zip_archive->close();
419         $zip_archive->open($archive, file_archive::OPEN);
420         $this->assertEquals(1, $zip_archive->count());
421         $zip_archive->close();
423         unlink($archive);
424     }
426     /**
427      * Tests the progress reporting.
428      */
429     public function test_file_progress() {
430         global $CFG;
432         // Set up.
433         $this->resetAfterTest(true);
434         $packer = get_file_packer('application/zip');
435         $archive = "$CFG->tempdir/archive.zip";
436         $context = context_system::instance();
438         // Archive to pathname.
439         $this->progress = array();
440         $result = $packer->archive_to_pathname($this->files, $archive, true, $this);
441         $this->assertTrue($result);
442         // Should send progress at least once per file.
443         $this->assertTrue(count($this->progress) >= count($this->files));
444         // Each progress will be indeterminate.
445         $this->assertEquals(
446                 array(file_progress::INDETERMINATE, file_progress::INDETERMINATE),
447                 $this->progress[0]);
449         // Archive to storage.
450         $this->progress = array();
451         $archivefile = $packer->archive_to_storage($this->files, $context->id,
452                 'phpunit', 'test', 0, '/', 'archive.zip', null, true, $this);
453         $this->assertInstanceOf('stored_file', $archivefile);
454         $this->assertTrue(count($this->progress) >= count($this->files));
455         $this->assertEquals(
456                 array(file_progress::INDETERMINATE, file_progress::INDETERMINATE),
457                 $this->progress[0]);
459         // Extract to pathname.
460         $this->progress = array();
461         $target = "$CFG->tempdir/test/";
462         check_dir_exists($target);
463         $result = $packer->extract_to_pathname($archive, $target, null, $this);
464         remove_dir($target);
465         $this->assertEquals(count($this->files), count($result));
466         $this->assertTrue(count($this->progress) >= count($this->files));
467         $this->check_progress_toward_max();
469         // Extract to storage (from storage).
470         $this->progress = array();
471         $result = $packer->extract_to_storage($archivefile, $context->id,
472                 'phpunit', 'target', 0, '/', null, $this);
473         $this->assertEquals(count($this->files), count($result));
474         $this->assertTrue(count($this->progress) >= count($this->files));
475         $this->check_progress_toward_max();
477         // Extract to storage (from path).
478         $this->progress = array();
479         $result = $packer->extract_to_storage($archive, $context->id,
480                 'phpunit', 'target', 0, '/', null, $this);
481         $this->assertEquals(count($this->files), count($result));
482         $this->assertTrue(count($this->progress) >= count($this->files));
483         $this->check_progress_toward_max();
485         // Wipe created disk file.
486         unlink($archive);
487     }
489     /**
490      * Checks that progress reported is numeric rather than indeterminate,
491      * and follows the progress reporting rules.
492      */
493     private function check_progress_toward_max() {
494         $lastvalue = -1;
495         foreach ($this->progress as $progressitem) {
496             list($value, $max) = $progressitem;
497             $this->assertNotEquals(file_progress::INDETERMINATE, $max);
498             $this->assertTrue($value <= $max);
499             $this->assertTrue($value >= $lastvalue);
500             $lastvalue = $value;
501         }
502     }
504     /**
505      * Handles file_progress interface.
506      *
507      * @param int $progress
508      * @param int $max
509      */
510     public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
511         $this->progress[] = array($progress, $max);
512     }