source: branches/properties/CORE/arb_msg.cxx

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