From d201be184ec617ed9315e4a04fad0d201c7d6c87 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:50:56 -0700 Subject: [PATCH 01/34] src/strntoul-detail.hpp: Initial revision. --- src/strntoul-detail.hpp | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/strntoul-detail.hpp 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 From cb864b792d5720a9bb601309a29c76e6aef1d91d Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:50:56 -0700 Subject: [PATCH 02/34] src/strntoul-detail.cpp: Initial revision. --- src/strntoul-detail.cpp | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/strntoul-detail.cpp 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 From 01d5a45730f178c2d93f7354ebdcd37413f72211 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:50:56 -0700 Subject: [PATCH 03/34] src/strntoul-detail-unsigned-template.hpp: Initial revision. --- src/strntoul-detail-unsigned-template.hpp | 199 ++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/strntoul-detail-unsigned-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 From 6b579aaa831c78b8b688d471ce0b5f6598fe1733 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:50:56 -0700 Subject: [PATCH 04/34] src/strntoul-detail-signed-template.hpp: Initial revision. --- src/strntoul-detail-signed-template.hpp | 137 ++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/strntoul-detail-signed-template.hpp 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 From f1f2e71c99f0d28fa7e52cf47ee19b198aa3f62d Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 05/34] src/strntoll.h: Initial revision. --- src/strntoll.h | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/strntoll.h 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 */ From 6f4b628363069290524caa91104e603e43326176 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 06/34] src/strntoll.cpp: Initial revision. --- src/strntoll.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/strntoll.cpp 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)); +} From 21213f6c22989c15a420497b5fc269e44b93cea5 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 07/34] src/strntoull.h: Initial revision. --- src/strntoull.h | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/strntoull.h 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 */ From 202c4eba47cf8de26193dc23cb3e0c11aeb73337 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 08/34] src/strntoull.cpp: Initial revision. --- src/strntoull.cpp | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/strntoull.cpp 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)); +} From abc3504df4d79f4340e3ba686801c4920bf03d01 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 09/34] src/strntoimax.h: Initial revision. --- src/strntoimax.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/strntoimax.h 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 */ From a22d0b8015984e139eb44472cb123b2edf96e374 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 10/34] src/strntoimax.cpp: Initial revision. --- src/strntoimax.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/strntoimax.cpp 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))); +} From 57a3074222cd874f97d7e7d18bb560fd616a3754 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 11/34] src/strntoumax.h: Initial revision. --- src/strntoumax.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/strntoumax.h 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 */ From 03f0e1ee3fae4f17a3a914d4c9f9df532b2a628a Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 12/34] src/strntoumax.cpp: Initial revision. --- src/strntoumax.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/strntoumax.cpp 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))); +} From 52a3465904825cc72d4bb1e3da858bcaacf12b45 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 13/34] src/strntoq.h: Initial revision. --- src/strntoq.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/strntoq.h 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 */ From 2759168409060e52abd9414422cc8039e7977fb0 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 14/34] src/strntoq.cpp: Initial revision. --- src/strntoq.cpp | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/strntoq.cpp 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))); +} From f8c58eb67049816c08be0350158db1250aa0984e Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 15/34] src/strntouq.h: Initial revision. --- src/strntouq.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/strntouq.h 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 */ From 7c41db05a96966d7f68ca4ea09d0dedd80cb1d82 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:53:41 -0700 Subject: [PATCH 16/34] src/strntouq.cpp: Initial revision. --- src/strntouq.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/strntouq.cpp 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))); +} From 51ca634b752a7bad5f3f95fdcdbe2b49dbf43142 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 17/34] src/tests/Test_strntoll.cpp: Initial revision. --- src/tests/Test_strntoll.cpp | 790 ++++++++++++++++++++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100644 src/tests/Test_strntoll.cpp 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); +} From ab69d69d49831131a964f89fb79dec638f5341b3 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 18/34] src/tests/Test_strntoull.cpp: Initial revision. --- src/tests/Test_strntoull.cpp | 757 +++++++++++++++++++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 src/tests/Test_strntoull.cpp 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); +} From 216017ab5b3d893285be0f9912af5fb93e4ac808 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 19/34] src/tests/Test_strntoimax.cpp: Initial revision. --- src/tests/Test_strntoimax.cpp | 790 ++++++++++++++++++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100644 src/tests/Test_strntoimax.cpp 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); +} From b53e2a23d21a9566f793f4561fff44c319a28fc1 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 20/34] src/tests/Test_strntoumax.cpp: Initial revision. --- src/tests/Test_strntoumax.cpp | 757 ++++++++++++++++++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 src/tests/Test_strntoumax.cpp 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); +} From 512385b6ee222ae3f6d637f242461f1d64ec12e6 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 21/34] src/tests/Test_strntoq.cpp: Initial revision. --- src/tests/Test_strntoq.cpp | 790 +++++++++++++++++++++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100644 src/tests/Test_strntoq.cpp 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); +} From 6727cb225981ec5dbf0f9f85e7f070243ed6d6c5 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:54:53 -0700 Subject: [PATCH 22/34] src/tests/Test_strntouq.cpp: Initial revision. --- src/tests/Test_strntouq.cpp | 757 ++++++++++++++++++++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 src/tests/Test_strntouq.cpp 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); +} From 7a5f0e8205c272dbe3c80ad9ad99aab8dd40d3ec Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:56:49 -0700 Subject: [PATCH 23/34] src/Makefile.am: Added 'strntoul-detail{,-signed-template,-unsigned-template}.hpp' to 'noinst_HEADERS'. --- src/Makefile.am | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Makefile.am b/src/Makefile.am index 9e25a28..756eefd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,6 +30,9 @@ 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. From c0547f7634caa398bfc4bc53c1f36706398a187e Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:57:51 -0700 Subject: [PATCH 24/34] src/Makefile.am: Added 'strnto{ll,ull,imax,umax,q,uq}.h' to 'include_HEADERS'. --- src/Makefile.am | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Makefile.am b/src/Makefile.am index 756eefd..d01fadb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,8 +38,14 @@ noinst_HEADERS = \ # 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 = \ From a93a15b56f013aa9a4ea60b39efca9f43ab7537f Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:58:19 -0700 Subject: [PATCH 25/34] src/Makefile.am: Added '-D_BSD_SOURCE' to 'libstrntoul_la_CPPFLAGS'. --- src/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Makefile.am b/src/Makefile.am index d01fadb..dcc4a16 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -53,6 +53,7 @@ libstrntoul_la_LDFLAGS = \ $(NULL) libstrntoul_la_CPPFLAGS = \ + -D_BSD_SOURCE \ -I$(top_srcdir)/src/include \ $(NULL) From c3622f61ae1691009907d79c2be69d52f0799d07 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 15:59:21 -0700 Subject: [PATCH 26/34] src/Makefile.am: Added 'strnto{ll,ull,imax,umax,q,uq,ul-detail}.cpp' to 'libstrntoul_la_SOURCES'. --- src/Makefile.am | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Makefile.am b/src/Makefile.am index dcc4a16..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. @@ -58,8 +58,15 @@ libstrntoul_la_CPPFLAGS = \ $(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 From 6d8bc7c01750c789e11021928773363ec8001c2c Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:00:07 -0700 Subject: [PATCH 27/34] src/tests/Makefile.am: Added test targets, variables, and sources for 'strnto{ll,ull,imax,umax,q,uq}'. --- src/tests/Makefile.am | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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 # From c30bf64f5ffc82b0842036e53aba9a004e101888 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:02:25 -0700 Subject: [PATCH 28/34] src/tests/Test_strntol.cpp: Removed 'fprintf' development/debug statements. --- src/tests/Test_strntol.cpp | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/tests/Test_strntol.cpp b/src/tests/Test_strntol.cpp index 0764472..847bf97 100644 --- a/src/tests/Test_strntol.cpp +++ b/src/tests/Test_strntol.cpp @@ -401,8 +401,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 +410,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 +423,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 +432,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 +443,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,8 +452,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); @@ -509,8 +497,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 +506,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 +548,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 +570,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 +579,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); @@ -682,8 +660,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 +669,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); From 253fc6d035b693eceefc59756c33b8914d13bc1c Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:03:46 -0700 Subject: [PATCH 29/34] src/tests/Test_strntol.cpp: Use 'TestType' typedef to neutralize the return type. --- src/tests/Test_strntol.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/tests/Test_strntol.cpp b/src/tests/Test_strntol.cpp index 847bf97..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; @@ -458,9 +461,9 @@ static void TestOverflow(nlTestSuite *inSuite __attribute__((unused)), } 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; @@ -643,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; From 0b0646b20930d19e5263ca79ea573818fd91d410 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:03:53 -0700 Subject: [PATCH 30/34] src/tests/Test_strntoul.cpp: Use 'TestType' typedef to neutralize the return type. --- src/tests/Test_strntoul.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) 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; From 340ea395dc3bcf7fa275f31cae54f6ff70fdf45f Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:06:10 -0700 Subject: [PATCH 31/34] src/strntol.cpp: Leverage 'Detail::StringExtentToSignedType' template. --- src/strntol.cpp | 88 +++++-------------------------------------------- 1 file changed, 8 insertions(+), 80 deletions(-) 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)); } From 85331fdaef4e2fdc7fd174dc43718a9b4b262962 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:06:25 -0700 Subject: [PATCH 32/34] src/strntoul.cpp: Leverage 'Detail::StringExtentToUnsignedType' template. --- src/strntoul.cpp | 256 +---------------------------------------------- 1 file changed, 5 insertions(+), 251 deletions(-) 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)); } From 5a3afcbc8443638f5c6d86b4f8065a9eeb8a10cb Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:07:12 -0700 Subject: [PATCH 33/34] CHANGES.md: Update change log / release notes for 2.0. --- CHANGES.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) 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). From 7a3c50a2d76748d3c732694dc2b512c961b210b0 Mon Sep 17 00:00:00 2001 From: Grant Erickson Date: Mon, 6 Apr 2026 16:07:45 -0700 Subject: [PATCH 34/34] .default-version: Change default version from 1.0.1 to 2.0. --- .default-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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