154 lines
6.9 KiB
C
154 lines
6.9 KiB
C
#include "rgb_controller.h"
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
struct mock {
|
|
unsigned step, fail_step, recovery_fail_step;
|
|
int short_step;
|
|
uint16_t count;
|
|
uint8_t lamp[29];
|
|
uint8_t range[10];
|
|
unsigned recovery_calls, host_calls, range_calls;
|
|
};
|
|
static struct mock fresh(void)
|
|
{
|
|
struct mock m = {.short_step = -1, .count = 1, .lamp = {0x43}};
|
|
m.lamp[23] = 255; m.lamp[24] = 255; m.lamp[25] = 255; m.lamp[26] = 100; m.lamp[27] = 1;
|
|
return m;
|
|
}
|
|
static int fault(struct mock *m)
|
|
{
|
|
++m->step;
|
|
if (m->step == m->fail_step || m->step == m->recovery_fail_step) { errno = EIO; return -1; }
|
|
return 0;
|
|
}
|
|
static int get(void *context, uint8_t *r, size_t n)
|
|
{
|
|
struct mock *m = context;
|
|
for (size_t i = 1; i < n; ++i) assert(!r[i]);
|
|
if (fault(m) < 0) return -1;
|
|
if (n == 23) {
|
|
assert(r[0] == 0x41 && m->step == 1);
|
|
memset(r, 0, n); r[0] = 0x41; r[1] = (uint8_t)m->count; r[2] = (uint8_t)(m->count >> 8);
|
|
r[19] = 0xb8; r[20] = 0x0b; /* 3000 us, deliberate LE fixture. */
|
|
} else {
|
|
assert(n == 29 && r[0] == 0x43 && m->step == 3);
|
|
memcpy(r, m->lamp, n);
|
|
}
|
|
return (int)m->step == m->short_step ? (int)n-1 : (int)n;
|
|
}
|
|
static int set(void *context, const uint8_t *r, size_t n)
|
|
{
|
|
struct mock *m = context;
|
|
if (n == 2 && r[0] == 0x46 && r[1] == 1) ++m->recovery_calls;
|
|
else if (n == 2) { assert(r[0] == 0x46 && r[1] == 0 && m->step == 3); ++m->host_calls; }
|
|
else if (n == 3) { assert(r[0] == 0x42 && !r[1] && !r[2] && m->step == 1); }
|
|
else { assert(n == 10 && m->step == 5); memcpy(m->range, r, n); ++m->range_calls; }
|
|
if (fault(m) < 0) return -1;
|
|
return (int)m->step == m->short_step ? (int)n-1 : (int)n;
|
|
}
|
|
static int wait_for(void *context, uint32_t us)
|
|
{
|
|
struct mock *m = context;
|
|
assert(m->step == 4 && us == 3000);
|
|
return fault(m);
|
|
}
|
|
static struct rgb_transport transport(struct mock *m)
|
|
{
|
|
return (struct rgb_transport){.context = m, .get = get, .set = set, .wait = wait_for};
|
|
}
|
|
int main(void)
|
|
{
|
|
const uint8_t lavender[3] = {180, 190, 254};
|
|
for (unsigned max = 0; max <= 255; ++max) {
|
|
assert(rgb_scale(0, (uint8_t)max) == 0);
|
|
assert(rgb_scale(255, (uint8_t)max) == max);
|
|
unsigned previous = 0;
|
|
for (unsigned value = 0; value <= 255; ++value) {
|
|
unsigned wire = rgb_scale((uint8_t)value, (uint8_t)max);
|
|
assert(wire <= max && wire >= previous);
|
|
/* Bound the rounding error independently, rather than reimplementing conversion. */
|
|
int delta = (int)(wire*255) - (int)(value*max);
|
|
assert(delta >= -127 && delta <= 127);
|
|
previous = wire;
|
|
}
|
|
}
|
|
struct mock m = fresh(); struct rgb_transport t = transport(&m);
|
|
struct rgb_lamp l;
|
|
assert(rgb_lamp_decode(m.lamp, 29, &l));
|
|
uint8_t v[4];
|
|
assert(rgb_convert(&l, lavender, 60, v) && v[0] == 180 && v[1] == 190 && v[2] == 254 && v[3] == 60);
|
|
l.levels[0] = 10; l.levels[1] = 20; l.levels[2] = 30; l.levels[3] = 3;
|
|
assert(rgb_convert(&l, lavender, 60, v) && v[0] == 7 && v[1] == 15 && v[2] == 30 && v[3] == 2);
|
|
for (unsigned max = 1; max <= 255; ++max) {
|
|
l.levels[3] = (uint8_t)max;
|
|
assert(rgb_convert(&l, lavender, 0, v) && v[3] == 0);
|
|
assert(rgb_convert(&l, lavender, 100, v) && v[3] == max);
|
|
unsigned previous = 0;
|
|
for (unsigned p = 0; p <= 100; ++p) {
|
|
assert(rgb_convert(&l, lavender, p, v) && v[3] <= max && v[3] >= previous);
|
|
int delta = (int)v[3]*100 - (int)(p*max);
|
|
assert(delta >= -50 && delta <= 50);
|
|
previous = v[3];
|
|
}
|
|
}
|
|
assert(!rgb_convert(&l, lavender, 101, v));
|
|
l.programmable = 0; assert(!rgb_convert(&l, lavender, 60, v)); l.programmable = 1;
|
|
for (unsigned i = 0; i < 4; ++i) {
|
|
uint8_t saved = l.levels[i]; l.levels[i] = 0;
|
|
assert(!rgb_convert(&l, lavender, 100, v)); l.levels[i] = saved;
|
|
}
|
|
assert(!rgb_lamp_decode(m.lamp, 28, &l) && !rgb_lamp_decode(m.lamp, 30, &l));
|
|
m.lamp[0] = 0x33; assert(!rgb_lamp_decode(m.lamp, 29, &l)); m = fresh();
|
|
m.lamp[27] = 2; assert(!rgb_lamp_decode(m.lamp, 29, &l)); m = fresh();
|
|
m.lamp[18] = 0x80; assert(!rgb_lamp_decode(m.lamp, 29, &l)); m = fresh();
|
|
struct rgb_result r = rgb_apply(&t, lavender, 100, false);
|
|
const uint8_t expected[10] = {0x45, 1, 0, 0, 0, 0, 180, 190, 254, 100};
|
|
assert(!r.error && m.step == 6 && m.host_calls == 1 && !m.recovery_calls && !memcmp(m.range, expected, 10));
|
|
/* Failure injection covers every operation in order. Before host control,
|
|
* no recovery write; after ANY attempted host SET, exactly one restoration. */
|
|
for (unsigned fail = 1; fail <= 6; ++fail) {
|
|
m = fresh(); m.fail_step = fail;
|
|
r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == EIO && r.recovery_attempted == (fail >= 4));
|
|
assert(m.recovery_calls == (fail >= 4 ? 1u : 0u) && !r.recovery_error);
|
|
assert(m.step == fail + (fail >= 4 ? 1u : 0u));
|
|
}
|
|
for (int short_step = 1; short_step <= 6; ++short_step) {
|
|
if (short_step == 5) continue;
|
|
m = fresh(); m.short_step = short_step;
|
|
r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == EPROTO && m.recovery_calls == (short_step >= 4 ? 1u : 0u));
|
|
}
|
|
m = fresh(); m.fail_step = 6; m.recovery_fail_step = 7;
|
|
r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == EIO && r.recovery_error == EIO && m.recovery_calls == 1 && m.step == 7);
|
|
m = fresh(); m.fail_step = 6; m.short_step = 7;
|
|
r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == EIO && r.recovery_error == EPROTO && m.recovery_calls == 1);
|
|
m = fresh(); m.lamp[1] = 1; r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == EPROTO && !m.host_calls && !m.recovery_calls);
|
|
m = fresh(); m.count = 2; r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == ENOTSUP && m.step == 1);
|
|
m = fresh(); m.lamp[23] = 0; r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == ENOTSUP && !m.host_calls);
|
|
m = fresh(); m.lamp[27] = 0; r = rgb_apply(&t, lavender, 100, false);
|
|
assert(r.error == ENOTSUP && !m.host_calls);
|
|
m = fresh(); m.lamp[27] = 0;
|
|
r = rgb_apply(&t, lavender, 0, true);
|
|
assert(!r.error && !m.recovery_calls);
|
|
for (size_t i = 6; i < 10; ++i) assert(m.range[i] == 0);
|
|
m = fresh(); r = rgb_auto(&t);
|
|
assert(!r.error && m.step == 1 && m.recovery_calls == 1 && !m.host_calls);
|
|
m = fresh(); m.fail_step = 1; r = rgb_auto(&t);
|
|
assert(r.error == EIO && m.step == 1 && !r.recovery_attempted);
|
|
m = fresh();
|
|
struct la_attributes a;
|
|
assert(rgb_probe(&t, &a, &l) == 0 && m.step == 3 && !m.host_calls && !m.recovery_calls);
|
|
assert(rgb_apply(NULL, lavender, 0, false).error == EINVAL);
|
|
assert(rgb_apply(&t, NULL, 0, false).error == EINVAL);
|
|
assert(rgb_auto(NULL).error == EINVAL);
|
|
puts("Controller conversion, exact sequence, failure and recovery tests passed (memory transport only).");
|
|
return 0;
|
|
}
|