1/*
2Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
3
4The 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
6MySQL Connectors. There are special exceptions to the terms and
7conditions of the GPLv2 as it is applied to this software, see the
8FLOSS License Exception
9<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
10
11This program is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published
13by the Free Software Foundation; version 2 of the License.
14
15This program is distributed in the hope that it will be useful, but
16WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18for more details.
19
20You should have received a copy of the GNU General Public License along
21with this program; if not, write to the Free Software Foundation, Inc.,
2251 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) \
67if (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) \
75if (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)\
82if (expr)\
83{\
84 diag("Error: %s (%s: %d)", (reason) ? reason : "", __FILE__, __LINE__);\
85 return FAIL;\
86}
87
88#define FAIL_UNLESS(expr, reason)\
89if (!(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
108struct my_option_st
109{
110 enum mysql_option option;
111 char *value;
112};
113
114struct 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
124MYSQL *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
133static const char *schema = 0;
134static char *hostname = 0;
135static char *password = 0;
136static unsigned int port = 0;
137static char *socketname = 0;
138static char *username = 0;
139static int force_tls= 0;
140static uchar is_mariadb= 0;
141static char *this_host= 0;
142static unsigned char travis_test= 0;
143/*
144static 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
177int 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
225void 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
243int 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/*
265static my_bool
266get_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
281int 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
310error:
311 mysql_free_result(result);
312 return FAIL;
313}
314
315my_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
342static 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
355void 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
395int 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 */
421MYSQL *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
458static 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 */
477void 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
518MYSQL *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
546void 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