/* A12:命令行的扁平 JSON 解析器(cmd_json.h)。 这一层的测试重点不是「合法的能解出来」,而是**不合法的一律拒**: 请求形状越窄,越界输入的面就越小。每一条「刻意不支持」都在这里钉一条用例 —— 哪天有人为了「兼容」放宽了其中一条,这里先红。 */ #include #include #include "cmd_json.h" #include "unity.h" void setUp(void) {} void tearDown(void) {} static cj_obj_t o; static void test_parses_a_flat_command(void) { TEST_ASSERT_TRUE(cj_parse("{\"seq\":17,\"cmd\":\"key\",\"rail\":3,\"keys\":\"pwr+vdn\"," "\"ms\":2000,\"on\":true,\"x\":false,\"neg\":-5}\r\n", &o)); int64_t v = 0; const char *s = NULL; bool bv = false; TEST_ASSERT_EQUAL_INT(1, cj_get_int(&o, "seq", &v)); TEST_ASSERT_EQUAL_INT64(17, v); TEST_ASSERT_EQUAL_INT(1, cj_get_str(&o, "cmd", &s)); TEST_ASSERT_EQUAL_STRING("key", s); TEST_ASSERT_EQUAL_INT(1, cj_get_str(&o, "keys", &s)); TEST_ASSERT_EQUAL_STRING("pwr+vdn", s); TEST_ASSERT_EQUAL_INT(1, cj_get_bool(&o, "on", &bv)); TEST_ASSERT_TRUE(bv); TEST_ASSERT_EQUAL_INT(1, cj_get_bool(&o, "x", &bv)); TEST_ASSERT_FALSE(bv); TEST_ASSERT_EQUAL_INT(1, cj_get_int(&o, "neg", &v)); TEST_ASSERT_EQUAL_INT64(-5, v); TEST_ASSERT_EQUAL_INT(0, cj_get_int(&o, "missing", &v)); } static void test_whitespace_and_empty_object(void) { TEST_ASSERT_TRUE(cj_parse(" { \"a\" : 1 ,\t\"b\":\"x\" } ", &o)); TEST_ASSERT_EQUAL_INT(2, o.n); TEST_ASSERT_TRUE(cj_parse("{}", &o)); TEST_ASSERT_EQUAL_INT(0, o.n); } /* 「有但类型不对」与「没有」是两回事:`"mv":"3900"` 是上位机的 bug,不是「没给电压」。 */ static void test_wrong_type_is_distinguished_from_missing(void) { TEST_ASSERT_TRUE(cj_parse("{\"mv\":\"3900\",\"on\":1,\"cmd\":5}", &o)); int64_t v = 0; const char *s = NULL; bool b = false; TEST_ASSERT_EQUAL_INT(-1, cj_get_int(&o, "mv", &v)); TEST_ASSERT_EQUAL_INT(-1, cj_get_bool(&o, "on", &b)); /* 1 不是布尔 */ TEST_ASSERT_EQUAL_INT(-1, cj_get_str(&o, "cmd", &s)); } static void test_rejects_everything_outside_the_flat_shape(void) { static const char *const bad[] = { "", /* 空行 */ "[1,2]", /* 不是对象 */ "{\"a\":{\"b\":1}}", /* 嵌套 */ "{\"a\":[1]}", /* 数组 */ "{\"a\":null}", /* null */ "{\"a\":3.9}", /* 浮点:不许被截成 3 */ "{\"a\":1e3}", /* 指数 */ "{\"a\":010}", /* 前导零 */ "{\"a\":1234567890123456789}", /* 19 位:不回绕 */ "{\"a\":\"x\\\"y\"}", /* 转义:见头文件 */ "{\"a\":\"line\nbreak\"}", /* 控制字符 */ "{\"mv\":3900,\"mv\":4300}", /* 重复键:有人拿来绕校验的那种 */ "{\"a\":1}{\"b\":2}", /* 两条命令粘在一行 */ "{\"a\":1} x", /* 尾巴 */ "{\"a\":1,}", /* 尾逗号 */ "{\"a\" 1}", /* 缺冒号 */ "{\"\":1}", /* 空键 */ "{a:1}", /* 键没引号 */ "{\"a\":tru}", /* 半个 true */ "{\"a\":1", /* 没收尾 */ "{\"a\":-}", /* 光一个负号 */ }; for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { TEST_ASSERT_FALSE_MESSAGE(cj_parse(bad[i], &o), bad[i]); } TEST_ASSERT_FALSE(cj_parse(NULL, &o)); } /* 超长的键 / 字符串不截断:截断会把 "set_voltage_x" 读成另一条命令。 */ static void test_overlong_tokens_are_rejected_not_truncated(void) { char line[128]; memset(line, 0, sizeof(line)); strcpy(line, "{\"cmd\":\""); memset(line + strlen(line), 'a', CMD_JSON_STR_MAX); /* 32 个字符:装不下结尾 0 */ strcat(line, "\"}"); TEST_ASSERT_FALSE(cj_parse(line, &o)); memset(line, 0, sizeof(line)); strcpy(line, "{\""); memset(line + 2, 'k', CMD_JSON_KEY_MAX); /* 16 个:装不下结尾 0 */ line[2 + CMD_JSON_KEY_MAX] = '\0'; strcat(line, "\":1}"); TEST_ASSERT_FALSE(cj_parse(line, &o)); memset(line, 0, sizeof(line)); strcpy(line, "{\"cmd\":\""); memset(line + strlen(line), 'a', CMD_JSON_STR_MAX - 1); /* 31 个 + 结尾 0:刚好装得下 */ strcat(line, "\"}"); TEST_ASSERT_TRUE(cj_parse(line, &o)); } static void test_field_count_is_bounded(void) { char line[256] = "{"; for (int i = 0; i < CMD_JSON_MAX_FIELDS + 1; i++) { char f[16]; snprintf(f, sizeof(f), "%s\"k%d\":%d", i ? "," : "", i, i); strcat(line, f); } strcat(line, "}"); TEST_ASSERT_FALSE(cj_parse(line, &o)); } static void test_writer_never_overruns(void) { char buf[8]; cj_writer_t w; cj_w_init(&w, buf, sizeof(buf)); cj_w(&w, "abc"); TEST_ASSERT_FALSE(w.overflow); cj_w(&w, "defgh"); /* 3 + 5 = 8,装不下结尾 0 */ TEST_ASSERT_TRUE(w.overflow); TEST_ASSERT_EQUAL_STRING("abc", buf); /* 写了一半的那段撤掉 */ cj_w(&w, "x"); TEST_ASSERT_EQUAL_STRING("abc", buf); /* 溢出之后一律不写 */ } int main(void) { UNITY_BEGIN(); RUN_TEST(test_parses_a_flat_command); RUN_TEST(test_whitespace_and_empty_object); RUN_TEST(test_wrong_type_is_distinguished_from_missing); RUN_TEST(test_rejects_everything_outside_the_flat_shape); RUN_TEST(test_overlong_tokens_are_rejected_not_truncated); RUN_TEST(test_field_count_is_bounded); RUN_TEST(test_writer_never_overruns); return UNITY_END(); }