looper/tests.cpp
2024-09-28 10:31:18 -07:00

369 lines
No EOL
12 KiB
C++

#include <gtest/gtest.h>
#include "file_backend.hpp"
#include <string>
#include <stdlib.h>
#include <filesystem>
#include <time.h>
#include <stdlib.h>
#include "base85.h"
#include "playback_backend.hpp"
#include "backend.hpp"
#include "playback_process.hpp"
#include "assets/test_assets.h"
#include "license.hpp"
#include "log.hpp"
#include <fmt/core.h>
#include <fmt/format.h>
namespace fs = std::filesystem;
std::unordered_set<LicenseData> license_data;
std::unordered_set<LicenseData> &get_license_data() {
return license_data;
}
char *executable_path;
fs::path tmpname() {
fs::path tmpdir = fs::temp_directory_path();
std::string randname = "";
std::string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
srand(clock());
for (size_t i = 0; i < 64; i++) {
char chr = chars[rand() % chars.length()];
randname += chr;
}
randname += ".tmp";
return tmpdir / fs::path(randname);
}
size_t byte_diff(void *a, void *b, size_t len) {
uint8_t *a8 = (uint8_t*)a;
uint8_t *b8 = (uint8_t*)b;
size_t output = 0;
for (size_t i = 0; i < len; i++) {
if (a8[i] != b8[i]) {
output += 1;
}
}
return output;
}
std::string print_diff(void *a, void *b, size_t len, testing::Test *test = nullptr) {
uint8_t *a8 = (uint8_t*)a;
uint8_t *b8 = (uint8_t*)b;
std::string output = "";
for (size_t i = 0; i < len; i++) {
uint8_t aval = a8[i];
uint8_t bval = b8[i];
std::string key = fmt::format("diff_{0}", i);
std::string astring = "";
std::string bstring = "";
std::string value = "";
if (aval != bval) {
uint8_t diff = aval ^ bval;
output += fmt::format("Differences at bytepos {0}: ", i);
for (size_t i = 0; i < 8; i++) {
if (diff & (0x80 >> i)) {
value += (aval & (0x80 >> i)) ? "A" : "B";
} else {
value += (aval & (0x80 >> i)) ? "1" : "0";
}
}
output += value;
output += "\n";
} else {
for (size_t i = 0; i < 8; i++) {
value += (aval & (0x80 >> i)) ? "1" : "0";
}
}
for (size_t i = 0; i < 8; i++) {
astring += (aval & (0x80 >> i)) ? "1" : "0";
}
for (size_t i = 0; i < 8; i++) {
bstring += (aval & (0x80 >> i)) ? "1" : "0";
}
if (test != nullptr) {
test->RecordProperty(fmt::format("a_{0}", i), astring);
test->RecordProperty(fmt::format("b_{0}", i), bstring);
test->RecordProperty(fmt::format("diff_{0}", i), value);
}
}
return output;
}
fs::path make_file() {
fs::path path = tmpname();
FILE *file = fopen(path.c_str(), "wb+");
for (size_t i = 0; i < 1024 * 1024 / 2; i++) {
uint16_t val = rand() % 65536;
fwrite(&val, 2, 1, file);
}
fclose(file);
return path;
}
fs::path make_audio_file(const void *ptr, size_t len) {
fs::path path = tmpname().replace_extension("flac");
DEBUG.writefln("Creating audio file at path: %s", path.c_str());
FILE *file = fopen(path.c_str(), "wb+");
fwrite(ptr, 1, len, file);
fclose(file);
return path;
}
template<class T>
struct vec2 {
public:
T value[2];
vec2(T values[2]) {
this->value = {*values};
}
vec2(std::initializer_list<T> values) {
size_t i = 0;
for (auto &tmp : values) {
value[i] = tmp;
i += 1;
if (i >= 2) break;
}
}
T &operator[](int idx) {
return value[idx];
}
template<int i>
T get() {
return (*this)[i];
}
};
template<class T>
struct std::tuple_size<vec2<T>> {
constexpr const static int value = 2;
};
template<class T>
struct std::tuple_element<0, vec2<T>> {
using type = T;
};
template<class T>
struct std::tuple_element<1, vec2<T>> {
using type = T;
};
class FileBackendTest : public testing::Test {
protected:
fs::path first;
fs::path second;
SDL_RWops *first_file;
SDL_RWops *second_file;
size_t flen;
void *ptra;
void *ptrb;
Sint64 rand_offset;
int rand_type;
void randomize() {
rand_offset = rand() % flen;
if (rand() % 2 == 0) {
rand_offset *= -1;
}
int tmp = rand() % 3;
rand_type = tmp == 0 ? RW_SEEK_CUR : tmp == 1 ? RW_SEEK_SET : RW_SEEK_END;
size = 1024 * 1024;
len = rand() % (1024 * 1024);
size /= len;
if (rand() % 2 == 0) {
size_t tmp = size;
size = len;
len = tmp;
}
total = size * len;
}
void SetUp() override {
srand(clock());
first = make_file();
second = tmpname();
fs::copy_file(first, second);
first_file = SDL_RWFromFile(first.c_str(), "rb");
File *sfile_base = open_file(second.c_str());
second_file = get_sdl_file(sfile_base);
flen = sfile_base->get_len();
ptra = malloc(1024 * 1024);
ptrb = malloc(1024 * 1024);
}
~FileBackendTest() {
SDL_RWclose(first_file);
SDL_RWclose(second_file);
fs::remove(first);
fs::remove(second);
free(ptra);
free(ptrb);
}
size_t size;
size_t len;
size_t total;
size_t _read(bool first) {
memset(first ? ptra : ptrb, 0, 1024 * 1024);
return SDL_RWread(first ? first_file : second_file, first ? ptra : ptrb, size, len);
}
vec2<size_t> read() {
size_t a = _read(true);
size_t b = _read(false);
return {a, b};
}
vec2<Sint64> seek() {
Sint64 first_value;
Sint64 second_value;
first_value = SDL_RWseek(first_file, rand_offset, rand_type);
second_value = SDL_RWseek(second_file, rand_offset, rand_type);
return {first_value, second_value};
}
Sint64 get_pos(bool second) {
return SDL_RWtell(second ? second_file : first_file);
}
void iter(bool seek, bool read) {
randomize();
if (seek) {
printf("\e7");
const char *seeking_str = "[Seeking] ";
printf("%s", seeking_str);
size_t oldpos = get_pos(false);
auto[first_pos, second_pos] = this->seek();
ASSERT_EQ(first_pos, second_pos) << "From " << (rand_type == RW_SEEK_CUR ? "current position" : rand_type == RW_SEEK_END ? "end of file" : "beginning of file") << ", " << rand_offset;
}
if (seek && read) {
printf("\e8\e[0K");
}
if (read) {
printf("[Reading] ");
size_t oldpos = get_pos(false);
auto[first_len, second_len] = this->read();
ASSERT_EQ(first_len, second_len);
ASSERT_EQ(get_pos(false), get_pos(true)) << "size: " << size << "\nlen: " << len << "\ntotal: " << total << "\nfile length: " << flen << "\nold position: " << oldpos;
size_t newpos = get_pos(false);
ASSERT_EQ(byte_diff(ptra, ptrb, total), 0) << "size: " << size << "\nlen: " << len << "\ntotal: " << total << "\nfile length: " << flen << "\npositions: " << oldpos << "-" << newpos;
}
}
void run(size_t run_count, bool seek, bool read) {
printf("\e[?25l");
for (size_t i = 0; i < run_count; i++) {
printf("\r[%3ld%%] [%ld/%ld] \e[K", (i * 100) / run_count, i, run_count);
iter(seek, read);
if (get_pos(false) != get_pos(true)) {
break; // Can't continue
}
}
printf("\e[1K\r", run_count, run_count);
printf("\e[?25h");
}
};
#define MAKE_AUDIO_FILE(id) (make_audio_file(testdata_##id##_data, testdata_##id##_size))
TEST_F(FileBackendTest, Seeking) {
run(65536, true, false);
}
TEST_F(FileBackendTest, Reading) {
run(100, false, true);
}
TEST_F(FileBackendTest, ReadingSeeking) {
run(100, true, true);
}
class CustomLogger : public Looper::Log::LogStream {
private:
static std::stringstream stream;
public:
void _writec(const char chr) override {
stream << chr;
}
void writeprefix() override { }
static std::string get_string() {
return stream.str();
}
CustomLogger() : Looper::Log::LogStream({}, INT_MIN, false, NULL) { }
};
std::stringstream CustomLogger::stream;
Looper::Log::LogStream *_init_custom_logger(CustomLogger* logger, int idx) {
return new Looper::Log::LogStream({Looper::Log::get_log_name_by_idx(idx)}, {logger}, idx);
}
void init_custom_logger() {
CustomLogger *logger = new CustomLogger();
Looper::Log::init_logging_custom([logger](int idx) -> Looper::Log::LogStream* {
return _init_custom_logger(logger, idx);
});
}
class PlaybackBackendTest : public testing::Test {
public:
fs::path audio_path = "";
protected:
void SetUp() override {
init_custom_logger();
//SDL_Init(SDL_INIT_AUDIO);
init_playback_backends();
init_audio_data();
}
void TearDown() override {
fs::remove(audio_path);
PlaybackBackend::deinit_backends();
}
std::string get_log_output() {
return CustomLogger::get_string();
}
};
TEST_F(PlaybackBackendTest, Looping) {
audio_path = MAKE_AUDIO_FILE(test_flac);
PlaybackProcess process(audio_path);
AudioSpec *spec = process.get_audio_spec();
size_t insize = 1024 * spec->bits() / 8;
void *tmp = malloc(insize);
audio_data_t data {
.size = spec->bits(),
.endian = spec->endian() == EndianID::BIG,
.is_signed = spec->format_type() != FormatType::UNSIGNED,
.is_float = spec->format_type() == FormatType::FLOAT
};
SDL_AudioStream *stream = SDL_NewAudioStream(sample_spec_to_sdl(data), 1, 48000, AUDIO_U8, 1, 48000);
uint32_t bits = spec->bits();
auto format_type = spec->format_type();
process.render(tmp, insize);
SDL_AudioStreamPut(stream, tmp, insize);
memset(tmp, 0, 1024);
SDL_AudioStreamGet(stream, tmp, 1024);
uint8_t *tmp2 = (uint8_t*)malloc(1024);
size_t copies = 2;
size_t offs = 0;
for (size_t i = 0; i < copies * 32; i++) {
tmp2[i + offs] = (uint8_t)(i / copies);
}
for (size_t i = 32 * copies; i < 1024 - offs; i++) {
tmp2[i + offs] = (uint8_t)((((i / 2) - 32) % 32) + 32);
}
ASSERT_EQ(byte_diff(tmp, tmp2, 1024), 0) << get_log_output() << std::endl << print_diff(tmp, tmp2, 1024, this);
free(tmp);
free(tmp2);
}
TEST_F(PlaybackBackendTest, PlayingWAV) {
audio_path = MAKE_AUDIO_FILE(test_wav);
PlaybackProcess process(audio_path);
AudioSpec *spec = process.get_audio_spec();
size_t insize = 1024 * spec->bits() / 8;
void *tmp = malloc(insize);
audio_data_t data {
.size = spec->bits(),
.endian = spec->endian() == EndianID::BIG,
.is_signed = spec->format_type() != FormatType::UNSIGNED,
.is_float = spec->format_type() == FormatType::FLOAT
};
SDL_AudioStream *stream = SDL_NewAudioStream(sample_spec_to_sdl(data), 1, 48000, AUDIO_U8, 1, 48000);
uint32_t bits = spec->bits();
auto format_type = spec->format_type();
process.render(tmp, insize);
SDL_AudioStreamPut(stream, tmp, insize);
memset(tmp, 0, 1024);
SDL_AudioStreamGet(stream, tmp, 1024);
uint8_t *tmp2 = (uint8_t*)malloc(1024);
size_t copies = 2;
size_t offs = 0;
for (size_t i = 0, j = 0; i < 1024 - offs; i++) {
tmp2[i + offs] = (uint8_t)(j);
if ((i % 2) == 1) {
size_t k = i & 0x1FE;
if (k != 128 && k != 382) {
j += 1;
}
if (k == 510) {
j = 0;
}
}
}
ASSERT_EQ(byte_diff(tmp, tmp2, 1024), 0) << get_log_output() << std::endl << print_diff(tmp, tmp2, 1024, this);
free(tmp);
free(tmp2);
}