detailed tests
This commit is contained in:
@@ -3,13 +3,42 @@ project(tests LANGUAGES C)
|
|||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
# The headers are documented as C23-first but expected to work on older standards.
|
||||||
|
# Pinned here so that the claim is actually exercised; configure with
|
||||||
|
# -DDLIBC_C_STANDARD=23 to build them the way the README describes.
|
||||||
|
set(DLIBC_C_STANDARD 11 CACHE STRING "C standard the tests are compiled against")
|
||||||
|
|
||||||
|
# The library's stated goal is memory safety, so the take, aliasing and destructor
|
||||||
|
# tests are worth a lot more when the allocator is watching. Off by default, as it
|
||||||
|
# slows the tests down and needs a compiler that ships the sanitizer runtimes.
|
||||||
|
option(DLIBC_SANITIZE "Build the tests with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
|
||||||
|
|
||||||
add_executable(test_vector
|
add_executable(test_vector
|
||||||
tests/test_vector.c
|
tests/test_vector.c
|
||||||
|
tests/vector_altcap.c
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(test_set
|
add_executable(test_set
|
||||||
tests/test_set.c
|
tests/test_set.c
|
||||||
|
tests/set_altcap.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
foreach(dlibc_target test_vector test_set)
|
||||||
|
set_target_properties(${dlibc_target} PROPERTIES
|
||||||
|
C_STANDARD ${DLIBC_C_STANDARD}
|
||||||
|
C_STANDARD_REQUIRED ON
|
||||||
|
C_EXTENSIONS OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT MSVC)
|
||||||
|
target_compile_options(${dlibc_target} PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||||
|
|
||||||
|
if(DLIBC_SANITIZE)
|
||||||
|
target_compile_options(${dlibc_target} PRIVATE -g -fno-omit-frame-pointer -fsanitize=address,undefined)
|
||||||
|
target_link_options(${dlibc_target} PRIVATE -fsanitize=address,undefined)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
add_test(NAME test_vector COMMAND test_vector)
|
add_test(NAME test_vector COMMAND test_vector)
|
||||||
add_test(NAME test_set COMMAND test_set)
|
add_test(NAME test_set COMMAND test_set)
|
||||||
@@ -20,7 +20,16 @@ If you want to contribute, please make a pull request and I'll review it. If you
|
|||||||
|
|
||||||
If you change an existing type, please run **make** in *build/* directory and **ctest** to ensure compatibility hasn't been broken.
|
If you change an existing type, please run **make** in *build/* directory and **ctest** to ensure compatibility hasn't been broken.
|
||||||
|
|
||||||
If it's new, please write a **test** for it in *tests/test_<name>.c* so others can see the guidelines on how the type is used.
|
Since the whole point is memory safety, please also run the suite with the sanitizers before opening a pull request:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build-asan -DDLIBC_SANITIZE=ON && cmake --build build-asan
|
||||||
|
ctest --test-dir build-asan --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `ctest -V` to see every individual assertion, and `-DDLIBC_C_STANDARD=23` to build against C23 instead of the default C11.
|
||||||
|
|
||||||
|
If it's new, please write a **test** for it in *tests/test_<name>.c* so others can see the guidelines on how the type is used. The tests use a tiny in-tree harness, *tests/dtest.h*, which is just a list of named cases and some `CHECK_` macros - there is no external framework to install.
|
||||||
|
|
||||||
## Legal Mumbo Jumbo
|
## Legal Mumbo Jumbo
|
||||||
Licensed under the MIT License (because GPL doesn't make sense for this (sorry Stallman))
|
Licensed under the MIT License (because GPL doesn't make sense for this (sorry Stallman))
|
||||||
|
|||||||
+320
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
dtest.h - A tiny test harness for the DLibC test suites.
|
||||||
|
|
||||||
|
There is no external test framework here on purpose, as the library itself is
|
||||||
|
header-only and dependency-free and the tests should be too. A suite is an array
|
||||||
|
of named cases, each a void(void) function that runs assertions through the
|
||||||
|
CHECK_ and REQUIRE_ macros below, handed to dtest_main() from main().
|
||||||
|
|
||||||
|
Assertions come in two strengths:
|
||||||
|
- CHECK_* records the failure and keeps going, so one case reports every
|
||||||
|
problem it finds rather than only the first.
|
||||||
|
- REQUIRE_* records the failure and returns from the case, for the cases where
|
||||||
|
continuing is meaningless (a create() that returned NULL, say).
|
||||||
|
|
||||||
|
Every assertion prints a line to stdout, so ctest -V shows exactly what was
|
||||||
|
verified. Failures also go to stderr with file:line. The process exit code is
|
||||||
|
what ctest actually judges: 0 if every case passed, 1 otherwise.
|
||||||
|
|
||||||
|
@attention The counters below are file-scope statics, so a translation unit that
|
||||||
|
includes this header keeps its own tallies. Only the TU that calls dtest_main()
|
||||||
|
should run assertions. Helper TUs (the alternate-capacity probes) must expose
|
||||||
|
plain functions and let the main TU do the checking.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DTEST_H
|
||||||
|
#define DTEST_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef void (*dtest_fn_t)(void); // Function pointer type for a test case
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* name; // Name of the case, as shown in the output
|
||||||
|
dtest_fn_t fn; // The case itself
|
||||||
|
} dtest_case_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Builds a dtest_case_t entry from a case function, using the function's own name as the case name.
|
||||||
|
@param fn The case function. Must be a void(void).
|
||||||
|
*/
|
||||||
|
#define DTEST_CASE(fn) { #fn, fn }
|
||||||
|
|
||||||
|
static int dtest_checks_run = 0; // Assertions evaluated in the current case
|
||||||
|
static int dtest_checks_failed = 0; // Assertions failed in the current case
|
||||||
|
static int dtest_total_checks = 0; // Assertions evaluated across the whole suite
|
||||||
|
static int dtest_total_failed = 0; // Assertions failed across the whole suite
|
||||||
|
static int dtest_cases_failed = 0; // Cases with at least one failed assertion
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Records the outcome of a single assertion and prints it.
|
||||||
|
@param ok Non-zero if the assertion held.
|
||||||
|
@param label A short description of what was being asserted.
|
||||||
|
@param detail The observed value, already formatted, or NULL if there is nothing useful to show.
|
||||||
|
@param file The source file the assertion sits in.
|
||||||
|
@param line The line the assertion sits on.
|
||||||
|
@attention This is an internal helper. Use the CHECK_ and REQUIRE_ macros instead.
|
||||||
|
*/
|
||||||
|
static inline void dtest_record(int ok, const char* label, const char* detail, const char* file, int line) {
|
||||||
|
++dtest_checks_run;
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
printf("[ check ] %-58s ok", label);
|
||||||
|
if (detail) {
|
||||||
|
printf(" (%s)", detail);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++dtest_checks_failed;
|
||||||
|
printf("[ check ] %-58s FAILED\n", label);
|
||||||
|
fprintf(stderr, "%s:%d: %s", file, line, label);
|
||||||
|
if (detail) {
|
||||||
|
fprintf(stderr, " -- %s", detail);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a condition holds, then keeps going either way.
|
||||||
|
@param cond The condition to evaluate.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_TRUE(cond, label) \
|
||||||
|
dtest_record((cond) ? 1 : 0, (label), NULL, __FILE__, __LINE__)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a condition holds, and returns from the case if it does not.
|
||||||
|
@param cond The condition to evaluate.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
@attention Only usable inside a case function, as it expands to a return statement.
|
||||||
|
*/
|
||||||
|
#define REQUIRE_TRUE(cond, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = (cond) ? 1 : 0; \
|
||||||
|
dtest_record(dtest_ok_, (label), NULL, __FILE__, __LINE__); \
|
||||||
|
if (!dtest_ok_) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that an int expression equals an expected value. Used for the 0/1/-1 return codes.
|
||||||
|
@param actual The expression to evaluate.
|
||||||
|
@param expected The value it should have.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_EQ_INT(actual, expected, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_a_ = (int)(actual); \
|
||||||
|
int dtest_e_ = (int)(expected); \
|
||||||
|
char dtest_buf_[96]; \
|
||||||
|
snprintf(dtest_buf_, sizeof(dtest_buf_), "got %d, want %d", dtest_a_, dtest_e_); \
|
||||||
|
dtest_record(dtest_a_ == dtest_e_, (label), dtest_buf_, __FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a size_t expression equals an expected value. Used for sizes, capacities and SET_NPOS.
|
||||||
|
@param actual The expression to evaluate.
|
||||||
|
@param expected The value it should have.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_EQ_SIZE(actual, expected, label) \
|
||||||
|
do { \
|
||||||
|
size_t dtest_a_ = (size_t)(actual); \
|
||||||
|
size_t dtest_e_ = (size_t)(expected); \
|
||||||
|
char dtest_buf_[96]; \
|
||||||
|
snprintf(dtest_buf_, sizeof(dtest_buf_), "got %zu, want %zu", dtest_a_, dtest_e_); \
|
||||||
|
dtest_record(dtest_a_ == dtest_e_, (label), dtest_buf_, __FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a pointer is NULL.
|
||||||
|
@param ptr The pointer to evaluate.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_PTR_NULL(ptr, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = ((ptr) == NULL); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected NULL", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a pointer is not NULL, then keeps going either way.
|
||||||
|
@param ptr The pointer to evaluate.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_PTR_NOT_NULL(ptr, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = ((ptr) != NULL); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected non-NULL", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a pointer is not NULL, and returns from the case if it is.
|
||||||
|
@param ptr The pointer to evaluate.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
@attention Only usable inside a case function, as it expands to a return statement.
|
||||||
|
*/
|
||||||
|
#define REQUIRE_PTR_NOT_NULL(ptr, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = ((ptr) != NULL); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected non-NULL", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
if (!dtest_ok_) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that two pointers are the same address. Used for identity checks, i.e. that an operation did not reallocate.
|
||||||
|
@param a The first pointer.
|
||||||
|
@param b The second pointer.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_PTR_EQ(a, b, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = ((const void*)(a) == (const void*)(b)); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected same address", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that two pointers are different addresses.
|
||||||
|
@param a The first pointer.
|
||||||
|
@param b The second pointer.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_PTR_NE(a, b, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = ((const void*)(a) != (const void*)(b)); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected different addresses", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that two buffers hold the same bytes.
|
||||||
|
@param a The first buffer.
|
||||||
|
@param b The second buffer.
|
||||||
|
@param n The number of bytes to compare.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_MEM_EQ(a, b, n, label) \
|
||||||
|
do { \
|
||||||
|
int dtest_ok_ = (memcmp((a), (b), (n)) == 0); \
|
||||||
|
dtest_record(dtest_ok_, (label), dtest_ok_ ? NULL : "expected identical bytes", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Asserts that a string equals an expected value.
|
||||||
|
@param actual The string to evaluate. May be NULL, which always fails.
|
||||||
|
@param expected The string it should equal.
|
||||||
|
@param label A short description of what is being asserted.
|
||||||
|
*/
|
||||||
|
#define CHECK_EQ_STR(actual, expected, label) \
|
||||||
|
do { \
|
||||||
|
const char* dtest_a_ = (actual); \
|
||||||
|
const char* dtest_e_ = (expected); \
|
||||||
|
char dtest_buf_[160]; \
|
||||||
|
snprintf(dtest_buf_, sizeof(dtest_buf_), "got \"%s\", want \"%s\"", \
|
||||||
|
dtest_a_ ? dtest_a_ : "(null)", dtest_e_ ? dtest_e_ : "(null)"); \
|
||||||
|
dtest_record(dtest_a_ && dtest_e_ && strcmp(dtest_a_, dtest_e_) == 0, \
|
||||||
|
(label), dtest_buf_, __FILE__, __LINE__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int dtest_destructor_calls = 0; // Number of times a dtest destructor has run since the last reset
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Resets the destructor call counter. Call at the top of any case that asserts a call count.
|
||||||
|
*/
|
||||||
|
static inline void dtest_reset_destructor_calls(void) {
|
||||||
|
dtest_destructor_calls = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief An element destructor that only counts its invocations and frees nothing.
|
||||||
|
@param element A pointer to the element's slot inside the container's data array.
|
||||||
|
@attention Use this on containers of plain values, where the point of the case is the call count rather than the cleanup.
|
||||||
|
*/
|
||||||
|
static inline void dtest_count_destructor(void* element) {
|
||||||
|
(void)element;
|
||||||
|
++dtest_destructor_calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief An element destructor for a container of char*, which frees the string and counts the call.
|
||||||
|
@param element A pointer to the element's slot, i.e. a char** .
|
||||||
|
@attention This frees what the element owns and never the slot itself, exactly as the container headers require.
|
||||||
|
*/
|
||||||
|
static inline void dtest_string_destructor(void* element) {
|
||||||
|
++dtest_destructor_calls;
|
||||||
|
free(*(char**)element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Duplicates a string onto the heap.
|
||||||
|
@param str The string to duplicate.
|
||||||
|
@return A newly allocated copy, or NULL on allocation failure.
|
||||||
|
@attention Hand-rolled rather than strdup(), which is POSIX rather than ISO C and is not visible under -std=c11 with extensions off.
|
||||||
|
*/
|
||||||
|
static inline char* dtest_dup(const char* str) {
|
||||||
|
size_t len = strlen(str) + 1;
|
||||||
|
char* copy = (char*)malloc(len);
|
||||||
|
if (!copy) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy, str, len);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Runs every case in a suite and reports the results.
|
||||||
|
@param cases The array of cases to run.
|
||||||
|
@param count The number of cases in the array.
|
||||||
|
@param suite The suite's name, used in the output banner.
|
||||||
|
@return 0 if every assertion in every case passed, 1 otherwise. Return this straight out of main() so ctest sees it.
|
||||||
|
*/
|
||||||
|
static inline int dtest_main(const dtest_case_t* cases, size_t count, const char* suite) {
|
||||||
|
printf("[==========] %s: running %zu cases\n", suite, count);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
dtest_checks_run = 0;
|
||||||
|
dtest_checks_failed = 0;
|
||||||
|
dtest_reset_destructor_calls();
|
||||||
|
|
||||||
|
printf("[ RUN ] %s\n", cases[i].name);
|
||||||
|
cases[i].fn();
|
||||||
|
|
||||||
|
dtest_total_checks += dtest_checks_run;
|
||||||
|
dtest_total_failed += dtest_checks_failed;
|
||||||
|
|
||||||
|
if (dtest_checks_failed > 0) {
|
||||||
|
++dtest_cases_failed;
|
||||||
|
printf("[ FAILED ] %s (%d of %d checks failed)\n",
|
||||||
|
cases[i].name, dtest_checks_failed, dtest_checks_run);
|
||||||
|
fprintf(stderr, "FAILED: %s\n", cases[i].name);
|
||||||
|
} else if (dtest_checks_run == 0) {
|
||||||
|
// A case that asserts nothing is almost certainly a mistake, so it
|
||||||
|
// is treated as a failure rather than quietly counted as a pass
|
||||||
|
++dtest_cases_failed;
|
||||||
|
printf("[ FAILED ] %s (ran no checks)\n", cases[i].name);
|
||||||
|
fprintf(stderr, "FAILED: %s ran no checks\n", cases[i].name);
|
||||||
|
} else {
|
||||||
|
printf("[ OK ] %s (%d checks)\n", cases[i].name, dtest_checks_run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("[==========] %s: %zu cases, %d checks, %d failed checks in %d cases\n",
|
||||||
|
suite, count, dtest_total_checks, dtest_total_failed, dtest_cases_failed);
|
||||||
|
|
||||||
|
return dtest_cases_failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DTEST_H
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
set_altcap.c - Probes set.h with a non-default DLIBC_SET_INITIAL_CAPACITY.
|
||||||
|
|
||||||
|
The macro is read at include time, so exercising it needs a translation unit of
|
||||||
|
its own. Every function in set.h is static inline, so this TU gets its own
|
||||||
|
copies built around the smaller constant while the set_t layout stays identical
|
||||||
|
to the one in test_set.c. That means a set created here is a perfectly ordinary
|
||||||
|
set to the rest of the program.
|
||||||
|
|
||||||
|
@attention This TU deliberately does not include dtest.h. The harness counters
|
||||||
|
are file-scope statics, so an assertion made here would be tallied separately
|
||||||
|
and never reach the summary. The probes below only gather observations, and
|
||||||
|
test_set.c does the asserting.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DLIBC_SET_INITIAL_CAPACITY 1
|
||||||
|
#include "../set.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Records the set's capacity after each of count successive insert() calls of distinct values, starting from a freshly created set.
|
||||||
|
@param out A buffer of at least count size_t values, filled with the capacity observed after each insert.
|
||||||
|
@param count The number of inserts to perform.
|
||||||
|
@return 0 on success, -1 if out is NULL, count is 0, or any insert fails.
|
||||||
|
@attention With DLIBC_SET_INITIAL_CAPACITY at 1, the expected sequence is 1, 2, 4, 4, 8, 8, 8, 8, ...
|
||||||
|
*/
|
||||||
|
int set_altcap_growth_sequence(size_t* out, size_t count) {
|
||||||
|
if (!out || count == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_t* set = set_create(sizeof(int));
|
||||||
|
if (!set) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
int value = (int)i;
|
||||||
|
if (set_insert(set, &value) != 0) {
|
||||||
|
set_destroy(&set);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[i] = set_capacity(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_destroy(&set);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Reports the capacity of a freshly created set in this translation unit.
|
||||||
|
@return The initial capacity, or 0 if creation failed.
|
||||||
|
*/
|
||||||
|
size_t set_altcap_initial_capacity(void) {
|
||||||
|
set_t* set = set_create(sizeof(int));
|
||||||
|
if (!set) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t capacity = set_capacity(set);
|
||||||
|
set_destroy(&set);
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
+1877
-22
File diff suppressed because it is too large
Load Diff
+1089
-24
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
vector_altcap.c - Probes vector.h with a non-default DLIBC_VECTOR_INITIAL_CAPACITY.
|
||||||
|
|
||||||
|
The macro is read at include time, so exercising it needs a translation unit of
|
||||||
|
its own. Every function in vector.h is static inline, so this TU gets its own
|
||||||
|
copies built around the smaller constant while the vector_t layout stays
|
||||||
|
identical to the one in test_vector.c. That means a vector created here is a
|
||||||
|
perfectly ordinary vector to the rest of the program.
|
||||||
|
|
||||||
|
@attention This TU deliberately does not include dtest.h. The harness counters
|
||||||
|
are file-scope statics, so an assertion made here would be tallied separately
|
||||||
|
and never reach the summary. The probes below only gather observations, and
|
||||||
|
test_vector.c does the asserting.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DLIBC_VECTOR_INITIAL_CAPACITY 1
|
||||||
|
#include "../vector.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Records the vector's capacity after each of count successive push_back() calls, starting from a freshly created vector.
|
||||||
|
@param out A buffer of at least count size_t values, filled with the capacity observed after each push.
|
||||||
|
@param count The number of pushes to perform.
|
||||||
|
@return 0 on success, -1 if out is NULL, count is 0, or any allocation fails.
|
||||||
|
@attention With DLIBC_VECTOR_INITIAL_CAPACITY at 1, the expected sequence is 1, 2, 4, 4, 8, 8, 8, 8, ...
|
||||||
|
*/
|
||||||
|
int vector_altcap_growth_sequence(size_t* out, size_t count) {
|
||||||
|
if (!out || count == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector_t* vec = vector_create(sizeof(int));
|
||||||
|
if (!vec) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
int value = (int)i;
|
||||||
|
if (vector_push_back(vec, &value) != 0) {
|
||||||
|
vector_destroy(&vec);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[i] = vector_capacity(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector_destroy(&vec);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@brief Reports the capacity of a freshly created vector in this translation unit.
|
||||||
|
@return The initial capacity, or 0 if creation failed.
|
||||||
|
*/
|
||||||
|
size_t vector_altcap_initial_capacity(void) {
|
||||||
|
vector_t* vec = vector_create(sizeof(int));
|
||||||
|
if (!vec) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t capacity = vector_capacity(vec);
|
||||||
|
vector_destroy(&vec);
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user