/* * See the file LICENSE for redistribution information. * * Copyright (c) 1996, 2015 Oracle and/or its affiliates. All rights reserved. * */ /* * This compilation unit contains functions related to the generation * of a simple smoke test for the generated storage layer. */ #include "generation.h" extern int maxbinsz; /* defined in buildpt.c */ static FILE *test_file; /* stream for generated test code */ static int test_indent_level = 0; static void pr_test(char *, ...); static void pr_test_comment(char *, ...); static void callback_function_enter_entop(ENTITY *); static void callback_function_exit_entop(ENTITY *); static void callback_function_attrop(ENTITY *, ATTRIBUTE *, int, int); static void declare_record_instances_enter_entop(ENTITY *); static void initialize_database_enter_entop(ENTITY *); static void initialize_index_enter_entop(DB_INDEX *); static void insertion_test_enter_entop(ENTITY *); static void insertion_test_exit_entop(ENTITY *); static void insertion_test_attrop(ENTITY *, ATTRIBUTE *, int, int); static void retrieval_test_enter_entop(ENTITY *); static void retrieval_test_exit_entop(ENTITY *); static void retrieval_test_attrop(ENTITY *, ATTRIBUTE *, int, int); static void invoke_full_iteration_enter_entop(ENTITY *); static void invoke_full_iteration_exit_entop(ENTITY *e); static void invoke_query_iteration_idxop(DB_INDEX *); static void deletion_test_enter_entop(ENTITY *); static void deletion_test_exit_entop(ENTITY *e); static void close_secondary_test_idxop(DB_INDEX *); static void close_primary_test_enter_entop(ENTITY *); static void remove_secondary_test_idxop(DB_INDEX *); static void remove_primary_test_enter_entop(ENTITY *); /* * Generate_test is the sole entry point in this module. It * orchestrates the generation of the C code for the smoke test. */ void generate_test(tfile, hfilename) FILE *tfile; char *hfilename; { test_file = tfile; pr_test_comment( "Simple test for a Berkeley DB implementation \n\ generated from SQL DDL by db_sql \n\ "); pr_test("\n#include \"%s\"\n\n", hfilename); if (maxbinsz != 0) { pr_test_comment("Test data for raw binary types"); pr_test("#define MAXBINSZ %d\n", maxbinsz); pr_test("char binary_data[MAXBINSZ];\n\n"); pr_test_comment("A very simple binary comparison function"); pr_test( "char * compare_binary(char *p, int len) \n\ { \n\ if (memcmp(p, binary_data, len) == 0) \n\ return \"*binary values match*\"; \n\ return \"*binary values don't match*\"; \n\ } \n\ \n\ "); } pr_test_comment( "These are the iteration callback functions. One is defined per \n\ database(table). They are used for both full iterations and for \n\ secondary index queries. When a retrieval returns multiple records, \n\ as in full iteration over an entire database, one of these functions \n\ is called for each record found"); iterate_over_entities(&callback_function_enter_entop, &callback_function_exit_entop, &callback_function_attrop); pr_test( " \n\ main(int argc, char **argv) \n\ { \n\ int i; \n\ int ret; \n\ \n\ "); test_indent_level++; iterate_over_entities(&declare_record_instances_enter_entop, NULL, NULL); iterate_over_entities(&initialize_database_enter_entop, NULL, NULL); iterate_over_indexes(&initialize_index_enter_entop); pr_test("\n"); if (maxbinsz != 0) { pr_test_comment( "Fill the binary test data with random values"); pr_test( "for (i = 0; i < MAXBINSZ; i++) binary_data[i] = rand();\n\n"); } pr_test_comment( "Use the convenience method to initialize the environment. \n\ The initializations for each entity and environment can be \n\ done discretely if you prefer, but this is the easy way."); pr_test( "ret = initialize_%s_environment(); \n\ if (ret != 0) { \n\ printf(\"Initialize error\"); \n\ goto exit_error; \n\ } \n\ \n\ ", the_schema.environment.name); pr_test_comment( "Now that everything is initialized, insert a single \n\ record into each database, using the ...insert_fields \n\ functions. These functions take each field of the \n\ record as a separate argument"); iterate_over_entities(&insertion_test_enter_entop, &insertion_test_exit_entop, &insertion_test_attrop); pr_test("\n"); pr_test_comment( "Next, retrieve the records just inserted, looking them up \n\ by their key values"); iterate_over_entities(&retrieval_test_enter_entop, &retrieval_test_exit_entop, &retrieval_test_attrop); pr_test("\n"); pr_test_comment( "Now try iterating over every record, using the ...full_iteration \n\ functions for each database. For each record found, the \n\ appropriate ...iteration_callback_test function will be invoked \n\ (these are defined above)."); iterate_over_entities(&invoke_full_iteration_enter_entop, &invoke_full_iteration_exit_entop, NULL); pr_test("\n"); pr_test_comment( "For the secondary indexes, query for the known keys. This also \n\ results in the ...iteration_callback_test function's being called \n\ for each record found."); iterate_over_indexes(&invoke_query_iteration_idxop); pr_test("\n"); pr_test_comment( "Now delete a record from each database using its primary key."); iterate_over_entities(&deletion_test_enter_entop, &deletion_test_exit_entop, NULL); pr_test("\n"); test_indent_level--; pr_test("exit_error:\n"); test_indent_level++; pr_test_comment("Close the secondary index databases"); iterate_over_indexes(&close_secondary_test_idxop); pr_test_comment("Close the primary databases"); iterate_over_entities(&close_primary_test_enter_entop, NULL, NULL); pr_test("\n"); pr_test_comment("Delete the secondary index databases"); iterate_over_indexes(&remove_secondary_test_idxop); pr_test("\n"); pr_test_comment("Delete the primary databases"); iterate_over_entities(&remove_primary_test_enter_entop, NULL, NULL); if (the_schema.environment.transactional) { pr_test( " \n\ if (ret != 0) \n\ %s_txnp->abort(%s_txnp); \n\ else \n\ %s_txnp->commit(%s_txnp, 0); \n\ ", the_schema.environment.name, the_schema.environment.name, the_schema.environment.name, the_schema.environment.name); } pr_test("\n"); pr_test_comment("Finally, close the environment"); pr_test("%s_envp->close(%s_envp, 0);\n", the_schema.environment.name, the_schema.environment.name); pr_test("return ret;\n"); test_indent_level--; pr_test("}\n"); } /* * Emit a printf-formatted string into the test code file. */ static void pr_test(char *fmt, ...) { va_list ap; char *s; static int enable_indent = 1; s = prepare_string(fmt, enable_indent ? test_indent_level : 0, 0); va_start(ap, fmt); vfprintf(test_file, s, ap); va_end(ap); /* * If the last char emitted was a newline, enable indentation * for the next time. */ if (s[strlen(s) - 1] == '\n') enable_indent = 1; else enable_indent = 0; } /* * Emit a formatted comment into the test file. */ static void pr_test_comment(char *fmt, ...) { va_list ap; char *s; s = prepare_string(fmt, test_indent_level, 1); va_start(ap, fmt); vfprintf(test_file, s, ap); va_end(ap); } /* * Return the appropriate printf format string for the given type. */ static char * format_string_for_type(ATTR_TYPE *t) { char *c_type = t->c_type; if (is_array(t)) { return ("%s"); } else if (strcmp(c_type, "char") == 0 || strcmp(c_type, "short") == 0 || strcmp(c_type, "int") == 0) { return ("%d"); } else if (strcmp(c_type, "long") == 0) { return ("%ld"); } else if (strcmp(c_type, "float") == 0) { return ("%f"); } else if (strcmp(c_type, "double") == 0) { return ("%lf"); } else { fprintf(stderr, "Unexpected C type in schema: %s", c_type); assert(0); } return NULL; /*NOTREACHED*/ } /* * Return a data literal appropriate for the given type, to use as test data. */ static char * data_value_for_type(ATTR_TYPE *t) { char *c_type = t->c_type; if (is_string(t)) { /* If the field is really short, use a tiny string */ if (t->array_dim < 12) return ("\"n\""); return ("\"ninety-nine\""); } else if (is_array(t)) { return ("binary_data"); } else if (strcmp(c_type, "char") == 0 || strcmp(c_type, "short") == 0 || strcmp(c_type, "int") == 0 || strcmp(c_type, "long") == 0) { return ("99"); } else if (strcmp(c_type, "float") == 0 || strcmp(c_type, "double") == 0) { return ("99.5"); } else { fprintf(stderr, "Unexpected C type in schema: %s", c_type); assert(0); } return NULL; /*NOTREACHED*/ } /* * This entity operation function is called by the attribute iterator * when producing test code that declares record instances. */ static void declare_record_instances_enter_entop(ENTITY *e) { pr_test("%s_data %s_record;\n", e->name, e->name); } /* * This entity operation function is called by the attribute iterator * when producing test code that initialized database handle. */ static void initialize_database_enter_entop(ENTITY *e) { pr_test("%s_dbp = NULL;\n", e->name); } /* * This entity operation function is called by the attribute iterator * when producing test code that initialized index handle. */ static void initialize_index_enter_entop(DB_INDEX *idx) { pr_test("%s_dbp = NULL;\n", idx->name); } static void invoke_full_iteration_enter_entop(ENTITY *e) { pr_test( "ret = %s_full_iteration(%s_dbp, %s%s&%s_iteration_callback_test, \n\ \"retrieval of %s record through full iteration\");\n", e->name, e->name, e->transactional ? the_schema.environment.name : "", e->transactional ? "_txnp, " : "", e->name, e->name); } static void invoke_full_iteration_exit_entop(ENTITY *e) { COMPQUIET(e, NULL); pr_test( "if (ret != 0) { \n\ printf(\"Full Iteration Error\\n\"); \n\ goto exit_error; \n\ } \n\ \n\ "); } /* * This index operation function is called by the attribute iterator * when producing test code that creates the secondary databases. */ static void invoke_query_iteration_idxop(DB_INDEX *idx) { ATTR_TYPE *key_type = idx->attribute->type; pr_test("%s_query_iteration(%s_dbp, %s%s", idx->name, idx->name, idx->primary->transactional ? the_schema.environment.name : "", idx->primary->transactional ? "_txnp, " : ""); pr_test("%s", data_value_for_type(key_type)); pr_test( ",\n &%s_iteration_callback_test, \n\ \"retrieval of %s record through %s query\");\n", idx->primary->name, idx->primary->name, idx->name); } /* * This next group of entity and attribute operation functions are * called by the attribute iterator when generating insertion test code. */ static void insertion_test_enter_entop(ENTITY *e) { pr_test("ret = %s_insert_fields(%s_dbp, %s%s", e->name, e->name, e->transactional ? the_schema.environment.name : "", e->transactional ? "_txnp, " : ""); } static void insertion_test_exit_entop(ENTITY *e) { COMPQUIET(e, NULL); pr_test(");\n"); pr_test( "if (ret != 0) { \n\ printf(\"Insert error\\n\"); \n\ goto exit_error; \n\ } \n\ \n\ "); } static void insertion_test_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last) { COMPQUIET(e, NULL); COMPQUIET(first, 0); pr_test("%s", data_value_for_type(a->type)); if (!last) pr_test(", "); } /* * This next group of index and attribute operation functions are * called by the attribute iterator when generating the iteration * callback function. */ static void callback_function_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last) { COMPQUIET(first, 0); COMPQUIET(last, 0); pr_test("printf(\"%s->%s: ", e->name, a->name); pr_test("%s", format_string_for_type(a->type)); if (is_array(a->type) && !is_string(a->type)) { pr_test("\\n\", compare_binary(%s_record->%s, %s));\n", e->name, a->name, array_dim_name(e, a) ); } else { pr_test("\\n\", %s_record->%s);\n", e->name, a->name); } } static void callback_function_enter_entop(ENTITY *e) { pr_test( " \n\ void %s_iteration_callback_test(void *msg, %s_data *%s_record) \n\ { \n\ printf(\"In iteration callback, message is: %%s\\n\", (char *)msg);\n\n", e->name, e->name, e->name); test_indent_level++; } static void callback_function_exit_entop(ENTITY *e) { COMPQUIET(e, NULL); test_indent_level--; pr_test("}\n\n"); } /* * This next group of entity and attribute operation functions are * called by the attribute iterator when generating retrieval test code */ static void retrieval_test_enter_entop(ENTITY *e) { pr_test("\nprintf(\"Retrieval of %s record by key\\n\");\n", e->name); pr_test("ret = get_%s_data( %s_dbp, %s%s%s, &%s_record);\n\n", e->name, e->name, e->transactional ? the_schema.environment.name : "", e->transactional ? "_txnp, " : "", data_value_for_type(e->primary_key->type), e->name); } static void retrieval_test_exit_entop(ENTITY *e) { COMPQUIET(e, NULL); pr_test( "if (ret != 0) \n\ { \n\ printf(\"Retrieve error\\n\"); \n\ goto exit_error; \n\ } \n\ \n\ "); } static void retrieval_test_attrop(ENTITY *e, ATTRIBUTE *a, int first, int last) { COMPQUIET(first, 0); COMPQUIET(last, 0); pr_test("printf(\"%s.%s: ", e->name, a->name); pr_test("%s", format_string_for_type(a->type)); if (is_array(a->type) && !is_string(a->type)) { pr_test("\\n\", compare_binary(%s_record.%s, %s));\n", e->name, a->name, array_dim_name(e, a) ); } else { pr_test("\\n\", %s_record.%s);\n", e->name, a->name); } } /* * This entity operation function is * called by the attribute iterator when generating deletion test code. */ static void deletion_test_enter_entop(ENTITY *e) { pr_test("ret = delete_%s_key( %s_dbp, %s%s%s);\n", e->name, e->name, e->transactional ? the_schema.environment.name : "", e->transactional ? "_txnp, " : "", data_value_for_type(e->primary_key->type)); } static void deletion_test_exit_entop(ENTITY *e) { COMPQUIET(e, NULL); pr_test( "if (ret != 0) { \n\ printf(\"Delete error\\n\"); \n\ goto exit_error; \n\ } \n\ \n\ "); } /* This entity operation function generates primary database closures. */ static void close_primary_test_enter_entop(ENTITY *e) { pr_test( "if (%s_dbp != NULL) \n\ %s_dbp->close(%s_dbp, 0); \n\ \n\ ", e->name, e->name, e->name); } /* This entity operation function generates secondary database closures. */ static void close_secondary_test_idxop(DB_INDEX *idx) { pr_test( "if (%s_dbp != NULL) \n\ %s_dbp->close(%s_dbp, 0); \n\ \n\ ", idx->name, idx->name, idx->name); } /* This entity operation function generates primary database closures. */ static void remove_primary_test_enter_entop(ENTITY *e) { pr_test("remove_%s_database(%s_envp%s%s%s);\n", e->name, the_schema.environment.name, e->transactional ? ", " : "", e->transactional ? the_schema.environment.name : "", e->transactional ? "_txnp" : ""); } /* This entity operation function generates secondary database closures. */ static void remove_secondary_test_idxop(DB_INDEX *idx) { pr_test("remove_%s_index(%s_envp%s%s%s);\n", idx->name, the_schema.environment.name, idx->primary->transactional ? ", " : "", idx->primary->transactional ? the_schema.environment.name : "", idx->primary->transactional ? "_txnp" : ""); }