pdfio/test.h
2025-04-18 17:22:15 -04:00

283 lines
7.1 KiB
C

//
// Unit test header for C/C++ programs.
//
// Copyright © 2021-2025 by Michael R Sweet.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
#ifndef TEST_H
# define TEST_H
# include <stdio.h>
# include <stdlib.h>
# include <stdarg.h>
# include <stdbool.h>
# include <string.h>
# if _WIN32
# include <io.h>
# define isatty(f) _isatty(f)
# else
# include <unistd.h>
# endif // !_WIN32
# ifdef __cplusplus
extern "C" {
# endif // __cplusplus
//
// This header implements a simple unit test framework for C/C++ programs.
// Inline functions are provided to write a test summary to stdout and the
// details to stderr. This allows unit test programs to output a summary to
// stdout with details sent to stderr, e.g.:
//
// mytestprogram 2>test.log
//
// Documentation:
//
// void testBegin(const char *title, ...)
//
// Start a test with a printf-style title message. "Title:" (the formatted
// title followed by a colon) is output.
//
// void testEnd(bool pass)
//
// End a test without an additional message. "pass" should be `true` if the
// test passed and `false` otherwise. "PASS" or "FAIL" is output.
//
// void testEndMessage(bool pass, const char *message, ...)
//
// End a test with an additional printf-style message. "pass" should be
// `true` if the test passed and `false` otherwise. "PASS (message)" or
// "FAIL (message)" is output.
//
// testError(const char *error, ...)
//
// Sends a formatted error string to stderr.
//
// testHexDump(const unsigned char *buffer, size_t bytes)
//
// Sends a hex dump of the specified buffer to stderr.
//
// testMessage(const char *error, ...)
//
// Outputs a formatted message string.
//
// testProgress(void)
//
// Shows a progress spinner for long-running tests.
//
// bool testsPassed
//
// This global variable specifies whether all tests have passed (`true`)
// or one or more have failed (`false`).
//
static bool testsPassed = true; // All tests passed?
static int test_progress; // Current progress
static char test_title[1024] = ""; // Current test title
// Start a test
static inline void
testBegin(const char *title, ...) // I - printf-style title string
{
va_list ap; // Pointer to additional arguments
// Format the title string
va_start(ap, title);
vsnprintf(test_title, sizeof(test_title), title, ap);
va_end(ap);
// Send the title to stdout and stderr...
test_progress = 0;
printf("%s: ", test_title);
fflush(stdout);
if (!isatty(2))
fprintf(stderr, "%s: ", test_title);
}
// End a test with no additional information
static inline void
testEnd(bool pass) // I - `true` if the test passed, `false` otherwise
{
// Send the test result to stdout and stderr
if (test_progress)
putchar('\b');
if (!pass)
testsPassed = false;
puts(pass ? "PASS" : "FAIL");
if (!isatty(2))
fputs(pass ? "PASS\n" : "FAIL\n", stderr);
test_title[0] = '\0';
}
// End a test with no additional information
static inline void
testEndMessage(bool pass, // I - `true` if the test passed, `false` otherwise
const char *message, ...)// I - printf-style message
{
char buffer[1024]; // Formatted title string
va_list ap; // Pointer to additional arguments
// Format the title string
va_start(ap, message);
vsnprintf(buffer, sizeof(buffer), message, ap);
va_end(ap);
// Send the test result to stdout and stderr
if (test_progress)
putchar('\b');
printf(pass ? "PASS (%s)\n" : "FAIL (%s)\n", buffer);
if (!isatty(2))
fprintf(stderr, pass ? "PASS (%s)\n" : "FAIL (%s)\n", buffer);
test_title[0] = '\0';
}
// Show/update a progress spinner
static inline void
testProgress(void)
{
if (test_progress)
putchar('\b');
putchar("-\\|/"[test_progress & 3]);
fflush(stdout);
test_progress ++;
}
// Show an error to stderr...
static inline void
testError(const char *error, ...) // I - printf-style error string
{
char buffer[1024]; // Formatted title string
va_list ap; // Pointer to additional arguments
// Format the error string
va_start(ap, error);
vsnprintf(buffer, sizeof(buffer), error, ap);
va_end(ap);
// Send the error to stderr...
fprintf(stderr, "%s\n", buffer);
if (test_title[0])
fprintf(stderr, "%s: ", test_title);
}
// Show a message to stdout and stderr...
static inline void
testMessage(const char *error, ...) // I - printf-style error string
{
char buffer[1024]; // Formatted title string
va_list ap; // Pointer to additional arguments
// Format the error string
va_start(ap, error);
vsnprintf(buffer, sizeof(buffer), error, ap);
va_end(ap);
// Send the message to stdout and stderr too if needed...
printf("%s\n", buffer);
if (test_title[0])
{
printf("%s: ", test_title);
fflush(stdout);
}
if (!isatty(2))
{
fprintf(stderr, "%s\n", buffer);
if (test_title[0])
fprintf(stderr, "%s: ", test_title);
}
}
// Show a hex dump of a buffer to stderr...
static inline void
testHexDump(const unsigned char *buffer,// I - Buffer
size_t bytes) // I - Number of bytes
{
size_t i, j; // Looping vars
int ch; // Current ASCII char
if (test_title[0])
fputs("\n", stderr);
// Show lines of 16 bytes at a time...
for (i = 0; i < bytes; i += 16)
{
// Show the offset...
fprintf(stderr, "%04x ", (unsigned)i);
// Then up to 16 bytes in hex...
for (j = 0; j < 16; j ++)
{
if ((i + j) < bytes)
fprintf(stderr, " %02x", buffer[i + j]);
else
fputs(" ", stderr);
}
// Then the ASCII representation of the bytes...
fputs(" ", stderr);
for (j = 0; j < 16 && (i + j) < bytes; j ++)
{
ch = buffer[i + j] & 127;
if (ch < ' ' || ch == 127)
fputc('.', stderr);
else
fputc(ch, stderr);
}
fputc('\n', stderr);
}
if (test_title[0])
fprintf(stderr, "%s: ", test_title);
}
# ifdef __cplusplus
}
# endif // __cplusplus
#endif // !TEST_H