Files
mongo/test/readonly/readonly.c
Alex Gorrod 9768c2db0b WT-2554 Add initial C test framework. (#2655)
* WT-2554 Add initial C test framework.

With a few sample applications. Need to do further work to cleanup
the same applications and share as much code/style as possible.

* WT-2554 Fixup build errors.

* Fix compiler warning.

* Cleanup

* Ensure each test runs in a different directory.

* Update C test suite names and add copyright.

* Move test code into subdirectories.

* Replace test_util.i with a real utility library now that we have one.

Nothing needs to be an inline function.

Mostly cherry picked from 7c18420.

* Use WiredTiger getopt in C test suite for platform portability.

* Add a header comment to each test case.

* Add a C test suite entry auto generator

* Style, KNF

* Implement review feedback.

* Build test/utility library on Windows

* Add comment to script.  Fix a printf.
2016-05-23 13:13:54 -04:00

400 lines
12 KiB
C

/*-
* Public Domain 2014-2016 MongoDB, Inc.
* Public Domain 2008-2014 WiredTiger, Inc.
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "test_util.h"
#include <sys/wait.h>
#define HOME_SIZE 512
static char home[HOME_SIZE]; /* Program working dir lock file */
#define HOME_WR_SUFFIX ".WRNOLOCK" /* Writable dir copy no lock file */
static char home_wr[HOME_SIZE + sizeof(HOME_WR_SUFFIX)];
#define HOME_RD_SUFFIX ".RD" /* Read-only dir */
static char home_rd[HOME_SIZE + sizeof(HOME_RD_SUFFIX)];
#define HOME_RD2_SUFFIX ".RDNOLOCK" /* Read-only dir no lock file */
static char home_rd2[HOME_SIZE + sizeof(HOME_RD2_SUFFIX)];
static const char *progname; /* Program name */
static const char *saved_argv0; /* Program command */
static const char * const uri = "table:main";
#define ENV_CONFIG \
"create,log=(file_max=10M,archive=false,enabled)," \
"transaction_sync=(enabled,method=none)"
#define ENV_CONFIG_RD "readonly=true"
#define ENV_CONFIG_WR "readonly=false"
#define MAX_VAL 4096
#define MAX_KV 10000
#define EXPECT_ERR 1
#define EXPECT_SUCCESS 0
#define OP_READ 0
#define OP_WRITE 1
static void
usage(void)
{
fprintf(stderr, "usage: %s [-h dir]\n", progname);
exit(EXIT_FAILURE);
}
static int
run_child(const char *homedir, int op, int expect)
{
WT_CONNECTION *conn;
WT_CURSOR *cursor;
WT_SESSION *session;
int i, ret;
const char *cfg;
/*
* We expect the read-only database will allow the second read-only
* handle to succeed because no one can create or set the lock file.
*/
if (op == OP_READ)
cfg = ENV_CONFIG_RD;
else
cfg = ENV_CONFIG_WR;
if ((ret = wiredtiger_open(homedir, NULL, cfg, &conn)) == 0) {
if (expect == EXPECT_ERR)
testutil_die(
ret, "wiredtiger_open expected error, succeeded");
} else {
if (expect == EXPECT_SUCCESS)
testutil_die(
ret, "wiredtiger_open expected success, error");
/*
* If we expect an error and got one, we're done.
*/
return (0);
}
/*
* Make sure we can read the data.
*/
if ((ret = conn->open_session(conn, NULL, NULL, &session)) != 0)
testutil_die(ret, "WT_CONNECTION:open_session");
if ((ret =
session->open_cursor(session, uri, NULL, NULL, &cursor)) != 0)
testutil_die(ret, "WT_SESSION.open_cursor: %s", uri);
i = 0;
while ((ret = cursor->next(cursor)) == 0)
++i;
if (i != MAX_KV)
testutil_die(EPERM, "cursor walk");
if ((ret = conn->close(conn, NULL)) != 0)
testutil_die(ret, "conn_close");
return (0);
}
/*
* Child process opens both databases readonly.
*/
static void
open_dbs(int op, const char *dir,
const char *dir_wr, const char *dir_rd, const char *dir_rd2)
{
int expect, ret;
/*
* The parent has an open connection to all directories.
* We expect opening the writeable homes to return an error.
* It is a failure if the child successfully opens that.
*/
expect = EXPECT_ERR;
if ((ret = run_child(dir, op, expect)) != 0)
testutil_die(ret, "wiredtiger_open readonly allowed");
if ((ret = run_child(dir_wr, op, expect)) != 0)
testutil_die(ret, "wiredtiger_open readonly allowed");
/*
* The parent must have a read-only connection open to the
* read-only databases. If the child is opening read-only
* too, we expect success. Otherwise an error if the child
* attempts to open read/write (permission error).
*/
if (op == OP_READ)
expect = EXPECT_SUCCESS;
if ((ret = run_child(dir_rd, op, expect)) != 0)
testutil_die(ret, "run child 1");
if ((ret = run_child(dir_rd2, op, expect)) != 0)
testutil_die(ret, "run child 2");
exit(EXIT_SUCCESS);
}
extern int __wt_optind;
extern char *__wt_optarg;
void (*custom_die)(void) = NULL;
int
main(int argc, char *argv[])
{
WT_CONNECTION *conn, *conn2, *conn3, *conn4;
WT_CURSOR *cursor;
WT_ITEM data;
WT_SESSION *session;
uint64_t i;
int ch, status, op, ret;
bool child;
const char *working_dir;
char cmd[512];
uint8_t buf[MAX_VAL];
if ((progname = strrchr(argv[0], DIR_DELIM)) == NULL)
progname = argv[0];
else
++progname;
/*
* Needed unaltered for system command later.
*/
saved_argv0 = argv[0];
working_dir = "WT_RD";
child = false;
op = OP_READ;
while ((ch = __wt_getopt(progname, argc, argv, "Rh:W")) != EOF)
switch (ch) {
case 'R':
child = true;
op = OP_READ;
break;
case 'W':
child = true;
op = OP_WRITE;
break;
case 'h':
working_dir = __wt_optarg;
break;
default:
usage();
}
argc -= __wt_optind;
argv += __wt_optind;
if (argc != 0)
usage();
/*
* Set up all the directory names.
*/
testutil_work_dir_from_path(home, sizeof(home), working_dir);
(void)snprintf(home_wr, sizeof(home_wr), "%s%s", home, HOME_WR_SUFFIX);
(void)snprintf(home_rd, sizeof(home_rd), "%s%s", home, HOME_RD_SUFFIX);
(void)snprintf(
home_rd2, sizeof(home_rd2), "%s%s", home, HOME_RD2_SUFFIX);
if (!child) {
testutil_make_work_dir(home);
testutil_make_work_dir(home_wr);
testutil_make_work_dir(home_rd);
testutil_make_work_dir(home_rd2);
} else
/*
* We are a child process, we just want to call
* the open_dbs with the directories we have.
* The child function will exit.
*/
open_dbs(op, home, home_wr, home_rd, home_rd2);
/*
* Parent creates a database and table. Then cleanly shuts down.
* Then copy database to read-only directory and chmod.
* Also copy database to read-only directory and remove the lock
* file. One read-only database will have a lock file in the
* file system and the other will not.
* Parent opens all databases with read-only configuration flag.
* Parent forks off child who tries to also open all databases
* with the read-only flag. It should error on the writeable
* directory, but allow it on the read-only directories.
* The child then confirms it can read all the data.
*/
/*
* Run in the home directory and create the table.
*/
if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG, &conn)) != 0)
testutil_die(ret, "wiredtiger_open");
if ((ret = conn->open_session(conn, NULL, NULL, &session)) != 0)
testutil_die(ret, "WT_CONNECTION:open_session");
if ((ret = session->create(session,
uri, "key_format=Q,value_format=u")) != 0)
testutil_die(ret, "WT_SESSION.create: %s", uri);
if ((ret =
session->open_cursor(session, uri, NULL, NULL, &cursor)) != 0)
testutil_die(ret, "WT_SESSION.open_cursor: %s", uri);
/*
* Write data into the table and then cleanly shut down connection.
*/
memset(buf, 0, sizeof(buf));
data.data = buf;
data.size = MAX_VAL;
for (i = 0; i < MAX_KV; ++i) {
cursor->set_key(cursor, i);
cursor->set_value(cursor, &data);
if ((ret = cursor->insert(cursor)) != 0)
testutil_die(ret, "WT_CURSOR.insert");
}
if ((ret = conn->close(conn, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
/*
* Copy the database. Remove any lock file from one copy
* and chmod the copies to be read-only permissions.
*/
(void)snprintf(cmd, sizeof(cmd),
"cp -rp %s/* %s; rm -f %s/WiredTiger.lock",
home, home_wr, home_wr);
(void)system(cmd);
(void)snprintf(cmd, sizeof(cmd),
"cp -rp %s/* %s; chmod 0555 %s; chmod -R 0444 %s/*",
home, home_rd, home_rd, home_rd);
(void)system(cmd);
(void)snprintf(cmd, sizeof(cmd),
"cp -rp %s/* %s; rm -f %s/WiredTiger.lock; "
"chmod 0555 %s; chmod -R 0444 %s/*",
home, home_rd2, home_rd2, home_rd2, home_rd2);
(void)system(cmd);
/*
* Run four scenarios. Sometimes expect errors, sometimes success.
* The writable database directories should always fail to allow the
* child to open due to the lock file. The read-only ones will only
* succeed when the child attempts read-only.
*
* 1. Parent has read-only handle to all databases. Child opens
* read-only also.
* 2. Parent has read-only handle to all databases. Child opens
* read-write.
* 3. Parent has read-write handle to writable databases and
* read-only to read-only databases. Child opens read-only.
* 4. Parent has read-write handle to writable databases and
* read-only to read-only databases. Child opens read-write.
*/
/*
* Open a connection handle to all databases.
*/
fprintf(stderr, " *** Expect several error messages from WT ***\n");
/*
* Scenario 1.
*/
if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0)
testutil_die(ret, "wiredtiger_open original home");
if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0)
testutil_die(ret, "wiredtiger_open write nolock");
if ((ret = wiredtiger_open(home_rd, NULL, ENV_CONFIG_RD, &conn3)) != 0)
testutil_die(ret, "wiredtiger_open readonly");
if ((ret = wiredtiger_open(home_rd2, NULL, ENV_CONFIG_RD, &conn4)) != 0)
testutil_die(ret, "wiredtiger_open readonly nolock");
/*
* Create a child to also open a connection handle to the databases.
* We cannot use fork here because using fork the child inherits the
* same memory image. Therefore the WT process structure is set in
* the child even though it should not be. So use 'system' to spawn
* an entirely new process.
*/
(void)snprintf(
cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir);
if ((status = system(cmd)) < 0)
testutil_die(status, "system");
/*
* The child will exit with success if its test passes.
*/
if (WEXITSTATUS(status) != 0)
testutil_die(WEXITSTATUS(status), "system");
/*
* Scenario 2. Run child with writable config.
*/
(void)snprintf(
cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir);
if ((status = system(cmd)) < 0)
testutil_die(status, "system");
if (WEXITSTATUS(status) != 0)
testutil_die(WEXITSTATUS(status), "system");
/*
* Reopen the two writable directories and rerun the child.
*/
if ((ret = conn->close(conn, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
if ((ret = conn2->close(conn2, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
if ((ret = wiredtiger_open(home, NULL, ENV_CONFIG_RD, &conn)) != 0)
testutil_die(ret, "wiredtiger_open original home");
if ((ret = wiredtiger_open(home_wr, NULL, ENV_CONFIG_RD, &conn2)) != 0)
testutil_die(ret, "wiredtiger_open write nolock");
/*
* Scenario 3. Child read-only.
*/
(void)snprintf(
cmd, sizeof(cmd), "%s -h %s -R", saved_argv0, working_dir);
if ((status = system(cmd)) < 0)
testutil_die(status, "system");
if (WEXITSTATUS(status) != 0)
testutil_die(WEXITSTATUS(status), "system");
/*
* Scenario 4. Run child with writable config.
*/
(void)snprintf(
cmd, sizeof(cmd), "%s -h %s -W", saved_argv0, working_dir);
if ((status = system(cmd)) < 0)
testutil_die(status, "system");
if (WEXITSTATUS(status) != 0)
testutil_die(WEXITSTATUS(status), "system");
/*
* Clean-up.
*/
if ((ret = conn->close(conn, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
if ((ret = conn2->close(conn2, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
if ((ret = conn3->close(conn3, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
if ((ret = conn4->close(conn4, NULL)) != 0)
testutil_die(ret, "WT_CONNECTION:close");
/*
* We need to chmod the read-only databases back so that they can
* be removed by scripts.
*/
(void)snprintf(cmd, sizeof(cmd), "chmod 0777 %s %s", home_rd, home_rd2);
(void)system(cmd);
(void)snprintf(cmd, sizeof(cmd), "chmod -R 0666 %s/* %s/*",
home_rd, home_rd2);
(void)system(cmd);
printf(" *** Readonly test successful ***\n");
return (EXIT_SUCCESS);
}