/* * The strto* family -- TODO.md section 3.1. * * These are the real implementation behind the ato* wrappers and the thing * akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because * the library would not do it. What they add over the ato* forms is a base and * an endptr: pass a non-NULL endptr to parse a number off the front of a longer * string and find out where it stopped, and the trailing-junk check goes away * because the caller has taken responsibility for what follows. * * tests/test_convert.c covers what the two families share; this file is the part * that is specific to having a base and an endptr. */ #include "aksl_capture.h" #include #include /* ---------------------------------------------------------------------- */ /* Base handling */ /* ---------------------------------------------------------------------- */ static int test_base_is_honoured(void) { long out = 0; AKSL_CHECK_OK(aksl_strtol("ff", NULL, 16, &out)); AKSL_CHECK(out == 255); AKSL_CHECK_OK(aksl_strtol("1010", NULL, 2, &out)); AKSL_CHECK(out == 10); AKSL_CHECK_OK(aksl_strtol("777", NULL, 8, &out)); AKSL_CHECK(out == 511); AKSL_CHECK_OK(aksl_strtol("z", NULL, 36, &out)); AKSL_CHECK(out == 35); return 0; } /* Base 0 auto-detects the 0x and 0 prefixes, which is the counterpart to * tests/test_convert.c's test that aksl_atoi refuses "0x10". */ static int test_base_zero_detects_the_prefix(void) { long out = 0; AKSL_CHECK_OK(aksl_strtol("0x10", NULL, 0, &out)); AKSL_CHECK(out == 16); AKSL_CHECK_OK(aksl_strtol("010", NULL, 0, &out)); AKSL_CHECK(out == 8); AKSL_CHECK_OK(aksl_strtol("10", NULL, 0, &out)); AKSL_CHECK(out == 10); return 0; } /* A digit that does not belong to the base is where the number ends. */ static int test_digits_outside_the_base_end_the_number(void) { long out = 0; char *end = NULL; /* "9" is not an octal digit, so with a NULL endptr it is trailing junk. */ AKSL_CHECK_STATUS(aksl_strtol("129", NULL, 8, &out), AKERR_VALUE); /* With an endptr the caller gets the partial parse and the stopping point. */ AKSL_CHECK_OK(aksl_strtol("129", &end, 8, &out)); AKSL_CHECK(out == 10); /* 012 octal */ AKSL_CHECK(end != NULL && *end == '9'); return 0; } /* An invalid base is EINVAL from strtol(3) rather than a silent answer. */ static int test_invalid_base_is_rejected(void) { long out = 99; AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 37, &out), EINVAL); AKSL_CHECK(out == 0); AKSL_CHECK_STATUS(aksl_strtol("10", NULL, 1, &out), EINVAL); AKSL_CHECK(out == 0); return 0; } /* ---------------------------------------------------------------------- */ /* endptr */ /* ---------------------------------------------------------------------- */ /* * With an endptr, trailing text is the caller's business and not an error. This * is the shape a tokenizer wants: parse a number, carry on from where it ended. */ static int test_endptr_reports_where_parsing_stopped(void) { const char *input = "42 rest of the line"; char *end = NULL; long out = 0; AKSL_CHECK_OK(aksl_strtol(input, &end, 10, &out)); AKSL_CHECK(out == 42); AKSL_CHECK(end == input + 2); AKSL_CHECK(strcmp(end, " rest of the line") == 0); return 0; } /* Consuming nothing is still an error, endptr or not: there was no number. */ static int test_no_digits_is_an_error_even_with_an_endptr(void) { char *end = (char *)0x1; long out = 99; AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtol("nope", &end, 10, &out), AKERR_VALUE, "no digits"); AKSL_CHECK(out == 0); return 0; } /* * Every form writes through endptr, not just the two the other tests happen to * use. Each type has its own function body, so "strtol handles endptr" says * nothing whatsoever about strtoull. */ static int test_every_form_writes_the_endptr(void) { const char *input = "42rest"; char *end = NULL; long lout = 0; long long llout = 0; unsigned long ulout = 0; unsigned long long ullout = 0; double dout = 0.0; float fout = 0.0f; long double ldout = 0.0L; end = NULL; AKSL_CHECK_OK(aksl_strtol(input, &end, 10, &lout)); AKSL_CHECK(lout == 42 && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtoll(input, &end, 10, &llout)); AKSL_CHECK(llout == 42 && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtoul(input, &end, 10, &ulout)); AKSL_CHECK(ulout == 42 && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtoull(input, &end, 10, &ullout)); AKSL_CHECK(ullout == 42 && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtod(input, &end, &dout)); AKSL_CHECK(dout == 42.0 && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtof(input, &end, &fout)); AKSL_CHECK(fout == 42.0f && strcmp(end, "rest") == 0); end = NULL; AKSL_CHECK_OK(aksl_strtold(input, &end, &ldout)); AKSL_CHECK(ldout == 42.0L && strcmp(end, "rest") == 0); return 0; } /* Successive calls chained through endptr walk a whole list of numbers. */ static int test_endptr_chains_across_a_list(void) { const char *input = "1,2,3"; char *cursor = (char *)input; long out = 0; long total = 0; int seen = 0; while ( *cursor != '\0' ) { AKSL_CHECK_OK(aksl_strtol(cursor, &cursor, 10, &out)); total += out; seen += 1; if ( *cursor == ',' ) { cursor++; } } AKSL_CHECK(seen == 3); AKSL_CHECK(total == 6); return 0; } /* ---------------------------------------------------------------------- */ /* Ranges and signs */ /* ---------------------------------------------------------------------- */ static int test_range_errors_per_type(void) { long lout = 99; long long llout = 99; unsigned long ulout = 99; unsigned long long ullout = 99; char buf[64]; snprintf(buf, sizeof(buf), "%lld9", (long long)LLONG_MAX); AKSL_CHECK_STATUS(aksl_strtol(buf, NULL, 10, &lout), ERANGE); AKSL_CHECK(lout == 0); AKSL_CHECK_STATUS(aksl_strtoll(buf, NULL, 10, &llout), ERANGE); AKSL_CHECK(llout == 0); snprintf(buf, sizeof(buf), "%llu9", (unsigned long long)ULLONG_MAX); AKSL_CHECK_STATUS(aksl_strtoul(buf, NULL, 10, &ulout), ERANGE); AKSL_CHECK(ulout == 0); AKSL_CHECK_STATUS(aksl_strtoull(buf, NULL, 10, &ullout), ERANGE); AKSL_CHECK(ullout == 0); return 0; } /* * strtoul(3) and strtoull(3) accept a leading '-' and hand back the negation * wrapped into the unsigned range, so "-1" parses as ULONG_MAX and reports * nothing at all. That is exactly the silent failure this library exists to * surface, so the sign is refused before the call. */ static int test_unsigned_forms_refuse_a_negative(void) { unsigned long ulout = 99; unsigned long long ullout = 99; AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_strtoul("-1", NULL, 10, &ulout), AKERR_VALUE, "negative"); AKSL_CHECK(ulout == 0); /* Including behind leading whitespace, which strtoul skips. */ AKSL_CHECK_STATUS(aksl_strtoul(" -1", NULL, 10, &ulout), AKERR_VALUE); AKSL_CHECK_STATUS(aksl_strtoull("-1", NULL, 10, &ullout), AKERR_VALUE); AKSL_CHECK(ullout == 0); /* A leading '+' is fine: it is a sign, not a negation. */ AKSL_CHECK_OK(aksl_strtoul("+7", NULL, 10, &ulout)); AKSL_CHECK(ulout == 7); return 0; } static int test_unsigned_boundaries_round_trip(void) { unsigned long ulout = 0; unsigned long long ullout = 0; char buf[64]; snprintf(buf, sizeof(buf), "%lu", ULONG_MAX); AKSL_CHECK_OK(aksl_strtoul(buf, NULL, 10, &ulout)); AKSL_CHECK(ulout == ULONG_MAX); snprintf(buf, sizeof(buf), "%llu", ULLONG_MAX); AKSL_CHECK_OK(aksl_strtoull(buf, NULL, 10, &ullout)); AKSL_CHECK(ullout == ULLONG_MAX); return 0; } /* ---------------------------------------------------------------------- */ /* Floating point */ /* ---------------------------------------------------------------------- */ static int test_floating_point_forms(void) { double dout = 0.0; float fout = 0.0f; long double ldout = 0.0L; char *end = NULL; AKSL_CHECK_OK(aksl_strtod("2.5", NULL, &dout)); AKSL_CHECK(dout == 2.5); AKSL_CHECK_OK(aksl_strtof("2.5", NULL, &fout)); AKSL_CHECK(fout == 2.5f); AKSL_CHECK_OK(aksl_strtold("2.5", NULL, &ldout)); AKSL_CHECK(ldout == 2.5L); AKSL_CHECK_OK(aksl_strtod("2.5rest", &end, &dout)); AKSL_CHECK(dout == 2.5); AKSL_CHECK(strcmp(end, "rest") == 0); AKSL_CHECK_STATUS(aksl_strtod("2.5rest", NULL, &dout), AKERR_VALUE); AKSL_CHECK_STATUS(aksl_strtof("nope", NULL, &fout), AKERR_VALUE); AKSL_CHECK_STATUS(aksl_strtold("nope", NULL, &ldout), AKERR_VALUE); /* float has a much smaller range than double, and says so. */ AKSL_CHECK_STATUS(aksl_strtof("1e300", NULL, &fout), ERANGE); AKSL_CHECK(fout == 0.0f); AKSL_CHECK_OK(aksl_strtod("1e300", NULL, &dout)); AKSL_CHECK(dout > 0.0); return 0; } /* ---------------------------------------------------------------------- */ /* Argument validation */ /* ---------------------------------------------------------------------- */ static int test_every_form_rejects_null_arguments(void) { long lout = 0; long long llout = 0; unsigned long ulout = 0; unsigned long long ullout = 0; double dout = 0.0; float fout = 0.0f; long double ldout = 0.0L; AKSL_CHECK_STATUS(aksl_strtol(NULL, NULL, 10, &lout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtol("1", NULL, 10, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoll(NULL, NULL, 10, &llout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoll("1", NULL, 10, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoul(NULL, NULL, 10, &ulout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoul("1", NULL, 10, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoull(NULL, NULL, 10, &ullout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtoull("1", NULL, 10, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtod(NULL, NULL, &dout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtod("1", NULL, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtof(NULL, NULL, &fout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtof("1", NULL, NULL), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtold(NULL, NULL, &ldout), AKERR_NULLPOINTER); AKSL_CHECK_STATUS(aksl_strtold("1", NULL, NULL), AKERR_NULLPOINTER); return 0; } int main(void) { int failures = 0; akerr_init(); AKSL_RUN(failures, test_base_is_honoured); AKSL_RUN(failures, test_base_zero_detects_the_prefix); AKSL_RUN(failures, test_digits_outside_the_base_end_the_number); AKSL_RUN(failures, test_invalid_base_is_rejected); AKSL_RUN(failures, test_endptr_reports_where_parsing_stopped); AKSL_RUN(failures, test_every_form_writes_the_endptr); AKSL_RUN(failures, test_no_digits_is_an_error_even_with_an_endptr); AKSL_RUN(failures, test_endptr_chains_across_a_list); AKSL_RUN(failures, test_range_errors_per_type); AKSL_RUN(failures, test_unsigned_forms_refuse_a_negative); AKSL_RUN(failures, test_unsigned_boundaries_round_trip); AKSL_RUN(failures, test_floating_point_forms); AKSL_RUN(failures, test_every_form_rejects_null_arguments); AKSL_REPORT(failures); }