Skip to content

Commit

Permalink
broker/boot_config: add unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
garlick committed Dec 19, 2019
1 parent 44ff9e3 commit b146d77
Show file tree
Hide file tree
Showing 2 changed files with 368 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/broker/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ TESTS = test_shutdown.t \
test_service.t \
test_reduce.t \
test_liblist.t \
test_pmiutil.t
test_pmiutil.t \
test_boot_config.t

test_ldadd = \
$(builddir)/libbroker.la \
Expand Down Expand Up @@ -127,3 +128,7 @@ test_liblist_t_LDADD = $(test_ldadd)
test_pmiutil_t_SOURCES = test/pmiutil.c
test_pmiutil_t_CPPFLAGS = $(test_cppflags)
test_pmiutil_t_LDADD = $(test_ldadd)

test_boot_config_t_SOURCES = test/boot_config.c
test_boot_config_t_CPPFLAGS = $(test_cppflags)
test_boot_config_t_LDADD = $(test_ldadd)
362 changes: 362 additions & 0 deletions src/broker/test/boot_config.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
/************************************************************\
* Copyright 2019 Lawrence Livermore National Security, LLC
* (c.f. AUTHORS, NOTICE.LLNS, COPYING)
*
* This file is part of the Flux resource manager framework.
* For details, see https://github.com/flux-framework.
*
* SPDX-License-Identifier: LGPL-3.0
\************************************************************/

#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/param.h>
#include <unistd.h>
#include <jansson.h>
#include <flux/core.h>

#include "src/common/libtap/tap.h"
#include "src/broker/boot_config.h"


static void
create_test_file (const char *dir, char *prefix, char *path, size_t pathlen,
const char *contents)
{
int fd;
if (snprintf (path,
pathlen,
"%s/%s.XXXXXX.toml",
dir ? dir : "/tmp",
prefix) >= pathlen)
BAIL_OUT ("snprintf overflow");
fd = mkstemps (path, 5);
if (fd < 0)
BAIL_OUT ("mkstemp %s: %s", path, strerror (errno));
if (write (fd, contents, strlen (contents)) != strlen (contents))
BAIL_OUT ("write %s: %s", path, strerror (errno));
if (close (fd) < 0)
BAIL_OUT ("close %s: %s", path, strerror (errno));
}

static void
create_test_dir (char *dir, int dirlen)
{
const char *tmpdir = getenv ("TMPDIR");
if (snprintf (dir,
dirlen,
"%s/cf.XXXXXXX",
tmpdir ? tmpdir : "/tmp") >= dirlen)
BAIL_OUT ("snprintf overflow");
if (!mkdtemp (dir))
BAIL_OUT ("mkdtemp %s: %s", dir, strerror (errno));
setenv ("FLUX_CONF_DIR", dir, 1);
}

static char *t1 = \
"[bootstrap]\n" \
"default_port = 42\n" \
"default_bind = \"tcp://en0:%p\"\n" \
"default_connect = \"tcp://x%h:%p\"\n" \
"hosts = [\n" \
" { host = \"foo0\" },\n" \
" { host = \"foo[1-62]\" },\n" \
" { host = \"foo63\" },\n" \
"]\n";

