369 lines
12 KiB
C++
369 lines
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);
|
||
|
}
|