mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 11:01:07 +00:00
110 lines
5.7 KiB
Python
110 lines
5.7 KiB
Python
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("cc"), "C compiler required")
|
|
class AndroidVolumeTest(unittest.TestCase):
|
|
def test_missing_or_invalid_initial_state_recovers_without_restarting_the_sink(self):
|
|
header = Path(__file__).resolve().parents[1] / "aaudio-sink/android-volume.h"
|
|
harness = r'''
|
|
#define _GNU_SOURCE
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#define PA_VOLUME_NORM 65536U
|
|
#define PA_USEC_PER_MSEC 1000
|
|
#define PA_SINK_INIT 0
|
|
#define PA_SUBSCRIPTION_EVENT_SINK 1
|
|
#define PA_SUBSCRIPTION_EVENT_CHANGE 2
|
|
#define PA_MIN(a,b) ((a)<(b)?(a):(b))
|
|
#define pa_xnew0(type,n) ((type*)calloc(n,sizeof(type)))
|
|
#define pa_xstrdup strdup
|
|
#define pa_xfree free
|
|
#define pa_log_warn(...) ((void)0)
|
|
typedef struct { unsigned value; } pa_cvolume;
|
|
typedef struct pa_time_event { int value; } pa_time_event;
|
|
typedef struct pa_mainloop_api { void (*time_free)(pa_time_event*); } pa_mainloop_api;
|
|
typedef struct { pa_mainloop_api *mainloop; } pa_core;
|
|
typedef struct pa_sink {
|
|
void *userdata; int state; bool muted; unsigned n_volume_steps, index;
|
|
struct { unsigned channels; } sample_spec;
|
|
pa_cvolume real_volume, reference_volume, soft_volume;
|
|
pa_core *core;
|
|
void (*set_volume)(struct pa_sink*), (*set_mute)(struct pa_sink*);
|
|
} pa_sink;
|
|
struct android_volume;
|
|
struct userdata { struct android_volume *android_volume; };
|
|
static int restarts, frees;
|
|
static uint64_t last_delay;
|
|
static char *pa_sprintf_malloc(const char *format, ...) {
|
|
va_list ap; va_start(ap,format); char *result; assert(vasprintf(&result,format,ap)>=0); va_end(ap); return result;
|
|
}
|
|
static void pa_cvolume_set(pa_cvolume *volume, unsigned channels, unsigned value) { volume->value=value; }
|
|
static unsigned pa_cvolume_max(const pa_cvolume *volume) { return volume->value; }
|
|
static void pa_cvolume_reset(pa_cvolume *volume, unsigned channels) { volume->value=PA_VOLUME_NORM; }
|
|
static bool pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { return a->value==b->value; }
|
|
static void pa_sink_volume_changed(pa_sink *sink, const pa_cvolume *volume) { sink->real_volume=*volume; }
|
|
static void pa_sink_mute_changed(pa_sink *sink, bool muted) { sink->muted=muted; }
|
|
static void pa_sink_set_set_volume_callback(pa_sink *sink, void (*callback)(pa_sink*)) { sink->set_volume=callback; }
|
|
static void pa_sink_set_set_mute_callback(pa_sink *sink, void (*callback)(pa_sink*)) { sink->set_mute=callback; }
|
|
static void pa_subscription_post(pa_core *core, int event, unsigned index) {}
|
|
static uint64_t pa_rtclock_now(void) { return 0; }
|
|
static void pa_core_rttime_restart(pa_core *core, pa_time_event *event, uint64_t time) { restarts++; last_delay=time; }
|
|
static pa_time_event *pa_core_rttime_new(pa_core *core, uint64_t time,
|
|
void (*callback)(pa_mainloop_api*,pa_time_event*,const struct timeval*,void*), void *userdata) {
|
|
return calloc(1,sizeof(pa_time_event));
|
|
}
|
|
static void free_timer(pa_time_event *event) { frees++; free(event); }
|
|
#include "android-volume.h"
|
|
static void state(const char *directory, const char *text) {
|
|
char *path=pa_sprintf_malloc("%s/volume.state",directory);
|
|
FILE *f=fopen(path,"w"); assert(f); fputs(text,f); fclose(f); free(path);
|
|
}
|
|
int main(int argc, char **argv) {
|
|
pa_mainloop_api api={.time_free=free_timer}; pa_core core={.mainloop=&api};
|
|
struct userdata userdata={0};
|
|
pa_sink sink={.userdata=&userdata,.core=&core,.state=1,.sample_spec={.channels=2},.real_volume={PA_VOLUME_NORM}};
|
|
userdata.android_volume=android_volume_new(&sink,argv[1]);
|
|
assert(userdata.android_volume && userdata.android_volume->timer && sink.set_volume && sink.set_mute);
|
|
state(argv[1],"invalid\n");
|
|
android_volume_refresh(&api,userdata.android_volume->timer,NULL,userdata.android_volume);
|
|
assert(sink.real_volume.value==PA_VOLUME_NORM && restarts==1 && last_delay==100*PA_USEC_PER_MSEC);
|
|
state(argv[1],"3 15 1\n");
|
|
android_volume_refresh(&api,userdata.android_volume->timer,NULL,userdata.android_volume);
|
|
assert(sink.real_volume.value==3*PA_VOLUME_NORM/15 && sink.muted && sink.n_volume_steps==16);
|
|
assert(sink.soft_volume.value==PA_VOLUME_NORM && restarts==2);
|
|
assert(last_delay==16*PA_USEC_PER_MSEC);
|
|
state(argv[1],"4 15 1\n");
|
|
android_volume_refresh(&api,userdata.android_volume->timer,NULL,userdata.android_volume);
|
|
assert(sink.real_volume.value==4*PA_VOLUME_NORM/15 && last_delay==16*PA_USEC_PER_MSEC);
|
|
for (int i = 0; i < ANDROID_VOLUME_ACTIVE_TICKS + 1; i++)
|
|
android_volume_refresh(&api,userdata.android_volume->timer,NULL,userdata.android_volume);
|
|
assert(last_delay==100*PA_USEC_PER_MSEC);
|
|
sink.real_volume.value=2*PA_VOLUME_NORM/3; sink.set_volume(&sink);
|
|
char *request=pa_sprintf_malloc("%s/volume.request",argv[1]);
|
|
FILE *f=fopen(request,"r"); unsigned level; assert(f && fscanf(f,"%u",&level)==1 && level==10); fclose(f); free(request);
|
|
android_volume_free(userdata.android_volume); assert(frees==1);
|
|
return 0;
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
(root / "pulsecore").mkdir()
|
|
for name in ("core-rtclock.h", "core-subscribe.h"):
|
|
(root / "pulsecore" / name).touch()
|
|
source = root / "test.c"
|
|
source.write_text(harness)
|
|
binary = root / "test"
|
|
subprocess.run(["cc", "-std=c11", "-I", str(root), "-I", str(header.parent),
|
|
str(source), "-o", str(binary)], check=True, capture_output=True)
|
|
subprocess.run([str(binary), str(root)], check=True, capture_output=True)
|