Files
Flipdot-display/test/test_driver/test_main.cpp
T

241 lines
6.2 KiB
C++
Raw Normal View History

// Tests for the real display driver (lib/BROSE9323, Nano build) on the PC, with the
// simulated panel of test/driver_mocks: pio test -e test_driver
//
// What matters most: drawing never blocks loop() (the Nano must keep reading the serial
// port, or bytes are lost and the picture ends up shifted), and the dots end up right.
#include <unity.h>
#include "../../lib/BROSE9323/BROSE9323.cpp"
static const int W = 84, H = 19;
// Like the user's settings: 3 pulses of 200 us with a 300 us gap
static BROSE9323 *make()
{
BROSE9323 *d = new BROSE9323(W, H, 28, 200);
d->setGap(300);
d->setPulses(3);
panel = FakePanel(); // before begin(): it sets the pins
d->begin();
return d;
}
// Runs update() until the frame and the refresh are done. Fails when one call takes
// longer than max_us (fake time): then loop() would not read the serial port in time.
static int finish(BROSE9323 &d, uint32_t max_us = 3000)
{
int calls = 0;
while (d.busy() || d.refreshing())
{
uint32_t start = fake_micros;
d.update();
uint32_t took = fake_micros - start;
if (took > max_us)
{
char msg[64];
snprintf(msg, sizeof(msg), "update() took %u us", (unsigned)took);
TEST_FAIL_MESSAGE(msg);
}
TEST_ASSERT_LESS_THAN(1000000, ++calls);
}
return calls;
}
static void assertPanelShows(BROSE9323 &d)
{
for (int x = 0; x < W; x++)
for (int y = 0; y < H; y++)
if (panel.dot[x][y] != d.shownPixel(x, y))
{
char msg[64];
snprintf(msg, sizeof(msg), "dot %d,%d differs", x, y);
TEST_FAIL_MESSAGE(msg);
}
}
static void drawPattern(BROSE9323 &d, int seed)
{
for (int x = 0; x < W; x++)
for (int y = 0; y < H; y++)
d.drawPixel(x, y, ((x * 7 + y * 3 + seed) % 5) == 0);
}
static int dotsFlippedAtLeast(int n)
{
int count = 0;
for (int x = 0; x < W; x++)
for (int y = 0; y < H; y++)
count += panel.flips[x][y] >= n;
return count;
}
void setUp() {}
void tearDown() {}
void test_first_frame_flips_every_dot_without_blocking()
{
BROSE9323 *d = make();
drawPattern(*d, 0);
uint32_t start = fake_micros;
d->display();
TEST_ASSERT_LESS_THAN(3000, fake_micros - start); // display() itself returns at once
TEST_ASSERT_TRUE(d->busy());
int calls = finish(*d);
TEST_ASSERT_GREATER_THAN(100, calls); // spread over many loop() passes
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
assertPanelShows(*d);
delete d;
}
void test_instant_frame_flips_only_the_changed_dots_with_all_pulses()
{
BROSE9323 *d = make();
drawPattern(*d, 0);
d->display();
finish(*d);
panel.clearCounts();
d->drawPixel(10, 5, !d->shownPixel(10, 5));
d->drawPixel(70, 18, !d->shownPixel(70, 18));
d->display(false, true);
finish(*d);
TEST_ASSERT_EQUAL(6, panel.strobes); // 2 dots, 3 pulses each
TEST_ASSERT_EQUAL(3, panel.flips[10][5]);
assertPanelShows(*d);
delete d;
}
void test_effects_end_with_the_right_image()
{
for (uint8_t effect = EFFECT_SPARKLE; effect < EFFECT_COUNT; effect++)
{
BROSE9323 *d = make();
d->setEffect(effect, 500);
drawPattern(*d, 0);
d->display();
finish(*d);
assertPanelShows(*d);
drawPattern(*d, 2);
d->display();
finish(*d, 5000);
assertPanelShows(*d);
delete d;
}
}
void test_refresh_flips_every_dot_once_without_blocking()
{
BROSE9323 *d = make();
drawPattern(*d, 0);
d->display();
finish(*d);
panel.clearCounts();
d->startRefresh();
TEST_ASSERT_TRUE(d->refreshing());
TEST_ASSERT_FALSE(d->busy()); // not a frame: loop() may take new frames meanwhile
int calls = finish(*d);
TEST_ASSERT_GREATER_THAN(100, calls);
TEST_ASSERT_EQUAL(W * H, panel.strobes); // one pulse each, nothing changed
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
assertPanelShows(*d);
delete d;
}
void test_refresh_pauses_while_a_frame_arrives_and_then_finishes()
{
BROSE9323 *d = make();
drawPattern(*d, 0);
d->display();
finish(*d);
panel.clearCounts();
d->startRefresh();
d->update(); // a part of the dots
int before = panel.strobes;
TEST_ASSERT_GREATER_THAN(0, before);
TEST_ASSERT_LESS_THAN(W * H, before);
// A new frame is being received: the refresh must not show half of it
bool old = d->shownPixel(83, 18);
d->drawPixel(83, 18, !old);
for (int i = 0; i < 50; i++)
d->update();
TEST_ASSERT_EQUAL(before, panel.strobes);
TEST_ASSERT_EQUAL(old, panel.dot[83][18]);
d->display(false, true);
finish(*d);
assertPanelShows(*d);
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1)); // the refresh got to the end
TEST_ASSERT_FALSE(d->refreshing());
delete d;
}
void test_refresh_waits_for_a_running_effect()
{
BROSE9323 *d = make();
d->setEffect(EFFECT_WIPE, 1000);
drawPattern(*d, 0);
d->display();
finish(*d);
drawPattern(*d, 1);
d->display();
d->startRefresh();
panel.clearCounts();
// The wipe goes left to right; the refresh would start at the left too and finish
// before the wipe reaches the right side if it ran at the same time
while (d->busy())
d->update();
TEST_ASSERT_TRUE(d->refreshing());
finish(*d);
assertPanelShows(*d);
delete d;
}
void test_interrupted_first_frame_still_reaches_every_dot()
{
BROSE9323 *d = make();
drawPattern(*d, 0);
d->display(); // after boot: every dot
for (int i = 0; i < 20; i++)
d->update();
TEST_ASSERT_LESS_THAN(W * H, dotsFlippedAtLeast(1));
// The next frame (scrolling text) arrives before it is done
d->drawPixel(0, 0, !d->shownPixel(0, 0));
d->display(false, true);
finish(*d);
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
assertPanelShows(*d);
delete d;
}
void test_crc_of_equal_images_is_equal()
{
BROSE9323 *a = make(), *b = make();
a->fillScreen(1); // also sets the unused bits at the end of each row
for (int x = 0; x < W; x++)
for (int y = 0; y < H; y++)
b->drawPixel(x, y, 1);
TEST_ASSERT_EQUAL_HEX16(a->crc(), b->crc());
b->drawPixel(40, 9, 0);
TEST_ASSERT_NOT_EQUAL(a->crc(), b->crc());
delete a;
delete b;
}
int main()
{
UNITY_BEGIN();
RUN_TEST(test_first_frame_flips_every_dot_without_blocking);
RUN_TEST(test_instant_frame_flips_only_the_changed_dots_with_all_pulses);
RUN_TEST(test_effects_end_with_the_right_image);
RUN_TEST(test_refresh_flips_every_dot_once_without_blocking);
RUN_TEST(test_refresh_pauses_while_a_frame_arrives_and_then_finishes);
RUN_TEST(test_refresh_waits_for_a_running_effect);
RUN_TEST(test_interrupted_first_frame_still_reaches_every_dot);
RUN_TEST(test_crc_of_equal_images_is_equal);
return UNITY_END();
}