void test_parse (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
json_t *hosts;
struct boot_conf conf;
uint32_t rank;
char uri[MAX_URI + 1];

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

create_test_file (dir, "boot", path, sizeof (path), t1);
hosts = boot_config_parse (h, &conf);
ok (hosts != NULL,
"boot_conf_parse worked");
ok (json_array_size (hosts) == 64,
"got 64 hosts");

ok (conf.default_port == 42,
"set default_port correctly");
ok (!strcmp (conf.default_bind, "tcp://en0:42"),
"and set default_bind correctly (with %%p substitution)");
ok (!strcmp (conf.default_connect, "tcp://x%h:42"),
"and set default_connect correctly (with %%p substitution)");

ok (boot_config_getrankbyname (hosts, "foo0", &rank) == 0
&& rank == 0,
"boot_config_getrankbyname found rank 0");
ok (boot_config_getrankbyname (hosts, "foo1", &rank) == 0
&& rank == 1,
"boot_config_getrankbyname found rank 1");
ok (boot_config_getrankbyname (hosts, "foo42", &rank) == 0
&& rank == 42,
"boot_config_getrankbyname found rank 42");
ok (boot_config_getrankbyname (hosts, "notfound", &rank) < 0,
"boot_config_getrankbyname fails on unknown entry");

ok (boot_config_getbindbyrank (hosts, &conf, 0, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://en0:42"),
"boot_config_getbindbyrank 0 works with expected value");
ok (boot_config_getbindbyrank (hosts, &conf, 1, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://en0:42"),
"boot_config_getbindbyrank 1 works with expected value");
ok (boot_config_getbindbyrank (hosts, &conf, 63, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://en0:42"),
"boot_config_getbindbyrank 63 works with expected value");
ok (boot_config_getbindbyrank (hosts, &conf, 64, uri, sizeof (uri)) < 0,
"boot_config_getbindbyrank 64 fails");

ok (boot_config_geturibyrank (hosts, &conf, 0, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://xfoo0:42"),
"boot_config_geturibyrank 0 works with expected value");
ok (boot_config_geturibyrank (hosts, &conf, 1, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://xfoo1:42"),
"boot_config_geturibyrank 1 works with expected value");
ok (boot_config_geturibyrank (hosts, &conf, 63, uri, sizeof (uri)) == 0
&& !strcmp (uri, "tcp://xfoo63:42"),
"boot_config_geturibyrank 63 works with expected value");
ok (boot_config_geturibyrank (hosts, &conf, 64, uri, sizeof (uri)) < 0,
"boot_config_geturibyrank 64 fails");

json_decref (hosts);

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

void test_overflow_bind (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
struct boot_conf conf;
char t[MAX_URI*2];

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

if (snprintf (t,
sizeof (t),
"[bootstrap]\ndefault_bind=\"%*s\"\nhosts=[\"foo\"]\n",
MAX_URI+2, "foo") >= sizeof (t))
BAIL_OUT ("snprintf overflow");
create_test_file (dir, "boot", path, sizeof (path), t);

ok (boot_config_parse (h, &conf) == NULL,
"boot_conf_parse caught default_bind overflow");

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

void test_overflow_connect (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
struct boot_conf conf;
char t[MAX_URI*2];

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

if (snprintf (t,
sizeof (t),
"[bootstrap]\ndefault_connect=\"%*s\"\nhosts=[\"foo\"]\n",
MAX_URI+2, "foo") >= sizeof (t))
BAIL_OUT ("snprintf overflow");
create_test_file (dir, "boot", path, sizeof (path), t);

ok (boot_config_parse (h, &conf) == NULL,
"boot_conf_parse caught default_connect overflow");

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

static char *t2 = \
"[bootstrap]\n" \
"hosts = [\n" \
" 42,\n" \
"]\n";

void test_bad_hosts_entry (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
struct boot_conf conf;

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

create_test_file (dir, "boot", path, sizeof (path), t2);

if ((boot_config_parse (h, &conf) != NULL))
BAIL_OUT ("boot_config_parse unexpectedly succeded");

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

static char *t3 = \
"[bootstrap]\n" \
"hosts = [\n" \
" { host = \"foo\" },\n" \
"]\n";

void test_missing_info (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
json_t *hosts;
struct boot_conf conf;
char uri[MAX_URI + 1];
uint32_t rank;

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

create_test_file (dir, "boot", path, sizeof (path), t3);

if (!(hosts = boot_config_parse (h, &conf)))
BAIL_OUT ("boot_config_parse unexpectedly failed");
ok (boot_config_getrankbyname (hosts, "foo", &rank) == 0
&& rank == 0,
"boot_config_getrankbyname found entry");
ok (boot_config_getbindbyrank (hosts, &conf, 0, uri, sizeof(uri)) < 0,
"boot_config_getbindbyrank fails due to missing bind uri");
ok (boot_config_geturibyrank (hosts, &conf, 0, uri, sizeof(uri)) < 0,
"boot_config_geturibyrank fails due to missing connect uri");

json_decref (hosts);

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

static char *t4 = \
"[bootstrap]\n" \
"hosts = [\n" \
" \"bar\",\n" \
" { host = \"foo\" },\n" \
"]\n";

/* Just double check that an array with mismatched types
* fails early with the expected libtomlc99 error.
*/
void test_toml_mixed_array (const char *dir)
{
char path[PATH_MAX + 1];
flux_t *h;
const flux_conf_t *conf;
flux_conf_error_t error;

if (!(h = flux_open ("loop://", 0)))
BAIL_OUT ("can't continue without loop handle");

create_test_file (dir, "boot", path, sizeof (path), t4);

conf = flux_get_conf (h, &error);
ok (conf == NULL && (strstr (error.errbuf, "array type mismatch")
|| strstr (error.errbuf, "string array can only contain strings")),
"Mixed type hosts array fails with reasonable error");
diag ("%s: line %d: %s", error.filename, error.lineno, error.errbuf);

if (unlink (path) < 0)
BAIL_OUT ("could not cleanup test file %s", path);

flux_close (h);
}

void test_format (void)
{
char buf[MAX_URI + 1];

ok (boot_config_format_uri (buf, sizeof (buf), "abcd", NULL, 0) == 0
&& !strcmp (buf, "abcd"),
"format: plain string copy works");
ok (boot_config_format_uri (buf, sizeof (buf), "abcd:%p", NULL, 42) == 0
&& !strcmp (buf, "abcd:42"),
"format: %%p substitution works end string");
ok (boot_config_format_uri (buf, sizeof (buf), "a%pb", NULL, 42) == 0
&& !strcmp (buf, "a42b"),
"format: %%p substitution works mid string");
ok (boot_config_format_uri (buf, sizeof (buf), "%p:abcd", NULL, 42) == 0
&& !strcmp (buf, "42:abcd"),
"format: %%p substitution works begin string");
ok (boot_config_format_uri (buf, sizeof (buf), "%h", NULL, 0) == 0
&& !strcmp (buf, "%h"),
"format: %%h passes through when host=NULL");
ok (boot_config_format_uri (buf, sizeof (buf), "%h", "foo", 0) == 0
&& !strcmp (buf, "foo"),
"format: %%h substitution works");
ok (boot_config_format_uri (buf, sizeof (buf), "%%", NULL, 0) == 0
&& !strcmp (buf, "%"),
"format: %%%% literal works");
ok (boot_config_format_uri (buf, sizeof (buf), "a%X", NULL, 0) == 0
&& !strcmp (buf, "a%X"),
"format: unknown token passes through");

ok (boot_config_format_uri (buf, 5, "abcd", NULL, 0) == 0
&& !strcmp (buf, "abcd"),
"format: copy abcd to buf[5] works");
ok (boot_config_format_uri (buf, 4, "abcd", NULL, 0) < 0,
"format: copy abcd to buf[4] fails");

ok (boot_config_format_uri (buf, 5, "a%p", NULL, 123) == 0
&& !strcmp (buf, "a123"),
"format: %%p substitution into exact size buf works");
ok (boot_config_format_uri (buf, 4, "a%p", NULL, 123) < 0,
"format: %%p substitution overflow detected");

ok (boot_config_format_uri (buf, 5, "a%h", "abc", 0) == 0
&& !strcmp (buf, "aabc"),
"format: %%h substitution into exact size buf works");
ok (boot_config_format_uri (buf, 4, "a%h", "abc", 0) < 0,
"format: %%h substitution overflow detected");
}

int main (int argc, char **argv)
{
char dir[PATH_MAX + 1];

plan (NO_PLAN);

test_format ();

create_test_dir (dir, sizeof (dir));

test_parse (dir);
test_overflow_bind (dir);
test_overflow_connect (dir);
test_bad_hosts_entry (dir);
test_missing_info (dir);
test_toml_mixed_array (dir);

if (rmdir (dir) < 0)
BAIL_OUT ("could not cleanup test dir %s", dir);

done_testing ();
return 0;
}

/*
* vi:ts=4 sw=4 expandtab
*/

0 comments on commit b146d77

Please sign in to comment.