1 | /* |
2 | Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. |
3 | |
4 | The MySQL Connector/C is licensed under the terms of the GPLv2 |
5 | <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most |
6 | MySQL Connectors. There are special exceptions to the terms and |
7 | conditions of the GPLv2 as it is applied to this software, see the |
8 | FLOSS License Exception |
9 | <http://www.mysql.com/about/legal/licensing/foss-exception.html>. |
10 | |
11 | This program is free software; you can redistribute it and/or modify |
12 | it under the terms of the GNU General Public License as published |
13 | by the Free Software Foundation; version 2 of the License. |
14 | |
15 | This program is distributed in the hope that it will be useful, but |
16 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
17 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
18 | for more details. |
19 | |
20 | You should have received a copy of the GNU General Public License along |
21 | with this program; if not, write to the Free Software Foundation, Inc., |
22 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
23 | */ |
24 | #include <ma_global.h> |
25 | #include <ma_sys.h> |
26 | #include <mysql.h> |
27 | #include <tap.h> |
28 | #include "ma_getopt.h" |
29 | #include <memory.h> |
30 | #include <string.h> |
31 | #include <errmsg.h> |
32 | #include <stdlib.h> |
33 | #include <ma_server_error.h> |
34 | |
35 | #ifndef WIN32 |
36 | #include <pthread.h> |
37 | #endif |
38 | |
39 | #ifndef OK |
40 | # define OK 0 |
41 | #endif |
42 | #ifndef FAIL |
43 | # define FAIL 1 |
44 | #endif |
45 | #ifndef SKIP |
46 | # define SKIP -1 |
47 | #endif |
48 | #ifndef FALSE |
49 | # define FALSE 0 |
50 | #endif |
51 | #ifndef TRUE |
52 | # define TRUE 1 |
53 | #endif |
54 | |
55 | #define MAX_KEY MAX_INDEXES |
56 | #define MAX_KEY_LENGTH_DECIMAL_WIDTH 4 /* strlen("4096") */ |
57 | |
58 | #define SL(s) (s), (unsigned long)strlen((s)) |
59 | #define SL_BIN(s) (s), (unsigned long)sizeof((s)) |
60 | |
61 | #define MAX_TEST_QUERY_LENGTH 300 /* MAX QUERY BUFFER LENGTH */ |
62 | |
63 | /* prevent warnings on Win64 by using STMT_LEN instead of strlen */ |
64 | #define STMT_LEN(A) (unsigned long)strlen((A)) |
65 | |
66 | #define check_mysql_rc(rc, mysql) \ |
67 | if (rc)\ |
68 | {\ |
69 | diag("Error (%d): %s (%d) in %s line %d", rc, mysql_error(mysql), \ |
70 | mysql_errno(mysql), __FILE__, __LINE__);\ |
71 | return(FAIL);\ |
72 | } |
73 | |
74 | #define check_stmt_rc(rc, stmt) \ |
75 | if (rc)\ |
76 | {\ |
77 | diag("Error: %s (%s: %d)", mysql_stmt_error(stmt), __FILE__, __LINE__);\ |
78 | return(FAIL);\ |
79 | } |
80 | |
81 | #define FAIL_IF(expr, reason)\ |
82 | if (expr)\ |
83 | {\ |
84 | diag("Error: %s (%s: %d)", (reason) ? reason : "", __FILE__, __LINE__);\ |
85 | return FAIL;\ |
86 | } |
87 | |
88 | #define FAIL_UNLESS(expr, reason)\ |
89 | if (!(expr))\ |
90 | {\ |
91 | diag("Error: %s (%s: %d)", reason, __FILE__, __LINE__);\ |
92 | return FAIL;\ |
93 | } |
94 | |
95 | #define SKIP_CONNECTION_HANDLER \ |
96 | if (hostname && strstr(hostname, "://"))\ |
97 | {\ |
98 | diag("Test skipped (connection handler)");\ |
99 | return SKIP;\ |
100 | } |
101 | |
102 | /* connection options */ |
103 | #define TEST_CONNECTION_DEFAULT 1 /* default connection */ |
104 | #define TEST_CONNECTION_NONE 2 /* tests creates own connection */ |
105 | #define TEST_CONNECTION_NEW 4 /* create a separate connection */ |
106 | #define TEST_CONNECTION_DONT_CLOSE 8 /* don't close connection */ |
107 | |
108 | struct my_option_st |
109 | { |
110 | enum mysql_option option; |
111 | char *value; |
112 | }; |
113 | |
114 | struct my_tests_st |
115 | { |
116 | const char *name; |
117 | int (*function)(MYSQL *); |
118 | int connection; |
119 | ulong connect_flags; |
120 | struct my_option_st *options; |
121 | const char *skipmsg; |
122 | }; |
123 | |
124 | MYSQL *my_test_connect(MYSQL *mysql, |
125 | const char *host, |
126 | const char *user, |
127 | const char *passwd, |
128 | const char *db, |
129 | unsigned int port, |
130 | const char *unix_socket, |
131 | unsigned long clientflag); |
132 | |
133 | static const char *schema = 0; |
134 | static char *hostname = 0; |
135 | static char *password = 0; |
136 | static unsigned int port = 0; |
137 | static char *socketname = 0; |
138 | static char *username = 0; |
139 | static int force_tls= 0; |
140 | static uchar is_mariadb= 0; |
141 | static char *this_host= 0; |
142 | static unsigned char travis_test= 0; |
143 | /* |
144 | static struct my_option test_options[] = |
145 | { |
146 | {"schema", 'd', "database to use", (uchar **) &schema, (uchar **) &schema, |
147 | 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
148 | {"help", '?', "Display this help and exit", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, |
149 | 0, 0, 0, 0, 0}, |
150 | {"host", 'h', "Connect to host", (uchar **) &hostname, (uchar **) &hostname, |
151 | 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
152 | {"password", 'p', |
153 | "Password to use when connecting to server.", (uchar **) &password, (uchar **) &password, |
154 | 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, |
155 | {"port", 'P', "Port number to use for connection or 0 for default to, in " |
156 | "order of preference, my.cnf, $MYSQL_TCP_PORT, " |
157 | #if MYSQL_PORT_DEFAULT == 0 |
158 | "/etc/services, " |
159 | #endif |
160 | "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", |
161 | (uchar **) &port, |
162 | (uchar **) &port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
163 | {"socket", 'S', "Socket file to use for connection", |
164 | (uchar **) &socketname, (uchar **) &socketname, 0, GET_STR, |
165 | REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
166 | {"user", 'u', "User for login if not current user", (uchar **) &username, |
167 | (uchar **) &username, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
168 | { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} |
169 | }; |
170 | */ |
171 | #define verify_prepare_field(result,no,name,org_name,type,table,\ |
172 | org_table,db,length,def) \ |
173 | do_verify_prepare_field((result),(no),(name),(org_name),(type), \ |
174 | (table),(org_table),(db),(length),(def), \ |
175 | __FILE__, __LINE__) |
176 | |
177 | int do_verify_prepare_field(MYSQL_RES *result, |
178 | unsigned int no, const char *name, |
179 | const char *org_name, |
180 | enum enum_field_types type __attribute__((unused)), |
181 | const char *table, |
182 | const char *org_table, const char *db, |
183 | unsigned long length __attribute__((unused)), |
184 | const char *def __attribute__((unused)), |
185 | const char *file __attribute__((unused)), |
186 | int line __attribute__((unused))) |
187 | { |
188 | MYSQL_FIELD *field; |
189 | /* MARIADB_CHARSET_INFO *cs; */ |
190 | |
191 | FAIL_IF(!(field= mysql_fetch_field_direct(result, no)), "FAILED to get result" ); |
192 | /* cs= mysql_find_charset_nr(field->charsetnr); |
193 | FAIL_UNLESS(cs, "Couldn't get character set"); */ |
194 | FAIL_UNLESS(strcmp(field->name, name) == 0, "field->name differs" ); |
195 | FAIL_UNLESS(strcmp(field->org_name, org_name) == 0, "field->org_name differs" ); |
196 | /* |
197 | if ((expected_field_length= length * cs->mbmaxlen) > UINT_MAX32) |
198 | expected_field_length= UINT_MAX32; |
199 | */ |
200 | /* |
201 | XXX: silent column specification change works based on number of |
202 | bytes a column occupies. So CHAR -> VARCHAR upgrade is possible even |
203 | for CHAR(2) column if its character set is multibyte. |
204 | VARCHAR -> CHAR downgrade won't work for VARCHAR(3) as one would |
205 | expect. |
206 | */ |
207 | // if (cs->char_maxlen == 1) |
208 | // FAIL_UNLESS(field->type == type, "field->type differs"); |
209 | if (table) |
210 | FAIL_UNLESS(strcmp(field->table, table) == 0, "field->table differs" ); |
211 | if (org_table) |
212 | FAIL_UNLESS(strcmp(field->org_table, org_table) == 0, "field->org_table differs" ); |
213 | if (strcmp(field->db,db)) |
214 | diag("%s / %s" , field->db, db); |
215 | FAIL_UNLESS(strcmp(field->db, db) == 0, "field->db differs" ); |
216 | /* |
217 | Character set should be taken into account for multibyte encodings, such |
218 | as utf8. Field length is calculated as number of characters * maximum |
219 | number of bytes a character can occupy. |
220 | */ |
221 | |
222 | return OK; |
223 | } |
224 | |
225 | void get_this_host(MYSQL *mysql) |
226 | { |
227 | MYSQL_RES *res; |
228 | MYSQL_ROW row; |
229 | |
230 | if (mysql_query(mysql, "select substr(current_user(), locate('@', current_user())+1)" )) |
231 | return; |
232 | |
233 | if ((res= mysql_store_result(mysql))) |
234 | { |
235 | if ((row= mysql_fetch_row(res))) |
236 | this_host= strdup(row[0]); |
237 | mysql_free_result(res); |
238 | } |
239 | } |
240 | |
241 | /* Prepare statement, execute, and process result set for given query */ |
242 | |
243 | int my_stmt_result(MYSQL *mysql, const char *buff) |
244 | { |
245 | MYSQL_STMT *stmt; |
246 | int row_count= 0; |
247 | int rc; |
248 | |
249 | stmt= mysql_stmt_init(mysql); |
250 | |
251 | rc= mysql_stmt_prepare(stmt, buff, (unsigned long)strlen(buff)); |
252 | FAIL_IF(rc, mysql_stmt_error(stmt)); |
253 | |
254 | rc= mysql_stmt_execute(stmt); |
255 | FAIL_IF(rc, mysql_stmt_error(stmt)); |
256 | |
257 | while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) |
258 | row_count++; |
259 | |
260 | mysql_stmt_close(stmt); |
261 | |
262 | return row_count; |
263 | } |
264 | /* |
265 | static my_bool |
266 | get_one_option(int optid, const struct my_option *opt __attribute__((unused)), |
267 | char *argument) |
268 | { |
269 | switch (optid) { |
270 | case '?': |
271 | case 'I': |
272 | my_print_help(test_options); |
273 | exit(0); |
274 | break; |
275 | } |
276 | return 0; |
277 | } |
278 | */ |
279 | /* Utility function to verify a particular column data */ |
280 | |
281 | int verify_col_data(MYSQL *mysql, const char *table, const char *col, |
282 | const char *exp_data) |
283 | { |
284 | static char query[MAX_TEST_QUERY_LENGTH]; |
285 | MYSQL_RES *result; |
286 | MYSQL_ROW row; |
287 | int rc; |
288 | |
289 | if (table && col) |
290 | { |
291 | sprintf(query, "SELECT %s FROM %s LIMIT 1" , col, table); |
292 | rc= mysql_query(mysql, query); |
293 | check_mysql_rc(rc, mysql); |
294 | } |
295 | result= mysql_use_result(mysql); |
296 | FAIL_IF(!result, "Invalid result set" ); |
297 | |
298 | if (!(row= mysql_fetch_row(result)) || !row[0]) { |
299 | diag("Failed to get the result" ); |
300 | goto error; |
301 | } |
302 | if(strcmp(row[0], exp_data)) { |
303 | diag("Expected %s, got %s" , exp_data, row[0]); |
304 | goto error; |
305 | } |
306 | mysql_free_result(result); |
307 | |
308 | return OK; |
309 | |
310 | error: |
311 | mysql_free_result(result); |
312 | return FAIL; |
313 | } |
314 | |
315 | my_bool query_int_variable(MYSQL *con, const char *var_name, int *var_value) |
316 | { |
317 | MYSQL_RES *rs; |
318 | MYSQL_ROW row; |
319 | |
320 | char query_buffer[MAX_TEST_QUERY_LENGTH]; |
321 | |
322 | my_bool is_null; |
323 | |
324 | sprintf(query_buffer, |
325 | "SELECT %s" , |
326 | (const char *) var_name); |
327 | |
328 | FAIL_IF(mysql_query(con, query_buffer), "Query failed" ); |
329 | FAIL_UNLESS(rs= mysql_store_result(con), "Invaliid result set" ); |
330 | FAIL_UNLESS(row= mysql_fetch_row(rs), "Nothing to fetch" ); |
331 | |
332 | is_null= row[0] == NULL; |
333 | |
334 | if (!is_null) |
335 | *var_value= atoi(row[0]); |
336 | |
337 | mysql_free_result(rs); |
338 | |
339 | return is_null; |
340 | } |
341 | |
342 | static void usage() |
343 | { |
344 | printf("Execute test with the following options:\n" ); |
345 | printf("-h hostname\n" ); |
346 | printf("-u username\n" ); |
347 | printf("-p password\n" ); |
348 | printf("-d database\n" ); |
349 | printf("-S socketname\n" ); |
350 | printf("-t force use of TLS\n" ); |
351 | printf("-P port number\n" ); |
352 | printf("? displays this help and exits\n" ); |
353 | } |
354 | |
355 | void get_options(int argc, char **argv) |
356 | { |
357 | int c= 0; |
358 | |
359 | while ((c=getopt(argc,argv, "h:u:p:d:w:P:S:t:?" )) >= 0) |
360 | { |
361 | switch(c) { |
362 | case 'h': |
363 | hostname= optarg; |
364 | break; |
365 | case 'u': |
366 | username= optarg; |
367 | break; |
368 | case 'p': |
369 | password= optarg; |
370 | break; |
371 | case 'd': |
372 | schema= optarg; |
373 | break; |
374 | case 'P': |
375 | port= atoi(optarg); |
376 | break; |
377 | case 'S': |
378 | socketname= optarg; |
379 | break; |
380 | case 't': |
381 | force_tls= 1; |
382 | break; |
383 | case '?': |
384 | usage(); |
385 | exit(0); |
386 | break; |
387 | default: |
388 | usage(); |
389 | BAIL_OUT("Unknown option %c\n" , c); |
390 | } |
391 | } |
392 | } |
393 | |
394 | |
395 | int check_variable(MYSQL *mysql, const char *variable, const char *value) |
396 | { |
397 | char query[MAX_TEST_QUERY_LENGTH]; |
398 | MYSQL_RES *result; |
399 | MYSQL_ROW row; |
400 | |
401 | sprintf(query, "SELECT %s" , variable); |
402 | result= mysql_store_result(mysql); |
403 | if (!result) |
404 | return FAIL; |
405 | |
406 | if ((row = mysql_fetch_row(result))) |
407 | if (strcmp(row[0], value) == 0) { |
408 | mysql_free_result(result); |
409 | return OK; |
410 | } |
411 | mysql_free_result(result); |
412 | return FAIL; |
413 | } |
414 | |
415 | /* |
416 | * function *test_connect |
417 | * |
418 | * returns a new connection. This function will be called, if the test doesn't |
419 | * use default_connection. |
420 | */ |
421 | MYSQL *test_connect(struct my_tests_st *test) |
422 | { |
423 | MYSQL *mysql; |
424 | int i= 0; |
425 | int timeout= 10; |
426 | my_bool truncation_report= 1; |
427 | if (!(mysql = mysql_init(NULL))) { |
428 | BAIL_OUT("Not enough memory available - mysql_init failed" ); |
429 | } |
430 | mysql_options(mysql, MYSQL_REPORT_DATA_TRUNCATION, &truncation_report); |
431 | mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); |
432 | |
433 | /* option handling */ |
434 | if (test && test->options) { |
435 | |
436 | while (test->options[i].option) |
437 | { |
438 | if (mysql_options(mysql, test->options[i].option, test->options[i].value)) { |
439 | diag("Couldn't set option %d. Error (%d) %s" , test->options[i].option, |
440 | mysql_errno(mysql), mysql_error(mysql)); |
441 | mysql_close(mysql); |
442 | return(NULL); |
443 | } |
444 | i++; |
445 | } |
446 | } |
447 | if (!(my_test_connect(mysql, hostname, username, password, |
448 | schema, port, socketname, (test) ? test->connect_flags:0))) |
449 | { |
450 | diag("Couldn't establish connection to server %s. Error (%d): %s" , |
451 | hostname, mysql_errno(mysql), mysql_error(mysql)); |
452 | mysql_close(mysql); |
453 | return(NULL); |
454 | } |
455 | return(mysql); |
456 | } |
457 | |
458 | static int reset_connection(MYSQL *mysql) { |
459 | int rc; |
460 | |
461 | if (is_mariadb) |
462 | rc= mysql_change_user(mysql, username, password, schema); |
463 | else |
464 | rc= mysql_reset_connection(mysql); |
465 | check_mysql_rc(rc, mysql); |
466 | rc= mysql_query(mysql, "SET sql_mode=''" ); |
467 | check_mysql_rc(rc, mysql); |
468 | |
469 | return OK; |
470 | } |
471 | |
472 | /* |
473 | * function get_envvars(( |
474 | * |
475 | * checks for connection related environment variables |
476 | */ |
477 | void get_envvars() { |
478 | char *envvar; |
479 | |
480 | if (getenv("MYSQL_TEST_TRAVIS" )) |
481 | travis_test= 1; |
482 | |
483 | if (!hostname && (envvar= getenv("MYSQL_TEST_HOST" ))) |
484 | hostname= envvar; |
485 | if (!username) |
486 | { |
487 | if ((envvar= getenv("MYSQL_TEST_USER" ))) |
488 | username= envvar; |
489 | else |
490 | username= (char *)"root" ; |
491 | } |
492 | if (!password && (envvar= getenv("MYSQL_TEST_PASSWD" ))) |
493 | password= envvar; |
494 | if (!schema && (envvar= getenv("MYSQL_TEST_DB" ))) |
495 | schema= envvar; |
496 | if (!schema) |
497 | schema= "test" ; |
498 | if (!port) |
499 | { |
500 | if ((envvar= getenv("MYSQL_TEST_PORT" ))) |
501 | port= atoi(envvar); |
502 | else if ((envvar= getenv("MASTER_MYPORT" ))) |
503 | port= atoi(envvar); |
504 | diag("port: %d" , port); |
505 | } |
506 | if (!force_tls && (envvar= getenv("MYSQL_TEST_TLS" ))) |
507 | force_tls= atoi(envvar); |
508 | if (!socketname) |
509 | { |
510 | if ((envvar= getenv("MYSQL_TEST_SOCKET" ))) |
511 | socketname= envvar; |
512 | else if ((envvar= getenv("MASTER_MYSOCK" ))) |
513 | socketname= envvar; |
514 | diag("socketname: %s" , socketname); |
515 | } |
516 | } |
517 | |
518 | MYSQL *my_test_connect(MYSQL *mysql, |
519 | const char *host, |
520 | const char *user, |
521 | const char *passwd, |
522 | const char *db, |
523 | unsigned int port, |
524 | const char *unix_socket, |
525 | unsigned long clientflag) |
526 | { |
527 | if (force_tls) |
528 | mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, &force_tls); |
529 | if (!mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket, clientflag)) |
530 | { |
531 | diag("error: %s" , mysql_error(mysql)); |
532 | return NULL; |
533 | } |
534 | |
535 | if (mysql && force_tls && !mysql_get_ssl_cipher(mysql)) |
536 | { |
537 | diag("Error: TLS connection not established" ); |
538 | return NULL; |
539 | } |
540 | if (!this_host) |
541 | get_this_host(mysql); |
542 | return mysql; |
543 | } |
544 | |
545 | |
546 | void run_tests(struct my_tests_st *test) { |
547 | int i, rc, total=0; |
548 | MYSQL *mysql, *mysql_default= NULL; /* default connection */ |
549 | |
550 | while (test[total].function) |
551 | total++; |
552 | plan(total); |
553 | |
554 | if ((mysql_default= test_connect(NULL))) |
555 | { |
556 | diag("Testing against MySQL Server %s" , mysql_get_server_info(mysql_default)); |
557 | diag("Host: %s" , mysql_get_host_info(mysql_default)); |
558 | diag("Client library: %s" , mysql_get_client_info()); |
559 | is_mariadb= mariadb_connection(mysql_default); |
560 | } |
561 | else |
562 | { |
563 | BAIL_OUT("Can't connect to a server. Aborting...." ); |
564 | } |
565 | |
566 | for (i=0; i < total; i++) { |
567 | if (!mysql_default && (test[i].connection & TEST_CONNECTION_DEFAULT)) |
568 | { |
569 | diag("MySQL server not running" ); |
570 | skip(1, "%s" , test[i].name); |
571 | } else if (!test[i].skipmsg) { |
572 | mysql= mysql_default; |
573 | if (test[i].connection & TEST_CONNECTION_NEW) |
574 | mysql= test_connect(&test[i]); |
575 | if (test[i].connection & TEST_CONNECTION_NONE) |
576 | mysql= NULL; |
577 | |
578 | /* run test */ |
579 | rc= test[i].function(mysql); |
580 | |
581 | if (rc == SKIP) |
582 | skip(1, "%s" , test[i].name); |
583 | else |
584 | ok(rc == OK, "%s" , test[i].name); |
585 | |
586 | /* if test failed, close and reopen default connection to prevent |
587 | errors for further tests */ |
588 | if ((rc == FAIL || mysql_errno(mysql_default)) && (test[i].connection & TEST_CONNECTION_DEFAULT)) { |
589 | mysql_close(mysql_default); |
590 | mysql_default= test_connect(&test[i]); |
591 | } |
592 | /* clear connection: reset default connection or close extra connection */ |
593 | else if (mysql_default && (test[i].connection & TEST_CONNECTION_DEFAULT)) { |
594 | if (reset_connection(mysql)) |
595 | return; /* default doesn't work anymore */ |
596 | } |
597 | else if (mysql && !(test[i].connection & TEST_CONNECTION_DONT_CLOSE)) |
598 | { |
599 | mysql_close(mysql); |
600 | } |
601 | } else { |
602 | skip(1, "%s" , test[i].skipmsg); |
603 | } |
604 | } |
605 | if (this_host) |
606 | free(this_host); |
607 | |
608 | if (mysql_default) { |
609 | diag("close default" ); |
610 | mysql_close(mysql_default); |
611 | } |
612 | } |
613 | |
614 | |