Bram Moolenaar | edf3f97 | 2016-08-29 22:49:24 +0200 | [diff] [blame] | 1 | /* vi:set ts=8 sts=4 sw=4 noet: |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 2 | * |
| 3 | * VIM - Vi IMproved by Bram Moolenaar |
| 4 | * |
| 5 | * Do ":help uganda" in Vim to read copying and usage conditions. |
| 6 | * Do ":help credits" in Vim to see a list of people who contributed. |
| 7 | * See README.txt for an overview of the Vim source code. |
| 8 | */ |
| 9 | |
| 10 | /* |
| 11 | * message_test.c: Unittests for message.c |
| 12 | */ |
| 13 | |
| 14 | #undef NDEBUG |
| 15 | #include <assert.h> |
| 16 | |
Bram Moolenaar | 85a2002 | 2019-12-21 18:25:54 +0100 | [diff] [blame] | 17 | // Must include main.c because it contains much more than just main() |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 18 | #define NO_VIM_MAIN |
| 19 | #include "main.c" |
| 20 | |
Bram Moolenaar | 85a2002 | 2019-12-21 18:25:54 +0100 | [diff] [blame] | 21 | // This file has to be included because some of the tested functions are |
| 22 | // static. |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 23 | #include "message.c" |
| 24 | |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 25 | #ifndef MIN |
| 26 | # define MIN(x,y) ((x) < (y) ? (x) : (y)) |
| 27 | #endif |
| 28 | |
| 29 | // These formats are not standard in C printf() function. |
| 30 | // Use a global variable rather than a literal format to disable |
| 31 | // -Wformat compiler warnings: |
| 32 | // |
| 33 | // - warning: '0' flag used with ‘%p’ gnu_printf format |
| 34 | // - warning: format ‘%S’ expects argument of type ‘wchar_t *’, but argument 4 has type ‘char *’ |
| 35 | // - warning: unknown conversion type character ‘b’ in format |
| 36 | // |
| 37 | // These formats are in practise only used from vim script printf() |
| 38 | // function and never as literals in C code. |
| 39 | char *fmt_012p = "%012p"; |
| 40 | char *fmt_5S = "%5S"; |
| 41 | char *fmt_06b = "%06b"; |
| 42 | |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 43 | /* |
| 44 | * Test trunc_string(). |
| 45 | */ |
| 46 | static void |
| 47 | test_trunc_string(void) |
| 48 | { |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 49 | char_u *buf; /*allocated every time to find uninit errors */ |
| 50 | char_u *s; |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 51 | |
Bram Moolenaar | 6281815 | 2021-02-14 15:37:30 +0100 | [diff] [blame] | 52 | // Should not write anything to destination if buflen is 0. |
| 53 | trunc_string((char_u *)"", NULL, 1, 0); |
| 54 | |
| 55 | // Truncating an empty string does nothing. |
| 56 | buf = alloc(1); |
| 57 | trunc_string((char_u *)"", buf, 1, 1); |
| 58 | assert(buf[0] == NUL); |
| 59 | vim_free(buf); |
| 60 | |
Bram Moolenaar | 85a2002 | 2019-12-21 18:25:54 +0100 | [diff] [blame] | 61 | // in place |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 62 | buf = alloc(40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 63 | STRCPY(buf, "text"); |
| 64 | trunc_string(buf, buf, 20, 40); |
| 65 | assert(STRCMP(buf, "text") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 66 | vim_free(buf); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 67 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 68 | buf = alloc(40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 69 | STRCPY(buf, "a short text"); |
| 70 | trunc_string(buf, buf, 20, 40); |
| 71 | assert(STRCMP(buf, "a short text") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 72 | vim_free(buf); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 73 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 74 | buf = alloc(40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 75 | STRCPY(buf, "a text tha just fits"); |
| 76 | trunc_string(buf, buf, 20, 40); |
| 77 | assert(STRCMP(buf, "a text tha just fits") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 78 | vim_free(buf); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 79 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 80 | buf = alloc(40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 81 | STRCPY(buf, "a text that nott fits"); |
| 82 | trunc_string(buf, buf, 20, 40); |
| 83 | assert(STRCMP(buf, "a text t...nott fits") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 84 | vim_free(buf); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 85 | |
Bram Moolenaar | 85a2002 | 2019-12-21 18:25:54 +0100 | [diff] [blame] | 86 | // copy from string to buf |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 87 | buf = alloc(40); |
| 88 | s = vim_strsave((char_u *)"text"); |
| 89 | trunc_string(s, buf, 20, 40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 90 | assert(STRCMP(buf, "text") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 91 | vim_free(buf); |
| 92 | vim_free(s); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 93 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 94 | buf = alloc(40); |
| 95 | s = vim_strsave((char_u *)"a text that fits"); |
| 96 | trunc_string(s, buf, 34, 40); |
| 97 | assert(STRCMP(buf, "a text that fits") == 0); |
| 98 | vim_free(buf); |
| 99 | vim_free(s); |
| 100 | |
| 101 | buf = alloc(40); |
| 102 | s = vim_strsave((char_u *)"a short text"); |
| 103 | trunc_string(s, buf, 20, 40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 104 | assert(STRCMP(buf, "a short text") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 105 | vim_free(buf); |
| 106 | vim_free(s); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 107 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 108 | buf = alloc(40); |
| 109 | s = vim_strsave((char_u *)"a text tha just fits"); |
| 110 | trunc_string(s, buf, 20, 40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 111 | assert(STRCMP(buf, "a text tha just fits") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 112 | vim_free(buf); |
| 113 | vim_free(s); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 114 | |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 115 | buf = alloc(40); |
| 116 | s = vim_strsave((char_u *)"a text that nott fits"); |
| 117 | trunc_string(s, buf, 20, 40); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 118 | assert(STRCMP(buf, "a text t...nott fits") == 0); |
Bram Moolenaar | b964443 | 2016-07-19 12:33:44 +0200 | [diff] [blame] | 119 | vim_free(buf); |
| 120 | vim_free(s); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 121 | } |
| 122 | |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 123 | /* |
Ralf Schandl | bc86987 | 2021-05-28 14:12:14 +0200 | [diff] [blame] | 124 | * Test trunc_string() with mbyte chars. |
| 125 | */ |
| 126 | static void |
| 127 | test_trunc_string_mbyte(void) |
| 128 | { |
| 129 | char_u *buf; // allocated every time to find uninit errors |
| 130 | char_u *s; |
| 131 | |
| 132 | buf = alloc(40); |
| 133 | s = vim_strsave((char_u *)"Ä text tha just fits"); |
| 134 | trunc_string(s, buf, 20, 40); |
| 135 | assert(STRCMP(buf, "Ä text tha just fits") == 0); |
| 136 | vim_free(buf); |
| 137 | vim_free(s); |
| 138 | |
| 139 | buf = alloc(40); |
| 140 | s = vim_strsave((char_u *)"a text ÄÖÜä nott fits"); |
| 141 | trunc_string(s, buf, 20, 40); |
| 142 | assert(STRCMP(buf, "a text Ä...nott fits") == 0); |
| 143 | vim_free(buf); |
| 144 | vim_free(s); |
| 145 | |
| 146 | buf = alloc(40); |
| 147 | s = vim_strsave((char_u *)"a text that not fitsÄ"); |
| 148 | trunc_string(s, buf, 20, 40); |
| 149 | assert(STRCMP(buf, "a text t...not fitsÄ") == 0); |
| 150 | vim_free(buf); |
| 151 | vim_free(s); |
| 152 | } |
| 153 | |
| 154 | /* |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 155 | * Test vim_snprintf() with a focus on checking that truncation is |
| 156 | * correct when buffer is small, since it cannot be tested from |
| 157 | * vim scrip tests. Check that: |
| 158 | * - no buffer overflows happens (with valgrind or asan) |
| 159 | * - output string is always NUL terminated. |
| 160 | * |
| 161 | * Not all formats of vim_snprintf() are checked here. They are |
| 162 | * checked more exhaustively in Test_printf*() vim script tests. |
| 163 | */ |
| 164 | static void |
| 165 | test_vim_snprintf(void) |
| 166 | { |
| 167 | int n; |
| 168 | size_t bsize; |
| 169 | int bsize_int; |
Bram Moolenaar | 4da6df4 | 2020-04-20 16:12:09 +0200 | [diff] [blame] | 170 | void *ptr = (void *)0x87654321; |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 171 | |
| 172 | // Loop on various buffer sizes to make sure that truncation of |
| 173 | // vim_snprintf() is correct. |
| 174 | for (bsize = 0; bsize < 15; ++bsize) |
| 175 | { |
| 176 | bsize_int = (int)bsize - 1; |
| 177 | |
| 178 | // buf is the heap rather than in the stack |
| 179 | // so valgrind can detect buffer overflows if any. |
| 180 | // Use malloc() rather than alloc() as test checks with 0-size |
| 181 | // buffer and its content should then never be used. |
| 182 | char *buf = malloc(bsize); |
| 183 | |
| 184 | n = vim_snprintf(buf, bsize, "%d", 1234567); |
| 185 | assert(n == 7); |
| 186 | assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); |
| 187 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 188 | |
| 189 | n = vim_snprintf(buf, bsize, "%ld", 1234567L); |
| 190 | assert(n == 7); |
| 191 | assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); |
| 192 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 193 | |
| 194 | n = vim_snprintf(buf, bsize, "%9ld", 1234567L); |
| 195 | assert(n == 9); |
| 196 | assert(bsize == 0 || STRNCMP(buf, " 1234567", bsize_int) == 0); |
| 197 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 198 | |
| 199 | n = vim_snprintf(buf, bsize, "%-9ld", 1234567L); |
| 200 | assert(n == 9); |
| 201 | assert(bsize == 0 || STRNCMP(buf, "1234567 ", bsize_int) == 0); |
| 202 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 203 | |
| 204 | n = vim_snprintf(buf, bsize, "%x", 0xdeadbeef); |
| 205 | assert(n == 8); |
| 206 | assert(bsize == 0 || STRNCMP(buf, "deadbeef", bsize_int) == 0); |
| 207 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 208 | |
Bram Moolenaar | 1470dc3 | 2020-01-14 22:02:14 +0100 | [diff] [blame] | 209 | n = vim_snprintf(buf, bsize, fmt_06b, (uvarnumber_T)12); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 210 | assert(n == 6); |
| 211 | assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0); |
| 212 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 213 | |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 214 | n = vim_snprintf(buf, bsize, "%f", 1.234); |
| 215 | assert(n == 8); |
| 216 | assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0); |
| 217 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 218 | |
| 219 | n = vim_snprintf(buf, bsize, "%e", 1.234); |
| 220 | assert(n == 12); |
| 221 | assert(bsize == 0 || STRNCMP(buf, "1.234000e+00", bsize_int) == 0); |
| 222 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 223 | |
| 224 | n = vim_snprintf(buf, bsize, "%f", 0.0/0.0); |
| 225 | assert(n == 3); |
| 226 | assert(bsize == 0 || STRNCMP(buf, "nan", bsize_int) == 0); |
| 227 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 228 | |
| 229 | n = vim_snprintf(buf, bsize, "%f", 1.0/0.0); |
| 230 | assert(n == 3); |
| 231 | assert(bsize == 0 || STRNCMP(buf, "inf", bsize_int) == 0); |
| 232 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 233 | |
| 234 | n = vim_snprintf(buf, bsize, "%f", -1.0/0.0); |
| 235 | assert(n == 4); |
| 236 | assert(bsize == 0 || STRNCMP(buf, "-inf", bsize_int) == 0); |
| 237 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 238 | |
| 239 | n = vim_snprintf(buf, bsize, "%f", -0.0); |
| 240 | assert(n == 9); |
| 241 | assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0); |
| 242 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 243 | |
| 244 | n = vim_snprintf(buf, bsize, "%s", "漢語"); |
| 245 | assert(n == 6); |
| 246 | assert(bsize == 0 || STRNCMP(buf, "漢語", bsize_int) == 0); |
| 247 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 248 | |
| 249 | n = vim_snprintf(buf, bsize, "%8s", "漢語"); |
| 250 | assert(n == 8); |
| 251 | assert(bsize == 0 || STRNCMP(buf, " 漢語", bsize_int) == 0); |
| 252 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 253 | |
| 254 | n = vim_snprintf(buf, bsize, "%-8s", "漢語"); |
| 255 | assert(n == 8); |
| 256 | assert(bsize == 0 || STRNCMP(buf, "漢語 ", bsize_int) == 0); |
| 257 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 258 | |
| 259 | n = vim_snprintf(buf, bsize, "%.3s", "漢語"); |
| 260 | assert(n == 3); |
| 261 | assert(bsize == 0 || STRNCMP(buf, "漢", bsize_int) == 0); |
| 262 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 263 | |
| 264 | n = vim_snprintf(buf, bsize, fmt_5S, "foo"); |
| 265 | assert(n == 5); |
| 266 | assert(bsize == 0 || STRNCMP(buf, " foo", bsize_int) == 0); |
| 267 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 268 | |
| 269 | n = vim_snprintf(buf, bsize, "%%%%%%"); |
| 270 | assert(n == 3); |
| 271 | assert(bsize == 0 || STRNCMP(buf, "%%%", bsize_int) == 0); |
| 272 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 273 | |
| 274 | n = vim_snprintf(buf, bsize, "%c%c", 1, 2); |
| 275 | assert(n == 2); |
| 276 | assert(bsize == 0 || STRNCMP(buf, "\x01\x02", bsize_int) == 0); |
| 277 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 278 | |
| 279 | // %p format is not tested in vim script tests Test_printf*() |
| 280 | // as it only makes sense in C code. |
Bram Moolenaar | d5b9914 | 2020-02-08 17:14:46 +0100 | [diff] [blame] | 281 | // NOTE: SunOS libc doesn't use the prefix "0x" on %p. |
| 282 | #ifdef SUN_SYSTEM |
| 283 | # define PREFIX_LEN 0 |
| 284 | # define PREFIX_STR1 "" |
| 285 | # define PREFIX_STR2 "00" |
| 286 | #else |
| 287 | # define PREFIX_LEN 2 |
| 288 | # define PREFIX_STR1 "0x" |
| 289 | # define PREFIX_STR2 "0x" |
| 290 | #endif |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 291 | n = vim_snprintf(buf, bsize, "%p", ptr); |
Bram Moolenaar | d5b9914 | 2020-02-08 17:14:46 +0100 | [diff] [blame] | 292 | assert(n == 8 + PREFIX_LEN); |
| 293 | assert(bsize == 0 |
| 294 | || STRNCMP(buf, PREFIX_STR1 "87654321", bsize_int) == 0); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 295 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 296 | |
| 297 | n = vim_snprintf(buf, bsize, fmt_012p, ptr); |
| 298 | assert(n == 12); |
Bram Moolenaar | d5b9914 | 2020-02-08 17:14:46 +0100 | [diff] [blame] | 299 | assert(bsize == 0 |
| 300 | || STRNCMP(buf, PREFIX_STR2 "0087654321", bsize_int) == 0); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 301 | assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); |
| 302 | |
| 303 | free(buf); |
| 304 | } |
| 305 | } |
| 306 | |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 307 | int |
| 308 | main(int argc, char **argv) |
| 309 | { |
Bram Moolenaar | a80faa8 | 2020-04-12 19:37:17 +0200 | [diff] [blame] | 310 | CLEAR_FIELD(params); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 311 | params.argc = argc; |
| 312 | params.argv = argv; |
| 313 | common_init(¶ms); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 314 | |
Bram Moolenaar | 31e5c60 | 2022-04-15 13:53:33 +0100 | [diff] [blame] | 315 | set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"utf-8", 0); |
Bram Moolenaar | d0337e3 | 2019-12-30 17:55:34 +0100 | [diff] [blame] | 316 | init_chartab(); |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 317 | test_trunc_string(); |
Ralf Schandl | bc86987 | 2021-05-28 14:12:14 +0200 | [diff] [blame] | 318 | test_trunc_string_mbyte(); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 319 | test_vim_snprintf(); |
Bram Moolenaar | d0337e3 | 2019-12-30 17:55:34 +0100 | [diff] [blame] | 320 | |
Bram Moolenaar | 31e5c60 | 2022-04-15 13:53:33 +0100 | [diff] [blame] | 321 | set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"latin1", 0); |
Bram Moolenaar | d0337e3 | 2019-12-30 17:55:34 +0100 | [diff] [blame] | 322 | init_chartab(); |
| 323 | test_trunc_string(); |
Bram Moolenaar | d2c946b | 2019-12-31 19:24:51 +0100 | [diff] [blame] | 324 | test_vim_snprintf(); |
Bram Moolenaar | d0337e3 | 2019-12-30 17:55:34 +0100 | [diff] [blame] | 325 | |
Bram Moolenaar | 502ae4b | 2016-07-16 19:50:13 +0200 | [diff] [blame] | 326 | return 0; |
| 327 | } |