326 lines
9.8 KiB
C
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;
|
|
}
|