Files

326 lines
9.8 KiB
C

/*
* vlibc — ctype.h classification + case mapping test (todo 9).
*
* Coverage:
*
* 1. classification sweep: every is* function, for all 256 unsigned char
* values AND EOF, must agree with the "C" locale ASCII ranges the
* standard specifies — the golden model below derives its expectation
* straight from those ranges, independent of the implementation's
* table;
* 2. case-mapping sweep: tolower/toupper over all 256 values + EOF (map
* A-Z/a-z, pass every non-letter through unchanged, EOF -> EOF);
* 3. negative-char sweep: every negative signed-char value passed as int
* must classify false in all is* functions, case-map to itself, and
* never fault — proving the unsigned char cast keeps negative values
* from indexing out of bounds (isalpha((char)0xe9) == 0 included);
* 4. spot checks from the todo acceptance list;
* 5. -f: the negative QA — isprint('\n') == 0, isalpha(EOF) == 0,
* tolower(EOF)/toupper(EOF) == EOF (a nonzero isprint('\n') is the
* defect).
*
* No host headers: <ctype.h> is vlibc's own (-Iinclude shadows the system
* one), and every diagnostic goes through the raw SYS_write helpers from
* <vlibc/internal/test.h>. Compiled with -DVLIBC_LEVEL=2 so both the L1
* functions and the L2 isascii/toascii gate are exercised (mirrors
* libvlibc-check.a, which builds the level-2 surface).
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <ctype.h>
#include <vlibc/internal/test.h>
/* EOF as the standard defines it; ctype.h deliberately does not provide it. */
#define TEST_EOF (-1)
static int failures;
/* ---- Golden model: the "C" locale classes, as the standard defines them ---- */
enum
{
CLS_ALNUM,
CLS_ALPHA,
CLS_BLANK,
CLS_CNTRL,
CLS_DIGIT,
CLS_GRAPH,
CLS_LOWER,
CLS_PRINT,
CLS_PUNCT,
CLS_SPACE,
CLS_UPPER,
CLS_XDIGIT
};
typedef int (*ctype_pred)(int);
/* The real functions under test, indexed by the CLS_* enum above. */
static const ctype_pred cls_preds[] = {isalnum, isalpha, isblank, iscntrl, isdigit, isgraph,
islower, isprint, ispunct, isspace, isupper, isxdigit};
static const char *const cls_names[] = {"isalnum", "isalpha", "isblank", "iscntrl",
"isdigit", "isgraph", "islower", "isprint",
"ispunct", "isspace", "isupper", "isxdigit"};
static int
expect_class(int c, int cls) // NOLINT(bugprone-easily-swappable-parameters)
{
if (c < 0 || c > 127)
{
return 0;
}
// NOLINTBEGIN(bugprone-switch-missing-default-case)
switch (cls)
{
case CLS_UPPER:
return c >= 'A' && c <= 'Z';
case CLS_LOWER:
return c >= 'a' && c <= 'z';
case CLS_DIGIT:
return c >= '0' && c <= '9';
case CLS_ALPHA:
return expect_class(c, CLS_UPPER) || expect_class(c, CLS_LOWER);
case CLS_ALNUM:
return expect_class(c, CLS_ALPHA) || expect_class(c, CLS_DIGIT);
case CLS_XDIGIT:
return expect_class(c, CLS_DIGIT) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
case CLS_CNTRL:
return (c >= 0x00 && c <= 0x1f) || c == 0x7f;
case CLS_BLANK:
return c == ' ' || c == '\t';
case CLS_SPACE:
return c == ' ' || (c >= '\t' && c <= '\r');
case CLS_PUNCT:
return (c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') ||
(c >= '{' && c <= '~');
case CLS_GRAPH:
return expect_class(c, CLS_ALNUM) || expect_class(c, CLS_PUNCT);
case CLS_PRINT:
return expect_class(c, CLS_GRAPH) || c == ' ';
}
// NOLINTEND(bugprone-switch-missing-default-case)
return 0;
}
static int
expect_tolower(int c)
{
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
}
static int
expect_toupper(int c)
{
return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
}
/* ---- Sweeps ---- */
/* 1. All 256 byte values + EOF against the golden model, per class. */
static void
classification_sweep(void)
{
size_t k;
for (k = 0; k < sizeof(cls_preds) / sizeof(cls_preds[0]); k++)
{
int c;
for (c = 0; c < 256; c++)
{
if ((cls_preds[k](c) != 0) == (expect_class(c, (int)k) != 0))
{
continue;
}
vlibc_test_say(2, "mismatch: ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "(0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, ")\n");
failures++;
break;
}
if (cls_preds[k](TEST_EOF) != 0)
{
vlibc_test_say(2, "mismatch: ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "(EOF)\n");
failures++;
}
}
}
/* 2. Case mapping over all 256 byte values + EOF. */
static void
case_mapping_sweep(void)
{
int c;
for (c = 0; c < 256; c++)
{
if (tolower(c) != expect_tolower(c))
{
vlibc_test_say(2, "tolower mismatch at 0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, "\n");
failures++;
}
if (toupper(c) != expect_toupper(c))
{
vlibc_test_say(2, "toupper mismatch at 0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, "\n");
failures++;
}
}
if (tolower(TEST_EOF) != TEST_EOF || toupper(TEST_EOF) != TEST_EOF)
{
vlibc_test_say(2, "case mapping of EOF did not return EOF unchanged\n");
failures++;
}
}
/* 3. Every negative signed-char value: classify false, case-map to itself. */
static void
negative_char_sweep(void)
{
signed char sc;
size_t k;
for (sc = (signed char)-128; (int)sc < 0; sc++)
{
int c = (int)sc;
for (k = 0; k < sizeof(cls_preds) / sizeof(cls_preds[0]); k++)
{
if (cls_preds[k](c) != 0)
{
vlibc_test_say(2, "negative value classified nonzero by ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "\n");
failures++;
}
}
if (tolower(c) != c || toupper(c) != c)
{
vlibc_test_say(2, "negative value case-mapped instead of passing through\n");
failures++;
}
#if VLIBC_LEVEL_GE(2)
if (isascii(c) != 0)
{
vlibc_test_say(2, "negative value classified ASCII by isascii\n");
failures++;
}
if (toascii(c) != (c & 0x7f))
{
vlibc_test_say(2, "toascii mismatch on a negative value\n");
failures++;
}
#endif /* VLIBC_LEVEL_GE(2) */
}
}
/* 4. Acceptance-list spot checks. */
static void
spot_checks(void)
{
TEST_ASSERT_EQ(tolower('A'), 'a');
TEST_ASSERT_EQ(toupper('a'), 'A');
TEST_ASSERT_EQ(tolower('a'), 'a'); /* lowercase identity */
TEST_ASSERT_EQ(toupper('Z'), 'Z'); /* uppercase identity */
TEST_ASSERT_EQ(tolower('Z'), 'z');
TEST_ASSERT_EQ(toupper('z'), 'Z');
TEST_ASSERT_EQ(tolower('0'), '0'); /* digit identity */
TEST_ASSERT_TRUE(isupper('A'));
TEST_ASSERT_TRUE(islower('a'));
TEST_ASSERT_TRUE(isalpha(TEST_EOF) == 0);
TEST_ASSERT_TRUE(isprint('\n') == 0); /* negative QA: no false positive */
TEST_ASSERT_TRUE(isdigit('5'));
TEST_ASSERT_TRUE(!isalpha('5'));
TEST_ASSERT_TRUE(isspace(' '));
TEST_ASSERT_TRUE(isspace('\n'));
TEST_ASSERT_TRUE(ispunct('!'));
TEST_ASSERT_TRUE(!ispunct('A'));
TEST_ASSERT_TRUE(isgraph('~'));
TEST_ASSERT_TRUE(!isgraph(' '));
TEST_ASSERT_TRUE(isprint(' '));
TEST_ASSERT_TRUE(isblank('\t'));
TEST_ASSERT_TRUE(isblank(' '));
TEST_ASSERT_TRUE(!isblank('x'));
TEST_ASSERT_TRUE(iscntrl(0x7f));
TEST_ASSERT_TRUE(!iscntrl(' '));
TEST_ASSERT_TRUE(isxdigit('f'));
TEST_ASSERT_TRUE(isxdigit('F'));
TEST_ASSERT_TRUE(!isxdigit('g'));
TEST_ASSERT_TRUE(!isalnum('@'));
TEST_ASSERT_TRUE(isalnum('A'));
TEST_ASSERT_TRUE(isalpha((char)0xe9) == 0); /* negative char: (char)0xe9 == -23 */
#if VLIBC_LEVEL_GE(2)
TEST_ASSERT_TRUE(!isascii(0x80));
TEST_ASSERT_TRUE(isascii(0x00));
TEST_ASSERT_TRUE(isascii(0x7f));
TEST_ASSERT_TRUE(!isascii(TEST_EOF));
TEST_ASSERT_EQ(toascii('A'), 'A'); /* c & 0x7f, not a predicate */
TEST_ASSERT_EQ(toascii(0x80), 0);
TEST_ASSERT_EQ(toascii(TEST_EOF), 0x7f);
#endif /* VLIBC_LEVEL_GE(2) */
}
/*
* Failure scenario (-f): the negative QA from the todo. Exits 0 only when
* each defect is absent.
*/
static int
failure_scenario(void)
{
if (isprint('\n') != 0)
{
vlibc_test_say(1, "isprint('\\n') != 0 (false positive)\n");
return 1;
}
if (isalpha(TEST_EOF) != 0)
{
vlibc_test_say(1, "isalpha(EOF) != 0\n");
return 1;
}
if (tolower(TEST_EOF) != TEST_EOF || toupper(TEST_EOF) != TEST_EOF)
{
vlibc_test_say(1, "tolower/toupper(EOF) did not return EOF unchanged\n");
return 1;
}
vlibc_test_say(1, "negative QA: isprint('\\n') == 0, isalpha(EOF) == 0, "
"tolower/toupper(EOF) == EOF\n");
return 0;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
return failure_scenario();
}
classification_sweep();
case_mapping_sweep();
negative_char_sweep();
spot_checks();
if (failures > 0 || vlibc_test_failures > 0)
{
vlibc_test_say(2, "FAILED (");
// NOLINTNEXTLINE(bugprone-misplaced-widening-cast)
vlibc_test_say_dec(2, (unsigned long)(failures + vlibc_test_failures));
vlibc_test_say(2, " check(s))\n");
return 1;
}
vlibc_test_say(1, "all ctype tests passed\n");
return 0;
}