source: tags/ms_r18q1/CORE/arb_msg.cxx

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