]> git.draconx.ca Git - cdecl99.git/blob - t/cdeclerr.c
Port to use getline.h from dxcommon.
[cdecl99.git] / t / cdeclerr.c
1 /*
2  * Helper application to test internal library error reporting.
3  * Copyright © 2023-2024 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <assert.h>
23 #include <ctype.h>
24
25 #include "cdecl-internal.h"
26 #include "cdecl.h"
27 #include "errmsg.h"
28 #include "tap.h"
29
30 /*
31  * Function called from output.c but not needed for error messaging.
32  */
33 const char *cdecl__token_name(unsigned token)
34 {
35         tap_bail_out("stub cdecl__token_name called");
36 }
37
38 static char *fmt_char(int c, char *buf)
39 {
40         int escape = 0;
41
42         switch (c) {
43         case '\a': escape = 'a';  break;
44         case '\b': escape = 'b';  break;
45         case '\t': escape = 't';  break;
46         case '\n': escape = 'n';  break;
47         case '\v': escape = 'v';  break;
48         case '\f': escape = 'f';  break;
49         case '\r': escape = 'r';  break;
50         case '\\': escape = '\\'; break;
51         case '\'': escape = '\''; break;
52         }
53
54         if (escape)
55                 sprintf(buf, "'\\%c'", escape);
56         else if (isprint((unsigned char)c))
57                 sprintf(buf, "'%c'", c);
58         else
59                 sprintf(buf, "'\\%.3o'", c & 0777u);
60
61         return buf;
62 }
63
64 static void check_code(const struct cdecl_error *err, unsigned expect)
65 {
66         if (!tap_result(err->code == expect, "returned error code")) {
67                 tap_diag("Failed, unexpected result");
68                 tap_diag("   Received: %u", err->code);
69                 tap_diag("   Expected: %u", expect);
70         }
71 }
72
73 static void check_fixed_string(size_t len)
74 {
75         const struct cdecl_error *err;
76         char *work1, *work2;
77         size_t errlen;
78
79         assert(len > 0);
80         work1 = calloc(2, len);
81         if (!work1)
82                 abort();
83
84         work2 = work1 + len;
85         memset(work1, 'X', len - 1);
86         memset(work2, 'X', len - 1);
87
88         tap_diag("cdecl__err w/ %lu-byte string", (unsigned long)len);
89         cdecl__err(work1, "XX");
90         memset(work1, 0, len);
91         err = cdecl_get_error();
92
93         check_code(err, CDECL_ENOPARSE);
94
95         errlen = strlen(err->str);
96         if (!tap_result(errlen == len-1, "returned string length")) {
97                 tap_diag("Failed, unexpected result");
98                 tap_diag("   Received: %lu", (unsigned long)errlen);
99                 tap_diag("   Expected: %lu", (unsigned long)len);
100         }
101
102         if (!tap_result(!memcmp(err->str, work2, len), "returned string")) {
103                 char buf[8];
104                 size_t n;
105
106                 n = strspn(err->str, "X");
107                 if (n >= len)
108                         n = len;
109
110                 tap_diag("Failed, first incorrect character at offset %lu",
111                          (unsigned long)n);
112                 tap_diag("   Received: %s", fmt_char(err->str[n], buf));
113                 tap_diag("   Expected: %s", fmt_char(work2[n], buf));
114         }
115
116         free(work1);
117 }
118
119 static void check_format_string(const char *fmt, const char *arg)
120 {
121         size_t sz = strlen(fmt) + strlen(arg);
122         const struct cdecl_error *err;
123         char *work;
124
125         work = malloc(sz + 1);
126         if (!work)
127                 abort();
128         sprintf(work, fmt, arg);
129
130         cdecl__err(fmt, arg);
131         err = cdecl_get_error();
132
133         tap_diag("cdecl__err(\"%s\", \"%s\")", fmt, arg);
134         check_code(err, CDECL_ENOPARSE);
135
136         if (!tap_result(!strcmp(err->str, work), "returned string")) {
137                 tap_diag("Failed, unexpected result");
138                 tap_diag("   Received: %.*s", (int)sz, err->str);
139                 tap_diag("   Expected: %s", work);
140         }
141
142         free(work);
143 }
144
145 static void check_enomem()
146 {
147         const char expmsg[] = "failed to allocate memory";
148         const struct cdecl_error *err;
149
150         tap_diag("cdecl__errmsg(CDECL__ENOMEM)");
151         cdecl__errmsg(CDECL__ENOMEM);
152         err = cdecl_get_error();
153
154         check_code(err, CDECL_ENOMEM);
155         if (!tap_result(!strcmp(err->str, "failed to allocate memory"),
156                         "returned string"))
157         {
158                 unsigned i;
159
160                 for (i = 0; i < sizeof expmsg; i++) {
161                         if (err->str[i] != expmsg[i])
162                                 break;
163                 }
164
165                 tap_diag("Failed, first incorrect character at offset %u", i);
166                 tap_diag("   Received: %.*s", (int)sizeof expmsg, err->str);
167                 tap_diag("   Expected: %s", expmsg);
168         }
169 }
170
171 #define IFNLS(a, b) (ENABLE_NLS ? (a) : (b))
172
173 int main(void)
174 {
175         tap_plan(3*3 + 2 + 2);
176
177         check_fixed_string(50);
178         check_fixed_string(500);
179         check_fixed_string(5000);
180
181         /*
182          * When NLS is disabled, for implementation simplicity only format
183          * strings ending with %s are supported as this is sufficient.
184          * Otherwise, %s may appear anywhere.
185          */
186         check_format_string(IFNLS("hello %s world", "hello world %s"), "za");
187
188         check_enomem();
189
190         tap_done();
191 }