diff --git a/.default-version b/.default-version index 7dea76e..cd5ac03 100644 --- a/.default-version +++ b/.default-version @@ -1 +1 @@ -1.0.1 +2.0 diff --git a/CHANGES.md b/CHANGES.md index 48400e2..20c958c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,20 @@ # Changes and What's New in strntoul +#### 2.0.0 (2026-04-06) + + * Added six new interfaces: + + 1. `strnto[u]ll` + 2. `strnto[iu]max` + 3. `strnto[u]q` + + * Fixed a bug in signed conversion where values representable in the + unsigned type but exceeding the signed range (for example, + 0x8000000000000000 for a 64-bit signed type) were silently cast rather + than clamped with `ERANGE`. Historically, this affected `strntol` for + inputs between `LONG_MAX` + 1 and `ULONG_MAX` that did not trigger unsigned + overflow. + #### 1.0.1 (2026-04-04) * Addresses an issue with single digit conversion of zero ('0') that should @@ -7,7 +22,5 @@ #### 1.0.0 (2024-02-28) - * Initial public release independent of [OpenHLX] - (https://github.com/gerickson/openhlx/), from which [these sources came] - (https://github.com/gerickson/openhlx/tree/main/src/lib/utilities). - + * Initial public release independent of [OpenHLX](https://github.com/gerickson/openhlx/), + from which [these sources came](https://github.com/gerickson/openhlx/tree/main/src/lib/utilities). diff --git a/src/Makefile.am b/src/Makefile.am index 9e25a28..8f026e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2024 Grant Erickson. All Rights Reserved. +# Copyright 2024-2026 Grant Erickson. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,13 +30,22 @@ SUBDIRS = \ $(NULL) noinst_HEADERS = \ + strntoul-detail-signed-template.hpp \ + strntoul-detail-unsigned-template.hpp \ + strntoul-detail.hpp \ $(NULL) # Public library headers to distribute and install. include_HEADERS = \ + strntoimax.h \ strntol.h \ + strntoll.h \ + strntoq.h \ strntoul.h \ + strntoull.h \ + strntoumax.h \ + strntouq.h \ $(NULL) libstrntoul_la_LDFLAGS = \ @@ -44,12 +53,20 @@ libstrntoul_la_LDFLAGS = \ $(NULL) libstrntoul_la_CPPFLAGS = \ + -D_BSD_SOURCE \ -I$(top_srcdir)/src/include \ $(NULL) libstrntoul_la_SOURCES = \ + strntoimax.cpp \ strntol.cpp \ + strntoll.cpp \ + strntoq.cpp \ + strntoul-detail.cpp \ strntoul.cpp \ + strntoull.cpp \ + strntoumax.cpp \ + strntouq.cpp \ $(NULL) install-headers: install-includeHEADERS diff --git a/src/strntoimax.cpp b/src/strntoimax.cpp new file mode 100644 index 0000000..34105ee --- /dev/null +++ b/src/strntoimax.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntoimax + * which adds a length parameter to the standard strtoimax. + * + */ + + +#include "strntoimax.h" + +#include "strntoll.h" + + +/** + * @brief + * Convert a string to an intmax_t integer. + * + * This function is similar to the C99 standard strtoimax, except + * that it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to an intmax_t integer value according to @a aBase, which + * must be between 2 and 36 inclusive, or be the special value 0 + * which implies that the base should be automatically-detected based + * on the content of @a aString. + * + * @a aString may begin with an arbitrary amount of white space (as + * determined by isspace(3)) followed by a single optional '+' or '-' + * sign. If base is zero (0) or 16, the string may then include a + * "0x" or a "0X" prefix, and the number will be read in base 16 + * (hexadecimal); otherwise, a zero (0) base is taken as ten (10) + * (decimal) unless the next character is '0', in which case it is + * taken as eight (8) (octal). + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered for the base, whichever + * is less. + * + * If @a aEnd is not null, this stores the address of the first + * invalid character in *@a aEnd. If there were no digits at all, + * this stores the original value of @a aString in *@a aEnd (and + * returns 0). In particular, if *@a aEnd is not null but **@a aEnd + * is '\0' or if @a aEnd - @a aString is equal to @a aLength on + * return, the entire string is valid and was fully converted. + * + * On error, @a errno may be set as follows: + * + * - EINVAL @a aBase was an unsupported value. + * - ERANGE The resulting conversion was out of range. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * Either the result of the conversion or, if there was a leading + * minus sign, the negation of the result of the conversion + * represented as an unsigned value, unless the original + * (nonnegated) value would overflow; in the latter case, this + * returns INTMAX_MAX and sets @a errno to ERANGE. + * + * @sa strtoimax + * @sa strntoumax + * + */ +intmax_t +strntoimax(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (static_cast(strntoll(aString, aLength, aEnd, aBase))); +} diff --git a/src/strntoimax.h b/src/strntoimax.h new file mode 100644 index 0000000..1c991b5 --- /dev/null +++ b/src/strntoimax.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" C11 / POSIX.1-2008 interface for + * strntoimax which adds a length parameter to the standard + * strtoimax. + * + */ + +#ifndef STRNTOIMAX_H +#define STRNTOIMAX_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern intmax_t strntoimax(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOIMAX_H */ diff --git a/src/strntol.cpp b/src/strntol.cpp index 8296ab9..fec25f7 100644 --- a/src/strntol.cpp +++ b/src/strntol.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Grant Erickson + * Copyright (c) 2021-2026 Grant Erickson * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,89 +23,13 @@ * */ + #include "strntol.h" -#include -#include #include -#include "strntoul.h" - -static long -_strntol(const char *aString, const size_t &aLength, char **aEnd, const int &aBase) -{ - const char * p = aString; - bool calledStrtoul = false; - bool isNegative = false; - signed long lRetval = 0; - - - if (aLength == 0) - { - goto done; - } - - // Skip any leading space and determine the sign, if any. - - while ((p < (aString + aLength)) && isspace(*p)) - { - p++; - } - - if (p < (aString + aLength)) - { - unsigned long lResult; - - if (*p == '-') - { - isNegative = true; +#include "strntoul-detail-signed-template.hpp" - p++; - - lResult = strntoul(p, - aLength - static_cast(p - aString), - aEnd, - aBase); - lRetval = static_cast(-lResult); - } - else - { - if (*p == '+') - { - p++; - } - - lResult = strntoul(p, - aLength - static_cast(p - aString), - aEnd, - aBase); - lRetval = static_cast(lResult); - } - - calledStrtoul = true; - } - - done: - // If no digits were converted, then the standard says that aEnd, - // if non-null, must be equal to aString. - - if ((lRetval == 0) && ((aEnd != nullptr) && (!calledStrtoul || (p == *aEnd)))) - { - *aEnd = const_cast(aString); - } - - // If the correct value is outside the range of representable - // values, the standards says LONG_MIN or LONG_MAX shall be - // returned (according to the sign of the value), and errno set to - // ERANGE (which was set from strtoul). - - if (errno == ERANGE) - { - lRetval = (isNegative) ? LONG_MIN : LONG_MAX; - } - - return (lRetval); -} /** * @brief @@ -147,5 +71,9 @@ _strntol(const char *aString, const size_t &aLength, char **aEnd, const int &aBa long strntol(const char *aString, size_t aLength, char **aEnd, int aBase) { - return (_strntol(aString, aLength, aEnd, aBase)); + return (Detail::StringExtentToSignedType(aString, aLength, aEnd, aBase)); } diff --git a/src/strntoll.cpp b/src/strntoll.cpp new file mode 100644 index 0000000..ab34918 --- /dev/null +++ b/src/strntoll.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntoll which + * adds a length parameter to the standard strtoll. + * + */ + + +#include "strntoll.h" + +#include + +#include "strntoul-detail-signed-template.hpp" + + +/** + * @brief + * Convert a string to an long long integer. + * + * This function is similar to the C99 standard strtoll, except that + * it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to a long long integer value according to @a aBase, which must + * be between 2 and 36 inclusive, or be the special value 0 which + * implies that the base should be automatically-detected based on + * the content of @a aString. + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered, whichever is less. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * The result of the conversion, unless the value would underflow + * or overflow. If an underflow occurs, this returns LLONG_MIN. If + * an overflow occurs, this returns LLONG_MAX. In both cases, errno + * is set to ERANGE. + * + * @sa strtoll + * @sa strntoull + * + */ +long long +strntoll(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (Detail::StringExtentToSignedType(aString, aLength, aEnd, aBase)); +} diff --git a/src/strntoll.h b/src/strntoll.h new file mode 100644 index 0000000..633e032 --- /dev/null +++ b/src/strntoll.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" C11 / POSIX.1-2008 interface for + * strntoll which adds a length parameter to the standard + * strtoll. + * + */ + +#ifndef STRNTOLL_H +#define STRNTOLL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern long long strntoll(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOLL_H */ diff --git a/src/strntoq.cpp b/src/strntoq.cpp new file mode 100644 index 0000000..13ad01d --- /dev/null +++ b/src/strntoq.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntoq + * which adds a length parameter to the standard strtoq. + * + */ + + +#include "strntoq.h" + +#include "strntoll.h" + + +/** + * @brief + * Convert a string to an quad_t integer. + * + * This function is similar to the C99 standard strtoq, except that + * it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to an quad_t integer value according to @a aBase, which + * must be between 2 and 36 inclusive, or be the special value 0 + * which implies that the base should be automatically-detected based + * on the content of @a aString. + * + * @a aString may begin with an arbitrary amount of white space (as + * determined by isspace(3)) followed by a single optional '+' or '-' + * sign. If base is zero (0) or 16, the string may then include a + * "0x" or a "0X" prefix, and the number will be read in base 16 + * (hexadecimal); otherwise, a zero (0) base is taken as ten (10) + * (decimal) unless the next character is '0', in which case it is + * taken as eight (8) (octal). + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered for the base, whichever + * is less. + * + * If @a aEnd is not null, this stores the address of the first + * invalid character in *@a aEnd. If there were no digits at all, + * this stores the original value of @a aString in *@a aEnd (and + * returns 0). In particular, if *@a aEnd is not null but **@a aEnd + * is '\0' or if @a aEnd - @a aString is equal to @a aLength on + * return, the entire string is valid and was fully converted. + * + * On error, @a errno may be set as follows: + * + * - EINVAL @a aBase was an unsupported value. + * - ERANGE The resulting conversion was out of range. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * Either the result of the conversion or, if there was a leading + * minus sign, the negation of the result of the conversion + * represented as an unsigned value, unless the original + * (nonnegated) value would overflow; in the latter case, this + * returns LLONG_MAX and sets @a errno to ERANGE. + * + * @sa strtoq + * @sa strntouq + * + */ +quad_t +strntoq(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (static_cast(strntoll(aString, aLength, aEnd, aBase))); +} diff --git a/src/strntoq.h b/src/strntoq.h new file mode 100644 index 0000000..eea2f94 --- /dev/null +++ b/src/strntoq.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" BSD interface for strntoq which + * adds a length parameter to the standard strtoq. + * + */ + +#ifndef STRNTOQ_H +#define STRNTOQ_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern quad_t strntoq(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOQ_H */ diff --git a/src/strntoul-detail-signed-template.hpp b/src/strntoul-detail-signed-template.hpp new file mode 100644 index 0000000..7231a19 --- /dev/null +++ b/src/strntoul-detail-signed-template.hpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a type-parameterized template for converting + * a length-delimited string to a signed integer value by + * wrapping the corresponding unsigned conversion template. + * + */ + +#ifndef STRNTOUL_DETAIL_SIGNED_TEMPLATE_HPP +#define STRNTOUL_DETAIL_SIGNED_TEMPLATE_HPP + + +#include +#include +#include + +#include "strntoul-detail-unsigned-template.hpp" + + +namespace Detail +{ + +template +SignedType +StringExtentToSignedType(const char *aString, const size_t &aLength, char **aEnd, const int &aBase) +{ + const char * p = aString; + bool calledStringExtentToUnsignedType = false; + bool isNegative = false; + SignedType lRetval = 0; + + + if (aLength == 0) + { + goto done; + } + + // Skip any leading space and determine the sign, if any. + + while ((p < (aString + aLength)) && isspace(*p)) + { + p++; + } + + if (p < (aString + aLength)) + { + UnsignedType lResult; + + if (*p == '-') + { + isNegative = true; + + p++; + + lResult = Detail::StringExtentToUnsignedType(p, + aLength - static_cast(p - aString), + aEnd, + aBase); + lRetval = static_cast(-lResult); + } + else + { + if (*p == '+') + { + p++; + } + + lResult = Detail::StringExtentToUnsignedType(p, + aLength - static_cast(p - aString), + aEnd, + aBase); + lRetval = static_cast(lResult); + } + + calledStringExtentToUnsignedType = true; + + // Check whether the successfully-converted unsigned result + // exceeds the representable range of the signed type. + + if (errno != ERANGE) + { + const UnsignedType kNegLimit = static_cast(SignedMaximumValue) + 1; + + if (isNegative ? (lResult > kNegLimit) : (lResult > static_cast(SignedMaximumValue))) + { + errno = ERANGE; + } + } + } + + done: + // If no digits were converted, then the standard says that aEnd, + // if non-null, must be equal to aString. + + if ((lRetval == 0) && ((aEnd != nullptr) && (!calledStringExtentToUnsignedType || (p == *aEnd)))) + { + *aEnd = const_cast(aString); + } + + // If the correct value is outside the range of representable values, the + // standards says 'SignedMinimumValue' or 'SignedMaximumValue' shall be + // returned (according to the sign of the value), and errno set to ERANGE + // (which was set from 'StringExtentToUnsignedType'). + + if (errno == ERANGE) + { + lRetval = (isNegative) ? SignedMinimumValue : SignedMaximumValue; + } + + return (lRetval); +} + +}; // namespace Detail + +#endif // STRNTOUL_DETAIL_SIGNED_TEMPLATE_HPP diff --git a/src/strntoul-detail-unsigned-template.hpp b/src/strntoul-detail-unsigned-template.hpp new file mode 100644 index 0000000..6a5ba7f --- /dev/null +++ b/src/strntoul-detail-unsigned-template.hpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a type-parameterized template for converting + * a length-delimited string to an unsigned integer value. + * + */ + +#ifndef STRNTOUL_DETAIL_UNSIGNED_TEMPLATE_HPP +#define STRNTOUL_DETAIL_UNSIGNED_TEMPLATE_HPP + + +#include +#include +#include + +#include "strntoul-detail.hpp" + + +namespace Detail +{ + +template +UnsignedType +StringExtentToUnsignedType(const char *aString, const size_t &aLength, char **aEnd, const int &aBase) +{ + const char * p = aString; + bool isNegative = false; + bool wouldOverflow = false; + bool convertedDigits = false; + int lBase; + UnsignedType lRetval = 0; + + if (aLength == 0) + { + goto done; + } + + // Skip any leading space and determine the sign, if any. + + while ((p < (aString + aLength)) && isspace(*p)) + { + p++; + } + + if (p < (aString + aLength)) + { + if (*p == '-') + { + isNegative = true; + p++; + } + else if (*p == '+') + { + p++; + } + } + + // Handle computing and sanity-checking the base. + + lBase = Detail::HandleBase(p, aLength - static_cast(p - aString), aBase); + if (lBase < 0) + { + errno = -lBase; + lRetval = 0; + + goto done; + } + + // Begin the conversion, based on the base and the available + // characters to convert. + // + // Special case a few, common power-of-two cases in which the + // shifts are substantially more efficient than the divides and + // multiplies required for the shift-and-accumulate conversion + // algorithm. + + if ((lBase == 2) || (lBase == 8) || (lBase == 16)) + { + const unsigned kShift = ((lBase == 2) ? 1 : + ((lBase == 8) ? 3 : 4)); + const UnsignedType lOverflowSentinel = MaximumValue >> kShift; + + while (p < (aString + aLength)) + { + const unsigned int lDigit = Detail::GetDigit(*p); + + if (lDigit >= static_cast(lBase)) + break; + + // Check-and-shift, checking first if the shift would + // cause an overflow. + + if (lRetval > lOverflowSentinel) + { + wouldOverflow = true; + } + + lRetval <<= kShift; + + // Check-and-accumulate, checking first if the accumulate + // would cause an overflow. + + if (lDigit > (MaximumValue - lRetval)) + { + wouldOverflow = true; + } + + lRetval += lDigit; + + convertedDigits = true; + + p++; + } + } + else if ((lBase >= 2) && (lBase <= 36)) + { + const UnsignedType lOverflowSentinel = MaximumValue / static_cast(lBase); + + while (p < (aString + aLength)) + { + const unsigned int lDigit = Detail::GetDigit(*p); + + if (lDigit >= static_cast(lBase)) + break; + + // Check-and-shift, checking first if the shift would + // cause an overflow. + + if (lRetval > lOverflowSentinel) + { + wouldOverflow = true; + } + + lRetval *= static_cast(lBase); + + // Check-and-accumulate, checking first if the accumulate + // would cause an overflow. + + if (lDigit > (MaximumValue - lRetval)) + { + wouldOverflow = true; + } + + lRetval += lDigit; + + convertedDigits = true; + + p++; + } + } + + done: + // If no digits were converted, then the standard says that aEnd, + // if non-null, must be equal to aString. + + if (!convertedDigits) + { + p = aString; + } + + if (aEnd != nullptr) + { + *aEnd = const_cast(p); + } + + if (wouldOverflow) { + errno = ERANGE; + lRetval = MaximumValue; + } + + if (isNegative) { + lRetval = -lRetval; + } + + return (lRetval); +} + +}; // namespace Detail + +#endif // STRNTOUL_DETAIL_UNSIGNED_TEMPLATE_HPP \ No newline at end of file diff --git a/src/strntoul-detail.cpp b/src/strntoul-detail.cpp new file mode 100644 index 0000000..f3a88dc --- /dev/null +++ b/src/strntoul-detail.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements internal support interfaces for the + * strntoul library. + * + */ + + +#include "strntoul-detail.hpp" + +#include +#include +#include + + +namespace Detail +{ + +int +HandleBase(const char *&aString, const size_t &aLength, const int &aBase) +{ + int lRetval = 0; + + if (aLength > 0) + { + if (aBase == 0) + { + // If the caller indicated a base of zero (0), then we + // automatically deduce the base from the content. + + if (*aString == '0') + { + // There is a leading zero (0). The content might be octal + // or hexadecimal. + + aString++; + + // Determine if it is hexadecimal or should be assumed + // octal. + + if ((aLength > 1) && ((*aString == 'x') || (*aString == 'X'))) + { + aString++; + lRetval = 16; + } + else + { + // Back up so the leading '0' is available to the + // conversion loop as a valid octal (or decimal) digit. + + aString--; + lRetval = 8; + } + } + else + { + lRetval = 10; + } + } + else if (aBase == 16) + { + lRetval = aBase; + + // If the caller indicated the content is hexadecimal, then + // skip any leading '0[xX]', if present. + + if (((aLength > 0) && (aString[0] == '0')) && + ((aLength > 1) && ((aString[1] == 'x') || aString[1] == 'X'))) + { + aString += 2; + } + } + else if ((aBase >=2) && (aBase <= 36)) + { + lRetval = aBase; + } + else + { + lRetval = -EINVAL; + } + } + + return (lRetval); +} + +unsigned int +GetDigit(const char &aCharacter) +{ + unsigned int lRetval; + + if (isdigit(aCharacter)) + { + lRetval = static_cast(aCharacter - '0'); + } + else if (isalpha(aCharacter)) + { + if (isupper(aCharacter)) + lRetval = static_cast((aCharacter - 'A') + 10); + else if (islower(aCharacter)) + lRetval = static_cast((aCharacter - 'a') + 10); + else + lRetval = INT_MAX; + } + else + { + lRetval = INT_MAX; + } + + return (lRetval); +} + +}; // namespace Detail diff --git a/src/strntoul-detail.hpp b/src/strntoul-detail.hpp new file mode 100644 index 0000000..823a80d --- /dev/null +++ b/src/strntoul-detail.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file declares internal support interfaces for the + * strntoul library. + * + */ + +#ifndef STRNTOUL_DETAIL_HPP +#define STRNTOUL_DETAIL_HPP + + +#include + + +namespace Detail +{ + +extern __attribute__((visibility("hidden"))) int HandleBase(const char *&aString, + const size_t &aLength, + const int &aBase); +extern __attribute__((visibility("hidden"))) unsigned int GetDigit(const char &aCharacter); + +}; // namespace Detail + +#endif // STRNTOUL_DETAIL_HPP diff --git a/src/strntoul.cpp b/src/strntoul.cpp index aeb5f87..bb76658 100644 --- a/src/strntoul.cpp +++ b/src/strntoul.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Grant Erickson + * Copyright (c) 2021-2026 Grant Erickson * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,260 +23,13 @@ * */ + #include "strntoul.h" -#include -#include #include +#include "strntoul-detail-unsigned-template.hpp" -static int -HandleBase(const char *&aString, const size_t &aLength, const int &aBase) -{ - int lRetval = 0; - - if (aLength > 0) - { - if (aBase == 0) - { - // If the caller indicated a base of zero (0), then we - // automatically deduce the base from the content. - - if (*aString == '0') - { - // There is a leading zero (0). The content might be octal - // or hexadecimal. - - aString++; - - // Determine if it is hexadecimal or should be assumed - // octal. - - if ((aLength > 1) && ((*aString == 'x') || (*aString == 'X'))) - { - aString++; - lRetval = 16; - } - else - { - // Back up so the leading '0' is available to the - // conversion loop as a valid octal (or decimal) digit. - - aString--; - lRetval = 8; - } - } - else - { - lRetval = 10; - } - } - else if (aBase == 16) - { - lRetval = aBase; - - // If the caller indicated the content is hexadecimal, then - // skip any leading '0[xX]', if present. - - if (((aLength > 0) && (aString[0] == '0')) && - ((aLength > 1) && ((aString[1] == 'x') || aString[1] == 'X'))) - { - aString += 2; - } - } - else if ((aBase >=2) && (aBase <= 36)) - { - lRetval = aBase; - } - else - { - lRetval = -EINVAL; - } - } - - return (lRetval); -} - -static unsigned int -GetDigit(const char &aCharacter) -{ - unsigned int lRetval; - - if (isdigit(aCharacter)) - { - lRetval = static_cast(aCharacter - '0'); - } - else if (isalpha(aCharacter)) - { - if (isupper(aCharacter)) - lRetval = static_cast((aCharacter - 'A') + 10); - else if (islower(aCharacter)) - lRetval = static_cast((aCharacter - 'a') + 10); - else - lRetval = INT_MAX; - } - else - { - lRetval = INT_MAX; - } - - return (lRetval); -} - -static unsigned long -_strntoul(const char *aString, const size_t &aLength, char **aEnd, const int &aBase) -{ - const char * p = aString; - bool isNegative = false; - bool wouldOverflow = false; - bool convertedDigits = false; - int lBase; - unsigned long lRetval = 0; - - if (aLength == 0) - { - goto done; - } - - // Skip any leading space and determine the sign, if any. - - while ((p < (aString + aLength)) && isspace(*p)) - { - p++; - } - - if (p < (aString + aLength)) - { - if (*p == '-') - { - isNegative = true; - p++; - } - else if (*p == '+') - { - p++; - } - } - - // Handle computing and sanity-checking the base. - - lBase = HandleBase(p, aLength - static_cast(p - aString), aBase); - if (lBase < 0) - { - errno = -lBase; - lRetval = 0; - - goto done; - } - - // Begin the conversion, based on the base and the available - // characters to convert. - // - // Special case a few, common power-of-two cases in which the - // shifts are substantially more efficient than the divides and - // multiplies required for the shift-and-accumulate conversion - // algorithm. - - if ((lBase == 2) || (lBase == 8) || (lBase == 16)) - { - const unsigned kShift = ((lBase == 2) ? 1 : - ((lBase == 8) ? 3 : 4)); - const unsigned long lOverflowSentinel = ULONG_MAX >> kShift; - - while (p < (aString + aLength)) - { - const unsigned int lDigit = GetDigit(*p); - - if (lDigit >= static_cast(lBase)) - break; - - // Check-and-shift, checking first if the shift would - // cause an overflow. - - if (lRetval > lOverflowSentinel) - { - wouldOverflow = true; - } - - lRetval <<= kShift; - - // Check-and-accumulate, checking first if the accumulate - // would cause an overflow. - - if (lDigit > (ULONG_MAX - lRetval)) - { - wouldOverflow = true; - } - - lRetval += lDigit; - - convertedDigits = true; - - p++; - } - } - else if ((lBase >= 2) && (lBase <= 36)) - { - const unsigned long lOverflowSentinel = ULONG_MAX / static_cast(lBase); - - while (p < (aString + aLength)) - { - const unsigned int lDigit = GetDigit(*p); - - if (lDigit >= static_cast(lBase)) - break; - - // Check-and-shift, checking first if the shift would - // cause an overflow. - - if (lRetval > lOverflowSentinel) - { - wouldOverflow = true; - } - - lRetval *= static_cast(lBase); - - // Check-and-accumulate, checking first if the accumulate - // would cause an overflow. - - if (lDigit > (ULONG_MAX - lRetval)) - { - wouldOverflow = true; - } - - lRetval += lDigit; - - convertedDigits = true; - - p++; - } - } - - done: - // If no digits were converted, then the standard says that aEnd, - // if non-null, must be equal to aString. - - if (!convertedDigits) - { - p = aString; - } - - if (aEnd != nullptr) - { - *aEnd = const_cast(p); - } - - if (wouldOverflow) { - errno = ERANGE; - lRetval = ULONG_MAX; - } - - if (isNegative) { - lRetval = -lRetval; - } - - return (lRetval); -} /** * @brief @@ -340,5 +93,6 @@ _strntoul(const char *aString, const size_t &aLength, char **aEnd, const int &aB unsigned long strntoul(const char *aString, size_t aLength, char **aEnd, int aBase) { - return (_strntoul(aString, aLength, aEnd, aBase)); + return (Detail::StringExtentToUnsignedType(aString, aLength, aEnd, aBase)); } diff --git a/src/strntoull.cpp b/src/strntoull.cpp new file mode 100644 index 0000000..ff46b62 --- /dev/null +++ b/src/strntoull.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntoull + * which adds a length parameter to the standard strtoull. + * + */ + +#include "strntoull.h" + +#include + +#include "strntoul-detail-unsigned-template.hpp" + + +/** + * @brief + * Convert a string to an unsigned long long integer. + * + * This function is similar to the C99 standard strtoull, except that + * it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to an unsigned long long integer value according to @a + * aBase, which must be between 2 and 36 inclusive, or be the special + * value 0 which implies that the base should be + * automatically-detected based on the content of @a aString. + * + * @a aString may begin with an arbitrary amount of white space (as + * determined by isspace(3)) followed by a single optional '+' or '-' + * sign. If base is zero (0) or 16, the string may then include a + * "0x" or a "0X" prefix, and the number will be read in base 16 + * (hexadecimal); otherwise, a zero (0) base is taken as ten (10) + * (decimal) unless the next character is '0', in which case it is + * taken as eight (8) (octal). + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered for the base, whichever + * is less. + * + * If @a aEnd is not null, this stores the address of the first + * invalid character in *@a aEnd. If there were no digits at all, + * this stores the original value of @a aString in *@a aEnd (and + * returns 0). In particular, if *@a aEnd is not null but **@a aEnd + * is '\0' or if @a aEnd - @a aString is equal to @a aLength on return, + * the entire string is valid and was fully converted. + * + * On error, @a errno may be set as follows: + * + * - EINVAL @a aBase was an unsupported value. + * - ERANGE The resulting conversion was out of range. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * Either the result of the conversion or, if there was a leading + * minus sign, the negation of the result of the conversion + * represented as an unsigned value, unless the original + * (nonnegated) value would overflow; in the latter case, this + * returns ULLONG_MAX and sets @a errno to ERANGE. + * + * @sa strtoull + * @sa strntoll + * + */ +unsigned long long +strntoull(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (Detail::StringExtentToUnsignedType(aString, aLength, aEnd, aBase)); +} diff --git a/src/strntoull.h b/src/strntoull.h new file mode 100644 index 0000000..50226be --- /dev/null +++ b/src/strntoull.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" C11 / POSIX.1-2008 interface for + * strntoull which adds a length parameter to the standard + * strtoull. + * + */ + +#ifndef STRNTOULL_H +#define STRNTOULL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned long long strntoull(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOULL_H */ diff --git a/src/strntoumax.cpp b/src/strntoumax.cpp new file mode 100644 index 0000000..3009dbe --- /dev/null +++ b/src/strntoumax.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntoumax + * which adds a length parameter to the standard strtoumax. + * + */ + + +#include "strntoumax.h" + +#include "strntoull.h" + + +/** + * @brief + * Convert a string to an uintmax_t integer. + * + * This function is similar to the C99 standard strtoumax, except + * that it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to an uintmax_t integer value according to @a aBase, which + * must be between 2 and 36 inclusive, or be the special value 0 + * which implies that the base should be automatically-detected based + * on the content of @a aString. + * + * @a aString may begin with an arbitrary amount of white space (as + * determined by isspace(3)) followed by a single optional '+' or '-' + * sign. If base is zero (0) or 16, the string may then include a + * "0x" or a "0X" prefix, and the number will be read in base 16 + * (hexadecimal); otherwise, a zero (0) base is taken as ten (10) + * (decimal) unless the next character is '0', in which case it is + * taken as eight (8) (octal). + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered for the base, whichever + * is less. + * + * If @a aEnd is not null, this stores the address of the first + * invalid character in *@a aEnd. If there were no digits at all, + * this stores the original value of @a aString in *@a aEnd (and + * returns 0). In particular, if *@a aEnd is not null but **@a aEnd + * is '\0' or if @a aEnd - @a aString is equal to @a aLength on + * return, the entire string is valid and was fully converted. + * + * On error, @a errno may be set as follows: + * + * - EINVAL @a aBase was an unsupported value. + * - ERANGE The resulting conversion was out of range. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * Either the result of the conversion or, if there was a leading + * minus sign, the negation of the result of the conversion + * represented as an unsigned value, unless the original + * (nonnegated) value would overflow; in the latter case, this + * returns UINTMAX_MAX and sets @a errno to ERANGE. + * + * @sa strtoumax + * @sa strntoumax + * + */ +uintmax_t +strntoumax(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (static_cast(strntoull(aString, aLength, aEnd, aBase))); +} diff --git a/src/strntoumax.h b/src/strntoumax.h new file mode 100644 index 0000000..71e35da --- /dev/null +++ b/src/strntoumax.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" C11 / POSIX.1-2008 interface for + * strntoumax which adds a length parameter to the standard + * strtoumax. + * + */ + +#ifndef STRNTOUMAX_H +#define STRNTOUMAX_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern uintmax_t strntoumax(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOUMAX_H */ diff --git a/src/strntouq.cpp b/src/strntouq.cpp new file mode 100644 index 0000000..6c1467a --- /dev/null +++ b/src/strntouq.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a "missing" C99 interface for strntouq + * which adds a length parameter to the standard strtouq. + * + */ + + +#include "strntouq.h" + +#include "strntoull.h" + + +/** + * @brief + * Convert a string to an u_quad_t integer. + * + * This function is similar to the C99 standard strtouq, except that + * it will use at most @a aLength bytes from @a aString; and @a + * aString does @b not need to be null-terminated if it contains @a + * aLength or more bytes. + * + * This attempts to convert the initial part of the string in @a + * aString to an u_quad_t integer value according to @a aBase, which + * must be between 2 and 36 inclusive, or be the special value 0 + * which implies that the base should be automatically-detected based + * on the content of @a aString. + * + * @a aString may begin with an arbitrary amount of white space (as + * determined by isspace(3)) followed by a single optional '+' or '-' + * sign. If base is zero (0) or 16, the string may then include a + * "0x" or a "0X" prefix, and the number will be read in base 16 + * (hexadecimal); otherwise, a zero (0) base is taken as ten (10) + * (decimal) unless the next character is '0', in which case it is + * taken as eight (8) (octal). + * + * The conversion continues up to @a aLength, a terminating null, or + * until an invalid character is encountered for the base, whichever + * is less. + * + * If @a aEnd is not null, this stores the address of the first + * invalid character in *@a aEnd. If there were no digits at all, + * this stores the original value of @a aString in *@a aEnd (and + * returns 0). In particular, if *@a aEnd is not null but **@a aEnd + * is '\0' or if @a aEnd - @a aString is equal to @a aLength on + * return, the entire string is valid and was fully converted. + * + * On error, @a errno may be set as follows: + * + * - EINVAL @a aBase was an unsupported value. + * - ERANGE The resulting conversion was out of range. + * + * @param[in] aString A pointer to the string to convert. + * @param[in] aLength The maximum number of characters, in bytes, + * of @a aString to process. + * @param[out] aEnd A pointer to storage for the first invalid + * or the last valid character in @a aString. + * @param[in] aBase The base to use to interpret @a aString for + * the conversion in the range 2 to 36, + * inclusive. + * + * @returns + * Either the result of the conversion or, if there was a leading + * minus sign, the negation of the result of the conversion + * represented as an unsigned value, unless the original + * (nonnegated) value would overflow; in the latter case, this + * returns ULLONG_MAX and sets @a errno to ERANGE. + * + * @sa strtouq + * @sa strntoq + * + */ +u_quad_t +strntouq(const char *aString, size_t aLength, char **aEnd, int aBase) +{ + return (static_cast(strntoull(aString, aLength, aEnd, aBase))); +} diff --git a/src/strntouq.h b/src/strntouq.h new file mode 100644 index 0000000..6f3bb8a --- /dev/null +++ b/src/strntouq.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file defines a "missing" BSD interface for strntouq which + * adds a length parameter to the standard strtouq. + * + */ + +#ifndef STRNTOUQ_H +#define STRNTOUQ_H + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern u_quad_t strntouq(const char *aString, size_t aLength, char **aEnd, int aBase); + +#ifdef __cplusplus +} +#endif + +#endif /* STRNTOUQ_H */ diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 7658048..5e1d473 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -63,8 +63,14 @@ NLFOREIGN_FILE_DEPENDENCIES = \ # Test applications that should be run when the 'check' target is run. check_PROGRAMS = \ + Test_strntoimax \ Test_strntol \ + Test_strntoll \ + Test_strntoq \ Test_strntoul \ + Test_strntoull \ + Test_strntoumax \ + Test_strntouq \ $(NULL) # Test applications and scripts that should be built and run when the @@ -82,12 +88,30 @@ TESTS_ENVIRONMENT = \ # Source, compiler, and linker options for test programs. +Test_strntoimax_SOURCES = Test_strntoimax.cpp +Test_strntoimax_LDADD = $(COMMON_LDADD) + Test_strntol_SOURCES = Test_strntol.cpp Test_strntol_LDADD = $(COMMON_LDADD) +Test_strntoll_SOURCES = Test_strntoll.cpp +Test_strntoll_LDADD = $(COMMON_LDADD) + +Test_strntoq_SOURCES = Test_strntoq.cpp +Test_strntoq_LDADD = $(COMMON_LDADD) + Test_strntoul_SOURCES = Test_strntoul.cpp Test_strntoul_LDADD = $(COMMON_LDADD) +Test_strntoull_SOURCES = Test_strntoull.cpp +Test_strntoull_LDADD = $(COMMON_LDADD) + +Test_strntoumax_SOURCES = Test_strntoumax.cpp +Test_strntoumax_LDADD = $(COMMON_LDADD) + +Test_strntouq_SOURCES = Test_strntouq.cpp +Test_strntouq_LDADD = $(COMMON_LDADD) + # # Foreign make dependencies # diff --git a/src/tests/Test_strntoimax.cpp b/src/tests/Test_strntoimax.cpp new file mode 100644 index 0000000..909e7bc --- /dev/null +++ b/src/tests/Test_strntoimax.cpp @@ -0,0 +1,790 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntoimax. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef intmax_t TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntoimax("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntoimax("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntoimax("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntoimax("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntoimax(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntoimax(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntoimax(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntoimax(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntoimax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntoimax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntoimax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + // 2.1 Test positive decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2.2 Test negative decimal overflow + + errno = 0; + lString = "-147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "-18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow (INTMAX_MAX + 1, INTMAX_MIN - 1) + + // 3.1: Test decimal INTMAX_MAX + 1 + + errno = 0; + lString = "9223372036854775808"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.2: Test decimal INTMAX_MIN - 1 + + errno = 0; + lString = "-9223372036854775809"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.3: Test hexadecimal at exactly INTMAX_MAX (should NOT overflow) + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.4: Test hexadecimal at INTMAX_MAX + 1 (0x8000000000000000) + + errno = 0; + lString = "0x8000000000000000"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == INTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntoimax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntoimax will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntoimax(lString, lLength, &lEnd, 0); // or strntoimax for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntoimax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntoimax", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +} diff --git a/src/tests/Test_strntol.cpp b/src/tests/Test_strntol.cpp index 0764472..d12442d 100644 --- a/src/tests/Test_strntol.cpp +++ b/src/tests/Test_strntol.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Grant Erickson + * Copyright (c) 2021-2026 Grant Erickson * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,11 +32,14 @@ #include +// MARK: Type Declarations + +typedef signed long TestType; static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; // A base of 1, just under the valid range, should fail @@ -74,7 +77,7 @@ static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; char * lEnd; @@ -139,7 +142,7 @@ static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { const int kBase = 0; - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -247,7 +250,7 @@ static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -289,7 +292,7 @@ static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -389,7 +392,7 @@ static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -401,8 +404,6 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MAX); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); @@ -412,8 +413,6 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MAX); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); @@ -427,8 +426,6 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MAX); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); @@ -438,8 +435,6 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MAX); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); @@ -451,8 +446,6 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MIN); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); @@ -462,17 +455,15 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == LONG_MIN); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == ERANGE); } static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), - void *inContext __attribute__((unused))) + void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -509,8 +500,6 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), lLength = 1; lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString); NL_TEST_ASSERT(inSuite, errno == 0); @@ -520,8 +509,6 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), lLength = 1; lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString); NL_TEST_ASSERT(inSuite, errno == 0); @@ -564,8 +551,6 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), lLength = 3; lResult = strntol(lString, lLength, &lEnd, 10); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == -2); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == 0); @@ -588,8 +573,6 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), lLength = 1; lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == 0); @@ -599,8 +582,6 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), lLength = 1; lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); NL_TEST_ASSERT(inSuite, errno == 0); @@ -665,7 +646,7 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - signed long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -682,8 +663,6 @@ static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString + 1); NL_TEST_ASSERT(inSuite, errno == 0); @@ -693,8 +672,6 @@ static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), lLength = strlen(lString); lResult = strntol(lString, lLength, &lEnd, 16); - fprintf(stderr, "%s: %d: lResult %ld lString %p lEnd %p\n", - __FILE__, __LINE__, lResult, lString, lEnd); NL_TEST_ASSERT(inSuite, lResult == 0); NL_TEST_ASSERT(inSuite, lEnd == lString + 1); NL_TEST_ASSERT(inSuite, errno == 0); diff --git a/src/tests/Test_strntoll.cpp b/src/tests/Test_strntoll.cpp new file mode 100644 index 0000000..301626b --- /dev/null +++ b/src/tests/Test_strntoll.cpp @@ -0,0 +1,790 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntoll. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef signed long long TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntoll("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntoll("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntoll("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntoll("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntoll(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntoll(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntoll(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntoll(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntoll(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntoll(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntoll(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + // 2.1 Test positive decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2.2 Test negative decimal overflow + + errno = 0; + lString = "-147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "-18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow (LLONG_MAX + 1, LLONG_MIN - 1) + + // 3.1: Test decimal LLONG_MAX + 1 + + errno = 0; + lString = "9223372036854775808"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.2: Test decimal LLONG_MIN - 1 + + errno = 0; + lString = "-9223372036854775809"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.3: Test hexadecimal at exactly LLONG_MAX (should NOT overflow) + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.4: Test hexadecimal at LLONG_MAX + 1 (0x8000000000000000) + + errno = 0; + lString = "0x8000000000000000"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntoll(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntoll will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntoll(lString, lLength, &lEnd, 0); // or strntoll for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntoll(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntoll", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +} diff --git a/src/tests/Test_strntoq.cpp b/src/tests/Test_strntoq.cpp new file mode 100644 index 0000000..7413119 --- /dev/null +++ b/src/tests/Test_strntoq.cpp @@ -0,0 +1,790 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntoq. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef quad_t TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntoq("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntoq("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntoq("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntoq("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntoq(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntoq(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntoq(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntoq(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntoq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntoq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntoq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + // 2.1 Test positive decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2.2 Test negative decimal overflow + + errno = 0; + lString = "-147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "-18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow (LLONG_MAX + 1, LLONG_MIN - 1) + + // 3.1: Test decimal LLONG_MAX + 1 + + errno = 0; + lString = "9223372036854775808"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.2: Test decimal LLONG_MIN - 1 + + errno = 0; + lString = "-9223372036854775809"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MIN); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3.3: Test hexadecimal at exactly LLONG_MAX (should NOT overflow) + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.4: Test hexadecimal at LLONG_MAX + 1 (0x8000000000000000) + + errno = 0; + lString = "0x8000000000000000"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == LLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntoq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == -2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntoq will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntoq(lString, lLength, &lEnd, 0); // or strntoq for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntoq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntoq", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +} diff --git a/src/tests/Test_strntoul.cpp b/src/tests/Test_strntoul.cpp index 152a1ec..2eddc50 100644 --- a/src/tests/Test_strntoul.cpp +++ b/src/tests/Test_strntoul.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Grant Erickson + * Copyright (c) 2021-2026 Grant Erickson * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,11 +32,14 @@ #include +// MARK: Type Declarations + +typedef unsigned long TestType; static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; // A base of 1, just under the valid range, should fail @@ -74,7 +77,7 @@ static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; char * lEnd; @@ -139,7 +142,7 @@ static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { const int kBase = 0; - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -247,7 +250,7 @@ static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -289,7 +292,7 @@ static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -389,7 +392,7 @@ static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -436,9 +439,9 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), } static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), - void *inContext __attribute__((unused))) + void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; @@ -621,7 +624,7 @@ static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), void *inContext __attribute__((unused))) { - unsigned long lResult; + TestType lResult; const char * lString; size_t lLength; char * lEnd; diff --git a/src/tests/Test_strntoull.cpp b/src/tests/Test_strntoull.cpp new file mode 100644 index 0000000..2b5b787 --- /dev/null +++ b/src/tests/Test_strntoull.cpp @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntoull. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef unsigned long long TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntoull("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntoull("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntoull("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntoull("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntoull(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntoull(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntoull(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntoull(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntoull(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntoull(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntoull(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551599ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551603ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow + + // 3.1: Test decimal at exactly ULLONG_MAX (should NOT overflow) + + errno = 0; + lString = "18446744073709551615"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.2: Test hexadecimal at exactly ULLONG_MAX (should NOT overflow) + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.3: Test hexadecimal at ULLONG_MAX + 1 (0x10000000000000000) + + errno = 0; + lString = "0x10000000000000000"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntoull(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551614ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntoull will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntoull(lString, lLength, &lEnd, 0); // or strntol for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntoull(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntoull", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +} diff --git a/src/tests/Test_strntoumax.cpp b/src/tests/Test_strntoumax.cpp new file mode 100644 index 0000000..9c9a91e --- /dev/null +++ b/src/tests/Test_strntoumax.cpp @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntoumax. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef uintmax_t TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntoumax("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntoumax("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntoumax("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntoumax("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntoumax(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntoumax(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntoumax(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntoumax(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntoumax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntoumax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntoumax(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551599ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551603ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow + + // 3.1: Test decimal at exactly UINTMAX_MAX (should NOT overflow) + + errno = 0; + lString = "18446744073709551615"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.2: Test hexadecimal at exactly UINTMAX_MAX (should NOT overflow) + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.3: Test hexadecimal at UINTMAX_MAX + 1 (0x10000000000000000) + + errno = 0; + lString = "0x10000000000000000"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == UINTMAX_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntoumax(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551614ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntoumax will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntoumax(lString, lLength, &lEnd, 0); // or strntol for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntoumax(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntoumax", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +} diff --git a/src/tests/Test_strntouq.cpp b/src/tests/Test_strntouq.cpp new file mode 100644 index 0000000..283ddcc --- /dev/null +++ b/src/tests/Test_strntouq.cpp @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2021-2026 Grant Erickson + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS + * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + */ + +/** + * @file + * This file implements a unit test for strntouq. + * + */ + +#include + +#include +#include +#include + +#include + +#include + +// MARK: Type Declarations + +typedef u_quad_t TestType; + +static void TestInvalidBases(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + + // A base of 1, just under the valid range, should fail + + errno = 0; + + lResult = strntouq("10", 2, nullptr, 1); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A base of 37, just above the valid range, should fail + + errno = 0; + + lResult = strntouq("10", 2, nullptr, 37); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // An arbitrarily-large base should fail + + errno = 0; + + lResult = strntouq("10", 2, nullptr, 2147483629); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); + + // A negative base should fail + + errno = 0; + + lResult = strntouq("10", 2, nullptr, -3); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, errno == EINVAL); +} + +static void TestZeroLength(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + char * lEnd; + + errno = 0; + lString = "0101"; + + lResult = strntouq(lString, 0, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "007"; + + lResult = strntouq(lString, 0, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "13"; + + lResult = strntouq(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + + lResult = strntouq(lString, 0, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0xaf"; + + lResult = strntouq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + + lResult = strntouq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "d5"; + + lResult = strntouq(lString, 0, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestImplicitBase(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + const int kBase = 0; + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + + // 1: Test bare zero with implicit base + + // 1.1: A bare "0" with implicit base should parse as the value + // zero (0), not be consumed as an octal prefix with no + // digits following. + + errno = 0; + lString = "0"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: A bare "00" with implicit base should parse as zero (0) + // in octal and consume both characters. + + errno = 0; + lString = "00"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.3: "01" with implicit base should parse as octal 1. + + errno = 0; + lString = "01"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test octal (8) + + errno = 0; + lString = "007"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test decimal (10) + + errno = 0; + lString = "13"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "11"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 4: Test hexadecimal (16) + + errno = 0; + lString = "0xaf"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0Xc3"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0x3B"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, kBase); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestSignedness(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test explicit negative sign. + + errno = 0; + lString = "-17"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551599ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test explicit positive sign. + + errno = 0; + lString = "+17"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test implicit positive sign. + + errno = 0; + lString = "17"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 17); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestLeadingSpace(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + errno = 0; + lString = " 0101"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 2); + NL_TEST_ASSERT(inSuite, lResult == 5); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " 007"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 8); + NL_TEST_ASSERT(inSuite, lResult == 7); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t13"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t-13"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551603ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t\t+13"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 13); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + + errno = 0; + lString = "\t \t11"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 11); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t 0xaf"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 175); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "\t\t 0Xc3"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 195); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\td5"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 213); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " \t\t\t0x3B"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 59); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test hexadecimal overflow + + errno = 0; + lString = "0x7FFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 2: Test decimal overflow + + errno = 0; + lString = "147573952589676412927"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + errno = 0; + lString = "18446744073709551616"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); + + // 3: Test tight boundary overflow + + // 3.1: Test decimal at exactly ULLONG_MAX (should NOT overflow) + + errno = 0; + lString = "18446744073709551615"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.2: Test hexadecimal at exactly ULLONG_MAX (should NOT overflow) + + errno = 0; + lString = "0xFFFFFFFFFFFFFFFF"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3.3: Test hexadecimal at ULLONG_MAX + 1 (0x10000000000000000) + + errno = 0; + lString = "0x10000000000000000"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == ULLONG_MAX); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == ERANGE); +} + +static void TestShortLengths(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test short signedness + + // 1.1: Test short signedness with no leading space + + errno = 0; + lString = "+23"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "-23"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2: Test short signedness with a leading space + + // 1.2.1: Test short signedness with a leading space and one + // character of length. + + errno = 0; + lString = " +23"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.2: Test short signedness with a leading space and two + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 2; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 2; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 1.2.3: Test short signedness with a leading space and three + // characters of length. + + errno = 0; + lString = " +23"; + lLength = 3; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 2); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = " -23"; + lLength = 3; + + lResult = strntouq(lString, lLength, &lEnd, 10); + NL_TEST_ASSERT(inSuite, lResult == 18446744073709551614ul); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test short hexadecimal leading "0x" and "0X". + + // 2.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space. + + // 2.1.1: Test short hexadecimal leading "0x" and "0X" with no + // leading space and one character of length. + // + // For these tests, with a length of one (1), strntouq will be + // unable to adequately test for and strip the leading "0x" or "0X". + // So, all it can look at is the leading zero ('0'), which should + // and will get correctly converted. + + errno = 0; + lString = "0xfe"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.2: Test short hexadecimal leading "0x" and "0X" with no + // leading space and two characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 2; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 2; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2.1.3: Test short hexadecimal leading "0x" and "0X" with no + // leading space and three characters of length. + + errno = 0; + lString = "0xfe"; + lLength = 3; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 15); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0XCD"; + lLength = 3; + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 12); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 3: Test short implicit base with bare "0". + // + // "0x2A" with length 1 and implicit base should see only the '0', + // treat it as the value zero, and consume the one character. + + errno = 0; + lString = "0x2A"; + lLength = 1; + + lResult = strntouq(lString, lLength, &lEnd, 0); // or strntol for the signed suite + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + lLength); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +static void TestBadHexLeading(nlTestSuite *inSuite __attribute__((unused)), + void *inContext __attribute__((unused))) +{ + TestType lResult; + const char * lString; + size_t lLength; + char * lEnd; + + // 1: Test valid leading "0" but bad following characters. + // + // Both of these tests should result in interpreting the '0' as a + // zero and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "0zCD"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "0%CD"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 0); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + // 2: Test invalid leading "1" but valid following characters. + // + // Both of these tests should result in interpreting the '1' as a + // one (1) and with one character of valid conversion since the + // leading "0x" and "0X" test will fail and thus will not be + // skipped. + + errno = 0; + lString = "1xCD"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); + + errno = 0; + lString = "1XCD"; + lLength = strlen(lString); + + lResult = strntouq(lString, lLength, &lEnd, 16); + NL_TEST_ASSERT(inSuite, lResult == 1); + NL_TEST_ASSERT(inSuite, lEnd == lString + 1); + NL_TEST_ASSERT(inSuite, errno == 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + NL_TEST_DEF("Invalid Bases", TestInvalidBases), + NL_TEST_DEF("Zero Length", TestZeroLength), + NL_TEST_DEF("Leading Space", TestLeadingSpace), + NL_TEST_DEF("Implicit Base", TestImplicitBase), + NL_TEST_DEF("Signedness", TestSignedness), + NL_TEST_DEF("Overflow", TestOverflow), + NL_TEST_DEF("Short Lengths", TestShortLengths), + NL_TEST_DEF("Bad Hex Leading", TestBadHexLeading), + + NL_TEST_SENTINEL() +}; + +int main(void) +{ + nlTestSuite theSuite = { + "strntouq", + &sTests[0], + nullptr, + nullptr, + nullptr, + nullptr, + 0, + 0, + 0, + 0, + 0 + }; + + // Generate human-readable output. + nlTestSetOutputStyle(OUTPUT_DEF); + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + + return nlTestRunnerStats(&theSuite); +}