#include "testing.h"
char cwd_start[PATH_MAX];
char cwd_workspace[PATH_MAX];
extern char *dirstack[];
extern const ssize_t dirstack_max;
extern ssize_t dirstack_len;
void test_listdir() {
const char *dirs[] = {
"test_listdir",
"test_listdir/1",
"test_listdir/2",
"test_listdir/3",
};
for (size_t i = 0; i < sizeof(dirs) / sizeof(*dirs); i++) {
mkdir(dirs[i], 0755);
}
struct StrList *listing;
listing = listdir(dirs[0]);
STASIS_ASSERT(listing != NULL, "listdir failed");
STASIS_ASSERT(listing && (strlist_count(listing) == (sizeof(dirs) / sizeof(*dirs)) - 1), "should have 3");
guard_strlist_free(&listing);
}
void test_redact_sensitive() {
const char *data[] = {
"100 dollars!",
"bananas apples pears",
"have a safe trip",
};
const char *to_redact[] = {
"dollars",
"bananas",
"have a safe trip",
NULL,
};
const char *expected[] = {
"100 ***REDACTED***!",
"***REDACTED*** apples pears",
"***REDACTED***",
};
for (size_t i = 0; i < sizeof(data) / sizeof(*data); i++) {
char *input = strdup(data[i]);
char output[100] = {0};
redact_sensitive(to_redact, sizeof(to_redact) / sizeof(*to_redact), input, output, sizeof(output) - 1);
STASIS_ASSERT(strcmp(output, expected[i]) == 0, "incorrect redaction");
guard_free(input);
}
}
void test_fix_tox_conf() {
const char *filename = "tox.ini";
const char *data = "[testenv]\n"
"key_before = 1\n"
"commands = pytest -sx tests\n"
"key_after = 2\n";
const char *expected = "{posargs}\n";
char *result = NULL;
FILE *fp;
remove(filename);
fp = fopen(filename, "w");
if (fp) {
fprintf(fp, "%s", data);
fclose(fp);
STASIS_ASSERT(fix_tox_conf(filename, &result) == 0, "fix_tox_conf failed");
} else {
STASIS_ASSERT(false, "writing mock tox.ini failed");
}
char **lines = file_readlines(result, 0, 0, NULL);
STASIS_ASSERT(strstr_array(lines, expected) != NULL, "{posargs} not found in result");
guard_array_free(lines);
remove(result);
guard_free(result);
}
void test_xml_pretty_print_in_place() {
FILE *fp;
const char *filename = "ugly.xml";
const char *data = "123321";
const char *expected = "\n"
"\n"
" 123\n"
" 321\n"
"\n";
remove(filename);
fp = fopen(filename, "w");
if (fp) {
fprintf(fp, "%s", data);
fclose(fp);
STASIS_ASSERT(xml_pretty_print_in_place(
filename,
STASIS_XML_PRETTY_PRINT_PROG,
STASIS_XML_PRETTY_PRINT_ARGS) == 0,
"xml pretty print failed (xmllint not installed?)");
} else {
STASIS_ASSERT(false, "failed to create input file");
}
fp = fopen(filename, "r");
char buf[BUFSIZ] = {0};
if (fread(buf, sizeof(*buf), sizeof(buf) - 1, fp) < 1) {
STASIS_ASSERT(false, "failed to consume formatted xml file contents");
}
STASIS_ASSERT(strcmp(expected, buf) == 0, "xml file was not reformatted");
fclose(fp);
}
void test_path_store() {
char *dest = NULL;
chdir(cwd_workspace);
int result = path_store(&dest, PATH_MAX, cwd_workspace, "test_path_store");
STASIS_ASSERT(result == 0, "path_store encountered an error");
STASIS_ASSERT(dest != NULL, "dest should not be NULL");
STASIS_ASSERT(dest && (access(dest, F_OK) == 0), "destination path was not created");
rmtree(dest);
guard_free(dest);
}
void test_isempty_dir() {
const char *dname = "test_isempty_dir";
rmtree(dname);
mkdir(dname, 0755);
STASIS_ASSERT(isempty_dir(dname) > 0, "empty directory went undetected");
char path[PATH_MAX];
sprintf(path, "%s/file.txt", dname);
touch(path);
STASIS_ASSERT(isempty_dir(dname) == 0, "populated directory went undetected");
}
void test_xmkstemp() {
FILE *tempfp = NULL;
char *tempfile;
const char *data = "Hello, world!\n";
tempfile = xmkstemp(&tempfp, "w");
STASIS_ASSERT(tempfile != NULL, "failed to create temporary file");
fprintf(tempfp, "%s", data);
fclose(tempfp);
char buf[100] = {0};
tempfp = fopen(tempfile, "r");
fgets(buf, sizeof(buf) - 1, tempfp);
fclose(tempfp);
STASIS_ASSERT(strcmp(buf, data) == 0, "data written to temp file is incorrect");
remove(tempfile);
free(tempfile);
}
void test_msg() {
int flags_indent[] = {
0,
STASIS_MSG_L1,
STASIS_MSG_L2,
STASIS_MSG_L3,
};
int flags_info[] = {
//STASIS_MSG_NOP,
STASIS_MSG_SUCCESS,
STASIS_MSG_WARN,
STASIS_MSG_ERROR,
//STASIS_MSG_RESTRICT,
};
for (size_t i = 0; i < sizeof(flags_indent) / sizeof(*flags_indent); i++) {
for (size_t x = 0; x < sizeof(flags_info) / sizeof(*flags_info); x++) {
msg(flags_info[x] | flags_indent[i], "Indent level %zu...Message %zu\n", i, x);
}
}
}
void test_git_clone_and_describe() {
struct Process proc;
memset(&proc, 0, sizeof(proc));
const char *local_workspace = "test_git_clone";
const char *repo = "localrepo";
const char *repo_git = "localrepo.git";
char *cwd = getcwd(NULL, PATH_MAX);
// remove the bare repo, and local clone
rmtree(repo);
rmtree(repo_git);
mkdir(local_workspace, 0755);
chdir(local_workspace);
// initialize a bare repo so we can clone it
char init_cmd[PATH_MAX];
sprintf(init_cmd, "git init --bare %s", repo_git);
system(init_cmd);
// clone the bare repo
STASIS_ASSERT(git_clone(&proc, repo_git, repo, NULL) == 0,
"a local clone of bare repository should have succeeded");
chdir(repo);
// interact with it
touch("README.md");
system("git config user.name stasis");
system("git config user.email null@null.null");
system("git add README.md");
system("git commit --no-gpg-sign -m Test");
system("git push -u origin");
system("git --no-pager log");
// test git_describe is functional
char *taginfo_none = git_describe(".");
STASIS_ASSERT(taginfo_none != NULL, "should be a git hash, not NULL");
system("git tag -a 1.0.0 -m Mock");
system("git push --tags origin");
char *taginfo = git_describe(".");
STASIS_ASSERT(taginfo != NULL, "should be 1.0.0, not NULL");
STASIS_ASSERT(strcmp(taginfo, "1.0.0") == 0, "just-created tag was not described correctly");
chdir("..");
char *taginfo_outer = git_describe(repo);
STASIS_ASSERT(taginfo_outer != NULL, "should be 1.0.0, not NULL");
STASIS_ASSERT(strcmp(taginfo_outer, "1.0.0") == 0, "just-created tag was not described correctly (out-of-dir invocation)");
char *taginfo_bad = git_describe("abc1234_not_here_or_there");
STASIS_ASSERT(taginfo_bad == NULL, "a repository that shouldn't exist... exists and has a tag.");
chdir(cwd);
guard_free(cwd);
}
void test_touch() {
STASIS_ASSERT(touch("touchedfile.txt") == 0, "touch failed");
STASIS_ASSERT(access("touchedfile.txt", F_OK) == 0, "touched file does not exist");
remove("touchedfile.txt");
}
void test_find_program() {
STASIS_ASSERT(find_program("willnotexist123") == NULL, "did not return NULL");
STASIS_ASSERT(find_program("find") != NULL, "program not available (OS dependent)");
}
static int file_readlines_callback_modify(size_t size, char **line) {
char *data = (*line);
for (size_t i = 0; i < strlen(data); i++) {
if (isalnum(data[i])) {
data[i] = 'x';
}
}
return 0;
}
static int file_readlines_callback_get_specific_line(size_t size, char **line) {
char *data = (*line);
for (size_t i = 0; i < strlen(data); i++) {
if (!strcmp(data, "see?\n")) {
return 0;
}
}
return 1;
}
void test_file_readlines() {
const char *filename = "file_readlines.txt";
const char *data = "I am\na file\nwith multiple lines\nsee?\n";
FILE *fp = fopen(filename, "w");
if (!fp) {
perror(filename);
return;
}
if (fwrite(data, sizeof(*data), strlen(data), fp) != strlen(data)) {
perror("short write");
return;
}
fclose(fp);
char **result = NULL;
result = file_readlines(filename, 0, 0, NULL);
int i;
for (i = 0; result[i] != NULL; i++);
STASIS_ASSERT(num_chars(data, '\n') == i, "incorrect number of lines in data");
STASIS_ASSERT(strcmp(result[3], "see?\n") == 0, "last line in data is incorrect'");
guard_array_free(result);
result = file_readlines(filename, 0, 0, file_readlines_callback_modify);
STASIS_ASSERT(strcmp(result[3], "xxx?\n") == 0, "last line should be: 'xxx?\\n'");
guard_array_free(result);
result = file_readlines(filename, 0, 0, file_readlines_callback_get_specific_line);
STASIS_ASSERT(strcmp(result[0], "see?\n") == 0, "the first record of the result is not the last line of the file 'see?\\n'");
guard_array_free(result);
remove(filename);
}
void test_path_dirname() {
const char *data[] = {
"a/b/c", "a/b",
"This/is/a/test", "This/is/a",
};
for (size_t i = 0; i < sizeof(data) / sizeof(*data); i += 2) {
const char *input = data[i];
const char *expected = data[i + 1];
char tmp[PATH_MAX] = {0};
strcpy(tmp, input);
char *result = path_dirname(tmp);
STASIS_ASSERT(strcmp(expected, result) == 0, NULL);
}
}
void test_path_basename() {
const char *data[] = {
"a/b/c", "c",
"This/is/a/test", "test",
};
for (size_t i = 0; i < sizeof(data) / sizeof(*data); i += 2) {
const char *input = data[i];
const char *expected = data[i + 1];
char *result = path_basename(input);
STASIS_ASSERT(strcmp(expected, result) == 0, NULL);
}
}
void test_expandpath() {
char *home;
const char *homes[] = {
"HOME",
"USERPROFILE",
};
for (size_t i = 0; i < sizeof(homes) / sizeof(*homes); i++) {
home = getenv(homes[i]);
if (home) {
break;
}
}
char path[PATH_MAX] = {0};
strcat(path, "~");
strcat(path, DIR_SEP);
char *expanded = expandpath(path);
STASIS_ASSERT(startswith(expanded, home) > 0, expanded);
STASIS_ASSERT(endswith(expanded, DIR_SEP) > 0, "the input string ends with a directory separator, the result did not");
free(expanded);
}
void test_rmtree() {
const char *root = "rmtree_dir";
const char *tree[] = {
"abc",
"123",
"cba",
"321"
};
chdir(cwd_workspace);
mkdir(root, 0755);
for (size_t i = 0; i < sizeof(tree) / sizeof(*tree); i++) {
char path[PATH_MAX];
char mockfile[PATH_MAX + 10];
sprintf(path, "%s/%s", root, tree[i]);
sprintf(mockfile, "%s/%zu.txt", path, i);
if (mkdir(path, 0755)) {
perror(path);
STASIS_ASSERT(false, NULL);
}
touch(mockfile);
}
STASIS_ASSERT(rmtree(root) == 0, "rmtree should have been able to remove the directory");
STASIS_ASSERT(access(root, F_OK) < 0, "the directory is still present");
}
void test_dirstack() {
const char *data[] = {
"testdir",
"1",
"2",
"3",
};
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
for (size_t i = 0; i < sizeof(data) / sizeof(*data); i++) {
mkdir(data[i], 0755);
pushd(data[i]);
getcwd(cwd, PATH_MAX);
}
STASIS_ASSERT(dirstack_len == sizeof(data) / sizeof(*data), NULL);
STASIS_ASSERT(strcmp(dirstack[0], cwd_workspace) == 0, NULL);
for (int i = 1; i < dirstack_len; i++) {
STASIS_ASSERT(endswith(dirstack[i], data[i - 1]), NULL);
}
for (size_t i = 0, x = dirstack_len - 1; x != 0 && i < sizeof(data) / sizeof(*data); i++, x--) {
char *expected = strdup(dirstack[x]);
popd();
getcwd(cwd, PATH_MAX);
STASIS_ASSERT(strcmp(cwd, expected) == 0, NULL);
free(expected);
}
}
void test_pushd_popd() {
const char *dname = "testdir";
chdir(cwd_workspace);
rmtree(dname);
STASIS_ASSERT(mkdir(dname, 0755) == 0, "directory should not exist yet");
STASIS_ASSERT(pushd(dname) == 0, "failed to enter directory");
char *cwd = getcwd(NULL, PATH_MAX);
// we should be inside the test directory, not the starting directory
STASIS_ASSERT(strcmp(cwd_workspace, cwd) != 0, "");
STASIS_ASSERT(popd() == 0, "return from directory failed");
guard_free(cwd);
char *cwd_after_popd = getcwd(NULL, PATH_MAX);
STASIS_ASSERT(strcmp(cwd_workspace, cwd_after_popd) == 0, "should be the path where we started");
guard_free(cwd_after_popd);
}
void test_pushd_popd_suggested_workflow() {
const char *dname = "testdir";
chdir(cwd_workspace);
rmtree(dname);
remove(dname);
if (!mkdir(dname, 0755)) {
if (!pushd(dname)) {
char *cwd = getcwd(NULL, PATH_MAX);
STASIS_ASSERT(strcmp(cwd_workspace, cwd) != 0, NULL);
// return from dir
popd();
free(cwd);
}
// cwd should be our starting directory
char *cwd = getcwd(NULL, PATH_MAX);
STASIS_ASSERT(strcmp(cwd_workspace, cwd) == 0, NULL);
guard_free(cwd);
} else {
STASIS_ASSERT(false, "mkdir function failed");
}
}
int main(int argc, char *argv[]) {
STASIS_TEST_BEGIN_MAIN();
STASIS_TEST_FUNC *tests[] = {
test_listdir,
test_redact_sensitive,
test_fix_tox_conf,
test_xml_pretty_print_in_place,
test_path_store,
test_isempty_dir,
test_xmkstemp,
test_msg,
test_git_clone_and_describe,
test_touch,
test_find_program,
test_file_readlines,
test_path_dirname,
test_path_basename,
test_expandpath,
test_rmtree,
test_dirstack,
test_pushd_popd,
test_pushd_popd_suggested_workflow,
};
const char *ws = "workspace";
getcwd(cwd_start, sizeof(cwd_start) - 1);
mkdir(ws, 0755);
chdir(ws);
getcwd(cwd_workspace, sizeof(cwd_workspace) - 1);
STASIS_TEST_RUN(tests);
chdir(cwd_start);
if (rmtree(cwd_workspace)) {
perror(cwd_workspace);
}
STASIS_TEST_END_MAIN();
}