| // Copyright 2020 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/avif.h" |
| |
| #include "testcase.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #if defined(_WIN32) |
| |
| #include <windows.h> |
| |
| typedef struct NextFilenameData |
| { |
| int didFirstFile; |
| HANDLE handle; |
| WIN32_FIND_DATA wfd; |
| } NextFilenameData; |
| |
| static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd) |
| { |
| for (;;) { |
| if (nfd->didFirstFile) { |
| if (FindNextFile(nfd->handle, &nfd->wfd) == 0) { |
| // No more files |
| break; |
| } |
| } else { |
| char filenameBuffer[2048]; |
| snprintf(filenameBuffer, sizeof(filenameBuffer), "%s\\*", parentDir); |
| filenameBuffer[sizeof(filenameBuffer) - 1] = 0; |
| nfd->handle = FindFirstFile(filenameBuffer, &nfd->wfd); |
| if (nfd->handle == INVALID_HANDLE_VALUE) { |
| return NULL; |
| } |
| nfd->didFirstFile = 1; |
| } |
| |
| // If we get here, we should have a valid wfd |
| const char * dot = strrchr(nfd->wfd.cFileName, '.'); |
| if (dot) { |
| ++dot; |
| if (!strcmp(dot, extension)) { |
| return nfd->wfd.cFileName; |
| } |
| } |
| } |
| |
| FindClose(nfd->handle); |
| nfd->handle = INVALID_HANDLE_VALUE; |
| nfd->didFirstFile = 0; |
| return NULL; |
| } |
| |
| #else |
| #include <dirent.h> |
| typedef struct NextFilenameData |
| { |
| DIR * dir; |
| } NextFilenameData; |
| |
| static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd) |
| { |
| if (!nfd->dir) { |
| nfd->dir = opendir(parentDir); |
| if (!nfd->dir) { |
| return NULL; |
| } |
| } |
| |
| struct dirent * entry; |
| while ((entry = readdir(nfd->dir)) != NULL) { |
| const char * dot = strrchr(entry->d_name, '.'); |
| if (dot) { |
| ++dot; |
| if (!strcmp(dot, extension)) { |
| return entry->d_name; |
| } |
| } |
| } |
| |
| closedir(nfd->dir); |
| nfd->dir = NULL; |
| return NULL; |
| } |
| #endif |
| |
| static int generateTests(const char * dataDir) |
| { |
| printf("AVIF Test Suite: Generating Tests...\n"); |
| |
| int retCode = 0; |
| cJSON * tests = cJSON_CreateArray(); |
| |
| struct QuantizerPairs |
| { |
| int minQP; |
| int maxQP; |
| } quantizerPairs[] = { |
| { 0, 0 }, // lossless |
| { 0, 10 }, // Q90 |
| { 4, 40 }, // Q60 |
| { 24, 60 } // Q40 |
| }; |
| const int quantizerPairsCount = sizeof(quantizerPairs) / sizeof(quantizerPairs[0]); |
| |
| NextFilenameData nfd; |
| memset(&nfd, 0, sizeof(nfd)); |
| const char * filename = nextFilename(dataDir, "y4m", &nfd); |
| for (; filename != NULL; filename = nextFilename(dataDir, "y4m", &nfd)) { |
| avifCodecChoice encodeChoices[] = { AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_CHOICE_RAV1E }; |
| const int encodeChoiceCount = sizeof(encodeChoices) / sizeof(encodeChoices[0]); |
| for (int encodeChoiceIndex = 0; encodeChoiceIndex < encodeChoiceCount; ++encodeChoiceIndex) { |
| avifCodecChoice decodeChoices[] = { AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_CHOICE_DAV1D }; |
| const int decodeChoiceCount = sizeof(decodeChoices) / sizeof(decodeChoices[0]); |
| for (int decodeChoiceIndex = 0; decodeChoiceIndex < decodeChoiceCount; ++decodeChoiceIndex) { |
| for (int qpIndex = 0; qpIndex < quantizerPairsCount; ++qpIndex) { |
| int speeds[] = { 0, 6, 8 }; |
| int speedCount = sizeof(speeds) / sizeof(speeds[0]); |
| for (int speedIndex = 0; speedIndex < speedCount; ++speedIndex) { |
| TestCase * tc = testCaseCreate(); |
| testCaseSetInputFilename(tc, filename); |
| tc->encodeChoice = encodeChoices[encodeChoiceIndex]; |
| tc->decodeChoice = decodeChoices[decodeChoiceIndex]; |
| tc->active = AVIF_TRUE; |
| tc->speed = speeds[speedIndex]; |
| tc->minQuantizer = quantizerPairs[qpIndex].minQP; |
| tc->maxQuantizer = quantizerPairs[qpIndex].maxQP; |
| testCaseGenerateName(tc); |
| |
| if (!testCaseRun(tc, dataDir, AVIF_TRUE)) { |
| printf("ERROR: Failed to run test case: %s\n", tc->name); |
| goto cleanup; |
| } |
| |
| cJSON_AddItemToArray(tests, testCaseToJSON(tc)); |
| testCaseDestroy(tc); |
| } |
| } |
| } |
| } |
| } |
| |
| char * jsonString = cJSON_PrintUnformatted(tests); |
| |
| char testJSONFilename[2048]; |
| snprintf(testJSONFilename, sizeof(testJSONFilename), "%s/tests.json", dataDir); |
| testJSONFilename[sizeof(testJSONFilename) - 1] = 0; |
| FILE * f = fopen(testJSONFilename, "wb"); |
| if (f) { |
| fprintf(f, "%s", jsonString); |
| fclose(f); |
| |
| printf("Wrote: %s\n", testJSONFilename); |
| } else { |
| printf("Failed to write: %s\n", testJSONFilename); |
| retCode = 1; |
| } |
| free(jsonString); |
| |
| cleanup: |
| cJSON_Delete(tests); |
| return retCode; |
| } |
| |
| static int runTests(const char * dataDir, const char * testFilter) |
| { |
| (void)testFilter; |
| printf("AVIF Test Suite: Running Tests...\n"); |
| |
| char testJSONFilename[2048]; |
| snprintf(testJSONFilename, sizeof(testJSONFilename), "%s/tests.json", dataDir); |
| testJSONFilename[sizeof(testJSONFilename) - 1] = 0; |
| FILE * f = fopen(testJSONFilename, "rb"); |
| if (!f) { |
| printf("ERROR: Failed to read: %s\n", testJSONFilename); |
| return 1; |
| } |
| |
| fseek(f, 0, SEEK_END); |
| size_t fileSize = ftell(f); |
| fseek(f, 0, SEEK_SET); |
| |
| char * rawJSON = malloc(fileSize + 1); |
| if (fread(rawJSON, 1, fileSize, f) != fileSize) { |
| printf("ERROR: Failed to read: %s\n", testJSONFilename); |
| free(rawJSON); |
| fclose(f); |
| return 1; |
| } |
| rawJSON[fileSize] = 0; |
| fclose(f); |
| |
| cJSON * tests = cJSON_Parse(rawJSON); |
| if (!tests || !cJSON_IsArray(tests)) { |
| if (tests) { |
| cJSON_Delete(tests); |
| } |
| printf("ERROR: Invalid JSON: %s\n", testJSONFilename); |
| return 1; |
| } |
| |
| int totalCount = 0; |
| int skippedCount = 0; |
| int failedCount = 0; |
| for (cJSON * t = tests->child; t != NULL; t = t->next) { |
| if (!cJSON_IsObject(t)) { |
| ++skippedCount; |
| continue; |
| } |
| |
| TestCase * tc = testCaseFromJSON(t); |
| if (!tc || !tc->active) { |
| ++skippedCount; |
| continue; |
| } |
| |
| if (testFilter) { |
| if (strstr(tc->name, testFilter) == NULL) { |
| ++skippedCount; |
| continue; |
| } |
| } |
| |
| if (!testCaseRun(tc, dataDir, AVIF_FALSE)) { |
| ++failedCount; |
| } |
| ++totalCount; |
| |
| testCaseDestroy(tc); |
| } |
| |
| printf("Complete. %d tests ran, %d skipped, %d failed.\n", totalCount, skippedCount, failedCount); |
| |
| cJSON_Delete(tests); |
| return (failedCount == 0) ? 0 : 1; |
| } |
| |
| int main(int argc, char * argv[]) |
| { |
| const char * dataDir = NULL; |
| const char * testFilter = NULL; |
| avifBool generate = AVIF_FALSE; |
| |
| // Parse cmdline |
| for (int i = 1; i < argc; ++i) { |
| char * arg = argv[i]; |
| if (!strcmp(arg, "-g")) { |
| generate = AVIF_TRUE; |
| } else if (dataDir == NULL) { |
| dataDir = arg; |
| } else if (testFilter == NULL) { |
| testFilter = arg; |
| } else { |
| fprintf(stderr, "Too many positional arguments: %s\n", arg); |
| return 1; |
| } |
| } |
| |
| // Verify all required args were set |
| if (dataDir == NULL) { |
| fprintf(stderr, "dataDir is required, bailing out.\n"); |
| return 1; |
| } |
| |
| printf("Test Data Dir: %s\n", dataDir); |
| |
| int retCode = 1; |
| if (generate) { |
| retCode = generateTests(dataDir); |
| } else { |
| retCode = runTests(dataDir, testFilter); |
| } |
| |
| if (retCode == 0) { |
| printf("AVIF Test Suite: Complete.\n"); |
| } else { |
| printf("AVIF Test Suite: Failed.\n"); |
| } |
| return retCode; |
| } |