#include #include "file_backend.hpp" #include #include #include #include #include #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 #include namespace fs = std::filesystem; std::unordered_set license_data; std::unordered_set &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 struct vec2 { public: T value[2]; vec2(T values[2]) { this->value = {*values}; } vec2(std::initializer_list 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 T get() { return (*this)[i]; } }; template struct std::tuple_size> { constexpr const static int value = 2; }; template struct std::tuple_element<0, vec2> { using type = T; }; template struct std::tuple_element<1, vec2> { 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 read() { size_t a = _read(true); size_t b = _read(false); return {a, b}; } vec2 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); }