Files
miniaudio/tests/testbench/testbench.c
T
2026-05-02 15:52:55 +10:00

632 lines
19 KiB
C

#include "../../external/fs/fs.c" /* Include this before miniaudio.c to avoid some C89 related errors. */
#include "../../miniaudio.c"
#include <stdio.h>
/* BEG ma_test.c */
typedef struct ma_test ma_test;
typedef int (* ma_test_proc)(ma_test* pUserData);
struct ma_test
{
const char* name;
ma_test_proc proc;
void* pUserData;
int result;
ma_test* pFirstChild;
ma_test* pNextSibling;
};
void ma_test_init(ma_test* pTest, const char* name, ma_test_proc proc, void* pUserData, ma_test* pParent)
{
if (pTest == NULL) {
return;
}
memset(pTest, 0, sizeof(ma_test));
pTest->name = name;
pTest->proc = proc;
pTest->pUserData = pUserData;
pTest->result = MA_SUCCESS;
pTest->pFirstChild = NULL;
pTest->pNextSibling = NULL;
if (pParent != NULL) {
if (pParent->pFirstChild == NULL) {
pParent->pFirstChild = pTest;
} else {
ma_test* pSibling = pParent->pFirstChild;
while (pSibling->pNextSibling != NULL) {
pSibling = pSibling->pNextSibling;
}
pSibling->pNextSibling = pTest;
}
}
}
void ma_test_count(ma_test* pTest, int* pCount, int* pPassed)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
*pCount += 1;
if (pTest->result == MA_SUCCESS) {
*pPassed += 1;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_count(pChild, pCount, pPassed);
pChild = pChild->pNextSibling;
}
}
int ma_test_run(ma_test* pTest)
{
/* Start our counts at -1 to exclude the root test. */
int testCount = -1;
int passedCount = -1;
if (pTest == NULL) {
return MA_ERROR;
}
if (pTest->name != NULL && pTest->proc != NULL) {
printf("Running Test: %s\n", pTest->name);
}
if (pTest->proc != NULL) {
pTest->result = pTest->proc(pTest);
if (pTest->result != MA_SUCCESS) {
return pTest->result;
}
}
/* Now we need to recursively execute children. If any child test fails, the parent test needs to be marked as failed as well. */
{
ma_test* pChild = pTest->pFirstChild;
while (pChild != NULL) {
int result = ma_test_run(pChild);
if (result != MA_SUCCESS) {
pTest->result = result;
}
pChild = pChild->pNextSibling;
}
}
/* Now count the number of failed tests and report success or failure depending on the result. */
ma_test_count(pTest, &testCount, &passedCount);
return (testCount == passedCount) ? MA_SUCCESS : MA_ERROR;
}
void ma_test_print_local_result(ma_test* pTest, int level)
{
if (pTest == NULL) {
return;
}
printf("[%s] %*s%s\n", pTest->result == MA_SUCCESS ? "PASS" : "FAIL", level * 2, "", pTest->name);
}
void ma_test_print_child_results(ma_test* pTest, int level)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_print_local_result(pChild, level);
ma_test_print_child_results(pChild, level + 1);
pChild = pChild->pNextSibling;
}
}
void ma_test_print_result(ma_test* pTest, int level)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
if (pTest->name != NULL) {
printf("[%s] %*s%s\n", pTest->result == MA_SUCCESS ? "PASS" : "FAIL", level * 2, "", pTest->name);
level += 1;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_print_result(pChild, level);
pChild = pChild->pNextSibling;
}
}
void ma_test_print_summary(ma_test* pTest)
{
/* Start our counts at -1 to exclude the root test. */
int testCount = -1;
int passedCount = -1;
if (pTest == NULL) {
return;
}
/* This should only be called on a root test. */
assert(pTest->name == NULL);
printf("=== Test Summary ===\n");
ma_test_print_result(pTest, 0);
/* We need to count how many tests failed. */
ma_test_count(pTest, &testCount, &passedCount);
printf("---\n%s%d / %d tests passed.\n", (testCount == passedCount) ? "[PASS]: " : "[FAIL]: ", passedCount, testCount);
}
/* END ma_test.c */
/* BEG ma_test_data_source.c */
typedef struct ma_test_data_source
{
ma_data_source_base base;
ma_test* pTest;
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint64 cursor; /* Relative to the beginning of the current range. */
ma_uint64 length; /* The full length of the data source, not including the range. */
ma_bool32 isInitialized;
ma_bool32 isLooping;
ma_uint64 rangeBegInFrames;
ma_uint64 rangeEndInFrames;
ma_uint64 loopBegInFrames;
ma_uint64 loopEndInFrames;
} ma_test_data_source;
static size_t ma_test_data_source_sizeof(void)
{
return sizeof(ma_test_data_source);
}
static void ma_test_data_source_uninit(ma_data_source* pDataSource)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
pTestDataSource->isInitialized = MA_FALSE;
}
static ma_result ma_test_data_source_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_test_data_source* pNewTestDataSource = (ma_test_data_source*)pNewDataSource;
pNewTestDataSource->format = pTestDataSource->format;
pNewTestDataSource->channels = pTestDataSource->channels;
pNewTestDataSource->sampleRate = pTestDataSource->sampleRate;
pNewTestDataSource->cursor = pTestDataSource->cursor;
pNewTestDataSource->length = pTestDataSource->length;
pNewTestDataSource->isInitialized = pTestDataSource->isInitialized;
pNewTestDataSource->isLooping = pTestDataSource->isLooping;
pNewTestDataSource->rangeBegInFrames = pTestDataSource->rangeBegInFrames;
pNewTestDataSource->rangeEndInFrames = pTestDataSource->rangeEndInFrames;
pNewTestDataSource->loopBegInFrames = pTestDataSource->loopBegInFrames;
pNewTestDataSource->loopEndInFrames = pTestDataSource->loopEndInFrames;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_read(ma_data_source* pDataSource, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_result result;
ma_uint64 length;
ma_uint64 framesRemaining;
/* miniaudio should always be giving us a valid pointer for pFramesRead. */
if (pFramesRead == NULL) {
printf("%s: pFramesRead is null.\n", pTestDataSource->pTest->name);
return MA_INVALID_ARGS;
}
result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve the length of the data source when reading.\n", pTestDataSource->pTest->name);
return result;
}
framesRemaining = length - pTestDataSource->cursor;
if (frameCount > framesRemaining) {
frameCount = framesRemaining;
}
MA_ZERO_MEMORY(pFrames, frameCount * ma_get_bytes_per_frame(pTestDataSource->format, pTestDataSource->channels));
pTestDataSource->cursor += frameCount;
if (pTestDataSource->cursor > length) {
MA_ASSERT(!"cursor > length. Test is incorrect.");
}
*pFramesRead = frameCount;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_result result;
ma_uint64 length;
result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve the length of the data source when reading.\n", pTestDataSource->pTest->name);
return result;
}
if (frameIndex > length) {
return MA_BAD_SEEK;
}
pTestDataSource->cursor = frameIndex;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_prop(ma_data_source* pDataSource, int prop, void* pData)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
switch (prop)
{
case MA_DATA_SOURCE_GET_DATA_FORMAT:
{
ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData;
pDataFormat->format = pTestDataSource->format;
pDataFormat->channels = pTestDataSource->channels;
pDataFormat->sampleRate = pTestDataSource->sampleRate;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_CURSOR:
{
*((ma_uint64*)pData) = pTestDataSource->cursor;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LENGTH:
{
*((ma_uint64*)pData) = pTestDataSource->length;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_LOOPING:
{
pTestDataSource->isLooping = *((ma_bool32*)pData);
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LOOPING:
{
*((ma_bool32*)pData) = pTestDataSource->isLooping;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_RANGE:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
if (pRange->begInFrames > pTestDataSource->length || pRange->endInFrames > pTestDataSource->length) {
return MA_INVALID_ARGS;
}
pTestDataSource->rangeBegInFrames = pRange->begInFrames;
pTestDataSource->rangeEndInFrames = pRange->endInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_RANGE:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
pRange->begInFrames = pTestDataSource->rangeBegInFrames;
pRange->endInFrames = pTestDataSource->rangeEndInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_LOOP_POINT:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
if (pRange->begInFrames > pTestDataSource->length || pRange->endInFrames > pTestDataSource->length) {
return MA_INVALID_ARGS;
}
pTestDataSource->loopBegInFrames = pRange->begInFrames;
pTestDataSource->loopEndInFrames = pRange->endInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LOOP_POINT:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
pRange->begInFrames = pTestDataSource->loopBegInFrames;
pRange->endInFrames = pTestDataSource->loopEndInFrames;
return MA_SUCCESS;
}
default:
{
return MA_NOT_IMPLEMENTED;
}
}
}
static ma_data_source_vtable ma_gDataSourceVTable_Test =
{
ma_test_data_source_sizeof,
ma_test_data_source_uninit,
ma_test_data_source_copy,
ma_test_data_source_read,
ma_test_data_source_seek,
ma_test_data_source_prop
};
ma_result ma_test_data_source_init(ma_test* pTest, ma_test_data_source* pTestDataSource)
{
ma_result result;
ma_data_source_config baseConfig;
if (pTestDataSource == NULL) {
MA_ASSERT(!"No input data source. Fix the tests!");
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pTestDataSource);
pTestDataSource->pTest = pTest;
pTestDataSource->format = ma_format_s16;
pTestDataSource->channels = 2;
pTestDataSource->sampleRate = 48000;
pTestDataSource->cursor = 0;
pTestDataSource->length = 1024;
pTestDataSource->isInitialized = MA_FALSE;
pTestDataSource->isLooping = MA_FALSE;
pTestDataSource->rangeBegInFrames = 0;
pTestDataSource->rangeEndInFrames = ~((ma_uint64)0);
pTestDataSource->loopBegInFrames = 0;
pTestDataSource->loopEndInFrames = ~((ma_uint64)0);
baseConfig = ma_data_source_config_init();
baseConfig.pVTable = &ma_gDataSourceVTable_Test;
result = ma_data_source_base_init(&baseConfig, &pTestDataSource->base);
if (result != MA_SUCCESS) {
printf("%s: Failed to initialize test data source.\n", pTest->name);
return result;
}
return MA_SUCCESS;
}
int ma_test_data_source_properties(ma_test* pTest)
{
/* This tests that properties are properly intercepted by data sources. To do this we need a special test data source. */
ma_result result;
ma_test_data_source testDataSource;
result = ma_test_data_source_init(pTest, &testDataSource);
if (result != MA_SUCCESS) {
printf("%s: Failed to initialize test data source.\n", pTest->name);
return 1;
}
/* Basic property tests first. */
{
ma_data_source_data_format dataFormat;
result = ma_data_source_get_data_format(&testDataSource, &dataFormat.format, &dataFormat.channels, &dataFormat.sampleRate);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve data format.\n", pTest->name);
return 1;
}
if (dataFormat.format != testDataSource.format) {
printf("%s: Sample format does not match.\n", pTest->name);
return 1;
}
if (dataFormat.channels != testDataSource.channels) {
printf("%s: Channel counts do not match.\n", pTest->name);
return 1;
}
if (dataFormat.sampleRate != testDataSource.sampleRate) {
printf("%s: Sample rates do not match.\n", pTest->name);
return 1;
}
}
/* Looping. */
{
ma_bool32 isLooping = MA_TRUE;
result = ma_data_source_set_looping(&testDataSource, isLooping);
if (result != MA_SUCCESS) {
printf("%s: Failed to set the looping state of the data source.\n", pTest->name);
return 1;
}
if (testDataSource.isLooping != isLooping) {
printf("%s: SET_LOOPING was not handled.\n", pTest->name);
return 1;
}
if (ma_data_source_is_looping(&testDataSource) != isLooping) {
printf("%s: is_looping() did not return expected value of %u.\n", pTest->name, isLooping);
return 1;
}
}
/* Loop Points. */
{
ma_uint64 loopPointBeg = 256;
ma_uint64 loopPointEnd = loopPointBeg + 128;
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, loopPointBeg, loopPointEnd);
if (result != MA_SUCCESS) {
printf("%s: Failed to set loop points.\n", pTest->name);
return 1;
}
if (testDataSource.loopBegInFrames != loopPointBeg || testDataSource.loopEndInFrames != loopPointEnd) {
printf("%s: SET_LOOP_POINT was not handled.\n", pTest->name);
return 1;
}
{
ma_uint64 queriedLoopPointBeg;
ma_uint64 queriedLoopPointEnd;
ma_data_source_get_loop_point_in_pcm_frames(&testDataSource, &queriedLoopPointBeg, &queriedLoopPointEnd);
if (queriedLoopPointBeg != loopPointBeg || queriedLoopPointEnd != loopPointEnd) {
printf("%s: get_loop_point_in_pcm_frames() returned enexpected values.\n", pTest->name);
return 1;
}
}
/* Erroneous Loop Points. */
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, 99999, 999999); /* Exceeds the length of the data source.*/
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_loop_point_in_pcm_frames().\n", pTest->name);
return 1;
}
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, 256, 0); /* Beg > End */
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_loop_point_in_pcm_frames().\n", pTest->name);
return 1;
}
}
/* Ranges. */
{
ma_uint64 rangeBeg = 128;
ma_uint64 rangeEnd = rangeBeg + 512;
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, rangeBeg, rangeEnd);
if (result != MA_SUCCESS) {
printf("%s: Failed to set range.\n", pTest->name);
return 1;
}
if (testDataSource.rangeBegInFrames != rangeBeg || testDataSource.rangeEndInFrames != rangeEnd) {
printf("%s: SET_RANGE was not handled.\n", pTest->name);
return 1;
}
{
ma_uint64 queriedRangeBeg;
ma_uint64 queriedRangeEnd;
ma_data_source_get_range_in_pcm_frames(&testDataSource, &queriedRangeBeg, &queriedRangeEnd);
if (queriedRangeBeg != rangeBeg || queriedRangeEnd != rangeEnd) {
printf("%s: get_range_in_pcm_frames() returned enexpected values.\n", pTest->name);
return 1;
}
}
/* Erroneous Ranges. */
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, 99999, 999999); /* Exceeds the length of the data source.*/
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_range_in_pcm_frames().\n", pTest->name);
return 1;
}
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, 256, 0); /* Beg > End */
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_range_in_pcm_frames().\n", pTest->name);
return 1;
}
}
/* Length. */
{
/*
The length from ma_data_source_get_length_in_pcm_frames() needs to be clamped to the range, but the
length returned by MA_DATA_SOURCE_GET_LENGTH needs to be unclamped.
*/
ma_uint64 clampedLength = 512;
ma_uint64 rangeBeg = 0;
ma_uint64 rangeEnd = rangeBeg + clampedLength;
ma_uint64 queriedLengthClamped;
ma_uint64 queriedLengthFull;
MA_ASSERT(clampedLength != testDataSource.length); /* The clamped length cannot equal the full length for this test to work. */
ma_data_source_set_range_in_pcm_frames(&testDataSource, rangeBeg, rangeEnd);
ma_data_source_get_length_in_pcm_frames(&testDataSource, &queriedLengthClamped);
if (queriedLengthClamped != clampedLength) {
printf("%s: get_length_in_pcm_frames() returned an unexpected value.\n", pTest->name);
return 1;
}
ma_data_source_prop(&testDataSource, MA_DATA_SOURCE_GET_LENGTH, &queriedLengthFull);
if (queriedLengthFull != testDataSource.length) {
printf("%s: MA_DATA_SOURCE_GET_LENGTH returned an unexpected value.\n", pTest->name);
return 1;
}
}
return FS_SUCCESS;
}
/* END ma_test_data_source.c */
int main(int argc, char** argv)
{
int result;
ma_test test_root;
ma_test test_data_source;
ma_test test_data_source_properties;
/* Root. Only used for execution. */
ma_test_init(&test_root, NULL, NULL, NULL, NULL);
/* Data source tests. */
ma_test_init(&test_data_source, "Data Source", NULL, NULL, &test_root);
ma_test_init(&test_data_source_properties, "Data Source Properties", ma_test_data_source_properties, NULL, &test_data_source);
result = ma_test_run(&test_root);
/* Print the test summary. */
printf("\n");
ma_test_print_summary(&test_root);
(void)argc;
(void)argv;
if (result == FS_SUCCESS) {
return 0;
} else {
return 1;
}
}