Merge ~dviererbe/ubuntu/+source/unzip:fix-missing-documentation-and-add-autopkgtest into ubuntu/+source/unzip:ubuntu/devel

Proposed by Dominik Viererbe
Status: Merged
Merge reported by: Dominik Viererbe
Merged at revision: 0aab383117bbab6e0fe1336d76238cec038f052b
Proposed branch: ~dviererbe/ubuntu/+source/unzip:fix-missing-documentation-and-add-autopkgtest
Merge into: ubuntu/+source/unzip:ubuntu/devel
Diff against target: 2212 lines (+1983/-40)
7 files modified
debian/changelog (+15/-0)
debian/patches/20-unzip60-alt-iconv-utf8.patch (+110/-40)
debian/patches/29-fix-troff-warning.patch (+422/-0)
debian/patches/series (+1/-0)
debian/tests/SmokeTests.py (+1422/-0)
debian/tests/control (+3/-0)
debian/tests/smoke-tests (+10/-0)
Reviewer Review Type Date Requested Status
Gianfranco Costamagna (community) Approve
Christian Ehrhardt  (community) Needs Information
git-ubuntu import Pending
Review via email: mp+444896@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Dominik Viererbe (dviererbe) wrote :

I have a PPA where the changes builds fine: https://launchpad.net/~dviererbe/+archive/ubuntu/unzip6-0-28ubuntu2

I also run successfully the autopkgtests locally with qemu and a fresh autopkgtest-buildvm-ubuntu-cloud mantic image.

Revision history for this message
Dominik Viererbe (dviererbe) wrote :

I just updated the changelog (with push force) because of https://bugs.launchpad.net/ubuntu/+source/unzip/+bug/1429939/comments/7

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

My first reaction was "have you submitted to Debian" but this is in fact about a Ubuntu-only delta.

Next I wanted to criticize how much whitespace damage this patch had, but then it is probably as taken from arch-linux. While we are changing it I wonder if we should not:
- find where it is from and mention it as origin if there is any link
- fix the whitespace damage (mostly tailing whitespace, mixed space/tab)
- combined with your fixes submit it back to archlinux

Looking at that I found that it is actually from https://src.fedoraproject.org/rpms/unzip/raw/rawhide/f/unzip-6.0-alt-iconv-utf8.patch

So - if nothing else - that should be mentioned in the dep-3 header.

But I've seen and appreciate that the last change (Updated 2015-02-11 by Marc Deslauriers <email address hidden> to fix buffer overflow in charset_to_intern()) was submitted there.

So I guess fedora is "the upstream" of that delta. To ease long term maintenance I think you should check if that evolved since then and submit your changes there - to then update both and in the future be able to pull their changes as well.

For example there is a follow on patch
https://src.fedoraproject.org/rpms/unzip/raw/rawhide/f/unzip-6.0-alt-iconv-utf8-print.patch

Also the signature changed massively
$ diffstat debian/patches/20-unzip60-alt-iconv-utf8.patch
 unix/unix.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 unix/unxcfg.h | 26 ++++++++++++++++++++++++++
 unzip.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 unzpriv.h | 2 +-
 zipinfo.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 279 insertions(+), 1 deletion(-)

vs
$ diffstat ~/unzip-6.0-alt-iconv-utf8-print.patch
 extract.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------
 unzpriv.h | 7 ++++
 2 files changed, 233 insertions(+), 63 deletions(-)

I think this is worth a deeper revisit to patch it once and patch it right - unless you disagree.

review: Needs Information
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

debian/tests/control also has trailing whitespace

debian/tests/smoke-tests has shellcheck reports
SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

debian/tests/SmokeTests.py also has a lot of whitespace damage
Since this is new how about throwing your favorite python linter and styler on it until it is silent?

Finally you either have written a lot of tests (thanks) or this started based on some other source.
In that case please link to where the inspiration or file came from.

0aab383... by Dominik Viererbe

changelog

Revision history for this message
Dominik Viererbe (dviererbe) wrote (last edit ):

> I think this is worth a deeper revisit to patch it once and patch it right - unless you disagree.
I think so too, but I would like to get these changes uploaded. I created a new bug ticked to address the mentioned concerns.
See LP: #2054625

> debian/tests/control also has trailing whitespace
>
> debian/tests/smoke-tests has shellcheck reports
> SC2164: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.
>
> debian/tests/SmokeTests.py also has a lot of whitespace damage
> Since this is new how about throwing your favorite python linter and styler on it until it is silent?
This is now fixed.

> Finally you either have written a lot of tests (thanks) or [...]
I wrote the test suite myself.

