1 /*
2  Copyright (c) 2010, 2021, Oracle and/or its affiliates.
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License, version 2.0,
6  as published by the Free Software Foundation.
7 
8  This program is also distributed with certain software (including
9  but not limited to OpenSSL) that is licensed under separate terms,
10  as designated in a particular file or component or in included license
11  documentation.  The authors of MySQL hereby grant you an additional
12  permission to link the program and your derivative works with the
13  separately licensed software that they have included with MySQL.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License, version 2.0, for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 #include "my_global.h"
26 #include <string.h> // not using namespaces yet
27 #include <stdio.h> // not using namespaces yet
28 #include <stdlib.h> // not using namespaces yet
29 
30 #include <util/NdbTap.hpp>
31 
32 #include "dbug_utils.hpp"
33 #include "decimal_utils.hpp"
34 #include "CharsetMap.hpp"
35 
36 #include "my_sys.h"
37 #include "mysql.h"
38 
39 // need two levels of macro substitution
40 #define STRINGIFY(x) #x
41 #define TOSTRING(x) STRINGIFY(x)
42 
43 // return non-zero value on failed condition
44 // C99's __func__ not supported by some C++ compilers yet (Solaris)
45 #define CHECK(cond)                                                     \
46     do {                                                                \
47         if (!(cond)) {                                                  \
48             fflush(stdout);                                             \
49             fprintf(stderr, "\n!!! failed check: " TOSTRING(cond)       \
50                     ", file: " __FILE__                                 \
51                     ", line: " TOSTRING(__LINE__)                       \
52                     ".\n");                                             \
53             fflush(stderr);                                             \
54             return 1;                                                   \
55         }                                                               \
56     } while (0)
57 
test_dbug_utils()58 int test_dbug_utils()
59 {
60     printf("\n==== DBUG Utilities ====\n");
61     const int DBUG_BUF_SIZE = 1024;
62     char buffer[DBUG_BUF_SIZE];
63 
64     const char * s = "some initial string";
65     const char * const s0 = "";
66     s = dbugExplain(buffer, DBUG_BUF_SIZE);
67     CHECK(!s || !strcmp(s, s0));
68 
69     s = dbugExplain(NULL, DBUG_BUF_SIZE);
70     CHECK(!s);
71 
72     s = dbugExplain(buffer, 0);
73     CHECK(!s);
74 
75     const char * const s1 = "t";
76     dbugSet(s1);
77     s = dbugExplain(buffer, DBUG_BUF_SIZE);
78     CHECK(!s || !strcmp(s, s1));
79 
80     dbugSet(NULL);
81     s = dbugExplain(buffer, DBUG_BUF_SIZE);
82     CHECK(!s || !strcmp(s, s1));
83 
84     const char * const s2 = "d,somename:o,/tmp/somepath";
85     dbugPush(s2);
86     s = dbugExplain(buffer, DBUG_BUF_SIZE);
87     CHECK(!s || !strcmp(s, s2));
88 
89     dbugPush(NULL);
90     s = dbugExplain(buffer, DBUG_BUF_SIZE);
91     CHECK(!s || !strcmp(s, s2));
92 
93     const char * const s3 = "d,a,b,c,x,y,z";
94     dbugPush(s3);
95     s = dbugExplain(buffer, DBUG_BUF_SIZE);
96     CHECK(!s || (strspn(s, s3) == strlen(s3))); // allow for different order
97 
98     dbugPop();
99     s = dbugExplain(buffer, DBUG_BUF_SIZE);
100     CHECK(!s || !strcmp(s, s2));
101 
102     dbugPop();
103     s = dbugExplain(buffer, DBUG_BUF_SIZE);
104     CHECK(!s || !strcmp(s, s1));
105 
106     dbugPush(NULL);
107     s = dbugExplain(buffer, DBUG_BUF_SIZE);
108     CHECK(!s || !strcmp(s, s1));
109 
110     dbugPop();
111     s = dbugExplain(buffer, DBUG_BUF_SIZE);
112     CHECK(!s || !strcmp(s, s0));
113 
114     return 0;
115 }
116 
test_decimal(const char * s,int prec,int scale,int expected_rv)117 int test_decimal(const char *s, int prec, int scale, int expected_rv)
118 {
119     char bin_buff[128], str_buff[128];
120     int r1, r2 = 0;
121 
122     str_buff[0] = 0;
123 
124     // cast: decimal_str2bin expects 'int' for size_t strlen()
125     r1 = decimal_str2bin(s, (int)strlen(s), prec, scale, bin_buff, 128);
126     if(r1 <= E_DEC_OVERFLOW) {
127         r2 = decimal_bin2str(bin_buff, 128, prec, scale, str_buff, 128);
128         CHECK(r2 == E_DEC_OK);
129     }
130     printf("[%-2d,%-2d] %-29s => res=%d,%d     %s\n",
131            prec, scale, s, r1, r2, str_buff);
132 
133     if(r1 != expected_rv)
134         printf("decimal_str2bin returned %d when %d was expected.\n",
135                r1, expected_rv);
136     CHECK(r1 == expected_rv);
137 
138     return 0;
139 }
140 
test_decimal_conv()141 int test_decimal_conv()
142 {
143     printf("\n==== decimal_str2bin() / decimal_bin2str() ====\n");
144     CHECK(test_decimal("100", 3, -1, E_DEC_BAD_SCALE) == 0);
145     CHECK(test_decimal("3.3", 2, 1, E_DEC_OK) == 0);
146     CHECK(test_decimal("124.000", 20, 4, E_DEC_OK) == 0);
147     CHECK(test_decimal("-11", 14, 1, E_DEC_OK) == 0);
148     CHECK(test_decimal("1.123456000000000", 20, 16, E_DEC_OK) == 0);
149     CHECK(test_decimal("-20.333", 4, 2, E_DEC_TRUNCATED) == 0);
150     CHECK(test_decimal("0", 20, 10, E_DEC_OK) == 0);
151     CHECK(test_decimal("1 ", 20, 10, E_DEC_OK) == 0);
152     CHECK(test_decimal("1,35", 20, 10, E_DEC_OK) == 0);
153     CHECK(test_decimal("text", 20, 10, E_DEC_BAD_NUM) == 0);
154 
155     return 0;
156 }
157 
test_charset_map()158 int test_charset_map()
159 {
160     printf("\n==== CharsetMap ====\n");
161     printf("init MySQL lib, CharsetMap...\n");
162     my_init();
163     CharsetMap::init();
164 
165     /* CharsetMap */
166     CharsetMap csmap;
167     int utf8_num = csmap.getUTF8CharsetNumber();
168     int utf16_num = csmap.getUTF16CharsetNumber();
169 
170     /* If this mysql build does not include UTF-8 and either UCS-2 or UTF-16
171        then the test suite must fail.
172     */
173     printf("UTF-8 charset num: %d     UTF-16 or UCS-2 charset num:  %d\n",
174            utf8_num, utf16_num);
175     CHECK(utf8_num != 0);
176     CHECK(utf16_num != 0);
177 
178     /* test csmap.getName()
179      */
180     const char *utf8 = csmap.getName(utf8_num);
181     CHECK(!strcmp(utf8,"UTF-8"));
182 
183     /* MySQL 5.1 and earlier will have UCS-2 but later versions may have true
184        UTF-16.  For information, print whether UTF-16 or UCS-2 is being used.
185     */
186     const char *utf16 = csmap.getMysqlName(csmap.getUTF16CharsetNumber());
187     printf("Using mysql's %s for UTF-16.\n", utf16);
188 
189     /* Now we're going to recode.
190        We test with the string "ülker", which begins with the character
191        LATIN SMALL LETTER U WITH DIARESIS - unicode code point U+00FC.
192        In the latin1 encoding this is a literal 0xFC,
193        but in the UTF-8 representation it is 0xC3 0xBC.
194     */
195     // use numeric escape sequencesx.. (or downcast integer literal to char)
196     // to avoid narrowing conversion compile warnings
197     const char my_word_latin1[6]    = { '\xFC', 'l', 'k', 'e', 'r', 0};
198     const char my_word_utf8[7]      = { '\xC3', '\xBC', 'l', 'k', 'e', 'r', 0};
199     const char my_word_truncated[5] = { '\xC3', '\xBC', 'l', 'k', 0};
200     // no need for 'unsigned char[]'
201     const char my_bad_utf8[5]       = { 'l', '\xBC', 'a', 'd', 0};
202     char result_buff_1[32];
203     char result_buff_2[32];
204     char result_buff_too_small[4];
205     int lengths[2];
206 
207     /* latin1 must be available to run the recode test */
208     int latin1_num = csmap.getCharsetNumber("latin1");
209     printf("latin1 charset number: %d  standard name: \"%s\" \n",
210            latin1_num, csmap.getName(latin1_num));
211     CHECK(latin1_num != 0);
212     CHECK(! strcmp(csmap.getName(latin1_num), "windows-1252"));
213 
214     printf("Latin1: \"%s\"                       UTF8:  \"%s\" \n",
215            my_word_latin1, my_word_utf8);
216 
217     /* RECODE TEST 1: recode from UTF-8 to Latin 1 */
218     lengths[0] = 7;
219     lengths[1] = 32;
220     CharsetMap::RecodeStatus rr1 = csmap.recode(lengths, utf8_num, latin1_num,
221                                                 my_word_utf8, result_buff_1);
222     printf("Recode Test 1 - UTF-8 to Latin-1: %d %d %d \"%s\" => \"%s\" \n",
223            rr1, lengths[0], lengths[1], my_word_utf8, result_buff_1);
224     CHECK(rr1 == CharsetMap::RECODE_OK);
225     CHECK(lengths[0] == 7);
226     CHECK(lengths[1] == 6);
227     CHECK(!strcmp(result_buff_1, my_word_latin1));
228 
229     /* RECODE TEST 2: recode from Latin1 to to UTF-8 */
230     lengths[0] = 6;
231     lengths[1] = 32;
232     CharsetMap::RecodeStatus rr2 = csmap.recode(lengths, latin1_num, utf8_num,
233                                                 my_word_latin1, result_buff_2);
234     printf("Recode Test 2 - Latin-1 to UTF-8: %d %d %d \"%s\" => \"%s\" \n",
235            rr2, lengths[0], lengths[1], my_word_latin1, result_buff_2);
236     CHECK(rr2 == CharsetMap::RECODE_OK);
237     CHECK(lengths[0] == 6);
238     CHECK(lengths[1] == 7);
239     CHECK(!(strcmp(result_buff_2, my_word_utf8)));
240 
241     /* RECODE TEST 3: recode with a too-small result buffer */
242     lengths[0] = 6;
243     lengths[1] = 4;
244     CharsetMap::RecodeStatus rr3 = csmap.recode(lengths, latin1_num, utf8_num,
245                                                 my_word_latin1, result_buff_too_small);
246     printf("Recode Test 3 - too-small buffer: %d %d %d \"%s\" => \"%s\" \n",
247            rr3, lengths[0], lengths[1], my_word_latin1, result_buff_too_small);
248     CHECK(rr3 == CharsetMap::RECODE_BUFF_TOO_SMALL);
249     CHECK(lengths[0] == 3);
250     CHECK(lengths[1] == 4);
251     /* Confirm that the first four characters were indeed recoded: */
252     CHECK(!(strncmp(result_buff_too_small, my_word_truncated, 4)));
253 
254     /* RECODE TEST 4: recode with an invalid character set */
255     CharsetMap::RecodeStatus rr4 = csmap.recode(lengths, 0, 999, my_word_latin1, result_buff_2);
256     printf("Recode Test 4 - invalid charset: %d \n", rr4);
257     CHECK(rr4 == CharsetMap::RECODE_BAD_CHARSET);
258 
259     /* RECODE TEST 5: source string is ill-formed UTF-8 */
260     lengths[0] = 5;
261     lengths[1] = 32;
262     int rr5 = csmap.recode(lengths, utf8_num, latin1_num,
263                            my_bad_utf8, result_buff_2);
264     printf("Recode Test 5 - ill-formed source string: %d \n", rr5);
265     CHECK(rr5 == CharsetMap::RECODE_BAD_SRC);
266 
267 
268     printf("isMultibyte TEST: ");
269     const bool * result1, * result2, * result3;
270     result1 = csmap.isMultibyte(latin1_num);
271     result2 = csmap.isMultibyte(utf16_num);
272     result3 = csmap.isMultibyte(utf8_num);
273     printf("latin 1: %s      UTF16: %s       UTF8: %s\n",
274            *result1 ? "Yes" : "No",
275            *result2 ? "Yes" : "No",
276            *result3 ? "Yes" : "No");
277     CHECK(! *result1);
278     CHECK(*result2);
279     CHECK(*result3);
280 
281     int nNull = 0, nSingle = 0, nMulti = 0;
282     for(int i = 0 ; i < 256 ; i++) {
283       const bool *r = csmap.isMultibyte(i);
284       if(r) {
285         if(*r) nMulti++;
286         else nSingle++;
287       }
288       else nNull++;
289     }
290     printf("Charset stats:  %d unused, %d single-byte, %d multi-byte\n",
291            nNull, nSingle, nMulti);
292     // If there is not at least one of each, then something is probably wrong
293     CHECK(nNull && nSingle && nMulti);
294 
295     printf("unload CharsetMap...\n");
296     CharsetMap::unload();
297 
298     return 0;
299 }
300 
main(int argc,const char ** argv)301 int main(int argc, const char** argv)
302 {
303     // TAP: print number of tests to run
304     plan(3);
305 
306     // init MySQL lib
307     if (my_init())
308         BAIL_OUT("my_init() failed");
309 
310     // TAP: report test result: ok(passed, non-null format string)
311     ok(test_dbug_utils() == 0, "subtest: dbug_utils");
312     ok(test_decimal_conv() == 0, "subtest: decimal_conv");
313     ok(test_charset_map() == 0, "subtest: charset_map");
314 
315     // TAP: print summary report and return exit status
316     return exit_status();
317 }
318