/* Set BROKEN to 1 to treat broken behavior as success */
#define BROKEN 1
#define VERBOSITY 2
#include <stdbool.h>
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdlib.h>
#include <cmocka.h>
#include <libudev.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <sys/sysmacros.h>
#include "structs.h"
#include "structs_vec.h"
#include "config.h"
#include "debug.h"
#include "defaults.h"
#include "pgpolicies.h"
#include "test-lib.h"
#include "print.h"
#include "util.h"
#define N_CONF_FILES 2
static const char tmplate[] = "/tmp/hwtable-XXXXXX";
/* pretend new dm, use minio_rq */
static const unsigned int dm_tgt_version[3] = { 1, 1, 1 };
struct key_value {
const char *key;
const char *value;
};
struct hwt_state {
char *tmpname;
char *dirname;
FILE *config_file;
FILE *conf_dir_file[N_CONF_FILES];
struct vectors *vecs;
void (*test)(const struct hwt_state *);
const char *test_name;
};
#define SET_TEST_FUNC(hwt, func) do { \
hwt->test = func; \
hwt->test_name = #func; \
} while (0)
static struct config *_conf;
struct udev *udev;
int logsink = -1;
struct config *get_multipath_config(void)
{
return _conf;
}
void put_multipath_config(void *arg)
{}
void make_config_file_path(char *buf, int buflen,
const struct hwt_state *hwt, int i)
{
static const char fn_template[] = "%s/test-%02d.conf";
if (i == -1)
/* main config file */
snprintf(buf, buflen, fn_template, hwt->tmpname, 0);
else
snprintf(buf, buflen, fn_template, hwt->dirname, i);
}
static void reset_vecs(struct vectors *vecs)
{
remove_maps(vecs);
free_pathvec(vecs->pathvec, FREE_PATHS);
vecs->pathvec = vector_alloc();
assert_ptr_not_equal(vecs->pathvec, NULL);
vecs->mpvec = vector_alloc();
assert_ptr_not_equal(vecs->mpvec, NULL);
}
static void free_hwt(struct hwt_state *hwt)
{
char buf[PATH_MAX];
int i;
if (hwt->config_file != NULL)
fclose(hwt->config_file);
for (i = 0; i < N_CONF_FILES; i++) {
if (hwt->conf_dir_file[i] != NULL)
fclose(hwt->conf_dir_file[i]);
}
if (hwt->tmpname != NULL) {
make_config_file_path(buf, sizeof(buf), hwt, -1);
unlink(buf);
rmdir(hwt->tmpname);
free(hwt->tmpname);
}
if (hwt->dirname != NULL) {
for (i = 0; i < N_CONF_FILES; i++) {
make_config_file_path(buf, sizeof(buf), hwt, i);
unlink(buf);
}
rmdir(hwt->dirname);
free(hwt->dirname);
}
if (hwt->vecs != NULL) {
if (hwt->vecs->mpvec != NULL)
remove_maps(hwt->vecs);
if (hwt->vecs->pathvec != NULL)
free_pathvec(hwt->vecs->pathvec, FREE_PATHS);
pthread_mutex_destroy(&hwt->vecs->lock.mutex);
free(hwt->vecs);
}
free(hwt);
}
static int setup(void **state)
{
struct hwt_state *hwt;
char buf[PATH_MAX];
int i;
*state = NULL;
hwt = calloc(1, sizeof(*hwt));
if (hwt == NULL)
return -1;
snprintf(buf, sizeof(buf), "%s", tmplate);
if (mkdtemp(buf) == NULL) {
condlog(0, "mkdtemp: %s", strerror(errno));
goto err;
}
hwt->tmpname = strdup(buf);
snprintf(buf, sizeof(buf), "%s", tmplate);
if (mkdtemp(buf) == NULL) {
condlog(0, "mkdtemp (2): %s", strerror(errno));
goto err;
}
hwt->dirname = strdup(buf);
make_config_file_path(buf, sizeof(buf), hwt, -1);
hwt->config_file = fopen(buf, "w+");
if (hwt->config_file == NULL)
goto err;
for (i = 0; i < N_CONF_FILES; i++) {
make_config_file_path(buf, sizeof(buf), hwt, i);
hwt->conf_dir_file[i] = fopen(buf, "w+");
if (hwt->conf_dir_file[i] == NULL)
goto err;
}
hwt->vecs = calloc(1, sizeof(*hwt->vecs));
if (hwt->vecs == NULL)
goto err;
pthread_mutex_init(&hwt->vecs->lock.mutex, NULL);
hwt->vecs->pathvec = vector_alloc();
hwt->vecs->mpvec = vector_alloc();
if (hwt->vecs->pathvec == NULL || hwt->vecs->mpvec == NULL)
goto err;
*state = hwt;
return 0;
err:
free_hwt(hwt);
return -1;
}
static int teardown(void **state)
{
if (state == NULL || *state == NULL)
return -1;
free_hwt(*state);
*state = NULL;
return 0;
}
/*
* Helpers for creating the config file(s)
*/
static void reset_config(FILE *ff)
{
if (ff == NULL)
return;
rewind(ff);
if (ftruncate(fileno(ff), 0) == -1)
condlog(1, "ftruncate: %s", strerror(errno));
}
static void reset_configs(const struct hwt_state *hwt)
{
int i;
reset_config(hwt->config_file);
for (i = 0; i < N_CONF_FILES; i++)
reset_config(hwt->conf_dir_file[i]);
}
static void write_key_values(FILE *ff, int nkv, const struct key_value *kv)
{
int i;
for (i = 0; i < nkv; i++) {
if (strchr(kv[i].value, ' ') == NULL &&
strchr(kv[i].value, '\"') == NULL)
fprintf(ff, "\t%s %s\n", kv[i].key, kv[i].value);
else
fprintf(ff, "\t%s \"%s\"\n", kv[i].key, kv[i].value);
}
}
static void begin_section(FILE *ff, const char *section)
{
fprintf(ff, "%s {\n", section);
}
static void end_section(FILE *ff)
{
fprintf(ff, "}\n");
}
static void write_section(FILE *ff, const char *section,
int nkv, const struct key_value *kv)
{
begin_section(ff, section);
write_key_values(ff, nkv, kv);
end_section(ff);
}
static void write_defaults(const struct hwt_state *hwt)
{
static const char bindings_name[] = "bindings";
static struct key_value defaults[] = {
{ "config_dir", NULL },
{ "bindings_file", NULL },
{ "multipath_dir", NULL },
{ "detect_prio", "no" },
{ "detect_checker", "no" },
};
char buf[sizeof(tmplate) + sizeof(bindings_name)];
char dirbuf[PATH_MAX];
snprintf(buf, sizeof(buf), "%s/%s", hwt->tmpname, bindings_name);
defaults[0].value = hwt->dirname;
defaults[1].value = buf;
assert_ptr_not_equal(getcwd(dirbuf, sizeof(dirbuf)), NULL);
strncat(dirbuf, "/lib", sizeof(dirbuf) - 5);
defaults[2].value = dirbuf;
write_section(hwt->config_file, "defaults",
ARRAY_SIZE(defaults), defaults);
}
static void begin_config(const struct hwt_state *hwt)
{
reset_configs(hwt);
write_defaults(hwt);
}
static void begin_section_all(const struct hwt_state *hwt, const char *section)
{
int i;
begin_section(hwt->config_file, section);
for (i = 0; i < N_CONF_FILES; i++)
begin_section(hwt->conf_dir_file[i], section);
}
static void end_section_all(const struct hwt_state *hwt)
{
int i;
end_section(hwt->config_file);
for (i = 0; i < N_CONF_FILES; i++)
end_section(hwt->conf_dir_file[i]);
}
static void finish_config(const struct hwt_state *hwt)
{
int i;
fflush(hwt->config_file);
for (i = 0; i < N_CONF_FILES; i++) {
fflush(hwt->conf_dir_file[i]);
}
}
static void write_device(FILE *ff, int nkv, const struct key_value *kv)
{
write_section(ff, "device", nkv, kv);
}
/*
* Some macros to avoid boilerplace code
*/
#define CHECK_STATE(state) ({ \
assert_ptr_not_equal(state, NULL); \
assert_ptr_not_equal(*(state), NULL); \
*state; })
#define WRITE_EMPTY_CONF(hwt) do { \
begin_config(hwt); \
finish_config(hwt); \
} while (0)
#define WRITE_ONE_DEVICE(hwt, kv) do { \
begin_config(hwt); \
begin_section_all(hwt, "devices"); \
write_device(hwt->config_file, ARRAY_SIZE(kv), kv); \
end_section_all(hwt); \
finish_config(hwt); \
} while (0)
#define WRITE_TWO_DEVICES(hwt, kv1, kv2) do { \
begin_config(hwt); \
begin_section_all(hwt, "devices"); \
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); \
write_device(hwt->config_file, ARRAY_SIZE(kv2), kv2); \
end_section_all(hwt); \
finish_config(hwt); \
} while (0)
#define WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2) do { \
begin_config(hwt); \
begin_section_all(hwt, "devices"); \
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1); \
write_device(hwt->conf_dir_file[0], \
ARRAY_SIZE(kv2), kv2); \
end_section_all(hwt); \
finish_config(hwt); \
} while (0)
#define LOAD_CONFIG(hwt) ({ \
char buf[PATH_MAX]; \
struct config *__cf; \
\
make_config_file_path(buf, sizeof(buf), hwt, -1); \
__cf = load_config(buf); \
assert_ptr_not_equal(__cf, NULL); \
assert_ptr_not_equal(__cf->hwtable, NULL); \
__cf->verbosity = VERBOSITY; \
memcpy(&__cf->version, dm_tgt_version, sizeof(__cf->version)); \
__cf; })
#define FREE_CONFIG(conf) do { \
free_config(conf); \
conf = NULL; \
} while (0)
static void replace_config(const struct hwt_state *hwt,
const char *conf_str)
{
FREE_CONFIG(_conf);
reset_configs(hwt);
fprintf(hwt->config_file, "%s", conf_str);
fflush(hwt->config_file);
_conf = LOAD_CONFIG(hwt);
}
#define TEST_PROP(prop, val) do { \
if (val == NULL) \
assert_ptr_equal(prop, NULL); \
else { \
assert_ptr_not_equal(prop, NULL); \
assert_string_equal(prop, val); \
} \
} while (0)
#if BROKEN
#define TEST_PROP_BROKEN(name, prop, bad, good) do { \
condlog(1, "%s: WARNING: Broken test for %s == \"%s\" on line %d, should be \"%s\"", \
__func__, name, bad ? bad : "NULL", \
__LINE__, good ? good : "NULL"); \
TEST_PROP(prop, bad); \
} while (0)
#else
#define TEST_PROP_BROKEN(name, prop, bad, good) TEST_PROP(prop, good)
#endif
/*
* Some predefined key/value pairs
*/
static const char _wwid[] = "wwid";
static const char _vendor[] = "vendor";
static const char _product[] = "product";
static const char _prio[] = "prio";
static const char _checker[] = "path_checker";
static const char _getuid[] = "getuid_callout";
static const char _uid_attr[] = "uid_attribute";
static const char _bl_product[] = "product_blacklist";
static const char _minio[] = "rr_min_io_rq";
static const char _no_path_retry[] = "no_path_retry";
/* Device identifiers */
static const struct key_value vnd_foo = { _vendor, "foo" };
static const struct key_value prd_bar = { _product, "bar" };
static const struct key_value prd_bam = { _product, "bam" };
static const struct key_value prd_baq = { _product, "\"bar\"" };
static const struct key_value prd_baqq = { _product, "\"\"bar\"\"" };
static const struct key_value prd_barz = { _product, "barz" };
static const struct key_value vnd_boo = { _vendor, "boo" };
static const struct key_value prd_baz = { _product, "baz" };
static const struct key_value wwid_test = { _wwid, default_wwid };
/* Regular expresssions */
static const struct key_value vnd__oo = { _vendor, ".oo" };
static const struct key_value vnd_t_oo = { _vendor, "^.oo" };
static const struct key_value prd_ba_ = { _product, "ba." };
static const struct key_value prd_ba_s = { _product, "(bar|baz|ba\\.)$" };
/* Pathological cases, see below */
static const struct key_value prd_barx = { _product, "ba[[rxy]" };
static const struct key_value prd_bazy = { _product, "ba[zy]" };
static const struct key_value prd_bazy1 = { _product, "ba(z|y)" };
/* Properties */
static const struct key_value prio_emc = { _prio, "emc" };
static const struct key_value prio_hds = { _prio, "hds" };
static const struct key_value prio_rdac = { _prio, "rdac" };
static const struct key_value chk_hp = { _checker, "hp_sw" };
static const struct key_value gui_foo = { _getuid, "/tmp/foo" };
static const struct key_value uid_baz = { _uid_attr, "BAZ_ATTR" };
static const struct key_value bl_bar = { _bl_product, "bar" };
static const struct key_value bl_baz = { _bl_product, "baz" };
static const struct key_value bl_barx = { _bl_product, "ba[[rxy]" };
static const struct key_value bl_bazy = { _bl_product, "ba[zy]" };
static const struct key_value minio_99 = { _minio, "99" };
static const struct key_value npr_37 = { _no_path_retry, "37" };
static const struct key_value npr_queue = { _no_path_retry, "queue" };
/***** BEGIN TESTS SECTION *****/
/*
* Dump the configuration, subistitute the dumped configuration
* for the current one, and verify that the result is identical.
*/
static void replicate_config(const struct hwt_state *hwt, bool local)
{
char *cfg1, *cfg2;
vector hwtable;
struct config *conf;
condlog(3, "--- %s: replicating %s configuration", __func__,
local ? "local" : "full");
conf = get_multipath_config();
if (!local)
/* "full" configuration */
cfg1 = snprint_config(conf, NULL, NULL, NULL);
else {
/* "local" configuration */
hwtable = get_used_hwes(hwt->vecs->pathvec);
cfg1 = snprint_config(conf, NULL, hwtable, hwt->vecs->mpvec);
}
assert_non_null(cfg1);
put_multipath_config(conf);
replace_config(hwt, cfg1);
/*
* The local configuration adds multipath entries, and may move device
* entries for local devices to the end of the list. Identical config
* strings therefore can't be expected in the "local" case.
* That doesn't matter. The important thing is that, with the reloaded
* configuration, the test case still passes.
*/
if (local) {
free(cfg1);
return;
}
conf = get_multipath_config();
cfg2 = snprint_config(conf, NULL, NULL, NULL);
assert_non_null(cfg2);
put_multipath_config(conf);
// #define DBG_CONFIG 1
#ifdef DBG_CONFIG
#define DUMP_CFG_STR(x) do { \
FILE *tmp = fopen("/tmp/hwtable-" #x ".txt", "w"); \
fprintf(tmp, "%s", x); \
fclose(tmp); \
} while (0)
DUMP_CFG_STR(cfg1);
DUMP_CFG_STR(cfg2);
#endif
assert_int_equal(strlen(cfg2), strlen(cfg1));
assert_string_equal(cfg2, cfg1);
free(cfg1);
free(cfg2);
}
/*
* Run hwt->test three times; once with the constructed configuration,
* once after re-reading the full dumped configuration, and once with the
* dumped local configuration.
*
* Expected: test passes every time.
*/
static void test_driver(void **state)
{
const struct hwt_state *hwt;
hwt = CHECK_STATE(state);
_conf = LOAD_CONFIG(hwt);
hwt->test(hwt);
replicate_config(hwt, false);
reset_vecs(hwt->vecs);
hwt->test(hwt);
replicate_config(hwt, true);
reset_vecs(hwt->vecs);
hwt->test(hwt);
reset_vecs(hwt->vecs);
FREE_CONFIG(_conf);
}
/*
* Sanity check for the test itself, because defaults may be changed
* in libmultipath.
*
* Our checking for match or non-match relies on the defaults being
* different from what our device sections contain.
*/
static void test_sanity_globals(void **state)
{
assert_string_not_equal(prio_emc.value, DEFAULT_PRIO);
assert_string_not_equal(prio_hds.value, DEFAULT_PRIO);
assert_string_not_equal(chk_hp.value, DEFAULT_CHECKER);
assert_int_not_equal(MULTIBUS, DEFAULT_PGPOLICY);
assert_int_not_equal(NO_PATH_RETRY_QUEUE, DEFAULT_NO_PATH_RETRY);
assert_int_not_equal(atoi(minio_99.value), DEFAULT_MINIO_RQ);
assert_int_not_equal(atoi(npr_37.value), DEFAULT_NO_PATH_RETRY);
}
/*
* Regression test for internal hwtable. NVME is an example of two entries
* in the built-in hwtable, one if which matches a subset of the other.
*/
static void test_internal_nvme(const struct hwt_state *hwt)
{
struct path *pp;
struct multipath *mp;
/*
* Generic NVMe: expect defaults for pgpolicy and no_path_retry
*/
pp = mock_path("NVME", "NoName");
mp = mock_multipath(pp);
assert_ptr_not_equal(mp, NULL);
TEST_PROP(checker_name(&pp->checker), NONE);
TEST_PROP(pp->uid_attribute, DEFAULT_NVME_UID_ATTRIBUTE);
assert_int_equal(mp->pgpolicy, DEFAULT_PGPOLICY);
assert_int_equal(mp->no_path_retry, DEFAULT_NO_PATH_RETRY);
assert_int_equal(mp->retain_hwhandler, RETAIN_HWHANDLER_OFF);
/*
* NetApp NVMe: expect special values for pgpolicy and no_path_retry
*/
pp = mock_path_wwid("NVME", "NetApp ONTAP Controller",
default_wwid_1);
mp = mock_multipath(pp);
assert_ptr_not_equal(mp, NULL);
TEST_PROP(checker_name(&pp->checker), NONE);
TEST_PROP(pp->uid_attribute, "ID_WWN");
assert_int_equal(mp->pgpolicy, MULTIBUS);
assert_int_equal(mp->no_path_retry, NO_PATH_RETRY_QUEUE);
assert_int_equal(mp->retain_hwhandler, RETAIN_HWHANDLER_OFF);
}
static int setup_internal_nvme(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_EMPTY_CONF(hwt);
SET_TEST_FUNC(hwt, test_internal_nvme);
return 0;
}
/*
* Device section with a simple entry qith double quotes ('foo:"bar"')
*/
static void test_quoted_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:"bar" matches */
pp = mock_path(vnd_foo.value, prd_baq.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
/* foo:bar doesn't match */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
}
static int setup_quoted_hwe(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv[] = { vnd_foo, prd_baqq, prio_emc };
WRITE_ONE_DEVICE(hwt, kv);
SET_TEST_FUNC(hwt, test_quoted_hwe);
return 0;
}
/*
* Device section with a single simple entry ("foo:bar")
*/
static void test_string_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar matches */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
/* boo:bar doesn't match */
pp = mock_path(vnd_boo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
}
static int setup_string_hwe(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv[] = { vnd_foo, prd_bar, prio_emc };
WRITE_ONE_DEVICE(hwt, kv);
SET_TEST_FUNC(hwt, test_string_hwe);
return 0;
}
/*
* Device section with a broken entry (no product)
* It should be ignored.
*/
static void test_broken_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar doesn't match, as hwentry is ignored */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
/* boo:bar doesn't match */
pp = mock_path(vnd_boo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
}
static int setup_broken_hwe(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv[] = { vnd_foo, prio_emc };
WRITE_ONE_DEVICE(hwt, kv);
SET_TEST_FUNC(hwt, test_broken_hwe);
return 0;
}
/*
* Like test_broken_hwe, but in config_dir file.
*/
static int setup_broken_hwe_dir(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv[] = { vnd_foo, prio_emc };
begin_config(hwt);
begin_section_all(hwt, "devices");
write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv), kv);
end_section_all(hwt);
finish_config(hwt);
hwt->test = test_broken_hwe;
hwt->test_name = "test_broken_hwe_dir";
return 0;
}
/*
* Device section with a single regex entry ("^.foo:(bar|baz|ba\.)$")
*/
static void test_regex_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar matches */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
/* foo:baz matches */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
/* boo:baz matches */
pp = mock_path(vnd_boo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
/* foo:BAR doesn't match */
pp = mock_path(vnd_foo.value, "BAR");
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
/* bboo:bar doesn't match */
pp = mock_path("bboo", prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
}
static int setup_regex_hwe(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv[] = { vnd_t_oo, prd_ba_s, prio_emc };
WRITE_ONE_DEVICE(hwt, kv);
SET_TEST_FUNC(hwt, test_regex_hwe);
return 0;
}
/*
* Two device entries, kv1 is a regex match ("^.foo:(bar|baz|ba\.)$"),
* kv2 a string match (foo:bar) which matches a subset of the regex.
* Both are added to the main config file.
*
* Expected: Devices matching both get properties from both, kv2 taking
* precedence. Devices matching kv1 only just get props from kv1.
*/
static void test_regex_string_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz matches kv1 */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* boo:baz matches kv1 */
pp = mock_path(vnd_boo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .oo:ba. matches kv1 */
pp = mock_path(vnd__oo.value, prd_ba_.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .foo:(bar|baz|ba\.) doesn't match */
pp = mock_path(vnd__oo.value, prd_ba_s.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches kv2 and kv1 */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_regex_string_hwe(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
WRITE_TWO_DEVICES(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_regex_string_hwe);
return 0;
}
/*
* Two device entries, kv1 is a regex match ("^.foo:(bar|baz|ba\.)$"),
* kv2 a string match (foo:bar) which matches a subset of the regex.
* kv1 is added to the main config file, kv2 to a config_dir file.
* This case is more important as you may think, because it's equivalent
* to kv1 being in the built-in hwtable and kv2 in multipath.conf.
*
* Expected: Devices matching kv2 (and thus, both) get properties
* from both, kv2 taking precedence.
* Devices matching kv1 only just get props from kv1.
*/
static void test_regex_string_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz matches kv1 */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* boo:baz matches kv1 */
pp = mock_path(vnd_boo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .oo:ba. matches kv1 */
pp = mock_path(vnd__oo.value, prd_ba_.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .oo:(bar|baz|ba\.)$ doesn't match */
pp = mock_path(vnd__oo.value, prd_ba_s.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches kv2 */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
/* Later match takes prio */
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_regex_string_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_regex_string_hwe_dir);
return 0;
}
/*
* Three device entries, kv1 is a regex match and kv2 and kv3 string
* matches, where kv3 is a substring of kv2. All in different config
* files.
*
* Expected: Devices matching kv3 get props from all, devices matching
* kv2 from kv2 and kv1, and devices matching kv1 only just from kv1.
*/
static void test_regex_2_strings_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz matches kv1 */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* boo:baz doesn't match */
pp = mock_path(vnd_boo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches kv2 and kv1 */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(pp->uid_attribute, uid_baz.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* foo:barz matches kv3 and kv2 and kv1 */
pp = mock_path_flags(vnd_foo.value, prd_barz.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_rdac.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(pp->uid_attribute, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_regex_2_strings_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_ba_, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, uid_baz };
const struct key_value kv3[] = { vnd_foo, prd_barz,
prio_rdac, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "devices");
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1);
write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv3), kv3);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_regex_2_strings_hwe_dir);
return 0;
}
/*
* Like test_regex_string_hwe_dir, but the order of kv1 and kv2 is exchanged.
*
* Expected: Devices matching kv1 (and thus, both) get properties
* from both, kv1 taking precedence.
* Devices matching kv1 only just get props from kv1.
*/
static void test_string_regex_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar matches kv2 and kv1 */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* foo:baz matches kv1 */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* boo:baz matches kv1 */
pp = mock_path(vnd_boo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .oo:ba. matches kv1 */
pp = mock_path(vnd__oo.value, prd_ba_.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* .oo:(bar|baz|ba\.)$ doesn't match */
pp = mock_path(vnd__oo.value, prd_ba_s.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
}
static int setup_string_regex_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_t_oo, prd_ba_s, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv2, kv1);
SET_TEST_FUNC(hwt, test_string_regex_hwe_dir);
return 0;
}
/*
* Two identical device entries kv1 and kv2, trival regex ("string").
* Both are added to the main config file.
* These entries are NOT merged.
* This could happen in a large multipath.conf file.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_strings_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_strings_hwe(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_ident_strings_hwe);
return 0;
}
/*
* Two identical device entries kv1 and kv2, trival regex ("string").
* Both are added to an extra config file.
* This could happen in a large multipath.conf file.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_strings_both_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_strings_both_dir(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "devices");
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv1), kv1);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_2_ident_strings_both_dir);
return 0;
}
/*
* Two identical device entries kv1 and kv2, trival regex ("string").
* Both are added to an extra config file.
* An empty entry kv0 with the same string exists in the main config file.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_strings_both_dir_w_prev(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_strings_both_dir_w_prev(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kv0[] = { vnd_foo, prd_bar };
const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
begin_config(hwt);
begin_section_all(hwt, "devices");
write_device(hwt->config_file, ARRAY_SIZE(kv0), kv0);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv1), kv1);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_2_ident_strings_both_dir_w_prev);
return 0;
}
/*
* Two identical device entries kv1 and kv2, trival regex ("string").
* kv1 is added to the main config file, kv2 to a config_dir file.
* These entries are merged.
* This case is more important as you may think, because it's equivalent
* to kv1 being in the built-in hwtable and kv2 in multipath.conf.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_strings_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_strings_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_ident_strings_hwe_dir);
return 0;
}
/*
* Like test_2_ident_strings_hwe_dir, but this time the config_dir file
* contains an additional, empty entry (kv0).
*
* Expected: matching devices get props from kv1 and kv2, kv2 taking precedence.
*/
static void test_3_ident_strings_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_3_ident_strings_hwe_dir(void **state)
{
const struct key_value kv0[] = { vnd_foo, prd_bar };
const struct key_value kv1[] = { vnd_foo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "devices");
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv0), kv0);
write_device(hwt->conf_dir_file[1], ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_3_ident_strings_hwe_dir);
return 0;
}
/*
* Two identical device entries kv1 and kv2, non-trival regex that matches
* itself (string ".oo" matches regex ".oo").
* kv1 is added to the main config file, kv2 to a config_dir file.
* This case is more important as you may think, because it's equivalent
* to kv1 being in the built-in hwtable and kv2 in multipath.conf.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_self_matching_re_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_self_matching_re_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd__oo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd__oo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_ident_self_matching_re_hwe_dir);
return 0;
}
/*
* Two identical device entries kv1 and kv2, non-trival regex that matches
* itself (string ".oo" matches regex ".oo").
* kv1 and kv2 are added to the main config file.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void test_2_ident_self_matching_re_hwe(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_self_matching_re_hwe(void **state)
{
const struct key_value kv1[] = { vnd__oo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd__oo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_ident_self_matching_re_hwe);
return 0;
}
/*
* Two identical device entries kv1 and kv2, non-trival regex that doesn't
* match itself (string "^.oo" doesn't match regex "^.oo").
* kv1 is added to the main config file, kv2 to a config_dir file.
* This case is more important as you may think, see above.
*
* Expected: matching devices get props from both, kv2 taking precedence.
*/
static void
test_2_ident_not_self_matching_re_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:baz doesn't match */
pp = mock_path(vnd_foo.value, prd_baz.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
/* foo:bar matches both */
pp = mock_path_flags(vnd_foo.value, prd_bar.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_ident_not_self_matching_re_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_t_oo, prd_bar, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_t_oo, prd_bar, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_ident_not_self_matching_re_hwe_dir);
return 0;
}
/*
* Two different non-trivial regexes kv1, kv2. The 1st one matches the 2nd, but
* it doesn't match all possible strings matching the second.
* ("ba[zy]" matches regex "ba[[rxy]", but "baz" does not).
*
* Expected: Devices matching both regexes get properties from both, kv2
* taking precedence. Devices matching just one regex get properties from
* that one regex only.
*/
static void test_2_matching_res_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar matches k1 only */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* foo:bay matches k1 and k2 */
pp = mock_path_flags(vnd_foo.value, "bay", USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
/* foo:baz matches k2 only. */
pp = mock_path_flags(vnd_foo.value, prd_baz.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
}
static int setup_2_matching_res_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_barx, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bazy, prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_matching_res_hwe_dir);
return 0;
}
/*
* Two different non-trivial regexes which match the same set of strings.
* But they don't match each other.
* "baz" matches both regex "ba[zy]" and "ba(z|y)"
*
* Expected: matching devices get properties from both, kv2 taking precedence.
*/
static void test_2_nonmatching_res_hwe_dir(const struct hwt_state *hwt)
{
struct path *pp;
/* foo:bar doesn't match */
pp = mock_path(vnd_foo.value, prd_bar.value);
TEST_PROP(prio_name(&pp->prio), DEFAULT_PRIO);
TEST_PROP(pp->getuid, NULL);
TEST_PROP(checker_name(&pp->checker), DEFAULT_CHECKER);
pp = mock_path_flags(vnd_foo.value, prd_baz.value, USE_GETUID);
TEST_PROP(prio_name(&pp->prio), prio_hds.value);
TEST_PROP(pp->getuid, gui_foo.value);
TEST_PROP(checker_name(&pp->checker), chk_hp.value);
}
static int setup_2_nonmatching_res_hwe_dir(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bazy, prio_emc, chk_hp };
const struct key_value kv2[] = { vnd_foo, prd_bazy1,
prio_hds, gui_foo };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES_W_DIR(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_2_nonmatching_res_hwe_dir);
return 0;
}
/*
* Simple blacklist test.
*
* NOTE: test failures in blacklisting tests will manifest as cmocka errors
* "Could not get value to mock function XYZ", because pathinfo() takes
* different code paths for blacklisted devices.
*/
static void test_blacklist(const struct hwt_state *hwt)
{
mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE);
mock_path(vnd_foo.value, prd_baz.value);
}
static int setup_blacklist(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "blacklist");
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist);
return 0;
}
/*
* Simple blacklist test with regex and exception
*/
static void test_blacklist_regex(const struct hwt_state *hwt)
{
mock_path(vnd_foo.value, prd_bar.value);
mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE);
mock_path(vnd_foo.value, prd_bam.value);
}
static int setup_blacklist_regex(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_ba_s };
const struct key_value kv2[] = { vnd_foo, prd_bar };
struct hwt_state *hwt = CHECK_STATE(state);
hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "blacklist");
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1);
end_section_all(hwt);
begin_section_all(hwt, "blacklist_exceptions");
write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist_regex);
return 0;
}
/*
* Simple blacklist test with regex and exception
* config file order inverted wrt test_blacklist_regex
*/
static int setup_blacklist_regex_inv(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_ba_s };
const struct key_value kv2[] = { vnd_foo, prd_bar };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "blacklist");
write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv1), kv1);
end_section_all(hwt);
begin_section_all(hwt, "blacklist_exceptions");
write_device(hwt->config_file, ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist_regex);
return 0;
}
/*
* Simple blacklist test with regex and exception
* config file order inverted wrt test_blacklist_regex
*/
static void test_blacklist_regex_matching(const struct hwt_state *hwt)
{
mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE);
mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE);
mock_path(vnd_foo.value, prd_bam.value);
}
static int setup_blacklist_regex_matching(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_barx };
const struct key_value kv2[] = { vnd_foo, prd_bazy };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "blacklist");
write_device(hwt->config_file, ARRAY_SIZE(kv1), kv1);
write_device(hwt->conf_dir_file[0], ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist_regex_matching);
return 0;
}
/*
* Test for blacklisting by WWID
*
* Note that default_wwid is a substring of default_wwid_1. Because
* matching is done by regex, both paths are blacklisted.
*/
static void test_blacklist_wwid(const struct hwt_state *hwt)
{
mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_WWID);
mock_path_wwid_flags(vnd_foo.value, prd_baz.value, default_wwid_1,
BL_BY_WWID);
}
static int setup_blacklist_wwid(void **state)
{
const struct key_value kv[] = { wwid_test };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
write_section(hwt->config_file, "blacklist", ARRAY_SIZE(kv), kv);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist_wwid);
return 0;
}
/*
* Test for blacklisting by WWID
*
* Here the blacklist contains only default_wwid_1. Thus the path
* with default_wwid is NOT blacklisted.
*/
static void test_blacklist_wwid_1(const struct hwt_state *hwt)
{
mock_path(vnd_foo.value, prd_bar.value);
mock_path_wwid_flags(vnd_foo.value, prd_baz.value, default_wwid_1,
BL_BY_WWID);
}
static int setup_blacklist_wwid_1(void **state)
{
const struct key_value kv[] = { { _wwid, default_wwid_1 }, };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
write_section(hwt->config_file, "blacklist", ARRAY_SIZE(kv), kv);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_blacklist_wwid_1);
return 0;
}
/*
* Test for product_blacklist. Two entries blacklisting each other.
*
* Expected: Both are blacklisted.
*/
static void test_product_blacklist(const struct hwt_state *hwt)
{
mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE);
mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE);
mock_path(vnd_foo.value, prd_bam.value);
}
static int setup_product_blacklist(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar, bl_baz };
const struct key_value kv2[] = { vnd_foo, prd_baz, bl_bar };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_product_blacklist);
return 0;
}
/*
* Test for product_blacklist. The second regex "matches" the first.
* This is a pathological example.
*
* Expected: "foo:bar", "foo:baz" are blacklisted.
*/
static void test_product_blacklist_matching(const struct hwt_state *hwt)
{
mock_path_flags(vnd_foo.value, prd_bar.value, BL_BY_DEVICE);
mock_path_flags(vnd_foo.value, prd_baz.value, BL_BY_DEVICE);
mock_path(vnd_foo.value, prd_bam.value);
}
static int setup_product_blacklist_matching(void **state)
{
const struct key_value kv1[] = { vnd_foo, prd_bar, bl_barx };
const struct key_value kv2[] = { vnd_foo, prd_baz, bl_bazy };
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_TWO_DEVICES(hwt, kv1, kv2);
SET_TEST_FUNC(hwt, test_product_blacklist_matching);
return 0;
}
/*
* Basic test for multipath-based configuration.
*
* Expected: properties, including pp->prio, are taken from multipath
* section.
*/
static void test_multipath_config(const struct hwt_state *hwt)
{
struct path *pp;
struct multipath *mp;
pp = mock_path(vnd_foo.value, prd_bar.value);
mp = mock_multipath(pp);
assert_ptr_not_equal(mp->mpe, NULL);
TEST_PROP(prio_name(&pp->prio), prio_rdac.value);
assert_int_equal(mp->minio, atoi(minio_99.value));
TEST_PROP(pp->uid_attribute, uid_baz.value);
/* test different wwid */
pp = mock_path_wwid(vnd_foo.value, prd_bar.value, default_wwid_1);
mp = mock_multipath(pp);
// assert_ptr_equal(mp->mpe, NULL);
TEST_PROP(prio_name(&pp->prio), prio_emc.value);
assert_int_equal(mp->minio, DEFAULT_MINIO_RQ);
TEST_PROP(pp->uid_attribute, uid_baz.value);
}
static int setup_multipath_config(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
const struct key_value kvm[] = { wwid_test, prio_rdac, minio_99 };
const struct key_value kvp[] = { vnd_foo, prd_bar, prio_emc, uid_baz };
begin_config(hwt);
begin_section_all(hwt, "devices");
write_section(hwt->conf_dir_file[0], "device", ARRAY_SIZE(kvp), kvp);
end_section_all(hwt);
begin_section_all(hwt, "multipaths");
write_section(hwt->config_file, "multipath", ARRAY_SIZE(kvm), kvm);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_multipath_config);
return 0;
}
/*
* Basic test for multipath-based configuration. Two sections for the same wwid.
*
* Expected: properties are taken from both multipath sections, later taking
* precedence
*/
static void test_multipath_config_2(const struct hwt_state *hwt)
{
struct path *pp;
struct multipath *mp;
pp = mock_path(vnd_foo.value, prd_bar.value);
mp = mock_multipath(pp);
assert_ptr_not_equal(mp, NULL);
assert_ptr_not_equal(mp->mpe, NULL);
TEST_PROP(prio_name(&pp->prio), prio_rdac.value);
assert_int_equal(mp->minio, atoi(minio_99.value));
assert_int_equal(mp->no_path_retry, atoi(npr_37.value));
}
static int setup_multipath_config_2(void **state)
{
const struct key_value kv1[] = { wwid_test, prio_rdac, npr_queue };
const struct key_value kv2[] = { wwid_test, minio_99, npr_37 };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "multipaths");
write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv1), kv1);
write_section(hwt->conf_dir_file[1], "multipath", ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_multipath_config_2);
return 0;
}
/*
* Same as test_multipath_config_2, both entries in the same config file.
*
* Expected: properties are taken from both multipath sections.
*/
static void test_multipath_config_3(const struct hwt_state *hwt)
{
struct path *pp;
struct multipath *mp;
pp = mock_path(vnd_foo.value, prd_bar.value);
mp = mock_multipath(pp);
assert_ptr_not_equal(mp, NULL);
assert_ptr_not_equal(mp->mpe, NULL);
TEST_PROP(prio_name(&pp->prio), prio_rdac.value);
assert_int_equal(mp->minio, atoi(minio_99.value));
assert_int_equal(mp->no_path_retry, atoi(npr_37.value));
}
static int setup_multipath_config_3(void **state)
{
const struct key_value kv1[] = { wwid_test, prio_rdac, npr_queue };
const struct key_value kv2[] = { wwid_test, minio_99, npr_37 };
struct hwt_state *hwt = CHECK_STATE(state);
begin_config(hwt);
begin_section_all(hwt, "multipaths");
write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv1), kv1);
write_section(hwt->config_file, "multipath", ARRAY_SIZE(kv2), kv2);
end_section_all(hwt);
finish_config(hwt);
SET_TEST_FUNC(hwt, test_multipath_config_3);
return 0;
}
/*
* Test for device with "hidden" attribute
*/
static void test_hidden(const struct hwt_state *hwt)
{
mock_path_flags("NVME", "NoName", DEV_HIDDEN|BL_MASK);
}
static int setup_hidden(void **state)
{
struct hwt_state *hwt = CHECK_STATE(state);
WRITE_EMPTY_CONF(hwt);
SET_TEST_FUNC(hwt, test_hidden);
return 0;
}
/*
* Create wrapper functions around test_driver() to avoid that cmocka
* always uses the same test name. That makes it easier to read test results.
*/
#define define_test(x) \
static void run_##x(void **state) \
{ \
return test_driver(state); \
}
define_test(string_hwe)
define_test(broken_hwe)
define_test(broken_hwe_dir)
define_test(quoted_hwe)
define_test(internal_nvme)
define_test(regex_hwe)
define_test(regex_string_hwe)
define_test(regex_string_hwe_dir)
define_test(regex_2_strings_hwe_dir)
define_test(string_regex_hwe_dir)
define_test(2_ident_strings_hwe)
define_test(2_ident_strings_both_dir)
define_test(2_ident_strings_both_dir_w_prev)
define_test(2_ident_strings_hwe_dir)
define_test(3_ident_strings_hwe_dir)
define_test(2_ident_self_matching_re_hwe_dir)
define_test(2_ident_self_matching_re_hwe)
define_test(2_ident_not_self_matching_re_hwe_dir)
define_test(2_matching_res_hwe_dir)
define_test(2_nonmatching_res_hwe_dir)
define_test(blacklist)
define_test(blacklist_wwid)
define_test(blacklist_wwid_1)
define_test(blacklist_regex)
define_test(blacklist_regex_inv)
define_test(blacklist_regex_matching)
define_test(product_blacklist)
define_test(product_blacklist_matching)
define_test(multipath_config)
define_test(multipath_config_2)
define_test(multipath_config_3)
define_test(hidden)
#define test_entry(x) \
cmocka_unit_test_setup(run_##x, setup_##x)
static int test_hwtable(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_sanity_globals),
test_entry(internal_nvme),
test_entry(string_hwe),
test_entry(broken_hwe),
test_entry(broken_hwe_dir),
test_entry(quoted_hwe),
test_entry(regex_hwe),
test_entry(regex_string_hwe),
test_entry(regex_string_hwe_dir),
test_entry(regex_2_strings_hwe_dir),
test_entry(string_regex_hwe_dir),
test_entry(2_ident_strings_hwe),
test_entry(2_ident_strings_both_dir),
test_entry(2_ident_strings_both_dir_w_prev),
test_entry(2_ident_strings_hwe_dir),
test_entry(3_ident_strings_hwe_dir),
test_entry(2_ident_self_matching_re_hwe_dir),
test_entry(2_ident_self_matching_re_hwe),
test_entry(2_ident_not_self_matching_re_hwe_dir),
test_entry(2_matching_res_hwe_dir),
test_entry(2_nonmatching_res_hwe_dir),
test_entry(blacklist),
test_entry(blacklist_wwid),
test_entry(blacklist_wwid_1),
test_entry(blacklist_regex),
test_entry(blacklist_regex_inv),
test_entry(blacklist_regex_matching),
test_entry(product_blacklist),
test_entry(product_blacklist_matching),
test_entry(multipath_config),
test_entry(multipath_config_2),
test_entry(multipath_config_3),
test_entry(hidden),
};
return cmocka_run_group_tests(tests, setup, teardown);
}
int main(void)
{
int ret = 0;
ret += test_hwtable();
return ret;
}