source: branches/help/CORE/arb_msg.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
File size: 22.9 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : arb_msg.cxx                                        //
4//   Purpose   :                                                    //
5//                                                                  //
6//   Coded by Ralf Westram (coder@reallysoft.de) in November 2010   //
7//   Institute of Microbiology (Technical University Munich)        //
8//   http://www.arb-home.de/                                        //
9//                                                                  //
10// ================================================================ //
11
12#include <arb_msg_fwd.h>
13#include <arb_string.h>
14#include <arb_backtrace.h>
15#include <smartptr.h>
16#include <arb_handlers.h>
17#include <arb_defs.h>
18#include "arb_strbuf.h"
19
20// AISC_MKPT_PROMOTE:#ifndef _GLIBCXX_CSTDLIB
21// AISC_MKPT_PROMOTE:#include <cstdlib>
22// AISC_MKPT_PROMOTE:#endif
23// AISC_MKPT_PROMOTE:#ifndef ARB_CORE_H
24// AISC_MKPT_PROMOTE:#include "arb_core.h"
25// AISC_MKPT_PROMOTE:#endif
26// AISC_MKPT_PROMOTE:#ifndef ARB_ASSERT_H
27// AISC_MKPT_PROMOTE:#include "arb_assert.h"
28// AISC_MKPT_PROMOTE:#endif
29// AISC_MKPT_PROMOTE:
30// AISC_MKPT_PROMOTE:// return error and ensure none is exported
31// AISC_MKPT_PROMOTE:#define RETURN_ERROR(err)  arb_assert(!GB_have_error()); return (err)
32// AISC_MKPT_PROMOTE:
33
34#if defined(DEBUG)
35#if defined(DEVEL_RALF)
36// #define TRACE_BUFFER_USAGE
37#endif // DEBUG
38#endif // DEVEL_RALF
39
40#define GLOBAL_STRING_BUFFERS 4
41
42static size_t last_global_string_size = 0;
43#define GBS_GLOBAL_STRING_SIZE 64000
44
45// --------------------------------------------------------------------------------
46
47#ifdef LINUX
48# define HAVE_VSNPRINTF
49#endif
50
51#ifdef HAVE_VSNPRINTF
52# define PRINT2BUFFER(buffer, bufsize, templat, parg) vsnprintf(buffer, bufsize, templat, parg)
53#else
54# define PRINT2BUFFER(buffer, bufsize, templat, parg) vsprintf(buffer, templat, parg)
55#endif
56
57#define PRINT2BUFFER_CHECKED(printed, buffer, bufsize, templat, parg)   \
58    (printed) = PRINT2BUFFER(buffer, bufsize, templat, parg);           \
59    if ((printed) < 0 || (size_t)(printed) >= (bufsize)) {              \
60        GBK_terminatef("Internal buffer overflow (size=%zu, used=%i)\n", \
61                       (bufsize), (printed));                           \
62    }
63
64// --------------------------------------------------------------------------------
65
66class GlobalStringBuffers {
67    char buffer[GLOBAL_STRING_BUFFERS][GBS_GLOBAL_STRING_SIZE+2]; // several buffers - used alternately
68    int  idx;
69    char lifetime[GLOBAL_STRING_BUFFERS];
70    char nextIdx[GLOBAL_STRING_BUFFERS];
71
72public:
73    GlobalStringBuffers()
74        : idx(0)
75    {
76        for (int i = 0; i<GLOBAL_STRING_BUFFERS; ++i) {
77            nextIdx[i]  = 0;
78            lifetime[i] = 0;
79        }
80    }
81
82    __ATTR__VFORMAT_MEMBER(1) const char *vstrf(const char *templat, va_list parg, int allow_reuse);
83};
84
85static GlobalStringBuffers globBuf;
86
87const char *GlobalStringBuffers::vstrf(const char *templat, va_list parg, int allow_reuse) {
88    int my_idx;
89    int psize;
90
91    if (nextIdx[0] == 0) { // initialize nextIdx
92        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
93            nextIdx[my_idx] = (my_idx+1)%GLOBAL_STRING_BUFFERS;
94        }
95    }
96
97    if (allow_reuse == -1) { // called from GBS_reuse_buffer
98        // buffer to reuse is passed in 'templat'
99
100        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
101            if (buffer[my_idx] == templat) {
102                lifetime[my_idx] = 0;
103#if defined(TRACE_BUFFER_USAGE)
104                printf("Reusing buffer #%i\n", my_idx);
105#endif // TRACE_BUFFER_USAGE
106                if (nextIdx[my_idx] == idx) idx = my_idx;
107                return NULp;
108            }
109#if defined(TRACE_BUFFER_USAGE)
110            else {
111                printf("(buffer to reuse is not buffer #%i (%p))\n", my_idx, buffer[my_idx]);
112            }
113#endif // TRACE_BUFFER_USAGE
114        }
115        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
116            printf("buffer[%i]=%p\n", my_idx, buffer[my_idx]);
117        }
118        arb_assert(0);       // GBS_reuse_buffer called with illegal buffer
119        return NULp;
120    }
121
122    if (lifetime[idx] == 0) {
123        my_idx = idx;
124    }
125    else {
126        for (my_idx = nextIdx[idx]; lifetime[my_idx]>0; my_idx = nextIdx[my_idx]) {
127#if defined(TRACE_BUFFER_USAGE)
128            printf("decreasing lifetime[%i] (%i->%i)\n", my_idx, lifetime[my_idx], lifetime[my_idx]-1);
129#endif // TRACE_BUFFER_USAGE
130            lifetime[my_idx]--;
131        }
132    }
133
134    PRINT2BUFFER_CHECKED(psize, buffer[my_idx], (size_t)GBS_GLOBAL_STRING_SIZE, templat, parg);
135
136#if defined(TRACE_BUFFER_USAGE)
137    printf("Printed into global buffer #%i ('%s')\n", my_idx, buffer[my_idx]);
138#endif // TRACE_BUFFER_USAGE
139
140    last_global_string_size = psize;
141
142    if (!allow_reuse) {
143        idx           = my_idx;
144        lifetime[idx] = 1;
145    }
146#if defined(TRACE_BUFFER_USAGE)
147    else {
148        printf("Allow reuse of buffer #%i\n", my_idx);
149    }
150#endif // TRACE_BUFFER_USAGE
151
152    return buffer[my_idx];
153}
154
155GlobalStringBuffers *GBS_store_global_buffers() {
156    GlobalStringBuffers *stored = new GlobalStringBuffers(globBuf);
157    globBuf                     = GlobalStringBuffers();
158    return stored;
159}
160
161void GBS_restore_global_buffers(GlobalStringBuffers *saved) {
162    globBuf = *saved;
163    delete saved;
164}
165
166const char *GBS_vglobal_string(const char *templat, va_list parg) {
167    // goes to header: __ATTR__VFORMAT(1)
168    return globBuf.vstrf(templat, parg, 0);
169}
170
171char *GBS_vglobal_string_copy(const char *templat, va_list parg) {
172    // goes to header: __ATTR__VFORMAT(1)
173    const char *gstr = globBuf.vstrf(templat, parg, 1);
174    return ARB_strduplen(gstr, last_global_string_size);
175}
176
177const char *GBS_global_string_to_buffer(char *buffer, size_t bufsize, const char *templat, ...) {
178    // goes to header: __ATTR__FORMAT(3)
179
180    // @@@ search for '\b(sprintf)\b\s*\(' and replace by GBS_global_string_to_buffer
181
182    va_list parg;
183    int     psize;
184
185    arb_assert(buffer);
186    va_start(parg, templat);
187    PRINT2BUFFER_CHECKED(psize, buffer, bufsize, templat, parg);
188    va_end(parg);
189
190    return buffer;
191}
192
193char *GBS_global_string_copy(const char *templat, ...) {
194    // goes to header: __ATTR__FORMAT(1)
195    va_list parg;
196    va_start(parg, templat);
197    char *result = GBS_vglobal_string_copy(templat, parg);
198    va_end(parg);
199    return result;
200}
201
202const char *GBS_global_string(const char *templat, ...) {
203    // goes to header: __ATTR__FORMAT(1)
204    va_list parg;
205    va_start(parg, templat);
206    const char *result = globBuf.vstrf(templat, parg, 0);
207    va_end(parg);
208    return result;
209}
210
211const char *GBS_static_string(const char *str) {
212    return GBS_global_string("%s", str);
213}
214
215GB_ERROR GBK_assert_msg(const char *assertion, const char *file, int linenr) {
216#define BUFSIZE 1000
217    static char *buffer   = NULp;
218    const char  *result   = NULp;
219    int          old_size = last_global_string_size;
220
221    if (!buffer) ARB_alloc(buffer, BUFSIZE);
222    result = GBS_global_string_to_buffer(buffer, BUFSIZE, "assertion '%s' failed in %s #%i", assertion, file, linenr);
223
224    last_global_string_size = old_size;
225
226    return result;
227#undef BUFSIZE
228}
229
230// -------------------------
231//      Error "handling"
232
233
234// @@@ redesign GB_export_error et al
235
236/* To clearly distinguish between the two ways of error handling
237 * (which are: return GB_ERROR
238 *  and:       export the error)
239 *
240 * GB_export_error() shall only export, not return the error message.
241 * if only used for formatting GBS_global_string shall be used
242 * (most cases where GB_export_errorf is used are candidates for this.
243 *  GB_export_error was generally misused for this, before
244 *  GBS_global_string was added!)
245 *
246 * GB_export_IO_error() shall not export and be renamed into GB_IO_error()
247 *
248 * GB_export_error() shall fail if there is already an exported error
249 * (maybe always remember a stack trace of last error export (try whether copy of backtrace-array works))
250 *
251 * use GB_get_error() to import AND clear the error
252 */
253
254static char *GB_error_buffer = NULp;
255
256GB_ERROR GB_export_error(const char *error) { // just a temp hack around format-warnings
257    arb_assert(error);
258    return GB_export_errorf("%s", error);
259}
260
261GB_ERROR GB_export_errorf(const char *templat, ...) {
262    /* goes to header:
263     * __ATTR__FORMAT(1)
264     * __ATTR__DEPRECATED_LATER("use GB_export_error(GBS_global_string(...))")
265     *          because it's misused (where GBS_global_string should be used)
266     *          old functionality will remain available via 'GB_export_error(GBS_global_string(...))'
267     */
268
269    char     buffer[GBS_GLOBAL_STRING_SIZE];
270    char    *p = buffer;
271    va_list  parg;
272
273    // @@@ dont prepend ARB ERROR here
274
275    p += sprintf(buffer, "ARB ERROR: ");
276    va_start(parg, templat);
277
278    vsprintf(p, templat, parg);
279
280    freedup(GB_error_buffer, buffer);
281    return GB_error_buffer;
282}
283
284GB_ERROR GB_IO_error(const char *action, const char *filename) {
285    /*! creates error message from current 'errno'
286     * @param action may be NULp (otherwise it should contain sth like "writing" or "deleting")
287     * @param filename may be NULp (otherwise it should contain the filename, the IO-Error occurred for)
288     * @return error message (in static buffer)
289     */
290
291    GB_ERROR io_error;
292    if (errno) {
293        io_error = strerror(errno);
294    }
295    else {
296        arb_assert(0);           // unhandled error (which is NOT an IO-Error)
297        io_error =
298            "Some unhandled error occurred, but it was not an IO-Error. "
299            "Please send detailed information about how the error occurred to devel@arb-home.de "
300            "or ignore this error (if possible).";
301    }
302
303    GB_ERROR error;
304    if (action) {
305        if (filename) error = GBS_global_string("While %s '%s': %s", action, filename, io_error);
306        else error          = GBS_global_string("While %s <unknown file>: %s", action, io_error);
307    }
308    else {
309        if (filename) error = GBS_global_string("Concerning '%s': %s", filename, io_error);
310        else error          = io_error;
311    }
312
313    return error;
314}
315
316// @@@ replace GB_export_IO_error() by GB_IO_error() and then export it if really needed
317GB_ERROR GB_export_IO_error(const char *action, const char *filename) {
318    // goes to header: __ATTR__DEPRECATED_TODO("use GB_export_error(GB_IO_error(...))")
319    return GB_export_error(GB_IO_error(action, filename));
320}
321
322// @@@ reactivate deprecations below
323GB_ERROR GB_print_error() {
324    // goes to header: __ATTR__DEPRECATED_TODO("will be removed completely")
325    if (GB_error_buffer) {
326        fflush(stdout);
327        fprintf(stderr, "%s\n", GB_error_buffer);
328    }
329    return GB_error_buffer;
330}
331
332GB_ERROR GB_get_error() {
333    // goes to header: __ATTR__DEPRECATED_TODO("consider using either GB_await_error() or GB_incur_error()")
334    return GB_error_buffer;
335}
336
337bool GB_have_error() {
338    return GB_error_buffer;
339}
340
341GB_ERROR GB_await_error() {
342    if (GB_error_buffer) {
343        static SmartCharPtr err;
344        err             = GB_error_buffer;
345        GB_error_buffer = NULp;
346        return &*err;
347    }
348    arb_assert(0);               // please correct error handling
349
350    return "Program logic error: Something went wrong, but reason is unknown";
351}
352
353void GB_clear_error() {         // clears the error buffer
354    freenull(GB_error_buffer);
355}
356
357// AISC_MKPT_PROMOTE:inline GB_ERROR GB_incur_error() {
358// AISC_MKPT_PROMOTE:    /*! Take over responsibility for any potential (exported) error.
359// AISC_MKPT_PROMOTE:     * @return NULp if no error was exported; the error otherwise
360// AISC_MKPT_PROMOTE:     * Postcondition: no error is exported
361// AISC_MKPT_PROMOTE:     */
362// AISC_MKPT_PROMOTE:    return GB_have_error() ? GB_await_error() : NULp;
363// AISC_MKPT_PROMOTE:}
364// AISC_MKPT_PROMOTE:inline GB_ERROR GB_incur_error_if(bool error_may_occur) {
365// AISC_MKPT_PROMOTE:    /*! similar to GB_incur_error.
366// AISC_MKPT_PROMOTE:     * Additionally asserts no error may occur if 'error_may_occur' is false!
367// AISC_MKPT_PROMOTE:     */
368// AISC_MKPT_PROMOTE:    arb_assert(implicated(!error_may_occur, !GB_have_error()));
369// AISC_MKPT_PROMOTE:    return error_may_occur ? GB_incur_error() : NULp;
370// AISC_MKPT_PROMOTE:}
371
372// @@@ search for 'GBS_global_string.*error' and replace with GB_failedTo_error or GB_append_exportedError
373
374GB_ERROR GB_failedTo_error(const char *do_something, const char *special, GB_ERROR error) {
375    if (error) {
376        if (special) {
377            error = GBS_global_string("Failed to %s '%s'.\n(Reason: %s)", do_something, special, error);
378        }
379        else {
380            error = GBS_global_string("Failed to %s.\n(Reason: %s)", do_something, error);
381        }
382    }
383    return error;
384}
385
386GB_ERROR GB_append_exportedError(GB_ERROR error) {
387    // If an error has been exported, it gets appended as reason to given 'error'.
388    // If error is NULp, the exported error is returned (if any)
389    //
390    // This is e.g. useful if you search for SOMETHING in the DB w/o success (i.e. get NULp as result).
391    // In that case you can't be sure, whether SOMETHING just does not exist or whether it was not
392    // found because some other error occurred.
393
394    if (GB_have_error()) {
395        if (error) return GBS_global_string("%s (Reason: %s)", error, GB_await_error());
396        return GB_await_error();
397    }
398    return error;
399}
400
401// ---------------------
402//      Backtracing
403
404class BackTraceInfo *GBK_get_backtrace(size_t skipFramesAtBottom) { // only used ifdef TRACE_ALLOCS
405    return new BackTraceInfo(skipFramesAtBottom);
406}
407void GBK_dump_former_backtrace(class BackTraceInfo *trace, FILE *out, const char *message) { // only used ifdef TRACE_ALLOCS
408    demangle_backtrace(*trace, out, message);
409}
410
411void GBK_free_backtrace(class BackTraceInfo *trace) { // only used ifdef TRACE_ALLOCS
412    delete trace;
413}
414
415void GBK_dump_backtrace(FILE *out, const char *message) {
416    demangle_backtrace(BackTraceInfo(1), out ? out : stderr, message);
417}
418
419// -------------------------------------------
420//      Error/notification functions
421
422void GB_internal_error(const char *message) {
423    /* Use GB_internal_error, when something goes badly wrong
424     * but you want to give the user a chance to save his database
425     *
426     * Note: it's NOT recommended to use this function!
427     */
428
429    char *full_message = GBS_global_string_copy("Internal ARB Error: %s", message);
430    active_arb_handlers->show_error(full_message);
431    active_arb_handlers->show_error("ARB is most likely unstable now (due to this error).\n"
432                                    "If you've made changes to the database, consider to save it using a different name.\n"
433                                    "Try to fix the cause of the error and restart ARB.");
434
435#ifdef ASSERTION_USED
436    fputs(full_message, stderr);
437    arb_assert(0);               // internal errors shall not happen, go fix it
438#else
439    GBK_dump_backtrace(stderr, full_message);
440#endif
441
442    free(full_message);
443}
444
445void GB_internal_errorf(const char *templat, ...) {
446    // goes to header: __ATTR__FORMAT(1)
447    FORWARD_FORMATTED_NORETURN(GB_internal_error, templat);
448}
449
450void GBK_terminate(const char *error) { // goes to header __ATTR__NORETURN
451    /* GBK_terminate is the emergency exit!
452     * only used if no other way to recover
453     */
454
455    fprintf(stderr, "Error: '%s'\n", error);
456    fputs("Can't continue - terminating..\n", stderr);
457    GBK_dump_backtrace(stderr, "GBK_terminate (reason above) ");
458
459    fflush(stderr);
460    ARB_SIGSEGV(0); // GBK_terminate shall not be called, fix reason for call (this will crash in RELEASE version)
461    exit(ARB_CRASH_CODE(0)); // should not be reached..just ensure ARB really terminates if somebody changes ARB_SIGSEGV
462}
463
464void GBK_terminatef(const char *templat, ...) {
465    // goes to header: __ATTR__FORMAT(1) __ATTR__NORETURN
466    FORWARD_FORMATTED_NORETURN(GBK_terminate, templat);
467}
468
469// AISC_MKPT_PROMOTE:inline void GBK_terminate_on_error(const char *error) { if (error) GBK_terminatef("Fatal error: %s", error); }
470
471void GB_warning(const char *message) {
472    /* If program uses GUI, the message is printed via aw_message, otherwise it goes to stdout
473     * see also : GB_information
474     */
475    active_arb_handlers->show_warning(message);
476}
477void GB_warningf(const char *templat, ...) {
478    // goes to header: __ATTR__FORMAT(1)
479    FORWARD_FORMATTED(GB_warning, templat);
480}
481
482void GB_information(const char *message) {
483    /* this message is always printed to stdout (regardless whether program uses GUI or not)
484     * (if it is not redirected using ARB_redirect_handlers_to)
485     * see also : GB_warning
486     */
487    active_arb_handlers->show_message(message);
488}
489void GB_informationf(const char *templat, ...) {
490    // goes to header: __ATTR__FORMAT(1)
491    FORWARD_FORMATTED(GB_information, templat);
492}
493
494
495#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
496
497void GBS_reuse_buffer(const char *global_buffer) {
498    // If you've just shortely used a buffer, you can put it back here
499    va_list empty;
500    globBuf.vstrf(global_buffer, empty, -1); // omg hax
501}
502
503// @@@ search for '\b(system)\b\s*\(' and use GBK_system instead
504
505GB_ERROR GBK_system(const char *system_command) {
506    // goes to header: __ATTR__USERESULT
507    fflush(stdout);
508    fprintf(stderr, "[Action: `%s`]\n", system_command); fflush(stderr);
509
510    int res = system(system_command);
511
512    fflush(stdout);
513    fflush(stderr);
514
515    GB_ERROR error = NULp;
516    if (res) {
517        if (res == -1) {
518            error = GB_IO_error("forking", system_command);
519            error = GBS_global_string("System call failed (Reason: %s)", error);
520        }
521        else {
522            error = GBS_global_string("System call failed (result=%i)", res);
523        }
524
525        error = GBS_global_string("%s\n"
526                                  "System call was `%s`%s",
527                                  error, system_command,
528                                  res == -1 ? "" : "\n(Note: console window may contain additional information)");
529    }
530    return error;
531}
532
533char *GBK_singlequote(const char *arg) {
534    /*! Enclose argument in single quotes (like 'arg') for POSIX shell commands.
535     */
536
537    if (!arg[0]) return ARB_strdup("''");
538
539    GBS_strstruct  out(500);
540    const char    *existing_quote = strchr(arg, '\'');
541
542    while (existing_quote) {
543        if (existing_quote>arg) {
544            out.put('\'');
545            out.ncat(arg, existing_quote-arg);
546            out.put('\'');
547        }
548        out.put('\\');
549        out.put('\'');
550        arg            = existing_quote+1;
551        existing_quote = strchr(arg, '\'');
552    }
553
554    if (arg[0]) {
555        out.put('\'');
556        out.cat(arg);
557        out.put('\'');
558    }
559    return out.release();
560}
561
562char *GBK_doublequote(const char *arg) {
563    /*! Enclose argument in single quotes (like "arg") for POSIX shell commands.
564     * Opposed to single quoted strings, shell will interpret double quoted strings.
565     */
566
567    const char    *charsToEscape = "\"\\";
568    const char    *escape      = arg+strcspn(arg, charsToEscape);
569    GBS_strstruct  out(500);
570
571    out.put('"');
572    while (escape[0]) {
573        out.ncat(arg, escape-arg);
574        out.put('\\');
575        out.put(escape[0]);
576        arg    = escape+1;
577        escape = arg+strcspn(arg, charsToEscape);
578    }
579    out.cat(arg);
580    out.put('"');
581    return out.release();
582}
583
584// --------------------------------------------------------------------------------
585
586#ifdef UNIT_TESTS
587#ifndef TEST_UNIT_H
588#include <test_unit.h>
589#endif
590
591#include "FileContent.h"
592#include <unistd.h>
593
594#define TEST_EXPECT_SQUOTE(plain,squote_expected) do {          \
595        char *plain_squoted = GBK_singlequote(plain);           \
596        TEST_EXPECT_EQUAL(plain_squoted, squote_expected);      \
597        free(plain_squoted);                                    \
598    } while(0)
599
600#define TEST_EXPECT_DQUOTE(plain,dquote_expected) do {          \
601        char *plain_dquoted = GBK_doublequote(plain);           \
602        TEST_EXPECT_EQUAL(plain_dquoted, dquote_expected);      \
603        free(plain_dquoted);                                    \
604    } while(0)
605
606#define TEST_EXPECT_ECHOED_EQUALS(echoarg,expected_echo) do {                   \
607        char *cmd = GBS_global_string_copy("echo %s > %s", echoarg, tmpfile);   \
608        TEST_EXPECT_NO_ERROR(GBK_system(cmd));                                  \
609        FileContent out(tmpfile);                                               \
610        TEST_EXPECT_NO_ERROR(out.has_error());                                  \
611        TEST_EXPECT_EQUAL(out.lines()[0], expected_echo);                       \
612        free(cmd);                                                              \
613    } while(0)
614
615#define TEST_EXPECT_SQUOTE_IDENTITY(plain) do {         \
616        char *plain_squoted = GBK_singlequote(plain);   \
617        TEST_EXPECT_ECHOED_EQUALS(plain_squoted,plain); \
618        free(plain_squoted);                            \
619    } while(0)
620
621#define TEST_EXPECT_DQUOTE_IDENTITY(plain) do {         \
622        char *plain_dquoted = GBK_doublequote(plain);   \
623        TEST_EXPECT_ECHOED_EQUALS(plain_dquoted,plain); \
624        free(plain_dquoted);                            \
625    } while(0)
626
627void TEST_quoting() {
628    const char *tmpfile = "quoted.output";
629
630    struct quoting {
631        const char *plain;
632        const char *squoted;
633        const char *dquoted;
634    } args[] = {
635        { "",                       "''",                          "\"\"" },  // empty
636        { " ",                      "' '",                         "\" \"" }, // a space
637        { "unquoted",               "'unquoted'",                  "\"unquoted\"" },
638        { "part 'is' squoted",      "'part '\\''is'\\'' squoted'", "\"part 'is' squoted\"" },
639        { "part \"is\" dquoted",    "'part \"is\" dquoted'",       "\"part \\\"is\\\" dquoted\"" },
640        { "'squoted'",              "\\''squoted'\\'",             "\"'squoted'\"" },
641        { "\"dquoted\"",            "'\"dquoted\"'",               "\"\\\"dquoted\\\"\"" },
642        { "'",                      "\\'",                         "\"'\"" },  // a single quote
643        { "\"",                     "'\"'",                        "\"\\\"\"" },  // a double quote
644        { "\\",                     "'\\'",                        "\"\\\\\"" },  // a backslash
645        { "'\"'\"",                 "\\''\"'\\''\"'",              "\"'\\\"'\\\"\"" },  // interleaved quotes
646        { "`wc -c <min_ascii.arb | tr -d ' '`",
647          "'`wc -c <min_ascii.arb | tr -d '\\'' '\\''`'",
648          "\"`wc -c <min_ascii.arb | tr -d ' '`\"" },  // interleaved quotes
649    };
650
651    for (unsigned a = 0; a<ARRAY_ELEMS(args); ++a) {
652        TEST_ANNOTATE(GBS_global_string("a=%i", a));
653        const quoting& arg = args[a];
654
655        TEST_EXPECT_SQUOTE(arg.plain, arg.squoted);
656        TEST_EXPECT_SQUOTE_IDENTITY(arg.plain);
657
658        TEST_EXPECT_DQUOTE(arg.plain, arg.dquoted);
659        if (a != 11) {
660            TEST_EXPECT_DQUOTE_IDENTITY(arg.plain);
661        }
662        else { // backticked wc call
663            char *dquoted = GBK_doublequote(arg.plain);
664            TEST_EXPECT_ECHOED_EQUALS(dquoted, "16"); // 16 is number of chars in min_ascii.arb
665            free(dquoted);
666        }
667    }
668
669    TEST_EXPECT_EQUAL(unlink(tmpfile), 0);
670}
671
672#endif // UNIT_TESTS
673
674// --------------------------------------------------------------------------------
675
Note: See TracBrowser for help on using the repository browser.