source: branches/alilink/CORE/arb_msg.cxx

Last change on this file was 18126, checked in by westram, 5 years ago
File size: 23.2 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#if defined(WARN_TODO)
181#warning search for '\b(sprintf)\b\s*\(' and replace by GBS_global_string_to_buffer
182#endif
183
184    va_list parg;
185    int     psize;
186
187    arb_assert(buffer);
188    va_start(parg, templat);
189    PRINT2BUFFER_CHECKED(psize, buffer, bufsize, templat, parg);
190    va_end(parg);
191
192    return buffer;
193}
194
195char *GBS_global_string_copy(const char *templat, ...) {
196    // goes to header: __ATTR__FORMAT(1)
197    va_list parg;
198    va_start(parg, templat);
199    char *result = GBS_vglobal_string_copy(templat, parg);
200    va_end(parg);
201    return result;
202}
203
204const char *GBS_global_string(const char *templat, ...) {
205    // goes to header: __ATTR__FORMAT(1)
206    va_list parg;
207    va_start(parg, templat);
208    const char *result = globBuf.vstrf(templat, parg, 0);
209    va_end(parg);
210    return result;
211}
212
213const char *GBS_static_string(const char *str) {
214    return GBS_global_string("%s", str);
215}
216
217GB_ERROR GBK_assert_msg(const char *assertion, const char *file, int linenr) {
218#define BUFSIZE 1000
219    static char *buffer   = NULp;
220    const char  *result   = NULp;
221    int          old_size = last_global_string_size;
222
223    if (!buffer) ARB_alloc(buffer, BUFSIZE);
224    result = GBS_global_string_to_buffer(buffer, BUFSIZE, "assertion '%s' failed in %s #%i", assertion, file, linenr);
225
226    last_global_string_size = old_size;
227
228    return result;
229#undef BUFSIZE
230}
231
232// -------------------------
233//      Error "handling"
234
235
236#if defined(WARN_TODO)
237#warning redesign GB_export_error et al
238/* To clearly distinguish between the two ways of error handling
239 * (which are: return GB_ERROR
240 *  and:       export the error)
241 *
242 * GB_export_error() shall only export, not return the error message.
243 * if only used for formatting GBS_global_string shall be used
244 * (most cases where GB_export_errorf is used are candidates for this.
245 *  GB_export_error was generally misused for this, before
246 *  GBS_global_string was added!)
247 *
248 * GB_export_IO_error() shall not export and be renamed into GB_IO_error()
249 *
250 * GB_export_error() shall fail if there is already an exported error
251 * (maybe always remember a stack trace of last error export (try whether copy of backtrace-array works))
252 *
253 * use GB_get_error() to import AND clear the error
254 */
255#endif
256
257static char *GB_error_buffer = NULp;
258
259GB_ERROR GB_export_error(const char *error) { // just a temp hack around format-warnings
260    arb_assert(error);
261    return GB_export_errorf("%s", error);
262}
263
264GB_ERROR GB_export_errorf(const char *templat, ...) {
265    /* goes to header:
266     * __ATTR__FORMAT(1)
267     * __ATTR__DEPRECATED_LATER("use GB_export_error(GBS_global_string(...))")
268     *          because it's misused (where GBS_global_string should be used)
269     *          old functionality will remain available via 'GB_export_error(GBS_global_string(...))'
270     */
271
272    char     buffer[GBS_GLOBAL_STRING_SIZE];
273    char    *p = buffer;
274    va_list  parg;
275
276#if defined(WARN_TODO)
277#warning dont prepend ARB ERROR here
278#endif
279
280    p += sprintf(buffer, "ARB ERROR: ");
281    va_start(parg, templat);
282
283    vsprintf(p, templat, parg);
284
285    freedup(GB_error_buffer, buffer);
286    return GB_error_buffer;
287}
288
289#if defined(WARN_TODO)
290#warning replace GB_export_IO_error() by GB_IO_error() and then export it if really needed
291#endif
292
293GB_ERROR GB_IO_error(const char *action, const char *filename) {
294    /*! creates error message from current 'errno'
295     * @param action may be NULp (otherwise it should contain sth like "writing" or "deleting")
296     * @param filename may be NULp (otherwise it should contain the filename, the IO-Error occurred for)
297     * @return error message (in static buffer)
298     */
299
300    GB_ERROR io_error;
301    if (errno) {
302        io_error = strerror(errno);
303    }
304    else {
305        arb_assert(0);           // unhandled error (which is NOT an IO-Error)
306        io_error =
307            "Some unhandled error occurred, but it was not an IO-Error. "
308            "Please send detailed information about how the error occurred to devel@arb-home.de "
309            "or ignore this error (if possible).";
310    }
311
312    GB_ERROR error;
313    if (action) {
314        if (filename) error = GBS_global_string("While %s '%s': %s", action, filename, io_error);
315        else error          = GBS_global_string("While %s <unknown file>: %s", action, io_error);
316    }
317    else {
318        if (filename) error = GBS_global_string("Concerning '%s': %s", filename, io_error);
319        else error          = io_error; 
320    }
321
322    return error;
323}
324
325GB_ERROR GB_export_IO_error(const char *action, const char *filename) {
326    // goes to header: __ATTR__DEPRECATED_TODO("use GB_export_error(GB_IO_error(...))")
327    return GB_export_error(GB_IO_error(action, filename));
328}
329
330#if defined(WARN_TODO)
331#warning reactivate deprecations below
332#endif
333
334
335GB_ERROR GB_print_error() {
336    // goes to header: __ATTR__DEPRECATED_TODO("will be removed completely")
337    if (GB_error_buffer) {
338        fflush(stdout);
339        fprintf(stderr, "%s\n", GB_error_buffer);
340    }
341    return GB_error_buffer;
342}
343
344GB_ERROR GB_get_error() {
345    // goes to header: __ATTR__DEPRECATED_TODO("consider using either GB_await_error() or GB_incur_error()")
346    return GB_error_buffer;
347}
348
349bool GB_have_error() {
350    return GB_error_buffer;
351}
352
353GB_ERROR GB_await_error() {
354    if (GB_error_buffer) {
355        static SmartCharPtr err;
356        err             = GB_error_buffer;
357        GB_error_buffer = NULp;
358        return &*err;
359    }
360    arb_assert(0);               // please correct error handling
361
362    return "Program logic error: Something went wrong, but reason is unknown";
363}
364
365void GB_clear_error() {         // clears the error buffer
366    freenull(GB_error_buffer);
367}
368
369// AISC_MKPT_PROMOTE:inline GB_ERROR GB_incur_error() {
370// AISC_MKPT_PROMOTE:    /*! Take over responsibility for any potential (exported) error.
371// AISC_MKPT_PROMOTE:     * @return NULp if no error was exported; the error otherwise
372// AISC_MKPT_PROMOTE:     * Postcondition: no error is exported
373// AISC_MKPT_PROMOTE:     */
374// AISC_MKPT_PROMOTE:    return GB_have_error() ? GB_await_error() : NULp;
375// AISC_MKPT_PROMOTE:}
376// AISC_MKPT_PROMOTE:inline GB_ERROR GB_incur_error_if(bool error_may_occur) {
377// AISC_MKPT_PROMOTE:    /*! similar to GB_incur_error.
378// AISC_MKPT_PROMOTE:     * Additionally asserts no error may occur if 'error_may_occur' is false!
379// AISC_MKPT_PROMOTE:     */
380// AISC_MKPT_PROMOTE:    arb_assert(implicated(!error_may_occur, !GB_have_error()));
381// AISC_MKPT_PROMOTE:    return error_may_occur ? GB_incur_error() : NULp;
382// AISC_MKPT_PROMOTE:}
383
384#if defined(WARN_TODO)
385#warning search for 'GBS_global_string.*error' and replace with GB_failedTo_error or GB_append_exportedError
386#endif
387GB_ERROR GB_failedTo_error(const char *do_something, const char *special, GB_ERROR error) {
388    if (error) {
389        if (special) {
390            error = GBS_global_string("Failed to %s '%s'.\n(Reason: %s)", do_something, special, error);
391        }
392        else {
393            error = GBS_global_string("Failed to %s.\n(Reason: %s)", do_something, error);
394        }
395    }
396    return error;
397}
398
399GB_ERROR GB_append_exportedError(GB_ERROR error) {
400    // If an error has been exported, it gets appended as reason to given 'error'.
401    // If error is NULp, the exported error is returned (if any)
402    //
403    // This is e.g. useful if you search for SOMETHING in the DB w/o success (i.e. get NULp as result).
404    // In that case you can't be sure, whether SOMETHING just does not exist or whether it was not
405    // found because some other error occurred.
406
407    if (GB_have_error()) {
408        if (error) return GBS_global_string("%s (Reason: %s)", error, GB_await_error());
409        return GB_await_error();
410    }
411    return error;
412}
413
414// ---------------------
415//      Backtracing
416
417class BackTraceInfo *GBK_get_backtrace(size_t skipFramesAtBottom) { // only used ifdef TRACE_ALLOCS
418    return new BackTraceInfo(skipFramesAtBottom);
419}
420void GBK_dump_former_backtrace(class BackTraceInfo *trace, FILE *out, const char *message) { // only used ifdef TRACE_ALLOCS
421    demangle_backtrace(*trace, out, message);
422}
423
424void GBK_free_backtrace(class BackTraceInfo *trace) { // only used ifdef TRACE_ALLOCS
425    delete trace;
426}
427
428void GBK_dump_backtrace(FILE *out, const char *message) {
429    demangle_backtrace(BackTraceInfo(1), out ? out : stderr, message);
430}
431
432// -------------------------------------------
433//      Error/notification functions
434
435void GB_internal_error(const char *message) {
436    /* Use GB_internal_error, when something goes badly wrong
437     * but you want to give the user a chance to save his database
438     *
439     * Note: it's NOT recommended to use this function!
440     */
441
442    char *full_message = GBS_global_string_copy("Internal ARB Error: %s", message);
443    active_arb_handlers->show_error(full_message);
444    active_arb_handlers->show_error("ARB is most likely unstable now (due to this error).\n"
445                                    "If you've made changes to the database, consider to save it using a different name.\n"
446                                    "Try to fix the cause of the error and restart ARB.");
447
448#ifdef ASSERTION_USED
449    fputs(full_message, stderr);
450    arb_assert(0);               // internal errors shall not happen, go fix it
451#else
452    GBK_dump_backtrace(stderr, full_message);
453#endif
454
455    free(full_message);
456}
457
458void GB_internal_errorf(const char *templat, ...) {
459    // goes to header: __ATTR__FORMAT(1)
460    FORWARD_FORMATTED_NORETURN(GB_internal_error, templat);
461}
462
463void GBK_terminate(const char *error) { // goes to header __ATTR__NORETURN
464    /* GBK_terminate is the emergency exit!
465     * only used if no other way to recover
466     */
467
468    fprintf(stderr, "Error: '%s'\n", error);
469    fputs("Can't continue - terminating..\n", stderr);
470    GBK_dump_backtrace(stderr, "GBK_terminate (reason above) ");
471
472    fflush(stderr);
473    ARB_SIGSEGV(0); // GBK_terminate shall not be called, fix reason for call (this will crash in RELEASE version)
474    exit(ARB_CRASH_CODE(0)); // should not be reached..just ensure ARB really terminates if somebody changes ARB_SIGSEGV
475}
476
477void GBK_terminatef(const char *templat, ...) {
478    // goes to header: __ATTR__FORMAT(1) __ATTR__NORETURN
479    FORWARD_FORMATTED_NORETURN(GBK_terminate, templat);
480}
481
482// AISC_MKPT_PROMOTE:inline void GBK_terminate_on_error(const char *error) { if (error) GBK_terminatef("Fatal error: %s", error); }
483
484void GB_warning(const char *message) {
485    /* If program uses GUI, the message is printed via aw_message, otherwise it goes to stdout
486     * see also : GB_information
487     */
488    active_arb_handlers->show_warning(message);
489}
490void GB_warningf(const char *templat, ...) {
491    // goes to header: __ATTR__FORMAT(1)
492    FORWARD_FORMATTED(GB_warning, templat);
493}
494
495void GB_information(const char *message) {
496    /* this message is always printed to stdout (regardless whether program uses GUI or not)
497     * (if it is not redirected using ARB_redirect_handlers_to)
498     * see also : GB_warning
499     */
500    active_arb_handlers->show_message(message);
501}
502void GB_informationf(const char *templat, ...) {
503    // goes to header: __ATTR__FORMAT(1)
504    FORWARD_FORMATTED(GB_information, templat);
505}
506
507
508#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
509
510void GBS_reuse_buffer(const char *global_buffer) {
511    // If you've just shortely used a buffer, you can put it back here
512    va_list empty;
513    globBuf.vstrf(global_buffer, empty, -1); // omg hax
514}
515
516#if defined(WARN_TODO)
517#warning search for '\b(system)\b\s*\(' and use GBK_system instead
518#endif
519GB_ERROR GBK_system(const char *system_command) {
520    // goes to header: __ATTR__USERESULT
521    fflush(stdout);
522    fprintf(stderr, "[Action: `%s`]\n", system_command); fflush(stderr);
523
524    int res = system(system_command);
525
526    fflush(stdout);
527    fflush(stderr);
528
529    GB_ERROR error = NULp;
530    if (res) {
531        if (res == -1) {
532            error = GB_IO_error("forking", system_command);
533            error = GBS_global_string("System call failed (Reason: %s)", error);
534        }
535        else {
536            error = GBS_global_string("System call failed (result=%i)", res);
537        }
538
539        error = GBS_global_string("%s\n"
540                                  "System call was `%s`%s",
541                                  error, system_command,
542                                  res == -1 ? "" : "\n(Note: console window may contain additional information)");
543    }
544    return error;
545}
546
547char *GBK_singlequote(const char *arg) {
548    /*! Enclose argument in single quotes (like 'arg') for POSIX shell commands.
549     */
550
551    if (!arg[0]) return ARB_strdup("''");
552
553    GBS_strstruct  out(500);
554    const char    *existing_quote = strchr(arg, '\'');
555
556    while (existing_quote) {
557        if (existing_quote>arg) {
558            out.put('\'');
559            out.ncat(arg, existing_quote-arg);
560            out.put('\'');
561        }
562        out.put('\\');
563        out.put('\'');
564        arg            = existing_quote+1;
565        existing_quote = strchr(arg, '\'');
566    }
567
568    if (arg[0]) {
569        out.put('\'');
570        out.cat(arg);
571        out.put('\'');
572    }
573    return out.release();
574}
575
576char *GBK_doublequote(const char *arg) {
577    /*! Enclose argument in single quotes (like "arg") for POSIX shell commands.
578     * Opposed to single quoted strings, shell will interpret double quoted strings.
579     */
580
581    const char    *charsToEscape = "\"\\";
582    const char    *escape      = arg+strcspn(arg, charsToEscape);
583    GBS_strstruct  out(500);
584
585    out.put('"');
586    while (escape[0]) {
587        out.ncat(arg, escape-arg);
588        out.put('\\');
589        out.put(escape[0]);
590        arg    = escape+1;
591        escape = arg+strcspn(arg, charsToEscape);
592    }
593    out.cat(arg);
594    out.put('"');
595    return out.release();
596}
597
598// --------------------------------------------------------------------------------
599
600#ifdef UNIT_TESTS
601#ifndef TEST_UNIT_H
602#include <test_unit.h>
603#endif
604
605#include "FileContent.h"
606#include <unistd.h>
607
608#define TEST_EXPECT_SQUOTE(plain,squote_expected) do {          \
609        char *plain_squoted = GBK_singlequote(plain);           \
610        TEST_EXPECT_EQUAL(plain_squoted, squote_expected);      \
611        free(plain_squoted);                                    \
612    } while(0)
613
614#define TEST_EXPECT_DQUOTE(plain,dquote_expected) do {          \
615        char *plain_dquoted = GBK_doublequote(plain);           \
616        TEST_EXPECT_EQUAL(plain_dquoted, dquote_expected);      \
617        free(plain_dquoted);                                    \
618    } while(0)
619
620#define TEST_EXPECT_ECHOED_EQUALS(echoarg,expected_echo) do {                   \
621        char *cmd = GBS_global_string_copy("echo %s > %s", echoarg, tmpfile);   \
622        TEST_EXPECT_NO_ERROR(GBK_system(cmd));                                  \
623        FileContent out(tmpfile);                                               \
624        TEST_EXPECT_NO_ERROR(out.has_error());                                  \
625        TEST_EXPECT_EQUAL(out.lines()[0], expected_echo);                       \
626        free(cmd);                                                              \
627    } while(0)
628
629#define TEST_EXPECT_SQUOTE_IDENTITY(plain) do {         \
630        char *plain_squoted = GBK_singlequote(plain);   \
631        TEST_EXPECT_ECHOED_EQUALS(plain_squoted,plain); \
632        free(plain_squoted);                            \
633    } while(0)
634
635#define TEST_EXPECT_DQUOTE_IDENTITY(plain) do {         \
636        char *plain_dquoted = GBK_doublequote(plain);   \
637        TEST_EXPECT_ECHOED_EQUALS(plain_dquoted,plain); \
638        free(plain_dquoted);                            \
639    } while(0)
640
641void TEST_quoting() {
642    const char *tmpfile = "quoted.output";
643
644    struct quoting {
645        const char *plain;
646        const char *squoted;
647        const char *dquoted;
648    } args[] = {
649        { "",                       "''",                          "\"\"" },  // empty
650        { " ",                      "' '",                         "\" \"" }, // a space
651        { "unquoted",               "'unquoted'",                  "\"unquoted\"" },
652        { "part 'is' squoted",      "'part '\\''is'\\'' squoted'", "\"part 'is' squoted\"" },
653        { "part \"is\" dquoted",    "'part \"is\" dquoted'",       "\"part \\\"is\\\" dquoted\"" },
654        { "'squoted'",              "\\''squoted'\\'",             "\"'squoted'\"" },
655        { "\"dquoted\"",            "'\"dquoted\"'",               "\"\\\"dquoted\\\"\"" },
656        { "'",                      "\\'",                         "\"'\"" },  // a single quote
657        { "\"",                     "'\"'",                        "\"\\\"\"" },  // a double quote
658        { "\\",                     "'\\'",                        "\"\\\\\"" },  // a backslash
659        { "'\"'\"",                 "\\''\"'\\''\"'",              "\"'\\\"'\\\"\"" },  // interleaved quotes
660        { "`wc -c <min_ascii.arb | tr -d ' '`",
661          "'`wc -c <min_ascii.arb | tr -d '\\'' '\\''`'",
662          "\"`wc -c <min_ascii.arb | tr -d ' '`\"" },  // interleaved quotes
663    };
664
665    for (unsigned a = 0; a<ARRAY_ELEMS(args); ++a) {
666        TEST_ANNOTATE(GBS_global_string("a=%i", a));
667        const quoting& arg = args[a];
668
669        TEST_EXPECT_SQUOTE(arg.plain, arg.squoted);
670        TEST_EXPECT_SQUOTE_IDENTITY(arg.plain);
671
672        TEST_EXPECT_DQUOTE(arg.plain, arg.dquoted);
673        if (a != 11) {
674            TEST_EXPECT_DQUOTE_IDENTITY(arg.plain);
675        }
676        else { // backticked wc call
677            char *dquoted = GBK_doublequote(arg.plain);
678            TEST_EXPECT_ECHOED_EQUALS(dquoted, "16"); // 16 is number of chars in min_ascii.arb
679            free(dquoted);
680        }
681    }
682
683    TEST_EXPECT_EQUAL(unlink(tmpfile), 0);
684}
685
686#endif // UNIT_TESTS
687
688// --------------------------------------------------------------------------------
689
Note: See TracBrowser for help on using the repository browser.