Revision history for this message
Dominik Viererbe (dviererbe) wrote (last edit ):
48235a1... by Dominik Viererbe

  * d/p/29-fix-troff-warning.patch:
    Removes monospace directives to fix troff warnings (LP: #2054670)

Revision history for this message
Dominik Viererbe (dviererbe) wrote :

The tests failed, because they detected non empty STDERR output when showing man pages (LP: #2054670). This is a win for the newly added smoke tests, because they caught a regression that would not have been caught otherwise. \o/

I uploaded a patch to the PPA and waiting for publication to rerun the autopkgtests.

Revision history for this message
Dominik Viererbe (dviererbe) wrote :
Revision history for this message
Dominik Viererbe (dviererbe) wrote :

This is ready for sponsoring.

Revision history for this message
Gianfranco Costamagna (costamagnagianfranco) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 250dfe6..42bad71 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,18 @@
6+unzip (6.0-28ubuntu3) noble; urgency=medium
7+
8+ * d/t/*: Added autopkgtest (LP: #2023994)
9+ * d/p/29-fix-troff-warning.patch:
10+ Removes monospace directives to fix troff warnings (LP: #2054670)
11+ * d/p/20-unzip60-alt-iconv-utf8.patch
12+ * Refreshed as ab-style patch.
13+ * Added documentation for `-I` and `-O` options to `unzip -hh`.
14+ * Added documentation for `-I` and `-O` options to unzip (man/unzip.1) and
15+ zipinfo (man/zipinfo.1) man pages (LP: #138307).
16+ * Fixed garbled output when `zipinfo` or `unzip -Z` is called
17+ without arguments (LP: #1429939).
18+
19+ -- Dominik Viererbe <dominik.viererbe@canonical.com> Thu, 22 Feb 2024 02:48:49 +0200
20+
21 unzip (6.0-28ubuntu2) noble; urgency=medium
22
23 * Properly handle Microsoft ZIP64 file (LP: #2051952)
24diff --git a/debian/patches/20-unzip60-alt-iconv-utf8.patch b/debian/patches/20-unzip60-alt-iconv-utf8.patch
25index b9e3777..3d45dcf 100644
26--- a/debian/patches/20-unzip60-alt-iconv-utf8.patch
27+++ b/debian/patches/20-unzip60-alt-iconv-utf8.patch
28@@ -1,14 +1,18 @@
29 From: Giovanni Scafora <giovanni.archlinux.org>
30 Subject: unzip files encoded with non-latin, non-unicode file names
31-Last-Update: 2015-02-11
32+Last-Update: 2024-02-22
33
34-Updated 2015-02-11 by Marc Deslauriers <marc.deslauriers@canonical.com>
35-to fix buffer overflow in charset_to_intern()
36+* Updated 2015-02-11 by Marc Deslauriers <marc.deslauriers@canonical.com>
37+ to fix buffer overflow in charset_to_intern()
38+* Updated 2023-06-15 by Dominik Viererbe <dominik.viererbe@canonical.com>
39+ to add documentation for `-I` and `-O` options (LP: #138307) and fixed
40+ garbled output when `zipinfo` or `unzip -Z` is called without arguments
41+ (LP: #1429939)
42
43-Index: unzip-6.0/unix/unix.c
44+Index: unzip/unix/unix.c
45 ===================================================================
46---- unzip-6.0.orig/unix/unix.c 2015-02-11 08:46:43.675324290 -0500
47-+++ unzip-6.0/unix/unix.c 2015-02-11 09:18:04.902081319 -0500
48+--- unzip.orig/unix/unix.c 2023-06-15 21:24:46.857092642 +0300
49++++ unzip/unix/unix.c 2023-06-15 21:24:46.845092668 +0300
50 @@ -30,6 +30,9 @@
51 #define UNZIP_INTERNAL
52 #include "unzip.h"
53@@ -52,7 +56,7 @@ Index: unzip-6.0/unix/unix.c
54 + const char *local_charset;
55 + int i;
56 +
57-+ /* Make a guess only if OEM_CP not already set. */
58++ /* Make a guess only if OEM_CP not already set. */
59 + if(*OEM_CP == '\0') {
60 + local_charset = nl_langinfo(CODESET);
61 + for(i = 0; i < sizeof(dos_charset_map)/sizeof(CHARSET_MAP); i++)
62@@ -122,23 +126,23 @@ Index: unzip-6.0/unix/unix.c
63 +{
64 + charset_to_intern(string, ISO_CP);
65 +}
66-Index: unzip-6.0/unix/unxcfg.h
67+Index: unzip/unix/unxcfg.h
68 ===================================================================
69---- unzip-6.0.orig/unix/unxcfg.h 2015-02-11 08:46:43.675324290 -0500
70-+++ unzip-6.0/unix/unxcfg.h 2015-02-11 08:46:43.671324260 -0500
71+--- unzip.orig/unix/unxcfg.h 2023-06-15 21:24:46.857092642 +0300
72++++ unzip/unix/unxcfg.h 2023-06-15 21:24:46.845092668 +0300
73 @@ -228,4 +228,30 @@
74 /* wild_dir, dirname, wildname, matchname[], dirnamelen, have_dirname, */
75 /* and notfirstcall are used by do_wild(). */
76
77 +
78-+#define MAX_CP_NAME 25
79-+
80++#define MAX_CP_NAME 25
81++
82 +#ifdef SETLOCALE
83 +# undef SETLOCALE
84 +#endif
85 +#define SETLOCALE(category, locale) setlocale(category, locale)
86 +#include <locale.h>
87-+
88++
89 +#ifdef _ISO_INTERN
90 +# undef _ISO_INTERN
91 +#endif
92@@ -155,12 +159,12 @@ Index: unzip-6.0/unix/unxcfg.h
93 +void iso_intern(char *);
94 +void oem_intern(char *);
95 +void init_conversion_charsets(void);
96-+
97++
98 #endif /* !__unxcfg_h */
99-Index: unzip-6.0/unzip.c
100+Index: unzip/unzip.c
101 ===================================================================
102---- unzip-6.0.orig/unzip.c 2015-02-11 08:46:43.675324290 -0500
103-+++ unzip-6.0/unzip.c 2015-02-11 08:46:43.675324290 -0500
104+--- unzip.orig/unzip.c 2023-06-15 21:24:46.857092642 +0300
105++++ unzip/unzip.c 2023-06-15 21:28:32.324638973 +0300
106 @@ -327,11 +327,21 @@
107 -2 just filenames but allow -h/-t/-z -l long Unix \"ls -l\" format\n\
108 -v verbose, multi-page format\n";
109@@ -168,14 +172,16 @@ Index: unzip-6.0/unzip.c
110 +#ifndef UNIX
111 static ZCONST char Far ZipInfoUsageLine3[] = "miscellaneous options:\n\
112 -h print header line -t print totals for listed files or for all\n\
113- -z print zipfile comment -T print file times in sortable decimal format\
114- \n -C be case-insensitive %s\
115+- -z print zipfile comment -T print file times in sortable decimal format\
116+-\n -C be case-insensitive %s\
117++ -z print zipfile comment -T print file times in sortable decimal format\n\
118++ -C be case-insensitive %s\
119 -x exclude filenames that follow from listing\n";
120 +#else /* UNIX */
121 +static ZCONST char Far ZipInfoUsageLine3[] = "miscellaneous options:\n\
122 + -h print header line -t print totals for listed files or for all\n\
123-+ -z print zipfile comment %c-T%c print file times in sortable decimal format\
124-+\n %c-C%c be case-insensitive %s\
125++ -z print zipfile comment -T print file times in sortable decimal format\n\
126++ -C be case-insensitive %s\
127 + -x exclude filenames that follow from listing\n\
128 + -O CHARSET specify a character encoding for DOS, Windows and OS/2 archives\n\
129 + -I CHARSET specify a character encoding for UNIX and other archives\n";
130@@ -220,7 +226,7 @@ Index: unzip-6.0/unzip.c
131 + extern char OEM_CP[MAX_CP_NAME];
132 + extern char ISO_CP[MAX_CP_NAME];
133 +#endif
134-+
135++
136 while (++argv, (--argc > 0 && *argv != NULL && **argv == '-')) {
137 s = *argv + 1;
138 while ((c = *s++) != 0) { /* "!= 0": prevent Turbo C warning */
139@@ -237,10 +243,10 @@ Index: unzip-6.0/unzip.c
140 + } else {
141 + if(*s) { /* Handle the -Icharset case */
142 + /* Assume that charsets can't start with a dash to spot arguments misuse */
143-+ if(*s == '-') {
144++ if(*s == '-') {
145 + Info(slide, 0x401, ((char *)slide,
146 + "error: a valid character encoding should follow the -I argument"));
147-+ return(PK_PARAM);
148++ return(PK_PARAM);
149 + }
150 + strncpy(ISO_CP, s, sizeof(ISO_CP));
151 + } else { /* -I charset */
152@@ -248,7 +254,7 @@ Index: unzip-6.0/unzip.c
153 + if(!(--argc > 0 && *argv != NULL && **argv != '-')) {
154 + Info(slide, 0x401, ((char *)slide,
155 + "error: a valid character encoding should follow the -I argument"));
156-+ return(PK_PARAM);
157++ return(PK_PARAM);
158 + }
159 + s = *argv;
160 + strncpy(ISO_CP, s, sizeof(ISO_CP));
161@@ -273,10 +279,10 @@ Index: unzip-6.0/unzip.c
162 + } else {
163 + if(*s) { /* Handle the -Ocharset case */
164 + /* Assume that charsets can't start with a dash to spot arguments misuse */
165-+ if(*s == '-') {
166++ if(*s == '-') {
167 + Info(slide, 0x401, ((char *)slide,
168 + "error: a valid character encoding should follow the -I argument"));
169-+ return(PK_PARAM);
170++ return(PK_PARAM);
171 + }
172 + strncpy(OEM_CP, s, sizeof(OEM_CP));
173 + } else { /* -O charset */
174@@ -284,7 +290,7 @@ Index: unzip-6.0/unzip.c
175 + if(!(--argc > 0 && *argv != NULL && **argv != '-')) {
176 + Info(slide, 0x401, ((char *)slide,
177 + "error: a valid character encoding should follow the -O argument"));
178-+ return(PK_PARAM);
179++ return(PK_PARAM);
180 + }
181 + s = *argv;
182 + strncpy(OEM_CP, s, sizeof(OEM_CP));
183@@ -296,10 +302,37 @@ Index: unzip-6.0/unzip.c
184 case ('p'): /* pipes: extract to stdout, no messages */
185 if (negative) {
186 uO.cflag = FALSE;
187-Index: unzip-6.0/unzpriv.h
188+@@ -2162,6 +2250,7 @@
189+ " ACORN_FTYPE_NFS] Translate filetype and append to name.",
190+ " -i [MacOS] Ignore filenames in MacOS extra field. Instead, use name in",
191+ " standard header.",
192++ " -I CHARSET [UNIX] Specify a character encoding for UNIX and other archives.",
193+ " -j Junk paths and deposit all files in extraction directory.",
194+ " -J [BeOS] Junk file attributes. [MacOS] Ignore MacOS specific info.",
195+ " -K [AtheOS, BeOS, Unix] Restore SUID/SGID/Tacky file attributes.",
196+@@ -2172,6 +2261,8 @@
197+ " -N [Amiga] Extract file comments as Amiga filenotes.",
198+ " -o Overwrite existing files without prompting. Useful with -f. Use with",
199+ " care.",
200++ " -O CHARSET [UNIX] Specify a character encoding for DOS, Windows",
201++ " and OS/2 archives.",
202+ " -P p Use password p to decrypt files. THIS IS INSECURE! Some OS show",
203+ " command line to other users.",
204+ " -q Perform operations quietly. The more q (as in -qq) the quieter.",
205+@@ -2264,6 +2355,9 @@
206+ " representing the Unicode character number of the character in hex.",
207+ " -UU [UNICODE] Disable use of any UTF-8 path information.",
208+ " -z Include archive comment if any in listing.",
209++ " -O CHARSET [UNIX] Specify a character encoding for DOS, Windows",
210++ " and OS/2 archives.",
211++ " -I CHARSET [UNIX] Specify a character encoding for UNIX and other archives.",
212+ "",
213+ "",
214+ "funzip stream extractor:",
215+Index: unzip/unzpriv.h
216 ===================================================================
217---- unzip-6.0.orig/unzpriv.h 2015-02-11 08:46:43.675324290 -0500
218-+++ unzip-6.0/unzpriv.h 2015-02-11 08:46:43.675324290 -0500
219+--- unzip.orig/unzpriv.h 2023-06-15 21:24:46.857092642 +0300
220++++ unzip/unzpriv.h 2023-06-15 21:24:46.849092659 +0300
221 @@ -3008,7 +3008,7 @@
222 !(((islochdr) || (isuxatt)) && \
223 ((hostver) == 25 || (hostver) == 26 || (hostver) == 40))) || \
224@@ -309,10 +342,10 @@ Index: unzip-6.0/unzpriv.h
225 _OEM_INTERN((string)); \
226 } else { \
227 _ISO_INTERN((string)); \
228-Index: unzip-6.0/zipinfo.c
229+Index: unzip/zipinfo.c
230 ===================================================================
231---- unzip-6.0.orig/zipinfo.c 2015-02-11 08:46:43.675324290 -0500
232-+++ unzip-6.0/zipinfo.c 2015-02-11 08:46:43.675324290 -0500
233+--- unzip.orig/zipinfo.c 2023-06-15 21:24:46.857092642 +0300
234++++ unzip/zipinfo.c 2023-06-15 21:24:46.849092659 +0300
235 @@ -457,6 +457,10 @@
236 int tflag_slm=TRUE, tflag_2v=FALSE;
237 int explicit_h=FALSE, explicit_t=FALSE;
238@@ -337,10 +370,10 @@ Index: unzip-6.0/zipinfo.c
239 + } else {
240 + if(*s) { /* Handle the -Icharset case */
241 + /* Assume that charsets can't start with a dash to spot arguments misuse */
242-+ if(*s == '-') {
243++ if(*s == '-') {
244 + Info(slide, 0x401, ((char *)slide,
245 + "error: a valid character encoding should follow the -I argument"));
246-+ return(PK_PARAM);
247++ return(PK_PARAM);
248 + }
249 + strncpy(ISO_CP, s, sizeof(ISO_CP));
250 + } else { /* -I charset */
251@@ -348,7 +381,7 @@ Index: unzip-6.0/zipinfo.c
252 + if(!(--argc > 0 && *argv != NULL && **argv != '-')) {
253 + Info(slide, 0x401, ((char *)slide,
254 + "error: a valid character encoding should follow the -I argument"));
255-+ return(PK_PARAM);
256++ return(PK_PARAM);
257 + }
258 + s = *argv;
259 + strncpy(ISO_CP, s, sizeof(ISO_CP));
260@@ -373,10 +406,10 @@ Index: unzip-6.0/zipinfo.c
261 + } else {
262 + if(*s) { /* Handle the -Ocharset case */
263 + /* Assume that charsets can't start with a dash to spot arguments misuse */
264-+ if(*s == '-') {
265++ if(*s == '-') {
266 + Info(slide, 0x401, ((char *)slide,
267 + "error: a valid character encoding should follow the -I argument"));
268-+ return(PK_PARAM);
269++ return(PK_PARAM);
270 + }
271 + strncpy(OEM_CP, s, sizeof(OEM_CP));
272 + } else { /* -O charset */
273@@ -384,7 +417,7 @@ Index: unzip-6.0/zipinfo.c
274 + if(!(--argc > 0 && *argv != NULL && **argv != '-')) {
275 + Info(slide, 0x401, ((char *)slide,
276 + "error: a valid character encoding should follow the -O argument"));
277-+ return(PK_PARAM);
278++ return(PK_PARAM);
279 + }
280 + s = *argv;
281 + strncpy(OEM_CP, s, sizeof(OEM_CP));
282@@ -396,3 +429,40 @@ Index: unzip-6.0/zipinfo.c
283 case 's': /* default: shorter "ls -l" type listing */
284 if (negative)
285 uO.lflag = -2, negative = 0;
286+Index: unzip/man/unzip.1
287+===================================================================
288+--- unzip.orig/man/unzip.1 2023-06-15 21:24:46.857092642 +0300
289++++ unzip/man/unzip.1 2023-06-15 21:24:46.849092659 +0300
290+@@ -325,6 +325,8 @@
291+ [MacOS only] ignore filenames stored in MacOS extra fields. Instead, the
292+ most compatible filename stored in the generic part of the entry's header
293+ is used.
294++.IP \fB\-I\fP\ \fICHARSET\fP
295++[UNIX only] Specify a character encoding for UNIX and other archives.
296+ .TP
297+ .B \-j
298+ junk paths. The archive's directory structure is not recreated; all files
299+@@ -386,6 +388,8 @@
300+ overwrite existing files without prompting. This is a dangerous option, so
301+ use it with care. (It is often used with \fB\-f\fP, however, and is the only
302+ way to overwrite directory EAs under OS/2.)
303++.IP \fB\-O\fP\ \fICHARSET\fP
304++[UNIX only] Specify a character encoding for DOS, Windows and OS/2 archives.
305+ .IP \fB\-P\fP\ \fIpassword\fP
306+ use \fIpassword\fP to decrypt encrypted zipfile entries (if any). \fBTHIS IS
307+ INSECURE!\fP Many multi-user operating systems provide ways for any user to
308+Index: unzip/man/zipinfo.1
309+===================================================================
310+--- unzip.orig/man/zipinfo.1 2023-06-15 21:24:46.857092642 +0300
311++++ unzip/man/zipinfo.1 2023-06-15 21:24:46.849092659 +0300
312+@@ -174,6 +174,10 @@
313+ .TP
314+ .B \-z
315+ include the archive comment (if any) in the listing.
316++.IP \fB\-I\fP\ \fICHARSET\fP
317++[UNIX only] Specify a character encoding for UNIX and other archives.
318++.IP \fB\-O\fP\ \fICHARSET\fP
319++[UNIX only] Specify a character encoding for DOS, Windows and OS/2 archives.
320+ .PD
321+ .\" =========================================================================
322+ .SH "DETAILED DESCRIPTION"
323diff --git a/debian/patches/29-fix-troff-warning.patch b/debian/patches/29-fix-troff-warning.patch
324new file mode 100644
325index 0000000..c6de74a
326--- /dev/null
327+++ b/debian/patches/29-fix-troff-warning.patch
328@@ -0,0 +1,422 @@
329+Description: Remove monospace font directives
330+ When viewing any man page the following warning is displayed:
331+ troff: warning: cannot select font 'C'
332+ .
333+ This is a new warning. Other projects replaced the \fC directives with
334+ nothing, as it is currently a no-op.
335+ .
336+ Context: \fC sets the font to a monospace font. The terminal can not change
337+ the font to any other font than a monospace font. Therefore removing the \fC
338+ directives should result in no visual change.
339+Author: Dominik Viererbe <dominik.viererbe@canonical.com>
340+Origin: vendor
341+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/unzip/+bug/2054670
342+Forwarded: no
343+Last-Update: 2024-02-22
344+---
345+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
346+--- a/man/funzip.1
347++++ b/man/funzip.1
348+@@ -115,11 +115,11 @@
349+ .SH URL
350+ The Info-ZIP home page is currently at
351+ .EX
352+-\fChttp://www.info-zip.org/pub/infozip/\fR
353++http://www.info-zip.org/pub/infozip/
354+ .EE
355+ or
356+ .EX
357+-\fCftp://ftp.info-zip.org/pub/infozip/\fR .
358++ftp://ftp.info-zip.org/pub/infozip/ .
359+ .EE
360+ .PD
361+ .\" =========================================================================
362+--- a/man/unzip.1
363++++ b/man/unzip.1
364+@@ -65,9 +65,9 @@
365+ (Be sure to quote any character that might otherwise be interpreted or
366+ modified by the operating system, particularly under Unix and VMS.) If no
367+ matches are found, the specification is assumed to be a literal filename;
368+-and if that also fails, the suffix \fC.zip\fR is appended. Note that
369++and if that also fails, the suffix .zip is appended. Note that
370+ self-extracting ZIP files are supported, as with any other ZIP archive;
371+-just specify the \fC.exe\fR suffix (if any) explicitly.
372++just specify the .exe suffix (if any) explicitly.
373+ .IP [\fIfile(s)\fP]
374+ An optional list of archive members to be processed, separated by spaces.
375+ (VMS versions compiled with VMSCLI defined must delimit files with commas
376+@@ -80,7 +80,7 @@
377+ Since wildcard characters normally match (`/') directory separators
378+ (for exceptions see the option \fB\-W\fP), this option may be used
379+ to exclude any files that are in subdirectories. For
380+-example, ``\fCunzip foo *.[ch] -x */*\fR'' would extract all C source files
381++example, ``unzip foo *.[ch] -x */*'' would extract all C source files
382+ in the main directory, but none in any subdirectories. Without the \fB\-x\fP
383+ option, all C source files in all directories within the zipfile would be
384+ extracted.
385+@@ -94,8 +94,8 @@
386+ specification, or between the \fIfile(s)\fP and the \fB\-x\fP option.
387+ The option and directory may be concatenated without any white space
388+ between them, but note that this may cause normal shell behavior to be
389+-suppressed. In particular, ``\fC\-d\ ~\fR'' (tilde) is expanded by Unix
390+-C shells into the name of the user's home directory, but ``\fC\-d~\fR''
391++suppressed. In particular, ``\-d\ ~'' (tilde) is expanded by Unix
392++C shells into the name of the user's home directory, but ``\-d~''
393+ is treated as a literal subdirectory ``\fB~\fP'' of the current directory.
394+ .\" =========================================================================
395+ .SH OPTIONS
396+@@ -156,7 +156,7 @@
397+ .B \-T
398+ [most OSes] set the timestamp on the archive(s) to that of the newest file
399+ in each one. This corresponds to \fIzip\fP's \fB\-go\fP option except that
400+-it can be used on wildcard zipfiles (e.g., ``\fCunzip \-T \e*.zip\fR'') and
401++it can be used on wildcard zipfiles (e.g., ``unzip \-T \e*.zip'') and
402+ is much faster.
403+ .TP
404+ .B \-u
405+@@ -180,7 +180,7 @@
406+ size of the encrypted compressed data stream for zipfile entries is reported
407+ by the more verbose \fIzipinfo\fP(1) reports, see the separate manual.)
408+ When no zipfile is specified (that is, the complete command is simply
409+-``\fCunzip \-v\fR''), a diagnostic screen is printed. In addition to
410++``unzip \-v''), a diagnostic screen is printed. In addition to
411+ the normal header with release date and version, \fIunzip\fP lists the
412+ home Info-ZIP ftp site and where to find a list of other ftp and non-ftp
413+ sites; the target operating system for which it was compiled, as well
414+@@ -212,7 +212,7 @@
415+ rather than the more common ASCII character set, and NT supports Unicode.)
416+ Note that \fIzip\fP's identification of text files is by no means perfect; some
417+ ``text'' files may actually be binary and vice versa. \fIunzip\fP therefore
418+-prints ``\fC[text]\fR'' or ``\fC[binary]\fR'' as a visual check for each file
419++prints ``[text]'' or ``[binary]'' as a visual check for each file
420+ it extracts when using the \fB\-a\fP option. The \fB\-aa\fP option forces
421+ all files to be extracted as text, regardless of the supposed file type.
422+ On VMS, see also \fB\-S\fP.
423+@@ -246,14 +246,14 @@
424+ This feature works similarly to the default behavior of \fIemacs\fP(1)
425+ in many locations.
426+ .IP
427+-Example: the old copy of ``\fCfoo\fR'' is renamed to ``\fCfoo~\fR''.
428++Example: the old copy of ``foo'' is renamed to ``foo~''.
429+ .IP
430+ Warning: Users should be aware that the \fB-B\fP option does not prevent
431+ loss of existing data under all circumstances. For example, when
432+-\fIunzip\fP is run in overwrite-all mode, an existing ``\fCfoo~\fR'' file
433+-is deleted before \fIunzip\fP attempts to rename ``\fCfoo\fR'' to
434+-``\fCfoo~\fR''. When this rename attempt fails (because of a file locks,
435+-insufficient privileges, or ...), the extraction of ``\fCfoo~\fR'' gets
436++\fIunzip\fP is run in overwrite-all mode, an existing ``foo~'' file
437++is deleted before \fIunzip\fP attempts to rename ``foo'' to
438++``foo~''. When this rename attempt fails (because of a file locks,
439++insufficient privileges, or ...), the extraction of ``foo~'' gets
440+ cancelled, but the old backup file is already lost. A similar scenario
441+ takes place when the sequence number range for numbered backup files gets
442+ exhausted (99999, or 65535 for 16-bit systems). In this case, the backup
443+@@ -269,15 +269,15 @@
444+ (notably those under the Unix operating system) and because
445+ both ZIP archives and \fIunzip\fP itself are portable across platforms,
446+ \fIunzip\fP's default behavior is to match both wildcard and literal
447+-filenames case-sensitively. That is, specifying ``\fCmakefile\fR''
448++filenames case-sensitively. That is, specifying ``makefile''
449+ on the command line will \fIonly\fP match ``makefile'' in the archive,
450+ not ``Makefile'' or ``MAKEFILE'' (and similarly for wildcard specifications).
451+ Since this does not correspond to the behavior of many other
452+ operating/file systems (for example, OS/2 HPFS, which preserves
453+ mixed case but is not sensitive to it), the \fB\-C\fP option may be
454+ used to force all filename matches to be case-insensitive. In the
455+-example above, all three files would then match ``\fCmakefile\fR''
456+-(or ``\fCmake*\fR'', or similar). The \fB\-C\fP option affects
457++example above, all three files would then match ``makefile''
458++(or ``make*'', or similar). The \fB\-C\fP option affects
459+ file specs in both the normal file list and the excluded-file list (xlist).
460+ .IP
461+ Please note that the \fB\-C\fP option does neither affect the search for
462+@@ -411,7 +411,7 @@
463+ .B \-s
464+ [OS/2, NT, MS-DOS] convert spaces in filenames to underscores. Since all PC
465+ operating systems allow spaces in filenames, \fIunzip\fP by default extracts
466+-filenames with spaces intact (e.g., ``\fCEA\ DATA.\ SF\fR''). This can be
467++filenames with spaces intact (e.g., ``EA\ DATA.\ SF''). This can be
468+ awkward, however, since MS-DOS in particular does not gracefully support
469+ spaces in filenames. Conversion of spaces to underscores can eliminate the
470+ awkwardness in some cases.
471+@@ -440,7 +440,7 @@
472+ .TP
473+ .B \-V
474+ retain (VMS) file version numbers. VMS files can be stored with a version
475+-number, in the format \fCfile.ext;##\fR. By default the ``\fC;##\fR'' version
476++number, in the format file.ext;##. By default the ``;##'' version
477+ numbers are stripped, but this option allows them to be retained. (On
478+ file systems that limit filenames to particularly short lengths, the version
479+ numbers may be truncated or stripped regardless of this option.)
480+@@ -629,7 +629,7 @@
481+ Some compiled versions of \fIunzip\fP may not support decryption.
482+ To check a version for crypt support, either attempt to test or extract
483+ an encrypted archive, or else check \fIunzip\fP's diagnostic
484+-screen (see the \fB\-v\fP option above) for ``\fC[decryption]\fR'' as one
485++screen (see the \fB\-v\fP option above) for ``[decryption]'' as one
486+ of the special compilation options.
487+ .PP
488+ As noted above, the \fB\-P\fP option may be used to supply a password on
489+@@ -787,7 +787,7 @@
490+ unzip \-ql file.zip
491+ .EE
492+ .PP
493+-(Note that the ``\fC.zip\fR'' is generally not necessary.) To do a standard
494++(Note that the ``.zip'' is generally not necessary.) To do a standard
495+ listing:
496+ .PP
497+ .EX
498+@@ -806,16 +806,16 @@
499+ .\" =========================================================================
500+ .SH TIPS
501+ The current maintainer, being a lazy sort, finds it very useful to define
502+-a pair of aliases: \fCtt\fR for ``\fCunzip \-tq\fR'' and \fCii\fR for
503+-``\fCunzip \-Z\fR'' (or ``\fCzipinfo\fR''). One may then simply type
504+-``\fCtt zipfile\fR'' to test an archive, something that is worth making a
505+-habit of doing. With luck \fIunzip\fP will report ``\fCNo errors detected
506+-in compressed data of zipfile.zip\fR,'' after which one may breathe a sigh
507++a pair of aliases: tt for ``unzip \-tq'' and ii for
508++``unzip \-Z'' (or ``zipinfo''). One may then simply type
509++``tt zipfile'' to test an archive, something that is worth making a
510++habit of doing. With luck \fIunzip\fP will report ``No errors detected
511++in compressed data of zipfile.zip,'' after which one may breathe a sigh
512+ of relief.
513+ .PP
514+ The maintainer also finds it useful to set the UNZIP environment variable
515+-to ``\fC\-aL\fR'' and is tempted to add ``\fC\-C\fR'' as well. His ZIPINFO
516+-variable is set to ``\fC\-z\fR''.
517++to ``\-aL'' and is tempted to add ``\-C'' as well. His ZIPINFO
518++variable is set to ``\-z''.
519+ .PD
520+ .\" =========================================================================
521+ .SH DIAGNOSTICS
522+@@ -882,11 +882,11 @@
523+ .SH BUGS
524+ Multi-part archives are not yet supported, except in conjunction with
525+ \fIzip\fP. (All parts must be concatenated together in order, and then
526+-``\fCzip \-F\fR'' (for \fIzip 2.x\fP) or ``\fCzip \-FF\fR'' (for
527++``zip \-F'' (for \fIzip 2.x\fP) or ``zip \-FF'' (for
528+ \fIzip 3.x\fP) must be performed on the concatenated archive in order to
529+ ``fix'' it. Also, \fIzip 3.0\fP and later can combine multi-part (split)
530+-archives into a combined single-file archive using ``\fCzip \-s\- inarchive
531+--O outarchive\fR''. See the \fIzip 3\fP manual page for more information.)
532++archives into a combined single-file archive using ``zip \-s\- inarchive
533++-O outarchive''. See the \fIzip 3\fP manual page for more information.)
534+ This will definitely be corrected in the next major release.
535+ .PP
536+ Archives read from standard input are not yet supported, except with
537+@@ -933,7 +933,7 @@
538+ are newer or older than those on disk. In practice this may mean a two-pass
539+ approach is required: first unpack the archive normally (with or without
540+ freshening/updating existing files), then overwrite just the directory entries
541+-(e.g., ``\fCunzip -o foo */\fR'').
542++(e.g., ``unzip -o foo */'').
543+ .PP
544+ [VMS] When extracting to another directory, only the \fI[.foo]\fP syntax is
545+ accepted for the \fB\-d\fP option; the simple Unix \fIfoo\fP syntax is
546+@@ -954,11 +954,11 @@
547+ .SH URL
548+ The Info-ZIP home page is currently at
549+ .EX
550+-\fChttp://www.info-zip.org/pub/infozip/\fR
551++http://www.info-zip.org/pub/infozip/
552+ .EE
553+ or
554+ .EX
555+-\fCftp://ftp.info-zip.org/pub/infozip/\fR .
556++ftp://ftp.info-zip.org/pub/infozip/ .
557+ .EE
558+ .PD
559+ .\" =========================================================================
560+--- a/man/unzipsfx.1
561++++ b/man/unzipsfx.1
562+@@ -89,7 +89,7 @@
563+ An optional list of archive members to be excluded from processing.
564+ Since wildcard characters match directory separators (`/'), this option
565+ may be used to exclude any files that are in subdirectories. For
566+-example, ``\fCfoosfx *.[ch] -x */*\fR'' would extract all C source files
567++example, ``foosfx *.[ch] -x */*'' would extract all C source files
568+ in the main directory, but none in any subdirectories. Without the \fB\-x\fP
569+ option, all C source files in all directories within the zipfile would be
570+ extracted.
571+@@ -103,8 +103,8 @@
572+ has permission to write to the directory). The option and directory may
573+ be concatenated without any white space between them, but note that this
574+ may cause normal shell behavior to be suppressed. In particular,
575+-``\fC\-d\ ~\fR'' (tilde) is expanded by Unix C shells into the name
576+-of the user's home directory, but ``\fC\-d~\fR'' is treated as a
577++``\-d\ ~'' (tilde) is expanded by Unix C shells into the name
578++of the user's home directory, but ``\-d~'' is treated as a
579+ literal subdirectory ``\fB~\fP'' of the current directory.
580+ .PD
581+ .\" =========================================================================
582+@@ -213,7 +213,7 @@
583+ .EE
584+ .PP
585+ (MakeSFX is included with the UnZip source distribution and with Amiga
586+-binary distributions. ``\fCzip -A\fR'' doesn't work on Amiga self-extracting
587++binary distributions. ``zip -A'' doesn't work on Amiga self-extracting
588+ archives.)
589+ To test (or list) the newly created self-extracting archive:
590+ .PP
591+@@ -235,13 +235,13 @@
592+ letters
593+ .EE
594+ .PP
595+-To extract all \fC*.txt\fR files (in Unix quote the `*'):
596++To extract all *.txt files (in Unix quote the `*'):
597+ .PP
598+ .EX
599+ letters *.txt
600+ .EE
601+ .PP
602+-To extract everything \fIexcept\fP the \fC*.txt\fR files:
603++To extract everything \fIexcept\fP the *.txt files:
604+ .PP
605+ .EX
606+ letters -x *.txt
607+@@ -295,7 +295,7 @@
608+ VMS users must know how to set up self-extracting archives as foreign
609+ commands in order to use any of \fIunzipsfx\fP's options. This is not
610+ necessary for simple extraction, but the command to do so then becomes,
611+-e.g., ``\fCrun letters\fR'' (to continue the examples given above).
612++e.g., ``run letters'' (to continue the examples given above).
613+ .PP
614+ \fIunzipsfx\fP on the Amiga requires the use of a special program, MakeSFX,
615+ in order to create working self-extracting archives; simple concatenation
616+@@ -320,11 +320,11 @@
617+ .SH URL
618+ The Info-ZIP home page is currently at
619+ .EX
620+-\fChttp://www.info-zip.org/pub/infozip/\fR
621++http://www.info-zip.org/pub/infozip/
622+ .EE
623+ or
624+ .EX
625+-\fCftp://ftp.info-zip.org/pub/infozip/\fR .
626++ftp://ftp.info-zip.org/pub/infozip/ .
627+ .EE
628+ .PD
629+ .\" =========================================================================
630+--- a/man/zipgrep.1
631++++ b/man/zipgrep.1
632+@@ -32,9 +32,9 @@
633+ expression accepted by \fIegrep\fP(1) may be used.
634+ .IR file [ .zip ]
635+ Path of the ZIP archive. (Wildcard expressions for the ZIP archive name are
636+-not supported.) If the literal filename is not found, the suffix \fC.zip\fR
637++not supported.) If the literal filename is not found, the suffix .zip
638+ is appended. Note that self-extracting ZIP files are supported, as with any
639+-other ZIP archive; just specify the \fC.exe\fR suffix (if any) explicitly.
640++other ZIP archive; just specify the .exe suffix (if any) explicitly.
641+ .IP [\fIfile(s)\fP]
642+ An optional list of archive members to be processed, separated by spaces.
643+ If no member files are specified, all members of the ZIP archive are searched.
644+@@ -58,7 +58,7 @@
645+ An optional list of archive members to be excluded from processing.
646+ Since wildcard characters match directory separators (`/'), this option
647+ may be used to exclude any files that are in subdirectories. For
648+-example, ``\fCzipgrep grumpy foo *.[ch] -x */*\fR'' would search for the
649++example, ``zipgrep grumpy foo *.[ch] -x */*'' would search for the
650+ string ``grumpy'' in all C source files in the main directory of the ``foo''
651+ archive, but none in any subdirectories. Without the \fB\-x\fP
652+ option, all C source files in all directories within the zipfile would be
653+@@ -76,11 +76,11 @@
654+ .SH URL
655+ The Info-ZIP home page is currently at
656+ .EX
657+-\fChttp://www.info-zip.org/pub/infozip/\fR
658++http://www.info-zip.org/pub/infozip/
659+ .EE
660+ or
661+ .EX
662+-\fCftp://ftp.info-zip.org/pub/infozip/\fR .
663++ftp://ftp.info-zip.org/pub/infozip/ .
664+ .EE
665+ .PD
666+ .\" =========================================================================
667+--- a/man/zipinfo.1
668++++ b/man/zipinfo.1
669+@@ -54,7 +54,7 @@
670+ behavior (with no options) is
671+ to list single-line entries for each file in the archive, with header and
672+ trailer lines providing summary information for the entire archive. The
673+-format is a cross between Unix ``\fCls \-l\fR'' and ``\fCunzip \-v\fR''
674++format is a cross between Unix ``ls \-l'' and ``unzip \-v''
675+ output. See
676+ .B "DETAILED DESCRIPTION"
677+ below. Note that \fIzipinfo\fP is the same program as \fIunzip\fP (under
678+@@ -87,9 +87,9 @@
679+ (Be sure to quote any character that might otherwise be interpreted or
680+ modified by the operating system, particularly under Unix and VMS.) If no
681+ matches are found, the specification is assumed to be a literal filename;
682+-and if that also fails, the suffix \fC.zip\fR is appended. Note that
683++and if that also fails, the suffix .zip is appended. Note that
684+ self-extracting ZIP files are supported, as with any other ZIP archive;
685+-just specify the \fC.exe\fR suffix (if any) explicitly.
686++just specify the .exe suffix (if any) explicitly.
687+ .IP [\fIfile(s)\fP]
688+ An optional list of archive members to be processed, separated by spaces.
689+ (VMS versions compiled with VMSCLI defined must delimit files with commas
690+@@ -113,16 +113,16 @@
691+ useful in cases where the stored filenames are particularly long.
692+ .TP
693+ .B \-s
694+-list zipfile info in short Unix ``\fCls \-l\fR'' format. This is the default
695++list zipfile info in short Unix ``ls \-l'' format. This is the default
696+ behavior; see below.
697+ .TP
698+ .B \-m
699+-list zipfile info in medium Unix ``\fCls \-l\fR'' format. Identical to the
700++list zipfile info in medium Unix ``ls \-l'' format. Identical to the
701+ \fB\-s\fP output, except that the compression factor, expressed as a
702+ percentage, is also listed.
703+ .TP
704+ .B \-l
705+-list zipfile info in long Unix ``\fCls \-l\fR'' format. As with \fB\-m\fP
706++list zipfile info in long Unix ``ls \-l'' format. As with \fB\-m\fP
707+ except that the compressed size (in bytes) is printed instead of the
708+ compression ratio.
709+ .TP
710+@@ -294,7 +294,7 @@
711+ their total uncompressed size, and their total compressed size (not
712+ including any of \fIzip\fP's internal overhead). If, however, one or
713+ more \fIfile(s)\fP are provided, the header and trailer lines are
714+-not listed. This behavior is also similar to that of Unix's ``\fCls \-l\fR'';
715++not listed. This behavior is also similar to that of Unix's ``ls \-l'';
716+ it may be overridden by specifying the \fB\-h\fP and \fB\-t\fP options
717+ explicitly.
718+ In such a case the listing format must also be specified explicitly,
719+@@ -334,7 +334,7 @@
720+ either of the above.
721+ .PP
722+ The default listing format, as noted above, corresponds roughly
723+-to the "\fCzipinfo \-hst\fR" command (except when individual zipfile members
724++to the "zipinfo \-hst" command (except when individual zipfile members
725+ are specified).
726+ A user who prefers the long-listing format (\fB\-l\fP) can make use of the
727+ \fIzipinfo\fP's environment variable to change this default:
728+@@ -355,7 +355,7 @@
729+ If, in addition, the user dislikes the trailer line, \fIzipinfo\fP's
730+ concept of ``negative options'' may be used to override the default
731+ inclusion of the line. This is accomplished by preceding the undesired
732+-option with one or more minuses: e.g., ``\fC\-l\-t\fR'' or ``\fC\-\-tl\fR'',
733++option with one or more minuses: e.g., ``\-l\-t'' or ``\-\-tl'',
734+ in this example. The first hyphen is the regular switch character, but the
735+ one before the `t' is a minus sign. The dual use of hyphens may seem a
736+ little awkward, but it's reasonably intuitive nonetheless: simply ignore
737+@@ -507,11 +507,11 @@
738+ .SH URL
739+ The Info-ZIP home page is currently at
740+ .EX
741+-\fChttp://www.info-zip.org/pub/infozip/\fR
742++http://www.info-zip.org/pub/infozip/
743+ .EE
744+ or
745+ .EX
746+-\fCftp://ftp.info-zip.org/pub/infozip/\fR .
747++ftp://ftp.info-zip.org/pub/infozip/ .
748+ .EE
749+ .PD
750+ .\" =========================================================================
751diff --git a/debian/patches/series b/debian/patches/series
752index 3793f99..2a5353a 100644
753--- a/debian/patches/series
754+++ b/debian/patches/series
755@@ -28,3 +28,4 @@
756 27-zipgrep-avoid-test-errors.patch
757 28-cve-2022-0529-and-cve-2022-0530.patch
758 handle_windows_zip64.patch
759+29-fix-troff-warning.patch
760diff --git a/debian/tests/SmokeTests.py b/debian/tests/SmokeTests.py
761new file mode 100755
762index 0000000..86c9d33
763--- /dev/null
764+++ b/debian/tests/SmokeTests.py
765@@ -0,0 +1,1422 @@
766+#!/usr/bin/env python3
767+
768+import os
769+import re
770+import shutil
771+import subprocess
772+import unittest
773+
774+# Debug Flag:
775+# When set to `False` the `tearDown` methods of the TestCases will not delete
776+# the temporary test directories.
777+CleanUp = True
778+
779+
780+def diff(expected: str, actual: str) -> str:
781+ """
782+ Wrapper function for the shell command 'diff' to compare and show the\
783+ difference of two strings.
784+
785+ Args:
786+ expected: The string value that was expected.
787+ actual: The string that was actually produced and that should be\
788+ compared against expected.
789+
790+ Returns:
791+ str: A string representation in a diff format that shows the\
792+ difference between `expected` and `actual`.
793+ """
794+
795+ bash_command = f'''/usr/bin/env bash -c "printf -- 'diff EXPECTED ACTUAL\\n---EXPECTED\\n+++ACTUAL\\n'; diff --minimal --unified <(cat << 'EOF'
796+{expected}
797+EOF
798+) <(cat << 'EOF'
799+{actual}
800+EOF
801+) | tail -n +3"'''
802+
803+ try:
804+ output = subprocess.check_output(bash_command,
805+ shell=True, stderr=subprocess.STDOUT)
806+ return output.decode()
807+ except subprocess.CalledProcessError as error:
808+ # Error can safely be ignored, because diff returns with failure
809+ # exit code if expected and actual have no difference.
810+ return error.output.decode()
811+
812+
813+def mktemp(createDirectory: bool = False,
814+ basePath: str | None = None,
815+ template: str = "tmp.XXXXXXXXXX") -> str:
816+ """
817+ Create a temporary file or directory and returns its path.
818+
819+ Remarks:
820+ Files are created u+rw, and directories u+rwx, minus umask\
821+ restrictions.
822+
823+ See `$ man mktemp` for more details.
824+
825+ Args:
826+ createDirectory: If True create a directory, otherwise a file.\
827+ (default: `False`)
828+ basePath: Base path of the provided `template`; if `None`, use current\
829+ working directory. (default: `None`)
830+ template: Template of the created temporary file/directory. `X`s will\
831+ be replaced by a random alpha-numeric character.\
832+ `template` must contain at least 3 consecutive 'X's in last\
833+ component. (default: `"tmp.XXXXXXXXXX"`)
834+
835+ Returns:
836+ str: Absolute path of the created temporary file or directory.
837+ """
838+
839+ command = "mktemp "
840+
841+ if createDirectory:
842+ command += "--directory "
843+
844+ if basePath is None:
845+ command += "--tmpdir=\"$PWD\" "
846+ else:
847+ command += f"--tmpdir='{str(basePath)}' "
848+
849+ command += f"'{str(template)}'"
850+
851+ return subprocess.check_output(command,
852+ shell=True, encoding="utf-8").strip()
853+
854+
855+def deleteRecursiveWithForce(path: str) -> None:
856+ """
857+ Deletes a directory or file for a given path recursively and forcefully.
858+
859+ Args:
860+ path: Path to the file/directory to delete.
861+
862+ Returns:
863+ `None`
864+ """
865+
866+ subprocess.check_call(f"rm -rf '{path}'", shell=True)
867+
868+
869+def dd(inputFile: str,
870+ outputFile: str,
871+ blockSize: str | int,
872+ blockCount: str | int,
873+ cwd: str | None = None) -> None:
874+ """
875+ Convert and copy a file; Wrapper function of the shell command `dd`.
876+
877+ Args:
878+ inputFile: The path of the file where `dd` should read the data from.
879+ outputFile: The path of the file where `dd` should write the data to.
880+ blockSize: The size of a block limits the amount of reads or writes\
881+ at a time in byte.
882+ blockCount: Copy the specified amount of blocks to the output file.
883+ cwd: Path of the working directory where `dd` will be executed.\
884+ If `None` is provided the working directory of the test script\
885+ process will be used. (default: `None`)
886+
887+ Returns:
888+ `None`
889+ """
890+
891+ command = ("dd"
892+ f" if='{inputFile}'"
893+ f" of='{outputFile}'"
894+ f" bs='{str(blockSize)}'"
895+ f" count='{str(blockCount)}'"
896+ " status='none'")
897+
898+ subprocess.check_call(command, cwd=cwd, shell=True)
899+
900+
901+def ddRandom(outputFile: str,
902+ outputFileSizeInMebibyte: int,
903+ cwd: str | None = None) -> None:
904+ """
905+ Writes random data to a file using the shell command `dd`.
906+
907+ Args:
908+ outputFile: The path of the file where the random data should be\
909+ written to.
910+ outputFileSizeInMebibyte: The amount of random data in MiB (Mebibyte)\
911+ that should be copied into the output file.
912+ cwd: Path of the working directory where `dd` will be executed.\
913+ If `None` is provided the working directory of the test script\
914+ process will be used. (default: `None`)
915+
916+ Returns:
917+ `None`
918+ """
919+
920+ dd(inputFile='/dev/random',
921+ outputFile=outputFile,
922+ blockSize=1024,
923+ blockCount=1024 * outputFileSizeInMebibyte,
924+ cwd=cwd)
925+
926+
927+def zip(zipArchive: str,
928+ fileToAdd: str,
929+ password: str | None = None,
930+ cwd: str | None = None) -> None:
931+ """
932+ Adds a file to a zip archive (and creates it if it does not exist).\
933+ Wrapper function of the shell command `zip`.
934+
935+ Args:
936+ zipArchive: Path of the zip archive, the file should be added to.\
937+ The file will be created if it does not exists.
938+ fileToAdd: Path of the file to add to the zip archive.
939+ password: Used to encrypt zipfile entries. If `None` is provided, the\
940+ zip archive will not be encrypted. (default: `None`)
941+ cwd: Path of the working directory where `zip` will be executed.\
942+ If `None` is provided the working directory of the test script\
943+ process will be used. (default: `None`)
944+
945+ Returns:
946+ `None`
947+ """
948+
949+ command = "zip -q"
950+ if password is not None:
951+ command += f" --password '{password}'"
952+ command += f" '{zipArchive}' '{fileToAdd}'"
953+
954+ subprocess.check_call(command, cwd=cwd, shell=True)
955+
956+
957+def prependUnZipSFX(zipArchive: str,
958+ outputFile: str,
959+ cwd: str | None = None) -> None:
960+ """
961+ Prepends UnZipSFX in fornt of a zip archive and configures it to be\
962+ ready to run.
963+
964+ Args:
965+ zipArchive: Path of the zip archive, UnZipSFX should be prepended to.
966+ outputFile: Path of the resulting file (UnZipSFX + zip archive).
967+ cwd: Path of the working directory where the operation will be\
968+ executed. If `None` is provided the working directory of the\
969+ test script process will be used. (default: `None`)
970+
971+ Returns:
972+ `None`
973+ """
974+
975+ subprocess.check_call(f"cat $(which unzipsfx) '{zipArchive}'"
976+ f" > '{outputFile}'", cwd=cwd, shell=True)
977+ subprocess.check_call(f"chmod u+x '{outputFile}'", cwd=cwd, shell=True)
978+ subprocess.check_call(f"zip -Aq '{outputFile}'", cwd=cwd, shell=True)
979+
980+
981+class ShellCommandResult(object):
982+ """
983+ Holds the result of a shell command and provides convenience functions\
984+ to run assertions on them.
985+ """
986+
987+ def __init__(self,
988+ command: str,
989+ exitcode: int,
990+ stdout: str,
991+ stderr: str) -> None:
992+ self.command = command
993+ self.exitcode = exitcode
994+ self.stdout = stdout
995+ self.stderr = stderr
996+
997+ def __CombinedOutput(self) -> str:
998+ result = ""
999+
1000+ if self.stdout != "":
1001+ result += "\n\nSTDOUT:\n" + self.stdout
1002+
1003+ if self.stderr != "":
1004+ result += "\n\nSTDERR:\n" + self.stderr
1005+
1006+ return result
1007+
1008+ def __AssertOutputStartsWith(self,
1009+ output: str,
1010+ outputName: str,
1011+ expectedStart: str) -> None:
1012+ difference = diff(expectedStart, output[:len(expectedStart)])
1013+
1014+ assert len(output) >= len(expectedStart), \
1015+ (f"Length of {outputName} output is smaller than expected!"
1016+ f"{self.__CombinedOutput()}\n\n{difference}")
1017+
1018+ for i in range(len(expectedStart)):
1019+ assert output[i] == expectedStart[i], \
1020+ (f"{outputName} output does not start with expected output!"
1021+ f"\n\nAt postion {i} character '{expectedStart[i]}' "
1022+ f"({hex(ord(expectedStart[i]))}) was expected, "
1023+ f"got '{output[i]}' "
1024+ f"({hex(ord(output[i]))}).\n\n{difference}")
1025+
1026+ def AssertSuccessExitCode(self) -> None:
1027+ """
1028+ Fail the test unless the exit code of the shell command was `0`.
1029+ """
1030+
1031+ assert self.exitcode == 0, \
1032+ ("Expected Success Exit Code (== 0), "
1033+ f"got {self.exitcode}!{self.__CombinedOutput()}")
1034+
1035+ def AssertFailureExitCode(self) -> None:
1036+ """
1037+ Fail the test unless the exit code of the shell command was NOT `0`.
1038+ """
1039+
1040+ assert self.exitcode != 0, \
1041+ ("Expected Failure Exit Code (!= 0), "
1042+ f"got {self.exitcode}!{self.__CombinedOutput()}")
1043+
1044+ def AssertStdoutIsEmpty(self) -> None:
1045+ """
1046+ Fail the test unless the standard output of the shell command\
1047+ was empty.
1048+ """
1049+
1050+ assert self.stdout == "", \
1051+ ("STDOUT output is not empty! "
1052+ f"Expected was an empty output.{self.__CombinedOutput()}")
1053+
1054+ def AssertStdoutIsNotEmpty(self) -> None:
1055+ """
1056+ Fail the test unless the standard output of the shell command\
1057+ was NOT empty.
1058+ """
1059+
1060+ assert self.stdout != "", \
1061+ ("STDOUT output is empty! "
1062+ f"Expected was a non-empty output.{self.__CombinedOutput()}")
1063+
1064+ def AssertStdoutEquals(self, expectedOutput) -> None:
1065+ """
1066+ Fail the test unless the standard output of the shell command\
1067+ equals an expected value.
1068+ """
1069+
1070+ assert self.stdout == expectedOutput, \
1071+ ("Unexpected STDOUT output!"
1072+ f"{self.__CombinedOutput()}\n\n"
1073+ f"{diff(expectedOutput, self.stdout)}")
1074+
1075+ def AssertStdoutStartsWith(self, expectedStart: str) -> None:
1076+ """
1077+ Fail the test unless the standard output of the shell command\
1078+ starts with an expected value.
1079+ """
1080+
1081+ self.__AssertOutputStartsWith(self.stdout, "STDOUT", expectedStart)
1082+
1083+ def AssertStderrIsEmpty(self) -> None:
1084+ """
1085+ Fail the test unless the standard error output of the shell command\
1086+ was empty.
1087+ """
1088+
1089+ assert self.stderr == "", \
1090+ ("STDERR output is not empty! "
1091+ f"Expected was an empty output.{self.__CombinedOutput()}")
1092+
1093+ def AssertStderrIsNotEmpty(self) -> None:
1094+ """
1095+ Fail the test unless the standard error output of the shell command\
1096+ was NOT empty.
1097+ """
1098+
1099+ assert self.stderr != "", \
1100+ ("STDERR output is empty! "
1101+ f"Expected was a non-empty output.{self.__CombinedOutput()}")
1102+
1103+ def AssertStderrEquals(self, expectedOutput) -> None:
1104+ """
1105+ Fail the test unless the standard error output of the shell command\
1106+ equals an expected value.
1107+ """
1108+
1109+ assert self.stderr == expectedOutput, \
1110+ ("Unexpected STDERR output!"
1111+ f"{self.__CombinedOutput()}\n\n"
1112+ f"{diff(expectedOutput, self.stderr)}")
1113+
1114+ def AssertStderrStartsWith(self, expectedStart: str) -> None:
1115+ """
1116+ Fail the test unless the standard error output of the shell command\
1117+ starts with an expected value.
1118+ """
1119+
1120+ self.__AssertOutputStartsWith(self.stderr, "STDERR", expectedStart)
1121+
1122+
1123+def RunShellCommand(command: str,
1124+ cwd: str | None = None) -> ShellCommandResult:
1125+ """
1126+ Runs a shell command and wraps the result it in a `ShellCommandResult`\
1127+ instance.
1128+
1129+ Args:
1130+ command: The `/bin/sh` command that should be executed.
1131+ cwd: The path of the working directory where the shell command\
1132+ should be executed. If `None` is provided the working directory\
1133+ of the test script process will be used. (default: `None`)
1134+
1135+ Returns:
1136+ ShellCommandResult: Instance that holds the result of the executed\
1137+ shell command.
1138+ """
1139+
1140+ result = subprocess.run(
1141+ args=command,
1142+ shell=True,
1143+ capture_output=True,
1144+ text=True,
1145+ encoding='utf-8',
1146+ cwd=cwd,
1147+ env={
1148+ "LANG": "C",
1149+ "LANGUAGE": "C",
1150+ "LC_ADDRESS": "C",
1151+ "LC_IDENTIFICATION": "C",
1152+ "LC_MEASUREMENT": "C",
1153+ "LC_MONETARY": "C",
1154+ "LC_NAME": "C",
1155+ "LC_NUMERIC": "C",
1156+ "LC_PAPER": "C",
1157+ "LC_TELEPHONE": "C",
1158+ "LC_TIME": "C",
1159+ })
1160+
1161+ return ShellCommandResult(command,
1162+ result.returncode, result.stdout, result.stderr)
1163+
1164+
1165+class ManPageTestCases(unittest.TestCase):
1166+ def AssertManPageContainsContent(self, command: str) -> None:
1167+ """
1168+ Fails unless a manpage for the command `command` exists, is not-empty\
1169+ and starts with the name of the command in upper case.
1170+ """
1171+
1172+ result = RunShellCommand("man -P cat " + command)
1173+ result.AssertSuccessExitCode()
1174+ result.AssertStderrIsEmpty()
1175+ result.AssertStdoutIsNotEmpty()
1176+ result.AssertStdoutStartsWith(command.upper())
1177+
1178+ def test_When_RunningFUnZipManpage_Expect_NonEmptyManPage(self):
1179+ """
1180+ Executing the `man funzip` command should print existing (non-empty)\
1181+ usage/help information to STDOUT.
1182+ """
1183+
1184+ self.AssertManPageContainsContent("funzip")
1185+
1186+ def test_When_RunningUnZipManpage_Expect_NonEmptyManPage(self):
1187+ """
1188+ Executing the `man unzip` command should print existing (non-empty)\
1189+ usage/help information to STDOUT.
1190+ """
1191+
1192+ self.AssertManPageContainsContent("unzip")
1193+
1194+ def test_When_RunningUnZipSFXManpage_Expect_NonEmptyManPage(self):
1195+ """
1196+ Executing the `man funzipsfx` command should print existing\
1197+ (non-empty) usage/help information to STDOUT.
1198+ """
1199+
1200+ self.AssertManPageContainsContent("unzipsfx")
1201+
1202+ def test_When_RunningZipGrepManpage_Expect_NonEmptyManPage(self):
1203+ """
1204+ Executing the `man zipgrep` command should print existing (non-empty)\
1205+ usage/help information to STDOUT.
1206+ """
1207+
1208+ self.AssertManPageContainsContent("zipgrep")
1209+
1210+ def test_When_RunningZipInfoManpage_Expect_NonEmptyManPage(self):
1211+ """
1212+ Executing the `man zipinfo` command should print existing (non-empty)\
1213+ usage/help information to STDOUT.
1214+ """
1215+
1216+ self.AssertManPageContainsContent("zipinfo")
1217+
1218+
1219+class UsageInfoTestCases(unittest.TestCase):
1220+ """
1221+ The tests of this class test if the commands provided by the unzip\
1222+ source packages show basic usage informations.
1223+ """
1224+
1225+ def test_When_RunningFUnZipCommandWithoutParameters_Expect_NonEmptyOutput(self) -> None: # noqa: E501
1226+ """
1227+ Executing the `funzip` command without any additional parameters\
1228+ should print short usage/help information to STDERR.
1229+ """
1230+
1231+ # Arrange
1232+ # coyprightHeader = "fUnZip (filter UnZip), version 3.95 of 20 January 2009\n" # noqa: E501
1233+
1234+ # Act
1235+ result = RunShellCommand("funzip")
1236+
1237+ # Assert
1238+ result.AssertFailureExitCode()
1239+ result.AssertStdoutIsEmpty()
1240+ result.AssertStderrIsNotEmpty()
1241+
1242+ # If unzip command is not called in tty mode it will not print the
1243+ # coypright header. When your run this script locally it would pass,
1244+ # but the autopkgtest runner has no tty attached.
1245+ # I could not figure out how to emulate this.
1246+ # result.AssertStderrStartsWith(coyprightHeader)
1247+
1248+ def test_When_RunningUnZipCommandWithoutParameters_Expect_NonEmptyOutput(self) -> None: # noqa: E501
1249+ """
1250+ Executing the `unzip` command without any additional parameters\
1251+ should print short usage/help information to STDOUT.
1252+ """
1253+
1254+ # Arrange
1255+ coyprightHeader = "UnZip 6.00 of 20 April 2009, by Debian. Original by Info-ZIP.\n" # noqa: E501
1256+
1257+ # Act
1258+ result = RunShellCommand("unzip")
1259+
1260+ # Assert
1261+ result.AssertSuccessExitCode()
1262+ result.AssertStderrIsEmpty()
1263+ result.AssertStdoutIsNotEmpty()
1264+ result.AssertStdoutStartsWith(coyprightHeader)
1265+
1266+ def test_When_RunningUnZipCommandWithHH_Expect_DisplayExtendedHelp(self) -> None: # noqa: E501
1267+ """
1268+ Executing the `unzip` command without any additional parameters should\
1269+ print short usage/help information to STDOUT.
1270+ """
1271+
1272+ # Arrange
1273+ header = ("\nExtended Help for UnZip\n\n"
1274+ "See the UnZip Manual for more detailed help\n\n\n")
1275+
1276+ # Act
1277+ result = RunShellCommand("unzip -hh")
1278+
1279+ # Assert
1280+ result.AssertSuccessExitCode()
1281+ result.AssertStderrIsEmpty()
1282+ result.AssertStdoutIsNotEmpty()
1283+ result.AssertStdoutStartsWith(header)
1284+
1285+ def test_When_RunningUnZipSFXCommandWithoutParameters_Expect_NonEmptyOutput(self) -> None: # noqa: E501
1286+ """
1287+ Executing the `unzipsfx` command without any additional parameters\
1288+ should print short usage/help information to STDERR.
1289+ """
1290+
1291+ # Arrange
1292+ coyprightHeader = "UnZipSFX 6.00 of 20 April 2009, by Info-ZIP (http://www.info-zip.org).\n" # noqa: E501
1293+
1294+ # Act
1295+ result = RunShellCommand("unzipsfx")
1296+
1297+ # Assert
1298+ result.AssertFailureExitCode()
1299+ result.AssertStdoutIsNotEmpty()
1300+ result.AssertStderrIsNotEmpty()
1301+ result.AssertStdoutStartsWith(coyprightHeader)
1302+
1303+ def test_When_RunningZipGrepCommandWithoutParameters_Expect_NonEmptyOutput(self) -> None: # noqa: E501
1304+ """
1305+ Executing the `zipgrep` command without any additional parameters\
1306+ should print short usage/help information to STDOUT.
1307+ """
1308+
1309+ # Arrange
1310+ expectedOutput = ("usage: zipgrep [egrep_options] pattern zipfile [members...]\n" # noqa: E501
1311+ "Uses unzip and egrep to search the zip members for a string or pattern.\n") # noqa: E501
1312+
1313+ # Act
1314+ result = RunShellCommand("zipgrep")
1315+
1316+ # Assert
1317+ result.AssertFailureExitCode()
1318+ result.AssertStderrIsEmpty()
1319+ result.AssertStdoutIsNotEmpty()
1320+ result.AssertStdoutEquals(expectedOutput)
1321+
1322+ def test_When_RunningZipInfoCommandWithoutParameters_Expect_NonEmptyOutput(self) -> None: # noqa: E501
1323+ """
1324+ Executing the `zipinfo` command without any additional parameters\
1325+ should print short usage/help information to STDOUT.
1326+ """
1327+
1328+ # Arrange
1329+ coyprightHeader = "ZipInfo 3.00 of 20 April 2009, by Greg Roelofs and the Info-ZIP group.\n" # noqa: E501
1330+
1331+ # Act
1332+ result = RunShellCommand("zipinfo")
1333+
1334+ # Assert
1335+ result.AssertSuccessExitCode()
1336+ result.AssertStderrIsEmpty()
1337+ result.AssertStdoutIsNotEmpty()
1338+ result.AssertStdoutStartsWith(coyprightHeader)
1339+
1340+
1341+class SingleFileZipTestCases(unittest.TestCase):
1342+ """
1343+ The tests of this class test if the commands provided by the unzip\
1344+ source packages can handle a zip archive with a simple single file.
1345+ """
1346+
1347+ ExampleFileName = "example.txt"
1348+ ExampleFileContent = "Hello Zip!"
1349+ SingleFileZipName = "single-file.zip"
1350+
1351+ @property
1352+ def ExampleFilePath(self) -> str:
1353+ return self.WorkingDirectory + "/" + self.ExampleFileName
1354+
1355+ def CreateTemporaryWorkingDirectory(self) -> None:
1356+ self.WorkingDirectory = mktemp(createDirectory=True)
1357+
1358+ def CreateExampleFile(self):
1359+ with open(self.ExampleFilePath, "w") as file:
1360+ file.write(self.ExampleFileContent)
1361+
1362+ def DeleteExampleFile(self):
1363+ os.remove(self.ExampleFilePath)
1364+
1365+ def CreateSingleFileZipArchive(self):
1366+ self.CreateExampleFile()
1367+
1368+ zip(zipArchive=self.SingleFileZipName,
1369+ fileToAdd=self.ExampleFileName,
1370+ cwd=self.WorkingDirectory)
1371+
1372+ def setUp(self) -> None:
1373+ self.CreateTemporaryWorkingDirectory()
1374+ self.CreateSingleFileZipArchive()
1375+
1376+ def tearDown(self) -> None:
1377+ if CleanUp:
1378+ deleteRecursiveWithForce(self.WorkingDirectory)
1379+
1380+ def AssertExampleFileExists(self) -> None:
1381+ exampleFilePath = self.ExampleFilePath
1382+
1383+ self.assertTrue(os.path.exists(exampleFilePath),
1384+ msg="Example file does not exist!")
1385+
1386+ with open(exampleFilePath, 'r') as file:
1387+ fileContent = file.read()
1388+ self.assertEqual(fileContent, self.ExampleFileContent,
1389+ msg="Example file has unexpected content!")
1390+
1391+ def test_When_FUnZipIsCalledWithAZipArchiveThatContainsOneFile_Expect_PrintsContentOfSingleFile(self) -> None: # noqa: E501
1392+ """
1393+ When the `funzip` is called with a zip archive that contains one file.\
1394+ `funzip` should print the content of the file to STDOUT.
1395+ """
1396+
1397+ # Arrange
1398+ pass
1399+
1400+ # Act
1401+ result = RunShellCommand(f"funzip ./{self.SingleFileZipName}",
1402+ cwd=self.WorkingDirectory)
1403+
1404+ # Assert
1405+ result.AssertSuccessExitCode()
1406+ result.AssertStdoutIsNotEmpty()
1407+ result.AssertStderrIsEmpty()
1408+ result.AssertStdoutEquals(self.ExampleFileContent)
1409+
1410+ def test_When_UnZipIsCalledWithAZipArchiveThatContainsOneFile_Expect_ExtractsSingleFile(self) -> None: # noqa: E501
1411+ """
1412+ When the `unzip` is called with a zip archive that contains one file.\
1413+ `unzip` should extract the single file.
1414+ """
1415+
1416+ # Arrange
1417+ self.DeleteExampleFile()
1418+
1419+ # Act
1420+ result = RunShellCommand(f"unzip ./{self.SingleFileZipName}",
1421+ cwd=self.WorkingDirectory)
1422+
1423+ # Assert
1424+ result.AssertSuccessExitCode()
1425+ self.AssertExampleFileExists()
1426+
1427+ def test_When_UnZipSFXIsPrependedToAZipArchiveThatContainsOneFileAndCalled_Expect_ExtractsSingleFile(self) -> None: # noqa: E501
1428+ """
1429+ When the `unzipsfx` is prepended in front of a zip archive that\
1430+ contains one file, it should extract the single file.
1431+ """
1432+
1433+ # Arrange
1434+ self.DeleteExampleFile()
1435+ prependUnZipSFX(zipArchive=self.SingleFileZipName,
1436+ outputFile="single-file-zip",
1437+ cwd=self.WorkingDirectory)
1438+ # Act
1439+ result = RunShellCommand("./single-file-zip",
1440+ cwd=self.WorkingDirectory)
1441+
1442+ # Assert
1443+ result.AssertSuccessExitCode()
1444+ self.AssertExampleFileExists()
1445+
1446+ def test_When_ZipInfoIsCalledWithAZipArchiveThatContainsOneFileAndNoAdditionalParameters_Expect_PrintUnixStyleFileListThatContainsSingleFile(self) -> None: # noqa: E501
1447+ """
1448+ When the shell command `zipinfo` is called with a zip archive that\
1449+ contains only one file and no additional parameters. It is expected\
1450+ that `unzip` will print a unix style file list\
1451+ (like `ls -l` containing only the single file.)
1452+ """
1453+
1454+ # Arrange
1455+ zipFileNameAsRegex = re.escape(self.SingleFileZipName)
1456+ exampleFileNameAsRegex = re.escape(self.ExampleFileName)
1457+ pattern = re.compile(rf"^Archive: ./{zipFileNameAsRegex}\nZip file size: \d+ bytes, number of entries: 1\n-rw-rw-r--.*{exampleFileNameAsRegex}\n1 file, \d+ bytes uncompressed, \d+ bytes compressed: .*%$") # noqa: E501
1458+
1459+ # Act
1460+ result = RunShellCommand(f"zipinfo ./{self.SingleFileZipName}",
1461+ cwd=self.WorkingDirectory)
1462+
1463+ # Assert
1464+ result.AssertSuccessExitCode()
1465+ result.AssertStdoutIsNotEmpty()
1466+ result.AssertStderrIsEmpty()
1467+ self.assertRegex(result.stdout, pattern,
1468+ msg="ZipInfo did not show the expected output.")
1469+
1470+ def test_When_ZipInfoIsCalledWithAZipArchiveThatContainsOneFileAndDash1_Expect_PrintSingleFileName(self) -> None: # noqa: E501
1471+ """
1472+ When the shell command `zipinfo` is called with a zip archive that\
1473+ contains only one file and the parameter `-1`. It is expected that\
1474+ `unzip` will print the name of the single file.)
1475+ """
1476+
1477+ # Arrange
1478+ pass
1479+
1480+ # Act
1481+ result = RunShellCommand(f"zipinfo -1 ./{self.SingleFileZipName}",
1482+ cwd=self.WorkingDirectory)
1483+
1484+ # Assert
1485+ result.AssertSuccessExitCode()
1486+ result.AssertStdoutIsNotEmpty()
1487+ result.AssertStderrIsEmpty()
1488+ result.AssertStdoutEquals(f"{self.ExampleFileName}\n")
1489+
1490+ def test_When_ZipGrepIsCalledWithAZipArchiveThatContainsOneFileAndAPatternThatMatchesTheContentOfTheSingleFile_Expect_ReturnSingleFile(self) -> None: # noqa: E501
1491+ """
1492+ When the shell command `zipgrep` is called with a zip archive that\
1493+ contains only one file and an egrep pattern that matches the single\
1494+ file content, it should return a reference to the single file.
1495+ """
1496+
1497+ # Arrange
1498+ pass
1499+
1500+ # Act
1501+ result = RunShellCommand(f"zipgrep '{self.ExampleFileContent}' "
1502+ f"./{self.SingleFileZipName} "
1503+ f"{self.ExampleFileName}",
1504+ cwd=self.WorkingDirectory)
1505+
1506+ # Assert
1507+ result.AssertSuccessExitCode()
1508+ result.AssertStdoutIsNotEmpty()
1509+ result.AssertStderrIsEmpty()
1510+ result.AssertStdoutEquals(f"{self.ExampleFileName}:"
1511+ f"{self.ExampleFileContent}\n")
1512+
1513+
1514+class MultiFileZipTestCases(unittest.TestCase):
1515+ """
1516+ The tests of this class test if the commands provided by the unzip\
1517+ source packages can handle a zip archive with a multiple files.
1518+ """
1519+
1520+ FileAmount = 10
1521+ MultiFileZipFileName = "multi-file.zip"
1522+
1523+ def CreateTemporaryWorkingDirectory(self) -> None:
1524+ self.WorkingDirectory = mktemp(createDirectory=True)
1525+
1526+ def TestFilePath(self, suffix, absolutePath: bool = False) -> str:
1527+ path = "test-file_" + str(suffix) + ".txt"
1528+
1529+ if absolutePath:
1530+ path = self.WorkingDirectory + "/" + path
1531+
1532+ return path
1533+
1534+ def CreateMultiFileZipArchive(self) -> None:
1535+ for i in range(self.FileAmount):
1536+ testFilePath = self.TestFilePath(i, absolutePath=False)
1537+
1538+ ddRandom(outputFile=testFilePath,
1539+ outputFileSizeInMebibyte=1,
1540+ cwd=self.WorkingDirectory)
1541+
1542+ zip(zipArchive=self.MultiFileZipFileName,
1543+ fileToAdd=testFilePath,
1544+ cwd=self.WorkingDirectory)
1545+
1546+ shutil.move(src=f"{self.WorkingDirectory}/{testFilePath}",
1547+ dst=f"{self.WorkingDirectory}/{testFilePath}.backup")
1548+
1549+ def AssertExtractedFileEqualsOriginal(self, i) -> None:
1550+ testFileName = self.TestFilePath(i, absolutePath=False)
1551+ testFilePath = self.TestFilePath(i, absolutePath=True)
1552+ originalTestFilePath = testFilePath + ".backup"
1553+
1554+ self.assertTrue(os.path.exists(testFilePath),
1555+ msg=f"'{testFileName}' was not extracted.")
1556+
1557+ self.assertEqual(
1558+ os.path.getsize(testFilePath),
1559+ os.path.getsize(originalTestFilePath),
1560+ msg="Extracted File {testFileName} does not match "
1561+ "the original file size.")
1562+
1563+ # compare file content:
1564+ with open(testFilePath, 'rb') as originalFile, \
1565+ open(originalTestFilePath, 'rb') as extractedFile:
1566+ while True:
1567+ block1 = originalFile.read(1024)
1568+ block2 = extractedFile.read(1024)
1569+
1570+ self.assertTrue(block1 == block2,
1571+ msg=f"Extracted File {testFileName} does not "
1572+ f"match the original file content.")
1573+
1574+ # Reached the end of both files without finding any differences
1575+ if not block1:
1576+ break
1577+
1578+ def AssertAllFilesExtractedFromMultiFileZipArchive(self):
1579+ for i in range(self.FileAmount):
1580+ self.AssertExtractedFileEqualsOriginal(i)
1581+
1582+ def setUp(self) -> None:
1583+ self.CreateTemporaryWorkingDirectory()
1584+ self.CreateMultiFileZipArchive()
1585+
1586+ def tearDown(self) -> None:
1587+ if CleanUp:
1588+ deleteRecursiveWithForce(self.WorkingDirectory)
1589+
1590+ def test_When_FUnZipIsCalledWithMultiFileArchive_Expect_PrintContentOfFirstFile(self) -> None: # noqa: E501
1591+ """
1592+ When the shell command `funzip` is called with a multi-file\
1593+ zip archive, it is expected that it prints the content of the\
1594+ first file contained in the multi-file archive to STDOUT.
1595+ """
1596+
1597+ # Arrange
1598+ testFilePath = self.TestFilePath(0, absolutePath=False)
1599+
1600+ # Act
1601+ result = RunShellCommand(f"funzip '{self.MultiFileZipFileName}' > "
1602+ f"'{testFilePath}'",
1603+ cwd=self.WorkingDirectory)
1604+ # Assert
1605+ result.AssertSuccessExitCode()
1606+ self.AssertExtractedFileEqualsOriginal(0)
1607+
1608+ def test_When_UnZipIsCalledWithMultiFileArchive_Expect_ExtractsFilesContainedInMultifileArchive(self) -> None: # noqa: E501
1609+ """
1610+ When the shell command `unzip` is called with a multi-file\
1611+ zip archive, it is expected that it extracts the files contained\
1612+ in the multi-file archive.
1613+ """
1614+
1615+ # Arrange
1616+ pass
1617+
1618+ # Act
1619+ result = RunShellCommand(f"unzip '{self.MultiFileZipFileName}'",
1620+ cwd=self.WorkingDirectory)
1621+
1622+ # Assert
1623+ result.AssertSuccessExitCode()
1624+ self.AssertAllFilesExtractedFromMultiFileZipArchive()
1625+
1626+ def test_When_UnZipSFXIsPrependedToMultiFileArchive_Expect_(self) -> None:
1627+ """
1628+ When the `unzipsfx` is prepended in front of a multi-file\
1629+ zip archive, it is expected that it extracts the files contained\
1630+ in the multi-file archive when it is called.
1631+ """
1632+
1633+ # Arrange
1634+ prependUnZipSFX(zipArchive=self.MultiFileZipFileName,
1635+ outputFile="multi-file-zip",
1636+ cwd=self.WorkingDirectory)
1637+
1638+ # Act
1639+ result = RunShellCommand("./multi-file-zip", cwd=self.WorkingDirectory)
1640+
1641+ # Assert
1642+ result.AssertSuccessExitCode()
1643+ self.AssertAllFilesExtractedFromMultiFileZipArchive()
1644+
1645+ def test_When_ZipGrepIsCalledWithMultiFileArchiveAndPattern_Expect_FindsFilesContainedInTheMultiFileArchive(self) -> None: # noqa: E501
1646+ """
1647+ When the shell command `zipgrep` is called with a multi-file\
1648+ zip archive and a pattern, it is expected that it find the files\
1649+ with the pattern contained in the multi-file archive and prints\
1650+ it as a list to STDOUT.
1651+ """
1652+
1653+ # Arrange
1654+ fileNameAsRegex = re.escape(self.TestFilePath("\\d+", absolutePath=False)) # noqa: E501
1655+
1656+ pattern = re.compile(f"^({fileNameAsRegex}:X\n)*$")
1657+
1658+ # Act
1659+ result = RunShellCommand(f"zipgrep -o 'X' '{self.MultiFileZipFileName}'", # noqa: E501
1660+ cwd=self.WorkingDirectory)
1661+ # Assert
1662+ result.AssertSuccessExitCode()
1663+ self.assertRegex(result.stdout, pattern)
1664+
1665+ def test_When_UsingZipInfoWithoutAdditionalParametersForMultiFileZipArchive_Expect_UnixLsDashLListOfContainedFiles(self) -> None: # noqa: E501
1666+ """
1667+ When the shell command `zipinfo` is called with a multi-file\
1668+ zip archive and no additional parameters it is expected that it\
1669+ will list the contained files as a unix style file list\
1670+ (like `ls -l`) to STDOUT.
1671+ """
1672+
1673+ # Arrange
1674+ regex = rf"^Archive: multi-file.zip\nZip file size: \d+ bytes, number of entries: {str(self.FileAmount)}\n" # noqa: E501
1675+ for i in range(self.FileAmount):
1676+ filenameAsRegex = re.escape(self.TestFilePath(i))
1677+ regex += rf"-rw-rw-r--.*{filenameAsRegex}\n"
1678+ regex += rf"{str(self.FileAmount)} files, \d+ bytes uncompressed, \d+ bytes compressed: .*%$" # noqa: E501
1679+
1680+ pattern = re.compile(regex)
1681+
1682+ # Act
1683+ result = RunShellCommand("zipinfo " + self.MultiFileZipFileName,
1684+ cwd=self.WorkingDirectory)
1685+
1686+ # Assert
1687+ result.AssertSuccessExitCode()
1688+ result.AssertStderrIsEmpty()
1689+ result.AssertStdoutIsNotEmpty()
1690+
1691+ self.assertRegex(result.stdout, pattern,
1692+ "STDOUT of unzip for a multi-file zip archive "
1693+ "does not match the expectations!")
1694+
1695+ def test_When_UsingZipInfoDash1ForMultiFileZipArchive_Expect_ListOfContainedFiles(self) -> None: # noqa: E501
1696+ """
1697+ When the shell command `zipinfo` is called with a multi-file\
1698+ zip archive and the additional parameter `-1` it is expected that\
1699+ it will list the contained files to STDOUT split by a `\\n` char.
1700+ """
1701+
1702+ # Arrange
1703+ expectedOutput = ""
1704+ for i in range(self.FileAmount):
1705+ expectedOutput += f"{self.TestFilePath(i)}\n"
1706+
1707+ # Act
1708+ result = RunShellCommand("zipinfo -1 " + self.MultiFileZipFileName,
1709+ cwd=self.WorkingDirectory)
1710+
1711+ # Assert
1712+ result.AssertSuccessExitCode()
1713+ result.AssertStderrIsEmpty()
1714+ result.AssertStdoutIsNotEmpty()
1715+ result.AssertStdoutEquals(expectedOutput)
1716+
1717+ def test_When_UsingZipInfoDash2htForMultiFileZipArchive_Expect_ListOfContainedFilesWithMetadata(self) -> None: # noqa: E501
1718+ """
1719+ When the shell command `zipinfo` is called with a multi-file\
1720+ zip archive and the additional parameter `-2ht` it is expected\
1721+ that it will list the contained files to STDOUT split by a `\\n`\
1722+ char with metadata about the zip archive.
1723+ """
1724+
1725+ # Arrange
1726+ regex = rf"^Archive: multi-file.zip\nZip file size: \d+ bytes, number of entries: {str(self.FileAmount)}\n" # noqa: E501
1727+ for i in range(self.FileAmount):
1728+ filenameAsRegex = re.escape(self.TestFilePath(i))
1729+ regex += filenameAsRegex + "\\n"
1730+ regex += rf"{str(self.FileAmount)} files, \d+ bytes uncompressed, \d+ bytes compressed: .*%$" # noqa: E501
1731+
1732+ pattern = re.compile(regex)
1733+
1734+ # Act
1735+ result = RunShellCommand("zipinfo -2ht " + self.MultiFileZipFileName,
1736+ cwd=self.WorkingDirectory)
1737+
1738+ # Assert
1739+ result.AssertSuccessExitCode()
1740+ result.AssertStderrIsEmpty()
1741+ result.AssertStdoutIsNotEmpty()
1742+
1743+ self.assertRegex(result.stdout, pattern,
1744+ "STDOUT of unzip for a multi-file zip archive"
1745+ "does not match the expectations!")
1746+
1747+
1748+class LargeZipArchiveTestCases(unittest.TestCase):
1749+ """
1750+ The tests of this class test if the commands provided by the\
1751+ unzip source packages can handle a zip archive with a large file.
1752+ """
1753+
1754+ NoLargeFile = False # Debug Flag
1755+
1756+ LargeFileName = "large-file.bin"
1757+ LargeFileSizeInMebibyte = 500 # 1 MiB = 1024 * 1024 byte
1758+ LargeZipArchiveFileName = "large.zip"
1759+
1760+ def CreateTemporaryWorkingDirectory(self) -> None:
1761+ self.WorkingDirectory = mktemp(createDirectory=True)
1762+
1763+ def CreateLargeZipArchive(self) -> None:
1764+ largeFilePath = self.WorkingDirectory + "/" + self.LargeFileName
1765+
1766+ print("creating large file (this may take a few seconds)")
1767+ ddRandom(outputFile=self.LargeFileName,
1768+ outputFileSizeInMebibyte=self.LargeFileSizeInMebibyte,
1769+ cwd=self.WorkingDirectory)
1770+
1771+ zip(zipArchive=self.LargeZipArchiveFileName,
1772+ fileToAdd=self.LargeFileName,
1773+ cwd=self.WorkingDirectory)
1774+
1775+ shutil.move(src=largeFilePath,
1776+ dst=largeFilePath + ".backup")
1777+
1778+ def setUp(self) -> None:
1779+ if self.NoLargeFile:
1780+ self.LargeFileSizeInMebibyte = 1
1781+
1782+ self.CreateTemporaryWorkingDirectory()
1783+ self.CreateLargeZipArchive()
1784+
1785+ def tearDown(self) -> None:
1786+ if CleanUp:
1787+ deleteRecursiveWithForce(self.WorkingDirectory)
1788+
1789+ def AssertBackedUpLargeFileEqualsExtracted(self):
1790+ largeFilePath = f"{self.WorkingDirectory}/{self.LargeFileName}"
1791+ backupFilePath = largeFilePath + ".backup"
1792+
1793+ self.assertTrue(os.path.exists(largeFilePath),
1794+ msg="Large file was not extracted from "
1795+ "large file zip.")
1796+
1797+ originalFileSize = os.path.getsize(backupFilePath)
1798+ extractedFileSize = os.path.getsize(largeFilePath)
1799+
1800+ self.assertEqual(extractedFileSize, originalFileSize,
1801+ msg="Extracted File does not have the same size "
1802+ "as the original file.")
1803+
1804+ # compare file content:
1805+ with open(backupFilePath, 'rb') as originalFile, \
1806+ open(largeFilePath, 'rb') as extractedFile:
1807+ while True:
1808+ block1 = originalFile.read(1024)
1809+ block2 = extractedFile.read(1024)
1810+
1811+ self.assertTrue(block1 == block2,
1812+ msg="Extracted File does not match "
1813+ "the original file content.")
1814+
1815+ # Reached the end of both files without finding any differences
1816+ if not block1:
1817+ break
1818+
1819+ def test_When_FUnZipIsCalledWithLargeZipArchive_Expect_PrintContentOfFirstFile(self) -> None: # noqa: E501
1820+ """
1821+ When the shell command `funzip` is called with a large zip archive,\
1822+ it is expected that it will print the content of the first contained\
1823+ file to STDOUT.
1824+ """
1825+
1826+ # Arrange
1827+ pass
1828+
1829+ # Act
1830+ result = RunShellCommand(f"funzip '{self.LargeZipArchiveFileName}' > "
1831+ f"'{self.LargeFileName}'",
1832+ cwd=self.WorkingDirectory)
1833+ # Assert
1834+ result.AssertSuccessExitCode()
1835+ self.AssertBackedUpLargeFileEqualsExtracted()
1836+
1837+ def test_When_UnZipIsCalledWithLargeZipArchive_Expect_ExtractsContainedFiles(self) -> None: # noqa: E501
1838+ """
1839+ When the shell command `unzip` is called with a large zip archive,\
1840+ it is expected that it will extract the contained files.
1841+ """
1842+
1843+ # Arrange
1844+ pass
1845+
1846+ # Act
1847+ result = RunShellCommand(f"unzip '{self.LargeZipArchiveFileName}'",
1848+ cwd=self.WorkingDirectory)
1849+ # Assert
1850+ result.AssertSuccessExitCode()
1851+ self.AssertBackedUpLargeFileEqualsExtracted()
1852+
1853+ def test_When_UnZipSFXIsPrependedToLargeArchiveAndCalled_Expect_ExtractsContainedFiles(self) -> None: # noqa: E501
1854+ """
1855+ When `unzipsfx` is prepended in front of a large zip archive and\
1856+ called, it is expected that it will extract the contained files.
1857+ """
1858+
1859+ # Arrange
1860+ prependUnZipSFX(self.LargeZipArchiveFileName,
1861+ outputFile="large-zip",
1862+ cwd=self.WorkingDirectory)
1863+ # Act
1864+ result = RunShellCommand("./large-zip",
1865+ cwd=self.WorkingDirectory)
1866+ # Assert
1867+ result.AssertSuccessExitCode()
1868+ self.AssertBackedUpLargeFileEqualsExtracted()
1869+
1870+ def test_When_ZipGrepIsCalledWithLargeArchiveAndPattern_Expect_FindsContainedFilesWithPattern(self) -> None: # noqa: E501
1871+ """
1872+ When the shell command `zipgrep` is called with a large zip archive\
1873+ and an egrep pattern it is expected that it will find and list\
1874+ refrences to the found matches.
1875+ """
1876+
1877+ # Arrange
1878+ fileNameAsRegex = self.LargeFileName.replace('.', '\\.')
1879+ pattern = re.compile(f"^({fileNameAsRegex}:X\n)*$")
1880+
1881+ # Act
1882+ result = RunShellCommand(f"zipgrep -o 'X' "
1883+ f"'{self.LargeZipArchiveFileName}'",
1884+ cwd=self.WorkingDirectory)
1885+ # Assert
1886+ result.AssertSuccessExitCode()
1887+ self.assertRegex(result.stdout, pattern)
1888+
1889+ def test_When_ZipInfoIsCalledWithLargeArchive_Expect_ListsContainedFiles(self) -> None: # noqa: E501
1890+ """
1891+ If the shell command `zipinfo` is called with a large zip archive\
1892+ it is expected that it will list the name of the contained files.
1893+ """
1894+
1895+ # Arrange
1896+ expectedOutput = f"{self.LargeFileName}\n"
1897+
1898+ # Act
1899+ result = RunShellCommand("zipinfo -1 " + self.LargeZipArchiveFileName,
1900+ cwd=self.WorkingDirectory)
1901+
1902+ # Assert
1903+ result.AssertSuccessExitCode()
1904+ result.AssertStderrIsEmpty()
1905+ result.AssertStdoutIsNotEmpty()
1906+ result.AssertStdoutEquals(expectedOutput)
1907+
1908+
1909+class PasswordProtectedZipTestCases(unittest.TestCase):
1910+ """
1911+ The tests of this class test if the commands provided by the unzip\
1912+ source packages can handle a simple password protected zip archive.
1913+ """
1914+
1915+ SecretFileName = "secret.txt"
1916+ SecretFileContent = ("Top Secret: Project Penguin Power - "
1917+ "Objective: Unleash Ubuntu-powered penguins - "
1918+ "Phase 1: Tux Training - "
1919+ "Phase 2: Fishy Integration - "
1920+ "Phase 3: Linux March - "
1921+ "Caution: Expect adorable waddling and occasional "
1922+ "Linux commands quacking.")
1923+ SecretFileContentFragment = "Top Secret"
1924+ PasswordProtectedZipFileName = "password-protected.zip"
1925+ Password = "WaddleFishLinux123"
1926+
1927+ @property
1928+ def SecretFilePath(self) -> str:
1929+ return f"{self.WorkingDirectory}/{self.SecretFileName}"
1930+
1931+ def CreateTemporaryWorkingDirectory(self) -> None:
1932+ self.WorkingDirectory = mktemp(createDirectory=True)
1933+
1934+ def CreatePasswordProtectedZipArchive(self) -> None:
1935+ secretFilePath = self.SecretFilePath
1936+
1937+ with open(secretFilePath, 'w') as secretFile:
1938+ secretFile.write(self.SecretFileContent)
1939+
1940+ zip(zipArchive=self.PasswordProtectedZipFileName,
1941+ fileToAdd=self.SecretFileName,
1942+ password=self.Password,
1943+ cwd=self.WorkingDirectory)
1944+
1945+ shutil.move(src=secretFilePath,
1946+ dst=secretFilePath + ".backup")
1947+
1948+ def AssertSecretFileExists(self) -> None:
1949+ secretFilePath = self.SecretFilePath
1950+
1951+ self.assertTrue(os.path.exists(secretFilePath),
1952+ msg="Secret file does not exist!")
1953+
1954+ with open(secretFilePath, 'r') as file:
1955+ fileContent = file.read()
1956+ self.assertEqual(fileContent, self.SecretFileContent,
1957+ msg="Secret file has unexpected content!")
1958+
1959+ def setUp(self) -> None:
1960+ self.CreateTemporaryWorkingDirectory()
1961+ self.CreatePasswordProtectedZipArchive()
1962+
1963+ def tearDown(self) -> None:
1964+ if CleanUp:
1965+ deleteRecursiveWithForce(self.WorkingDirectory)
1966+
1967+ def test_When_FUnZipIsCalledWithEncrypedZipArchiveAndPassword_Expect_DecryptZipArchiveAndPrintContentOfFirstFile(self) -> None: # noqa: E501
1968+ """
1969+ When the shell command `funzip` is called with an encryped\
1970+ zip archive and the corresponding password, it is expected that\
1971+ it will decrypt the archive and print the content of the first\
1972+ contained file to stdout.
1973+ """
1974+
1975+ # Arange
1976+ pass
1977+
1978+ # Act
1979+ result = RunShellCommand(f"funzip '-{self.Password}' "
1980+ f"'{self.PasswordProtectedZipFileName}'",
1981+ cwd=self.WorkingDirectory)
1982+ # Assert
1983+ result.AssertSuccessExitCode()
1984+ result.AssertStdoutIsNotEmpty()
1985+ result.AssertStderrIsEmpty()
1986+ result.AssertStdoutEquals(self.SecretFileContent)
1987+
1988+ def test_When_UnZipIsCalledWithEncrypedZipArchiveAndPassword_Expect_DecryptZipArchiveAndExtractFiles(self) -> None: # noqa: E501
1989+ """
1990+ When the shell command `unzip` is called with an encryped zip archive\
1991+ and the corresponding password, it is expected that it will decrypt\
1992+ the archive extract it's files.
1993+ """
1994+
1995+ # Arange
1996+ pattern = re.compile(rf"^Archive: {self.PasswordProtectedZipFileName}\n\s+inflating: {self.SecretFileName}\s+$") # noqa: E501
1997+
1998+ # Act
1999+ result = RunShellCommand(f"unzip -P '{self.Password}' "
2000+ f"'{self.PasswordProtectedZipFileName}'",
2001+ cwd=self.WorkingDirectory)
2002+ # Assert
2003+ result.AssertSuccessExitCode()
2004+ result.AssertStdoutIsNotEmpty()
2005+ result.AssertStderrIsEmpty()
2006+ self.assertRegex(result.stdout, pattern,
2007+ msg="STDOUT does not match expected output.")
2008+ self.AssertSecretFileExists()
2009+
2010+ def test_When_UnZipSFXIsPrependedToEncryptedZipArchive_Expect_FailureDueToUnsupportedUseCase(self) -> None: # noqa: E501
2011+ """
2012+ When the `unzipsfx` is prepended in fron of an encryped zip archive\
2013+ and called, it is expected that it will fail, because decryption is\
2014+ not supported.
2015+ """
2016+
2017+ # Arrange
2018+ copyrightHeader = "UnZipSFX 6.00 of 20 April 2009, by Info-ZIP (http://www.info-zip.org).\n" # noqa: E501
2019+ pattern = re.compile(f"^\\s+skipping: {self.SecretFileName}\\s+encrypted \\(not supported\\)\\s+$") # noqa: E501
2020+ prependUnZipSFX(zipArchive=self.PasswordProtectedZipFileName,
2021+ outputFile="password-protected-zip",
2022+ cwd=self.WorkingDirectory)
2023+ # Act
2024+ result = RunShellCommand("./password-protected-zip",
2025+ cwd=self.WorkingDirectory)
2026+ # Assert
2027+ result.AssertFailureExitCode()
2028+ result.AssertStdoutIsNotEmpty()
2029+ result.AssertStdoutEquals(copyrightHeader)
2030+ result.AssertStderrIsNotEmpty()
2031+ self.assertRegex(result.stderr, pattern,
2032+ msg="STDERR does not match expected output.")
2033+ self.assertFalse(os.path.exists(self.SecretFilePath),
2034+ msg="Output says, that encrypted files are not "
2035+ "supported, but encrypted file was extracted.")
2036+
2037+ # I can't get it to work with the autopkgtest ubuntu cloud vm image :/
2038+ # Probably the same tty problem as
2039+ # test_When_RunningFUnZipCommandWithoutParameters_Expect_NonEmptyOutput.
2040+ @unittest.skip(reason="flaky test")
2041+ def test_When_ZipGrepIsCalledWithEncryptedZipArchiveAndPattern_Expect_PromtsForPasswordAndFindsMatchesOfContainedFiles(self) -> None: # noqa: E501
2042+ """
2043+ When the shell command `zipgrep` is called with an encryped\
2044+ zip archive and an egrep pattern, it is expected that it will ask\
2045+ for the password, when given decrypt the archive and print matches\
2046+ of the patttern of the contained files to stdout.
2047+ """
2048+
2049+ # Arrange
2050+ expectScript = f"""
2051+log_user 0
2052+spawn zipgrep \"{self.SecretFileContentFragment}\" \"{self.PasswordProtectedZipFileName}\"
2053+expect "password:" {{
2054+ log_user 1
2055+ send "{self.Password}\\r"
2056+}}
2057+expect eof
2058+ """
2059+
2060+ with open(self.WorkingDirectory + "/expct", "w") as expectFile:
2061+ expectFile.write(expectScript)
2062+
2063+ # Act
2064+ result = RunShellCommand("expect expct", cwd=self.WorkingDirectory)
2065+
2066+ # Assert
2067+ result.AssertSuccessExitCode()
2068+ result.AssertStdoutIsNotEmpty()
2069+ result.AssertStderrIsEmpty()
2070+ result.AssertStdoutEquals(f"\n{self.SecretFileName}:{self.SecretFileContent}\n") # noqa: E501
2071+
2072+ def test_When_ZipInfoIsCalledWithEncryptedZipArchiveAndWithoutPassword_Expect_ListContainedFiles(self) -> None: # noqa: E501
2073+ """
2074+ When the shell command `zipinfo` is called with a encrypted\
2075+ zip archive, it es expected that it can list the contained files\
2076+ even without the password.
2077+ """
2078+
2079+ # Arange
2080+ pass
2081+
2082+ # Act
2083+ result = RunShellCommand(
2084+ f"zipinfo -1 '{self.PasswordProtectedZipFileName}'",
2085+ cwd=self.WorkingDirectory)
2086+
2087+ # Assert
2088+ result.AssertSuccessExitCode()
2089+ result.AssertStdoutIsNotEmpty()
2090+ result.AssertStderrIsEmpty()
2091+ result.AssertStdoutEquals(f"{self.SecretFileName}\n")
2092+
2093+
2094+class InvalidZipTestCases(unittest.TestCase):
2095+ """
2096+ The tests of this class test if the commands provided by the unzip\
2097+ source packages fail, when passed an incalid/corruped zip archive.
2098+ """
2099+
2100+ InvalidZipFileName = "invalid.zip"
2101+
2102+ def CreateTemporaryWorkingDirectory(self) -> None:
2103+ self.WorkingDirectory = mktemp(createDirectory=True)
2104+
2105+ def CreateInvalidZipFile(self) -> None:
2106+ ddRandom(outputFile=self.InvalidZipFileName,
2107+ outputFileSizeInMebibyte=1,
2108+ cwd=self.WorkingDirectory)
2109+
2110+ def setUp(self) -> None:
2111+ self.CreateTemporaryWorkingDirectory()
2112+ self.CreateInvalidZipFile()
2113+
2114+ def tearDown(self) -> None:
2115+ if CleanUp:
2116+ deleteRecursiveWithForce(self.WorkingDirectory)
2117+
2118+ def test_When_FUnZipIsCalledWithInvalidZipArchive_Expect_Fail(self) -> None: # noqa: E501
2119+ """
2120+ When the shell command `funzip` is called with an invalid\
2121+ zip archive, expect it to fail.
2122+ """
2123+
2124+ # Arange
2125+ pass
2126+
2127+ # Act
2128+ result = RunShellCommand(f"funzip '{self.InvalidZipFileName}'",
2129+ cwd=self.WorkingDirectory)
2130+ # Assert
2131+ result.AssertFailureExitCode()
2132+ result.AssertStderrIsNotEmpty()
2133+
2134+ def test_When_UnZipIsCalledWithInvalidZipArchive_Expect_Fail(self) -> None: # noqa: E501
2135+ """
2136+ When the shell command `unzip` is called with an invalid\
2137+ zip archive, expect it to fail.
2138+ """
2139+
2140+ # Arange
2141+ pass
2142+
2143+ # Act
2144+ result = RunShellCommand(f"unzip '{self.InvalidZipFileName}'",
2145+ cwd=self.WorkingDirectory)
2146+ # Assert
2147+ result.AssertFailureExitCode()
2148+ result.AssertStderrIsNotEmpty()
2149+
2150+ def test_When_ZipGrepIsCalledWithInvalidZipArchive_Expect_Fail(self) -> None: # noqa: E501
2151+ """
2152+ When the shell command `zipgrep` is called with an invalid\
2153+ zip archive, expect it to fail.
2154+ """
2155+
2156+ # Arange
2157+ pass
2158+
2159+ # Act
2160+ result = RunShellCommand(
2161+ f"zipgrep Pattern '{self.InvalidZipFileName}'",
2162+ cwd=self.WorkingDirectory)
2163+
2164+ # Assert
2165+ result.AssertFailureExitCode()
2166+ result.AssertStderrIsNotEmpty()
2167+
2168+ def test_When_ZipInfoIsCalledWithInvalidZipArchive_Expect_Fail(self) -> None: # noqa: E501
2169+ """
2170+ When the shell command `zipinfo` is called with an invalid\
2171+ zip archive, expect it to fail.
2172+ """
2173+
2174+ # Arange
2175+ pass
2176+
2177+ # Act
2178+ result = RunShellCommand(f"zipinfo '{self.InvalidZipFileName}'",
2179+ cwd=self.WorkingDirectory)
2180+ # Assert
2181+ result.AssertFailureExitCode()
2182+ result.AssertStdoutIsNotEmpty()
2183+
2184+
2185+# if script is not imported as library, run all tests
2186+if __name__ == "__main__":
2187+ unittest.main()
2188diff --git a/debian/tests/control b/debian/tests/control
2189new file mode 100644
2190index 0000000..fc221e9
2191--- /dev/null
2192+++ b/debian/tests/control
2193@@ -0,0 +1,3 @@
2194+Tests: smoke-tests
2195+Depends: unzip, python3, zip, man, diffutils
2196+Restrictions: allow-stderr
2197diff --git a/debian/tests/smoke-tests b/debian/tests/smoke-tests
2198new file mode 100755
2199index 0000000..f7bc50a
2200--- /dev/null
2201+++ b/debian/tests/smoke-tests
2202@@ -0,0 +1,10 @@
2203+#!/usr/bin/env bash
2204+
2205+set -e
2206+
2207+SmokeTests="$PWD/debian/tests/SmokeTests.py"
2208+cd "$AUTOPKGTEST_TMP"
2209+printenv
2210+
2211+echo "Running Tests (some test cases invole creating larger files; this may take a few minutes):"
2212+python3 "$SmokeTests"

Subscribers

People subscribed via source and target branches