/* * * Embedded Linux library * * Copyright (C) 2017 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include enum test_op { OP_NULL, OP_OPEN, OP_CREAT, OP_UNLINK, OP_TRUNCATE, OP_RENAME, }; struct test_result { const char *dir; const char *file; bool ignore; enum l_dir_watch_event event; }; typedef struct test_result test_result_t; #define MAX_RESULTS 2 struct test_data { enum test_op op; const char *orig_dir; const char *orig_file; const char *dest_dir; const char *dest_file; unsigned int length; const char *content; const struct test_result *results[MAX_RESULTS + 1]; }; struct test_entry { const char *name; const struct test_data *data; char **test_dirs; char **test_files; char **watch_dirs; unsigned int idx; unsigned int res_idx; struct l_queue *watch_list; bool failed; }; struct test_watch { char *pathname; struct test_entry *entry; }; static struct l_queue *test_queue; #define TEST_FULL(_dir, _file, _op) \ .orig_dir = _dir, .orig_file = _file, .op = _op #define test_open(_dir, _file, _length) \ TEST_FULL(_dir, _file, OP_OPEN), .length = _length #define test_creat(_dir, _file, _content) \ TEST_FULL(_dir, _file, OP_CREAT), .content = _content #define test_unlink(_dir, _file) \ TEST_FULL(_dir, _file, OP_UNLINK) #define test_truncate(_dir, _file, _length) \ TEST_FULL(_dir, _file, OP_TRUNCATE), .length = _length #define test_rename(_olddir, _oldfile, _newdir, _newfile) \ TEST_FULL(_olddir, _oldfile, OP_RENAME), \ .dest_dir = _newdir, .dest_file = _newfile #define RESULT_PTR(_dir, _file, _ignore, _event) \ (&(test_result_t) { _dir, _file, _ignore, _event }) #define results(args...) .results = { args, RESULT_PTR(NULL, NULL, true, 0) } #define created(_dir, _file) RESULT_PTR(_dir, _file, false, L_DIR_WATCH_EVENT_CREATED) #define removed(_dir, _file) RESULT_PTR(_dir, _file, false, L_DIR_WATCH_EVENT_REMOVED) #define modified(_dir, _file) RESULT_PTR(_dir, _file, false, L_DIR_WATCH_EVENT_MODIFIED) #define accessed(_dir, _file) RESULT_PTR(_dir, _file, false, L_DIR_WATCH_EVENT_ACCESSED) #define result_ignore() .results = { RESULT_PTR(NULL, NULL, true, 0) } #define result_created(_dir, _file) results(created(_dir, _file)) #define result_removed(_dir, _file) results(removed(_dir, _file)) #define result_modified(_dir, _file) results(modified(_dir, _file)) #define result_accessed(_dir, _file) results(accessed(_dir, _file)) static void start_single_test(void *user_data); static void event_callback(const char *pathname, enum l_dir_watch_event event, void *user_data) { struct test_watch *watch_data = user_data; struct test_entry *entry = watch_data->entry; const struct test_data *data = &entry->data[entry->idx]; const struct test_result *result = data->results[entry->res_idx]; const char *str; switch (event) { case L_DIR_WATCH_EVENT_CREATED: str = "CREATED"; break; case L_DIR_WATCH_EVENT_REMOVED: str = "REMOVED"; break; case L_DIR_WATCH_EVENT_MODIFIED: str = "MODIFIED"; break; case L_DIR_WATCH_EVENT_ACCESSED: str = "ACCESSED"; break; case L_DIR_WATCH_EVENT_ATTRIB: str = "ATTRIB"; break; default: str = "UNKNOWN"; break; } l_debug("l_dir_watch event:%s pathname:%s [%s]", str, pathname, watch_data->pathname); /* This will result in waiting for the timeout */ if (result->ignore) return; /* The watching directory needs to match the watch callback data */ if (strcmp(result->dir, watch_data->pathname)) return; /* The file inside the watch directory needs to match */ if (strcmp(result->file, pathname)) { entry->failed = true; return; } /* The expected event needs to match as well */ if (result->event != event) { entry->failed = true; return; } /* Successful match of the expected event data */ l_debug("l_dir_watch ==> MATCH index %u", entry->res_idx); if (entry->res_idx < MAX_RESULTS) { entry->res_idx++; result = data->results[entry->res_idx]; if (result->dir) { /* More results are required to match */ return; } } /* Move to next test case */ entry->idx++; entry->res_idx = 0; l_idle_oneshot(start_single_test, entry, NULL); } static void run_cleanup(char **dirs, char **files) { int i; for (i = 0; files[i]; i++) { l_debug("unlink(\"%s\")", files[i]); unlink(files[i]); } for (i = 0; dirs[i]; i++) { l_debug("rmdir(\"%s\")", dirs[i]); rmdir(dirs[i]); } } static void dir_watch_free(void *data) { struct l_dir_watch *watch = data; l_debug("free l_dir_watch [%p]", watch); l_dir_watch_destroy(watch); } static void free_test_entry(void *data) { struct test_entry *entry = data; l_debug("free test_entry [%s]", entry->name); l_queue_destroy(entry->watch_list, dir_watch_free); /* Clean run should remove any leftovers */ run_cleanup(entry->test_dirs, entry->test_files); l_strv_free(entry->test_dirs); l_strv_free(entry->test_files); l_strv_free(entry->watch_dirs); l_free(entry); } static void op_open(const char *dir, const char *file, unsigned int length) { char *pathname; int fd, err; pathname = l_strdup_printf("%s/%s", dir, file); l_debug("open(\"%s\", O_RDONLY)", pathname); fd = open(pathname, O_RDONLY); l_debug("=> %d", fd); if (length > 0) { unsigned char *buf = l_malloc(length); ssize_t res; l_debug("read(%d, %p, %u)", fd, buf, length); res = read(fd, buf, length); l_debug("=> %zd", res); l_free(buf); } l_debug("close(%d)", fd); err = close(fd); l_debug("=> %d", err); l_free(pathname); } static void op_creat(const char *dir, const char *file, const char *content) { char *pathname; int fd, err; pathname = l_strdup_printf("%s/%s", dir, file); l_debug("creat(\"%s\", 0600)", pathname); fd = creat(pathname, 0600); l_debug("=> %d", fd); if (content) { int len = strlen(content); ssize_t res; l_debug("write(%d, \"%s\", %d)", fd, content, len); res = write(fd, content, len); l_debug("=> %zd", res); } l_debug("close(%d)", fd); err = close(fd); l_debug("=> %d", err); l_free(pathname); } static void op_unlink(const char *dir, const char *file) { char *pathname; int err; pathname = l_strdup_printf("%s/%s", dir, file); l_debug("unlink(\"%s\")", pathname); err = unlink(pathname); l_debug("=> %d", err); l_free(pathname); } static void op_truncate(const char *dir, const char *file, unsigned int length) { char *pathname; int err; pathname = l_strdup_printf("%s/%s", dir, file); l_debug("truncate(\"%s\", %u)", pathname, length); err = truncate(pathname, length); l_debug("=> %d", err); l_free(pathname); } static void op_rename(const char *olddir, const char *oldfile, const char *newdir, const char *newfile) { char *oldpath, *newpath; int err; oldpath = l_strdup_printf("%s/%s", olddir, oldfile); newpath = l_strdup_printf("%s/%s", newdir, newfile); l_debug("rename(\"%s\", \"%s\")", oldpath, newpath); err = rename(oldpath, newpath); l_debug("=> %d", err); l_free(oldpath); l_free(newpath); } static void process_test_queue(void *user_data); static void start_single_test(void *user_data) { struct test_entry *entry = user_data; const struct test_data *data = &entry->data[entry->idx]; const struct test_result *result = data->results[entry->res_idx]; bool ignore = false; switch (data->op) { case OP_NULL: if (entry->failed) l_info("[%s] FAILED", entry->name); else l_info("[%s] PASSED", entry->name); free_test_entry(entry); l_idle_oneshot(process_test_queue, NULL, NULL); return; case OP_OPEN: op_open(data->orig_dir, data->orig_file, data->length); break; case OP_CREAT: op_creat(data->orig_dir, data->orig_file, data->content); break; case OP_UNLINK: op_unlink(data->orig_dir, data->orig_file); break; case OP_TRUNCATE: op_truncate(data->orig_dir, data->orig_file, data->length); break; case OP_RENAME: op_rename(data->orig_dir, data->orig_file, data->dest_dir, data->dest_file); break; default: ignore = true; break; } if (!result->ignore && !ignore) return; /* Move to next test case */ entry->idx++; entry->res_idx = 0; l_idle_oneshot(start_single_test, entry, NULL); } static void watch_data_free(void *data) { struct test_watch *watch_data = data; l_debug("free test_watch [%s]", watch_data->pathname); l_free(watch_data->pathname); l_free(watch_data); } static void process_test_queue(void *user_data) { struct test_entry *entry; int i; entry = l_queue_pop_head(test_queue); if (!entry) { l_main_quit(); return; } /* In case there is any leftovers */ run_cleanup(entry->test_dirs, entry->test_files); /* Create the directories in use */ for (i = 0; entry->test_dirs[i]; i++) { l_debug("mkdir(%s, 0700)", entry->test_dirs[i]); mkdir(entry->test_dirs[i], 0700); } for (i = 0; entry->watch_dirs[i]; i++) { struct test_watch *watch_data; struct l_dir_watch *watch; watch_data = l_new(struct test_watch, 1); watch_data->pathname = l_strdup(entry->watch_dirs[i]); watch_data->entry = entry; l_debug("new test_watch [%s]", watch_data->pathname); watch = l_dir_watch_new(watch_data->pathname, event_callback, watch_data, watch_data_free); l_debug("new l_dir_watch [%p]", watch); l_queue_push_tail(entry->watch_list, watch); } l_idle_oneshot(start_single_test, entry, NULL); } static void add_test(const char *name, const struct test_data data[]) { struct test_entry *entry; int i; entry = l_new(struct test_entry, 1); entry->name = name; entry->data = data; l_debug("new test_entry [%s]", entry->name); for (i = 0; data[i].op; i++) { char *file; int n; if (!l_strv_contains(entry->test_dirs, data[i].orig_dir)) entry->test_dirs = l_strv_append(entry->test_dirs, data[i].orig_dir); if (data[i].orig_dir) { file = l_strdup_printf("%s/%s", data[i].orig_dir, data[i].orig_file); if (!l_strv_contains(entry->test_files, file)) entry->test_files = l_strv_append(entry->test_files, file); l_free(file); } if (data[i].dest_dir) { file = l_strdup_printf("%s/%s", data[i].dest_dir, data[i].dest_file); if (!l_strv_contains(entry->test_files, file)) entry->test_files = l_strv_append(entry->test_files, file); l_free(file); } for (n = 0; n < MAX_RESULTS; n++) { if (!data[i].results[n]) break; if (l_strv_contains(entry->watch_dirs, data[i].results[n]->dir)) continue; entry->watch_dirs = l_strv_append(entry->watch_dirs, data[i].results[n]->dir); } } entry->watch_list = l_queue_new(); entry->failed = false; l_queue_push_tail(test_queue, entry); } #define DIR_1 "/tmp/ell-test-dir-1" #define DIR_2 "/tmp/ell-test-dir-2" #define FILE_1 "file-1" #define FILE_2 "file-2" #define FILE_3 "file-3" #define FILE_4 "file-4" static const struct test_data test_data_1[] = { { test_creat(DIR_1, FILE_1, NULL), result_created(DIR_1, FILE_1), }, { test_unlink(DIR_1, FILE_1), result_removed(DIR_1, FILE_1), }, { test_creat(DIR_1, FILE_2, "File content"), result_created(DIR_1, FILE_2), }, { test_rename(DIR_1, FILE_2, DIR_1, FILE_3), results(removed(DIR_1, FILE_2), created(DIR_1, FILE_3)), }, { test_open(DIR_1, FILE_3, 4), result_accessed(DIR_1, FILE_3), }, { test_truncate(DIR_1, FILE_3, 4), result_modified(DIR_1, FILE_3), }, { test_unlink(DIR_1, FILE_3), result_removed(DIR_1, FILE_3), }, { test_creat(DIR_2, FILE_4, NULL), result_ignore(), }, { test_rename(DIR_2, FILE_4, DIR_1, FILE_4), result_created(DIR_1, FILE_4), }, { test_rename(DIR_1, FILE_4, DIR_2, FILE_4), result_removed(DIR_1, FILE_4), }, { test_unlink(DIR_2, FILE_4), result_ignore(), }, { } }; static const struct test_data test_data_2[] = { { test_creat(DIR_1, FILE_1, NULL), result_created(DIR_1, FILE_1), }, { test_rename(DIR_1, FILE_1, DIR_2, FILE_1), results(removed(DIR_1, FILE_1), created(DIR_2, FILE_1)), }, { test_unlink(DIR_2, FILE_1), result_removed(DIR_2, FILE_1), }, { } }; static const struct test_data test_data_3[] = { { test_creat(DIR_1, FILE_1, "X"), result_created(DIR_1, FILE_1), }, { test_open(DIR_1, FILE_1, 1), result_accessed(DIR_1, FILE_1), }, { test_unlink(DIR_1, FILE_1), result_removed(DIR_1, FILE_1), }, { } }; static const struct test_data test_data_4[] = { { test_creat(DIR_1, FILE_1, "ABC"), result_created(DIR_1, FILE_1), }, { test_creat(DIR_1, FILE_2, "XYZ"), result_created(DIR_1, FILE_2), }, { test_rename(DIR_1, FILE_2, DIR_1, FILE_1), results(removed(DIR_1, FILE_2), created(DIR_1, FILE_1)), }, { test_unlink(DIR_1, FILE_1), result_removed(DIR_1, FILE_1), }, { } }; int main(int argc, char *argv[]) { int opt, exit_status; l_main_init(); l_log_set_stderr(); while ((opt = getopt(argc, argv, "d")) != -1) { switch (opt) { case 'd': l_debug_enable("*"); break; } } test_queue = l_queue_new(); add_test("Single directory test", test_data_1); add_test("Move between directories", test_data_2); add_test("Create and open file", test_data_3); add_test("Replace existing file", test_data_4); l_idle_oneshot(process_test_queue, NULL, NULL); exit_status = l_main_run(); l_queue_destroy(test_queue, free_test_entry); l_main_exit(); return exit_status; }