Files
vlibc/tests/test_unistd_misc.c

416 lines
11 KiB
C

/*
* vlibc — unistd misc / libgen / realpath / confstr test (todo 38).
*
* Coverage:
*
* 1. getcwd correctness: the result matches the raw SYS_getcwd answer,
* and the getcwd(NULL, 0) malloc form returns the same string;
* 2. terminal/identity probes: isatty on invalid descriptors is 0,
* ttyname(1) is NULL or a "/..." device path, ttyname_r returns 0
* or ENOTTY;
* 3. getopt round-trips: bundled options, separate and attached
* arguments, "--" termination, silent ':' and '?' error modes, and
* optind landing on the first non-option;
* 4. basename/dirname golden cases including the trailing-slash and
* degenerate "/" / "" names;
* 5. confstr: _CS_PATH queries return a length, copy the value into a
* big buffer and report the same length when the buffer is small;
* _CS_GNU_LIBC_VERSION is the empty string;
* 6. realpath: "." and "/" resolve correctly (also in the malloc'd
* form); getwd matches getcwd; swab swaps adjacent bytes.
*
* -f mode: the failure shapes — realpath(NULL) and of a nonexistent
* name, and getcwd(buf, 0) — return NULL without crashing.
*
* The whole body is level 2 (libgen/realpath/confstr are XSI; only the
* L1 functions appear inside it); at level 1 this TU compiles to a SKIP
* runner. The main always leaves through a raw SYS_exit_group: several
* probes (ttyname, realpath, getcwd failures) write the library's errno
* slot, which collides with the host TCB, so host cleanup must never
* run after them (house pattern).
*
* No host headers are included (-Iinclude shadows them); argv arrays are
* plain writable char*[] so getopt can scan them.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <unistd.h>
#include <vlibc/internal/test.h>
#if VLIBC_LEVEL_GE(2)
/* Reset the getopt globals between scenarios. */
static void
uopt_reset(void)
{
optarg = NULL;
optind = 1;
opterr = 0;
optopt = 0;
}
static int
test_getcwd(void)
{
char kern[4096];
char mine[4096];
char *dyn;
long n;
n = __syscall2(SYS_getcwd, (long)kern, (long)sizeof(kern));
TEST_ASSERT_TRUE(n > 0);
TEST_ASSERT_TRUE(getcwd(mine, sizeof(mine)) == mine);
TEST_ASSERT_STREQ(mine, kern);
dyn = getcwd(NULL, 0);
TEST_ASSERT_TRUE(dyn != NULL);
if (dyn != NULL)
{
TEST_ASSERT_STREQ(dyn, kern);
free(dyn);
}
return 0;
}
static int
test_isatty_ttyname(void)
{
char name[256];
char *t;
int rc;
TEST_ASSERT_EQ(isatty(-1), 0);
TEST_ASSERT_EQ(isatty(4096), 0);
/* fd 1 may or may not be a terminal in the harness; the contract is
* only that isatty answers 0 or 1 without crashing. */
TEST_ASSERT_TRUE(isatty(1) == 0 || isatty(1) == 1);
rc = ttyname_r(1, name, sizeof(name));
TEST_ASSERT_TRUE(rc == 0 || rc == ENOTTY || rc == ERANGE);
if (rc == 0)
{
TEST_ASSERT_TRUE(name[0] == '/');
}
t = ttyname(1);
TEST_ASSERT_TRUE(t == NULL || t[0] == '/');
return 0;
}
static int
test_getopt_basic(void)
{
char *av[] = {"prog", "-a", "-b", "rest", NULL};
uopt_reset();
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a');
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'b');
TEST_ASSERT_EQ(getopt(4, av, "ab"), -1);
TEST_ASSERT_EQ(optind, 3); /* optind sits on the first non-option */
TEST_ASSERT_TRUE(optarg == NULL);
return 0;
}
static int
test_getopt_bundled(void)
{
char *av[] = {"prog", "-ab", "tail", NULL};
uopt_reset();
TEST_ASSERT_EQ(getopt(3, av, "ab"), 'a');
TEST_ASSERT_EQ(getopt(3, av, "ab"), 'b');
TEST_ASSERT_EQ(getopt(3, av, "ab"), -1);
TEST_ASSERT_EQ(optind, 2); /* the first non-option is "tail" */
return 0;
}
static int
test_getopt_args(void)
{
char *av1[] = {"prog", "-barg", "tail", NULL};
char *av2[] = {"prog", "-b", "arg", "tail", NULL};
/* Attached argument: "-barg" gives optarg "arg" from the same
* element. */
uopt_reset();
TEST_ASSERT_EQ(getopt(3, av1, "b:"), 'b');
TEST_ASSERT_STREQ(optarg, "arg");
TEST_ASSERT_EQ(getopt(3, av1, "b:"), -1);
/* Separate argument: "-b arg". */
uopt_reset();
TEST_ASSERT_EQ(getopt(4, av2, "b:"), 'b');
TEST_ASSERT_STREQ(optarg, "arg");
TEST_ASSERT_EQ(getopt(4, av2, "b:"), -1);
TEST_ASSERT_EQ(optind, 3);
return 0;
}
static int
test_getopt_errors(void)
{
char *av1[] = {"prog", "-b", NULL};
char *av2[] = {"prog", "-z", NULL};
char *av3[] = {"prog", "-azb", NULL};
/* Missing argument with a leading ':' returns ':' silently. */
uopt_reset();
TEST_ASSERT_EQ(getopt(2, av1, ":b:"), ':');
TEST_ASSERT_EQ(optopt, 'b');
TEST_ASSERT_EQ(getopt(2, av1, ":b:"), -1);
/* Unknown option returns '?', optopt carries the character. */
uopt_reset();
TEST_ASSERT_EQ(getopt(2, av2, "ab"), '?');
TEST_ASSERT_EQ(optopt, 'z');
TEST_ASSERT_EQ(getopt(2, av2, "ab"), -1);
/* An unknown option in the middle of a bundle does not stop the
* scan: the remaining letters are still returned. */
uopt_reset();
TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'a');
TEST_ASSERT_EQ(getopt(2, av3, "ab"), '?');
TEST_ASSERT_EQ(optopt, 'z');
TEST_ASSERT_EQ(getopt(2, av3, "ab"), 'b');
TEST_ASSERT_EQ(getopt(2, av3, "ab"), -1);
return 0;
}
static int
test_getopt_dashdash(void)
{
char *av[] = {"prog", "-a", "--", "-b", NULL};
uopt_reset();
TEST_ASSERT_EQ(getopt(4, av, "ab"), 'a');
TEST_ASSERT_EQ(getopt(4, av, "ab"), -1);
TEST_ASSERT_EQ(optind, 3); /* "--" consumed; "-b" is the first non-option */
return 0;
}
static int
test_basename(void)
{
char s1[] = "/usr/lib";
char s2[] = "/usr/lib/";
char s3[] = "/";
char s4[] = "//";
char s5[] = "usr";
char s6[] = "";
char s7[] = "/usr";
TEST_ASSERT_STREQ(basename(s1), "lib");
TEST_ASSERT_STREQ(basename(s2), "lib");
TEST_ASSERT_STREQ(basename(s3), "/");
TEST_ASSERT_STREQ(basename(s4), "/");
TEST_ASSERT_STREQ(basename(s5), "usr");
TEST_ASSERT_STREQ(basename(s6), ".");
TEST_ASSERT_STREQ(basename(s7), "usr");
return 0;
}
static int
test_dirname(void)
{
char s1[] = "/usr/lib";
char s2[] = "/usr/lib/";
char s3[] = "/";
char s4[] = "//";
char s5[] = "usr";
char s6[] = "";
char s7[] = "/usr";
char s8[] = "usr/";
TEST_ASSERT_STREQ(dirname(s1), "/usr");
TEST_ASSERT_STREQ(dirname(s2), "/usr");
TEST_ASSERT_STREQ(dirname(s3), "/");
TEST_ASSERT_STREQ(dirname(s4), "/");
TEST_ASSERT_STREQ(dirname(s5), ".");
TEST_ASSERT_STREQ(dirname(s6), ".");
TEST_ASSERT_STREQ(dirname(s7), "/");
TEST_ASSERT_STREQ(dirname(s8), ".");
return 0;
}
static int
test_confstr(void)
{
char buf[128];
char small[8];
size_t n;
n = confstr(_CS_PATH, NULL, 0);
TEST_ASSERT_TRUE(n > 1);
TEST_ASSERT_EQ(confstr(_CS_PATH, buf, sizeof(buf)), n);
TEST_ASSERT_TRUE(buf[0] == '/');
TEST_ASSERT_TRUE(buf[n - 1] == '\0');
/* A too-small buffer is left alone but the same length is reported. */
TEST_ASSERT_EQ(confstr(_CS_PATH, small, 2), n);
/* Not glibc: the version keys are the empty string. */
TEST_ASSERT_EQ(confstr(_CS_GNU_LIBC_VERSION, buf, sizeof(buf)), 1);
TEST_ASSERT_STREQ(buf, "");
return 0;
}
static int
test_realpath(void)
{
char here[4096];
char in[4096];
char out[4096];
char parent[4096];
char *dyn;
char *slash;
TEST_ASSERT_TRUE(realpath(".", out) == out);
TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here);
TEST_ASSERT_STREQ(out, here);
TEST_ASSERT_STREQ(realpath("/", out), "/");
/* "/proc/self/cwd" is a symlink to the working directory: exercises
* readlink plus an absolute link target. NULL only when procfs is
* absent. */
if (realpath("/proc/self/cwd", out) != NULL)
{
TEST_ASSERT_STREQ(out, here);
}
/* ".." pops one component: cwd/.. resolves to cwd's parent, found by
* truncating here at its last slash (the root has no parent). The
* input and resolved buffers are distinct: realpath reads the path
* while it rewrites the resolved buffer. */
slash = strrchr(here, '/');
if (slash != NULL && slash != here)
{
size_t plen = (size_t)(slash - here);
memcpy(parent, here, plen);
parent[plen] = '\0';
strcpy(in, here);
strcpy(in + strlen(in), "/..");
TEST_ASSERT_TRUE(realpath(in, out) == out);
TEST_ASSERT_STREQ(out, parent);
}
dyn = realpath(".", NULL);
TEST_ASSERT_TRUE(dyn != NULL);
if (dyn != NULL)
{
TEST_ASSERT_STREQ(dyn, here);
free(dyn);
}
return 0;
}
static int
test_getwd_swab(void)
{
char wd[4096];
char here[4096];
char out[7] = {0};
static const char six[7] = "abcdef";
TEST_ASSERT_TRUE(getwd(wd) == wd);
TEST_ASSERT_TRUE(getcwd(here, sizeof(here)) == here);
TEST_ASSERT_STREQ(wd, here);
swab(six, out, 6);
TEST_ASSERT_STREQ(out, "badcfe");
return 0;
}
static const struct vlibc_test tests[] = {
{"getcwd", test_getcwd},
{"isatty-ttyname", test_isatty_ttyname},
{"getopt-basic", test_getopt_basic},
{"getopt-bundled", test_getopt_bundled},
{"getopt-args", test_getopt_args},
{"getopt-errors", test_getopt_errors},
{"getopt-dashdash", test_getopt_dashdash},
{"basename", test_basename},
{"dirname", test_dirname},
{"confstr", test_confstr},
{"realpath", test_realpath},
{"getwd-swab", test_getwd_swab},
};
/*
* Own main (not TEST_MAIN): supports the -f failure mode and always
* leaves through the raw SYS_exit_group (see the file header).
*/
int
main(int argc, char **argv)
{
const size_t count = sizeof tests / sizeof tests[0];
size_t passed = 0;
size_t i;
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
{
int before = vlibc_test_failures;
char out[64];
vlibc_test_say(1, "RUN failure-shapes: ");
TEST_ASSERT_TRUE(realpath(NULL, out) == NULL);
TEST_ASSERT_TRUE(realpath("/no/such/vlibc-xyz", out) == NULL);
TEST_ASSERT_TRUE(getcwd(out, 0) == NULL);
if (vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
}
else
{
vlibc_test_say(1, "FAIL\n");
}
__syscall1(SYS_exit_group, vlibc_test_failures == before ? 0 : 1);
/* not reached */
}
for (i = 0; i < count; i++)
{
int before = vlibc_test_failures;
vlibc_test_say(1, "RUN ");
vlibc_test_say(1, tests[i].name);
vlibc_test_say(1, ": ");
if (tests[i].run() == 0 && vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
passed++;
}
else
{
vlibc_test_say(1, "FAIL\n");
}
}
vlibc_test_say(1, "SUMMARY: ");
vlibc_test_say_dec(1, (unsigned long)passed);
vlibc_test_say(1, "/");
vlibc_test_say_dec(1, (unsigned long)count);
vlibc_test_say(1, " passed, ");
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
vlibc_test_say(1, " assertion failure(s)\n");
__syscall1(SYS_exit_group, passed == count ? 0 : 1);
/* not reached */
}
#else /* !VLIBC_LEVEL_GE(2) */
int
main(void)
{
vlibc_test_say(1, "SKIP: this surface is level 2, not available here\n");
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */