Mercurial > octave
comparison libinterp/dldfcn/xzip.cc @ 22160:766f934db568
Rewrite gzip and bzip2 in C++ instead of using its applications (bug #43431)
* bzip2.m, gzip.m, __xzip__.m: remove old implementation as m files that
copy all files into a temporary directory and then call gzip or bzip2
application. Add several new tests and remove duplication of existing
tests.
* scripts/miscellaneous/module.mk: unlist removed files.
* xzip.cc: new implementation of bzip2 and gzip functions making direct
use of the libraries in C++. Also add more tests.
* libinterp/dldfcn/module-files: list new file and required flags.
* configure.ac: add check for bzip2 library.
author | Carnë Draug <carandraug@octave.org> |
---|---|
date | Sun, 26 Jun 2016 13:32:03 +0200 |
parents | |
children | 8de49f15e182 |
comparison
equal
deleted
inserted
replaced
22159:63c806042c27 | 22160:766f934db568 |
---|---|
1 // Copyright (C) 2016 Carnë Draug | |
2 // | |
3 // This program is free software: you can redistribute it and/or modify | |
4 // it under the terms of the GNU General Public License as published by | |
5 // the Free Software Foundation, either version 3 of the License, or | |
6 // (at your option) any later version. | |
7 // | |
8 // This program is distributed in the hope that it will be useful, | |
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 // GNU General Public License for more details. | |
12 // | |
13 // You should have received a copy of the GNU General Public License | |
14 // along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | |
16 //! Octave interface to the compression and uncompression libraries. | |
17 /*! | |
18 This was originally implemented as an m file which directly called | |
19 bzip2 and gzip applications. This may look simpler but causes some | |
20 issues (see bug #43431) because we have no control over the output | |
21 file: | |
22 | |
23 - created file is always in the same directory as the original file; | |
24 - automatically skip files that already have gz/bz2/etc extension; | |
25 - some olders versions miss the --keep option. | |
26 | |
27 In addition, because system() does not have a method that allows | |
28 passing a list of arguments, there is the issue of having to escape | |
29 filenames. | |
30 | |
31 A solution is to pipe file contents into the applications instead of | |
32 filenames. However, that solution causes: | |
33 | |
34 # missing file header with original file information; | |
35 # implementing ourselves the recursive transversion of directories; | |
36 # do the above in a m file which will be slow; | |
37 # popen2 is frail on windows. | |
38 | |
39 */ | |
40 | |
41 #if defined (HAVE_CONFIG_H) | |
42 # include "config.h" | |
43 #endif | |
44 | |
45 #include <cstdio> | |
46 #include <cstring> | |
47 | |
48 #include <string> | |
49 #include <list> | |
50 #include <functional> | |
51 #include <stdexcept> | |
52 #include <iostream> | |
53 #include <fstream> | |
54 | |
55 #ifdef HAVE_BZLIB_H | |
56 # include <bzlib.h> | |
57 #endif | |
58 | |
59 #ifdef HAVE_ZLIB_H | |
60 # include <zlib.h> | |
61 #endif | |
62 | |
63 #include "Array.h" | |
64 #include "str-vec.h" | |
65 #include "glob-match.h" | |
66 #include "file-ops.h" | |
67 #include "dir-ops.h" | |
68 #include "file-stat.h" | |
69 #include "oct-env.h" | |
70 | |
71 #include "defun-dld.h" | |
72 #include "defun-int.h" | |
73 #include "errwarn.h" | |
74 | |
75 class CFile | |
76 { | |
77 public: | |
78 FILE* fp; | |
79 | |
80 CFile (const std::string& path, const std::string& mode) | |
81 { | |
82 fp = std::fopen (path.c_str (), mode.c_str ()); | |
83 if (! fp) | |
84 throw std::runtime_error ("unable to open file"); | |
85 } | |
86 | |
87 ~CFile () | |
88 { | |
89 if (std::fclose (fp)) | |
90 // Not pedantic. If this is dest, maybe it failed to flush | |
91 // so we should signal this before someone removes the source. | |
92 throw std::runtime_error ("unable to close file"); | |
93 } | |
94 }; | |
95 | |
96 #ifdef HAVE_BZ2 | |
97 class bz2 | |
98 { | |
99 private: | |
100 class zipper | |
101 { | |
102 private: | |
103 int status = BZ_OK; | |
104 CFile source; | |
105 CFile dest; | |
106 BZFILE* bz; | |
107 | |
108 public: | |
109 zipper (const std::string& source_path, const std::string& dest_path) | |
110 : source (source_path, "rb"), dest (dest_path, "wb") | |
111 { | |
112 bz = BZ2_bzWriteOpen (&status, dest.fp, 9, 0, 30); | |
113 if (status != BZ_OK) | |
114 throw std::runtime_error ("failed to open bzip2 stream"); | |
115 } | |
116 | |
117 void | |
118 deflate (void) | |
119 { | |
120 const std::size_t buf_len = 8192; | |
121 char buf[buf_len]; | |
122 std::size_t n_read; | |
123 while ((n_read = std::fread (buf, sizeof (buf[0]), buf_len, source.fp)) != 0) | |
124 { | |
125 if (std::ferror (source.fp)) | |
126 throw std::runtime_error ("failed to read from source file"); | |
127 BZ2_bzWrite (&status, bz, buf, n_read); | |
128 if (status == BZ_IO_ERROR) | |
129 throw std::runtime_error ("failed to write or compress"); | |
130 } | |
131 if (std::ferror (source.fp)) | |
132 throw std::runtime_error ("failed to read from source file"); | |
133 } | |
134 | |
135 ~zipper () | |
136 { | |
137 int abandon = (status == BZ_IO_ERROR) ? 1 : 0; | |
138 BZ2_bzWriteClose (&status, bz, abandon, 0, 0); | |
139 if (status != BZ_OK) | |
140 throw std::runtime_error ("failed to close bzip2 stream"); | |
141 } | |
142 }; | |
143 | |
144 public: | |
145 static const constexpr char* extension = ".bz2"; | |
146 | |
147 static void | |
148 zip (const std::string& source_path, const std::string& dest_path) | |
149 { | |
150 bz2::zipper (source_path, dest_path).deflate (); | |
151 } | |
152 | |
153 }; | |
154 #endif // HAVE_BZL2 | |
155 | |
156 // Note about zlib and gzip | |
157 // | |
158 // gzip is a format for compressed single files. zlib is a format | |
159 // designed for in-memory and communication channel applications. | |
160 // gzip uses the same format internally for the compressed data but | |
161 // has different headers and trailers. | |
162 // | |
163 // zlib is also a library but gzip is not. Very old versions of zlib do | |
164 // not include functions to create useful gzip headers and trailers: | |
165 // | |
166 // Note that you cannot specify special gzip header contents (e.g. | |
167 // a file name or modification date), nor will inflate tell you what | |
168 // was in the gzip header. If you need to customize the header or | |
169 // see what's in it, you can use the raw deflate and inflate | |
170 // operations and the crc32() function and roll your own gzip | |
171 // encoding and decoding. Read the gzip RFC 1952 for details of the | |
172 // header and trailer format. | |
173 // zlib FAQ | |
174 // | |
175 // Recent versions (on which we are already dependent) have deflateInit2() | |
176 // to do it. We still need to get the right metadata for the header | |
177 // ourselves though. | |
178 // | |
179 // The header is defined in RFC #1952 | |
180 // GZIP file format specification version 4.3 | |
181 | |
182 | |
183 #ifdef HAVE_Z | |
184 class gz | |
185 { | |
186 private: | |
187 | |
188 // Util class to get a non-const char* | |
189 class uchar_array | |
190 { | |
191 public: | |
192 // Bytef is a typedef for unsigned char | |
193 unsigned char* p; | |
194 | |
195 uchar_array (const std::string& str) | |
196 { | |
197 p = new Bytef[str.length () +1]; | |
198 std::strcpy (reinterpret_cast<char*> (p), str.c_str ()); | |
199 } | |
200 | |
201 ~uchar_array (void) { delete[] p; } | |
202 }; | |
203 | |
204 // This is the really thing that needs to be | |
205 class gzip_stream : public z_stream | |
206 { | |
207 public: | |
208 gzip_stream () | |
209 { | |
210 zalloc = Z_NULL; | |
211 zfree = Z_NULL; | |
212 opaque = Z_NULL; | |
213 } | |
214 | |
215 ~gzip_stream () | |
216 { | |
217 int status = deflateEnd (this); | |
218 if (status != Z_OK) | |
219 throw std::runtime_error ("failed to close zlib stream"); | |
220 } | |
221 }; | |
222 | |
223 class gzip_header : public gz_header | |
224 { | |
225 private: | |
226 uchar_array basename; | |
227 | |
228 public: | |
229 gzip_header (const std::string& source_path) | |
230 : basename (octave::sys::env::base_pathname (source_path)) | |
231 { | |
232 const octave::sys::file_stat source_stat (source_path); | |
233 if (! source_stat) | |
234 throw std::runtime_error ("unable to stat source file"); | |
235 | |
236 // time_t may be a signed int in which case it will be a | |
237 // positive number so it is safe to uLong. Or is it? Can | |
238 // unix_time really never be negative? | |
239 time = uLong (source_stat.mtime ().unix_time ()); | |
240 | |
241 // If FNAME is set, an original file name is present, | |
242 // terminated by a zero byte. The name must consist of ISO | |
243 // 8859-1 (LATIN-1) characters; on operating systems using | |
244 // EBCDIC or any other character set for file names, the name | |
245 // must be translated to the ISO LATIN-1 character set. This | |
246 // is the original name of the file being compressed, with any | |
247 // directory components removed, and, if the file being | |
248 // compressed is on a file system with case insensitive names, | |
249 // forced to lower case. | |
250 name = basename.p; | |
251 | |
252 // If we don't set it to Z_NULL, then it will set FCOMMENT (4th bit) | |
253 // on the FLG byte, and then write {0, 3} comment. | |
254 comment = Z_NULL; | |
255 | |
256 // Seems to already be the default but we are not taking chances. | |
257 extra = Z_NULL; | |
258 | |
259 // We do not want a CRC for the header. That would be only 2 more | |
260 // bytes, and maybe it would be a good thing but we want to generate | |
261 // gz files similar to the default gzip application. | |
262 hcrc = 0; | |
263 | |
264 // OS (Operating System): | |
265 // 0 - FAT filesystem (MS-DOS, OS/2, NT/Win32) | |
266 // 1 - Amiga | |
267 // 2 - VMS (or OpenVMS) | |
268 // 3 - Unix | |
269 // 4 - VM/CMS | |
270 // 5 - Atari TOS | |
271 // 6 - HPFS filesystem (OS/2, NT) | |
272 // 7 - Macintosh | |
273 // 8 - Z-System | |
274 // 9 - CP/M | |
275 // 10 - TOPS-20 | |
276 // 11 - NTFS filesystem (NT) | |
277 // 12 - QDOS | |
278 // 13 - Acorn RISCOS | |
279 // 255 - unknown | |
280 // | |
281 // The list is problematic because it mixes OS and filesystem. It | |
282 // also does not specify whether filesystem relates to source or | |
283 // destination file. | |
284 | |
285 #if defined (__WIN32__) | |
286 os = 0; // or should it be 11? | |
287 #elif defined (__APPLE__) | |
288 os = 7; | |
289 #else // unix by default? | |
290 os = 3; | |
291 #endif | |
292 } | |
293 }; | |
294 | |
295 class zipper | |
296 { | |
297 private: | |
298 CFile source; | |
299 CFile dest; | |
300 gzip_header header; | |
301 gzip_stream strm = gzip_stream (); | |
302 | |
303 public: | |
304 zipper (const std::string& source_path, const std::string& dest_path) | |
305 : source (source_path, "rb"), dest (dest_path, "wb"), | |
306 header (source_path) | |
307 { } | |
308 | |
309 void | |
310 deflate () | |
311 { | |
312 // int deflateInit2 (z_streamp strm, | |
313 // int level, // compression level (default is 8) | |
314 // int method, | |
315 // int windowBits, // 15 (default) + 16 (gzip format) | |
316 // int memLevel, // memory usage (default is 8) | |
317 // int strategy); | |
318 | |
319 int status = deflateInit2 (&strm, 8, Z_DEFLATED, 31, 8, | |
320 Z_DEFAULT_STRATEGY); | |
321 if (status != Z_OK) | |
322 throw std::runtime_error ("failed to open zlib stream"); | |
323 | |
324 deflateSetHeader (&strm, &header); | |
325 | |
326 const std::size_t buf_len = 8192; | |
327 unsigned char buf_in[buf_len]; | |
328 unsigned char buf_out[buf_len]; | |
329 | |
330 while ((strm.avail_in = std::fread (buf_in, sizeof (buf_in[0]), | |
331 buf_len, source.fp)) != 0) | |
332 { | |
333 if (std::ferror (source.fp)) | |
334 throw std::runtime_error ("failed to read source file"); | |
335 | |
336 strm.next_in = buf_in; | |
337 const int flush = std::feof (source.fp) ? Z_FINISH : Z_NO_FLUSH; | |
338 | |
339 // If deflate returns Z_OK and with zero avail_out, it must be | |
340 // called again after making room in the output buffer because | |
341 // there might be more output pending. | |
342 do | |
343 { | |
344 strm.avail_out = buf_len; | |
345 strm.next_out = buf_out; | |
346 status = ::deflate (&strm, flush); | |
347 if (status == Z_STREAM_ERROR) | |
348 throw std::runtime_error ("failed to deflate"); | |
349 | |
350 std::fwrite (buf_out, sizeof (buf_out[0]), | |
351 buf_len - strm.avail_out, dest.fp); | |
352 if (std::ferror (dest.fp)) | |
353 throw std::runtime_error ("failed to write file"); | |
354 } | |
355 while (strm.avail_out == 0); | |
356 | |
357 if (strm.avail_in != 0) | |
358 throw std::runtime_error ("failed to wrote file"); | |
359 } | |
360 } | |
361 }; | |
362 | |
363 public: | |
364 static const constexpr char* extension = ".gz"; | |
365 | |
366 static void | |
367 zip (const std::string& source_path, const std::string& dest_path) | |
368 { | |
369 gz::zipper (source_path, dest_path).deflate (); | |
370 } | |
371 }; | |
372 #endif // HAVE_Z | |
373 | |
374 | |
375 template<typename X> | |
376 string_vector | |
377 xzip (const Array<std::string>& source_patterns, | |
378 const std::function<std::string(const std::string&)>& mk_dest_path) | |
379 { | |
380 std::list<std::string> dest_paths; | |
381 | |
382 std::function<void(const std::string&)> walk; | |
383 walk = [&walk, &mk_dest_path, &dest_paths] (const std::string& path) -> void | |
384 { | |
385 const octave::sys::file_stat fs (path); | |
386 // is_dir and is_reg will return false if failed to stat. | |
387 if (fs.is_dir ()) | |
388 { | |
389 octave::sys::dir_entry dir (path); | |
390 if (dir) | |
391 { | |
392 // Collect the whole list of filenames first, before recursion | |
393 // to avoid issues with infinite loop if the action generates | |
394 // files in the same directory (highly likely). | |
395 string_vector dirlist = dir.read (); | |
396 for (octave_idx_type i = 0; i < dirlist.numel (); i++) | |
397 if (dirlist(i) != "." && dirlist(i) != "..") | |
398 walk (octave::sys::file_ops::concat (path, dirlist(i))); | |
399 } | |
400 // Note that we skip any problem with directories. | |
401 } | |
402 else if (fs.is_reg ()) | |
403 { | |
404 const std::string dest_path = mk_dest_path (path); | |
405 try | |
406 { | |
407 X::zip (path, dest_path); | |
408 } | |
409 catch (...) | |
410 { | |
411 // Error "handling" is not including filename on the output list. | |
412 // Also remove created file which maybe was not even created | |
413 // in the first place. Note that it is possible for the file | |
414 // to exist in the first place and for for X::zip to not have | |
415 // clobber it yet but we remove it anyway by design. | |
416 octave::sys::unlink (dest_path); | |
417 return; | |
418 } | |
419 dest_paths.push_front (dest_path); | |
420 } | |
421 // Skip all other file types and errors. | |
422 return; | |
423 }; | |
424 | |
425 for (octave_idx_type i = 0; i < source_patterns.numel (); i++) | |
426 { | |
427 const glob_match pattern (octave::sys::file_ops::tilde_expand (source_patterns(i))); | |
428 const string_vector filepaths = pattern.glob (); | |
429 for (octave_idx_type j = 0; j < filepaths.numel (); j++) | |
430 walk (filepaths(j)); | |
431 } | |
432 return string_vector (dest_paths); | |
433 } | |
434 | |
435 | |
436 template<typename X> | |
437 string_vector | |
438 xzip (const Array<std::string>& source_patterns) | |
439 { | |
440 const std::string ext = X::extension; | |
441 const std::function<std::string(const std::string&)> mk_dest_path | |
442 = [&ext] (const std::string& source_path) -> std::string | |
443 { | |
444 return source_path + ext; | |
445 }; | |
446 return xzip<X> (source_patterns, mk_dest_path); | |
447 } | |
448 | |
449 template<typename X> | |
450 string_vector | |
451 xzip (const Array<std::string>& source_patterns, const std::string& out_dir) | |
452 { | |
453 const std::string ext = X::extension; | |
454 const std::function<std::string(const std::string&)> mk_dest_path | |
455 = [&out_dir, &ext] (const std::string& source_path) -> std::string | |
456 { | |
457 const std::string basename = octave::sys::env::base_pathname (source_path); | |
458 return octave::sys::file_ops::concat (out_dir, basename + ext); | |
459 }; | |
460 | |
461 // We don't care if mkdir fails. Maybe it failed because it already | |
462 // exists, or maybe it can't bre created. If the first, then there's | |
463 // nothing to do, if the later, then it will be handled later. Any | |
464 // is to be handled by not listing files in the output. | |
465 octave::sys::mkdir (out_dir, 0777); | |
466 return xzip<X> (source_patterns, mk_dest_path); | |
467 } | |
468 | |
469 template<typename X> | |
470 static octave_value_list | |
471 xzip (const std::string& func_name, const octave_value_list& args) | |
472 { | |
473 const octave_idx_type nargin = args.length (); | |
474 if (nargin < 1 || nargin > 2) | |
475 print_usage (); | |
476 | |
477 const Array<std::string> source_patterns | |
478 = args(0).xcellstr_value ("%s: FILES must be a character array or cellstr", | |
479 func_name.c_str ()); | |
480 if (nargin == 1) | |
481 return octave_value (Cell (xzip<X> (source_patterns))); | |
482 else // nargin == 2 | |
483 { | |
484 const std::string out_dir = args(1).string_value (); | |
485 return octave_value (Cell (xzip<X> (source_patterns, out_dir))); | |
486 } | |
487 } | |
488 | |
489 DEFUN_DLD (gzip, args, , | |
490 doc: /* -*- texinfo -*- | |
491 @deftypefn {} {@var{filelist} =} gzip (@var{files}) | |
492 @deftypefnx {} {@var{filelist} =} gzip (@var{files}, @var{dir}) | |
493 Compress the list of files and directories specified in @var{files}. | |
494 | |
495 @var{files} is a character array or cell array of strings. Shell wildcards | |
496 in the filename such as @samp{*} or @samp{?} are accepted and expanded. | |
497 Each file is compressed separately and a new file with a @file{".gz"} | |
498 extension is created. The original files are not modified, but existing | |
499 compressed files will be silently overwritten. If a directory is | |
500 specified then @code{gzip} recursively compresses all files in the | |
501 directory. | |
502 | |
503 If @var{dir} is defined the compressed files are placed in this directory, | |
504 rather than the original directory where the uncompressed file resides. | |
505 Note that this does not replicate a directory tree in @var{dir} which may | |
506 lead to files overwritting each other if there are multiple files with the | |
507 same name. | |
508 | |
509 If @var{dir} does not exist it is created. | |
510 | |
511 The optional output @var{filelist} is a list of the compressed files. | |
512 @seealso{gunzip, unpack, bzip2, zip, tar} | |
513 @end deftypefn */) | |
514 { | |
515 #ifndef HAVE_Z | |
516 err_disabled_feature ("gzip", "gzip"); | |
517 #else | |
518 return xzip<gz> ("gzip", args); | |
519 #endif | |
520 } | |
521 | |
522 /* | |
523 %!error gzip () | |
524 %!error gzip ("1", "2", "3") | |
525 %!error <FILES must be a character array or cellstr> gzip (1) | |
526 */ | |
527 | |
528 DEFUN_DLD (bzip2, args, , | |
529 doc: /* -*- texinfo -*- | |
530 @deftypefn {} {@var{filelist} =} bzip2 (@var{files}) | |
531 @deftypefnx {} {@var{filelist} =} bzip2 (@var{files}, @var{dir}) | |
532 Compress the list of files specified in @var{files}. | |
533 | |
534 @var{files} is a character array or cell array of strings. Shell wildcards | |
535 in the filename such as @samp{*} or @samp{?} are accepted and expanded. | |
536 Each file is compressed separately and a new file with a @file{".bz2"} | |
537 extension is created. The original files are not modified, but existing | |
538 compressed files will be silently overwritten. | |
539 | |
540 If @var{dir} is defined the compressed files are placed in this directory, | |
541 rather than the original directory where the uncompressed file resides. | |
542 Note that this does not replicate a directory tree in @var{dir} which may | |
543 lead to files overwritting each other if there are multiple files with the | |
544 same name. | |
545 | |
546 If @var{dir} does not exist it is created. | |
547 | |
548 The optional output @var{filelist} is a list of the compressed files. | |
549 @seealso{bunzip2, unpack, gzip, zip, tar} | |
550 @end deftypefn */) | |
551 { | |
552 #ifndef HAVE_BZ2 | |
553 err_disabled_feature ("bzip2", "bzip2"); | |
554 #else | |
555 return xzip<bz2> ("bzip2", args); | |
556 #endif | |
557 } | |
558 | |
559 // Tests for both gzip/bzip2 and gunzip/bunzip2 | |
560 /* | |
561 | |
562 ## Takes a single argument, a function handle for the test. This other | |
563 ## function must accept two arguments, a directory for the tests, and | |
564 ## a cell array with zip function, unzip function, and file extension. | |
565 | |
566 %!function run_test_function (test_function) | |
567 %! enabled_zippers = cell (0, 0); | |
568 %! if (__octave_config_info__ ().build_features.BZ2) | |
569 %! enabled_zippers(1, end+1) = @bzip2; | |
570 %! enabled_zippers(2, end) = @bunzip2; | |
571 %! enabled_zippers(3, end) = ".bz2"; | |
572 %! endif | |
573 %! if (__octave_config_info__ ().build_features.Z) | |
574 %! enabled_zippers(1, end+1) = @gzip; | |
575 %! enabled_zippers(2, end) = @gunzip; | |
576 %! enabled_zippers(3, end) = ".gz"; | |
577 %! endif | |
578 %! | |
579 %! for z = enabled_zippers | |
580 %! test_dir = tempname (); | |
581 %! if (! mkdir (test_dir)) | |
582 %! error ("unable to create directory for tests"); | |
583 %! endif | |
584 %! unwind_protect | |
585 %! test_function (test_dir, z) | |
586 %! unwind_protect_cleanup | |
587 %! confirm_recursive_rmdir (false, "local"); | |
588 %! rmdir (test_dir, "s"); | |
589 %! end_unwind_protect | |
590 %! endfor | |
591 %!endfunction | |
592 | |
593 %!function create_file (fpath, data) | |
594 %! fid = fopen (fpath, "wb"); | |
595 %! if (fid < 0) | |
596 %! error ("unable to open file for writing"); | |
597 %! endif | |
598 %! if (fwrite (fid, data, class (data)) != numel (data)) | |
599 %! error ("unable to write to file"); | |
600 %! endif | |
601 %! if (fflush (fid) || fclose (fid)) | |
602 %! error ("unable to flush or close file"); | |
603 %! endif | |
604 %!endfunction | |
605 | |
606 ## Test with large files because of varied buffer size | |
607 %!function test_large_file (test_dir, z) | |
608 %! test_file = tempname (test_dir); | |
609 %! create_file (test_file, rand (500000, 1)); | |
610 %! md5 = hash ("md5", fileread (test_file)); | |
611 %! | |
612 %! expected_z_file = [test_file z{3}]; | |
613 %! z_files = z{1} (test_file); | |
614 %! assert (z_files, {expected_z_file}) | |
615 %! | |
616 %! unlink (test_file); | |
617 %! assert (z{2} (z_files{1}), {test_file}) | |
618 %! assert (hash ("md5", fileread (test_file)), md5) | |
619 %!endfunction | |
620 %!test run_test_function (@test_large_file) | |
621 | |
622 ## Test that xzipped files are rexzipped | |
623 %!function test_z_z (test_dir, z) | |
624 %! ori_file = tempname (test_dir); | |
625 %! create_file (ori_file, rand (100, 1)); | |
626 %! md5_ori = hash ("md5", fileread (ori_file)); | |
627 %! | |
628 %! z_file = [ori_file z{3}]; | |
629 %! filelist = z{1} (ori_file); | |
630 %! assert (filelist, {z_file}) # check output | |
631 %! assert (exist (z_file), 2) # confirm file exists | |
632 %! assert (exist (z_file), 2) # and did not remove original file | |
633 %! md5_z = hash ("md5", fileread (z_file)); | |
634 %! | |
635 %! unlink (ori_file); | |
636 %! assert (z{2} (z_file), {ori_file}) | |
637 %! ## bug #48597 | |
638 %! assert (exist (ori_file), 2) # bug #48597 (Xunzip should not remove file) | |
639 %! assert (hash ("md5", fileread (ori_file)), md5_ori) | |
640 %! | |
641 %! ## xzip should dutifully re-xzip files even if they already are zipped | |
642 %! z_z_file = [z_file z{3}]; | |
643 %! | |
644 %! filelist = z{1} (z_file); | |
645 %! assert (filelist, {z_z_file}) # check output | |
646 %! assert (exist (z_z_file), 2) # confirm file exists | |
647 %! assert (exist (z_z_file), 2) # and did not remove original file | |
648 %! | |
649 %! unlink (z_file); | |
650 %! assert (z{2} (z_z_file), {z_file}) | |
651 %! assert (hash ("md5", fileread (z_file)), md5_z) | |
652 %!endfunction | |
653 %!test run_test_function (@test_z_z) | |
654 | |
655 %!function test_xzip_dir (test_dir, z) | |
656 %! fpaths = fullfile (test_dir, {"test1", "test2", "test3"}); | |
657 %! z_files = strcat (fpaths, z{3}); | |
658 %! md5s = cell (1, 3); | |
659 %! for idx = 1:numel(fpaths) | |
660 %! create_file (fpaths{idx}, rand (100, 1)); | |
661 %! md5s(idx) = hash ("md5", fileread (fpaths{idx})); | |
662 %! endfor | |
663 %! | |
664 %! assert (sort (z{1} ([test_dir filesep()])), z_files(:)) | |
665 %! for idx = 1:numel(fpaths) | |
666 %! assert (exist (z_files{idx}), 2) | |
667 %! unlink (fpaths{idx}); | |
668 %! endfor | |
669 %! for idx = 1:numel(fpaths) | |
670 %! assert (z{2} (z_files{idx}), fpaths{idx}); # bug #48598 | |
671 %! assert (hash ("md5", fileread (fpaths{idx})), md5s{idx}) | |
672 %! endfor | |
673 %!endfunction | |
674 %!test run_test_function (@test_xzip_dir) | |
675 | |
676 %!function test_save_to_dir (test_dir, z) | |
677 %! filename = "test-file"; | |
678 %! filepath = fullfile (test_dir, filename); | |
679 %! create_file (filepath, rand (100, 1)); | |
680 %! md5 = hash ("md5", fileread (filepath)); | |
681 %! | |
682 %! ## test with existing and non-existing directory | |
683 %! out_dirs = {tempname (test_dir), tempname (test_dir)}; | |
684 %! if (! mkdir (out_dirs{1})) | |
685 %! error ("unable to create directory for test"); | |
686 %! endif | |
687 %! for idx = 1:numel(out_dirs) | |
688 %! out_dir = out_dirs{idx}; | |
689 %! z_file = fullfile (out_dir, [filename z{3}]); | |
690 %! assert (z{1} (filepath, out_dir), {z_file}) | |
691 %! assert (exist (z_file, "file"), 2) | |
692 %! uz_file = z_file(1:(end-numel(z{3}))); | |
693 %! assert (z{2} (z_file), uz_file); # bug #48598 | |
694 %! assert (hash ("md5", fileread (uz_file)), md5) | |
695 %! endfor | |
696 %!endfunction | |
697 %!test run_test_function (@test_save_to_dir) | |
698 */ |