SmartestHome/pebble-presence/test/wireformat_test.c

117 lines
5.0 KiB
C

/*
* Round-trips the JS writer's real output through the C reader.
*
* The writer (src/pkjs/index.js) and the reader (src/c/main.c) are two implementations
* of one format, in two languages, that never run in the same process. Nothing but a
* test like this can catch them drifting apart — and drift decodes as plausible-looking
* garbage rather than as an error, which is the failure worth spending a harness on.
*
* Build: see run-tests.sh. Reads the payload as hex on stdin.
*/
#include "pebble_stub.h"
/* main.c includes <pebble.h>; the build passes -include pebble_stub.h and -Dpebble_h
so that include resolves to nothing. See run-tests.sh. */
#define main pebble_main /* keep main.c's entry point out of the way */
#include "../src/c/main.c"
#undef main
static int failures = 0;
static void check(const char *label, bool ok) {
printf(" %s %s\n", ok ? "PASS" : "FAIL", label);
if (!ok) failures++;
}
/* The toggles line is a second format shared by the same two implementations — packed
in src/pkjs/index.js, parsed in src/c/main.c — so it gets the same treatment as the
plan: the JS writes it, this reads it, and a disagreement is a failed test rather
than a watch screen that quietly says the wrong microphone is live. */
static void check_toggles(const char *path) {
FILE *file = fopen(path, "rb");
if (!file) { printf(" FAIL could not open %s\n", path); failures++; return; }
static char packed[512];
size_t length = fread(packed, 1, sizeof(packed) - 1, file);
packed[length] = 0;
fclose(file);
parse_toggles(packed);
check("three toggles parsed", s_toggle_count == 3);
if (s_toggle_count != 3) return;
check("first name survives an apostrophe", strcmp(s_toggles[0].name, "Amir's desktop") == 0);
check("first is on", s_toggles[0].on);
check("first detail is the live mic", strcmp(s_toggles[0].detail, "Loggia") == 0);
/* The JS truncates to the C buffer sizes, so a long name must arrive already short
rather than being cut differently on each side. */
check("long name truncated by the writer", strlen(s_toggles[1].name) == 21);
check("second is off", !s_toggles[1].on);
check("long detail truncated by the writer", strlen(s_toggles[1].detail) == 15);
/* A value containing the field separator would split into extra fields; the writer
strips them, and this is what proves it still does. */
check("separators stripped from values", strcmp(s_toggles[2].name, "Weird name") == 0);
check("third detail intact", strcmp(s_toggles[2].detail, "Desk") == 0);
parse_toggles("");
check("empty payload means no toggles", s_toggle_count == 0);
parse_toggles("Half a record|1");
check("truncated record still yields a safe row",
s_toggle_count == 1 && s_toggles[0].on && s_toggles[0].detail[0] == 0);
}
int main(int argc, char **argv) {
static uint8_t buffer[4096];
uint16_t length = 0;
unsigned int byte;
while (scanf("%2x", &byte) == 1 && length < sizeof(buffer)) buffer[length++] = (uint8_t)byte;
printf("payload: %u bytes\n", length);
bool ok = parse_plan(buffer, length);
check("parses", ok);
if (!ok) { printf(" status: %s\n", s_status); return 1; }
check("room count is 3", s_room_count == 3);
check("room 0 is named Kitchen", strcmp(s_rooms[0].name, "Kitchen") == 0);
check("room 0 is occupied", s_rooms[0].state == 1);
check("room 0 has 4 vertices", s_rooms[0].vertex_count == 4);
check("room 0 has 1 occupant", s_rooms[0].occupant_count == 1);
check("occupant initial is A", s_rooms[0].occupants[0].initial == 'A');
check("occupant colour index 0", s_rooms[0].occupants[0].colour == 0);
check("occupant has a position", s_rooms[0].occupants[0].has_position);
check("occupant name survived", strcmp(s_rooms[0].occupants[0].name, "Amir") == 0);
check("room 1 is named Loggia", strcmp(s_rooms[1].name, "Loggia") == 0);
/* A radar target outranks "HA never reports this area": somebody IS in there, and a
positive detection beats no data. The first draft of this test asserted the
opposite and was wrong. */
check("room 1 is occupied (radar, nobody named)", s_rooms[1].state == 1);
check("room 1 has an anonymous target", s_rooms[1].target_count == 1);
check("room 1 has no occupants", s_rooms[1].occupant_count == 0);
check("room 2 is the unknown state", s_rooms[2].state == 2);
check("room 2 has no targets", s_rooms[2].target_count == 0);
check("1 unplaced person", s_unplaced_count == 1);
check("unplaced is Bibi", strcmp(s_unplaced[0].name, "Bibi") == 0);
/* A truncated payload must be refused wholesale, never half-decoded: a plan missing
its last rooms looks exactly like a real plan. */
bool truncated = parse_plan(buffer, length / 2);
check("truncated payload refused", !truncated && s_room_count == 0);
/* A version bump on the phone must not decode as garbage here. */
buffer[0] = 99;
check("wrong version refused", !parse_plan(buffer, length));
if (argc > 1) {
printf("\ntoggles line:\n");
check_toggles(argv[1]);
}
printf(failures ? "\nFAILURES: %d\n" : "\nall wire-format checks passed\n", failures);
return failures ? 1 : 0;
}