introspection.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /*
  2. * Copyright © 2025 Pierre Le Marre
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include "test-config.h"
  7. #include <assert.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include "darray.h"
  11. #include "utils-paths.h"
  12. #include "xkbcommon/xkbcommon.h"
  13. #include "src/xkbcomp/keymap-file-iterator.h"
  14. #include "src/xkbcomp/ast.h"
  15. #include "test.h"
  16. #include "utils.h"
  17. struct file_include_test_data {
  18. bool valid:1;
  19. bool explicit_section:1;
  20. enum merge_mode merge;
  21. const char *path; /* relative to test data */
  22. const char *file;
  23. const char *section;
  24. const char *modifier;
  25. enum xkb_map_flags flags;
  26. };
  27. struct section_test_data {
  28. const char *name;
  29. enum xkb_file_type file_type;
  30. enum xkb_map_flags flags;
  31. struct xkb_file_include_group include_groups[3];
  32. struct file_include_test_data includes[3];
  33. darray_size_t num_include_groups;
  34. darray_size_t num_includes;
  35. };
  36. static void
  37. test_resolve_file(struct xkb_context *ctx)
  38. {
  39. char *path;
  40. char resolved_path[PATH_MAX] = {0};
  41. char resolved_section[1024] = {0};
  42. FILE *file;
  43. /* Invalid paths */
  44. static const char invalid[] = "---invalid---";
  45. path = test_get_path(invalid);
  46. assert(path);
  47. assert(is_absolute_path(path));
  48. const char * paths[] = { path, invalid };
  49. static const char * sections[] = { NULL, "invalid" };
  50. /* Note: inferior or equal to _FILE_TYPE_NUM_ENTRIES is correct here */
  51. for (enum xkb_file_type t = 0; t <= _FILE_TYPE_NUM_ENTRIES; t++) {
  52. for (size_t p = 0; p < ARRAY_SIZE(paths); p++) {
  53. for (size_t s = 0; s < ARRAY_SIZE(sections); s++) {
  54. file = xkb_resolve_file(ctx, t, paths[p], sections[s],
  55. resolved_path, sizeof(resolved_path),
  56. resolved_section, sizeof(resolved_section));
  57. assert(!file);
  58. }
  59. }
  60. }
  61. free(path);
  62. /* Valid absolute path */
  63. path = test_get_path("types/numpad");
  64. assert(path);
  65. assert(is_absolute_path(path));
  66. const struct {
  67. const char *path;
  68. const char *section;
  69. const char *resolved_path;
  70. const char *resolved_section;
  71. enum xkb_file_type type;
  72. bool failure;
  73. } tests[] = {
  74. /* Absolute path */
  75. {
  76. .failure = false,
  77. .path = path,
  78. .section = NULL,
  79. .type = FILE_TYPE_INVALID,
  80. .resolved_path = path,
  81. .resolved_section = "pc" /* default */
  82. },
  83. {
  84. .failure = false,
  85. .path = path,
  86. .section = "shift3",
  87. .type = FILE_TYPE_INVALID,
  88. .resolved_path = path,
  89. .resolved_section = "shift3" /* non-default */
  90. },
  91. {
  92. .failure = true,
  93. .path = path,
  94. .section = NULL,
  95. .type = FILE_TYPE_SYMBOLS, /* invalid type */
  96. .resolved_path = NULL,
  97. .resolved_section = NULL
  98. },
  99. {
  100. .failure = false,
  101. .path = path,
  102. .section = NULL,
  103. .type = FILE_TYPE_TYPES, /* valid type */
  104. .resolved_path = path,
  105. .resolved_section = "pc" /* default */
  106. },
  107. {
  108. .failure = false,
  109. .path = path,
  110. .section = "shift3",
  111. .type = FILE_TYPE_TYPES, /* valid type */
  112. .resolved_path = path,
  113. .resolved_section = "shift3" /* non default */
  114. },
  115. /* Relative path */
  116. {
  117. .failure = true,
  118. .path = "numpad",
  119. .section = NULL,
  120. .type = FILE_TYPE_INVALID, /* type required */
  121. .resolved_path = NULL,
  122. .resolved_section = NULL
  123. },
  124. {
  125. .failure = true,
  126. .path = "numpad",
  127. .section = "shift3",
  128. .type = FILE_TYPE_INVALID, /* type required */
  129. .resolved_path = NULL,
  130. .resolved_section = NULL
  131. },
  132. {
  133. .failure = true,
  134. .path = "numpad",
  135. .section = NULL,
  136. .type = FILE_TYPE_KEYCODES, /* invalid type */
  137. .resolved_path = NULL,
  138. .resolved_section = NULL
  139. },
  140. {
  141. .failure = false,
  142. .path = "numpad",
  143. .section = NULL,
  144. .type = FILE_TYPE_TYPES, /* valid type */
  145. .resolved_path = path,
  146. .resolved_section = "pc" /* default */
  147. },
  148. {
  149. .failure = false,
  150. .path = "numpad",
  151. .section = "shift3",
  152. .type = FILE_TYPE_TYPES, /* valid type */
  153. .resolved_path = path,
  154. .resolved_section = "shift3" /* non default */
  155. },
  156. };
  157. for (size_t t = 0; t < ARRAY_SIZE(tests); t++) {
  158. fprintf(stderr, "------\n*** %s: #%zu ***\n", __func__, t);
  159. file = xkb_resolve_file(ctx, tests[t].type,
  160. tests[t].path, tests[t].section,
  161. resolved_path, sizeof(resolved_path),
  162. resolved_section, sizeof(resolved_section));
  163. if (tests[t].failure) {
  164. assert(!file);
  165. } else {
  166. assert(file);
  167. assert_streq_not_null("resolved path",
  168. resolved_path, tests[t].resolved_path);
  169. assert_streq_not_null("resolved section",
  170. resolved_section, tests[t].resolved_section);
  171. }
  172. }
  173. free(path);
  174. }
  175. static bool
  176. test_section(const struct section_test_data *data,
  177. const struct xkb_file_section *section)
  178. {
  179. assert(section->file_type == data->file_type);
  180. assert_streq_not_null("", data->name,
  181. xkb_file_section_get_string(section, section->name));
  182. assert_eq("Section flags", data->flags, section->flags, "%#x");
  183. assert_eq("Num include groups", data->num_include_groups,
  184. darray_size(section->include_groups), "%u");
  185. assert_eq("Num includes", data->num_includes,
  186. darray_size(section->includes), "%u");
  187. for (darray_size_t k = 0; k < data->num_include_groups; k++) {
  188. fprintf(stderr, "... %s: include group #%u ...\n", __func__, k);
  189. const struct xkb_file_include_group * const expected = &data->include_groups[k];
  190. const struct xkb_file_include_group * const got = &darray_item(section->include_groups, k);
  191. assert_eq("Start", expected->start, got->start, "%d");
  192. assert_eq("End", expected->end, got->end, "%d");
  193. }
  194. for (darray_size_t k = 0; k < data->num_includes; k++) {
  195. fprintf(stderr, "... %s: include #%u ...\n", __func__, k);
  196. const struct file_include_test_data * const expected = &data->includes[k];
  197. const struct xkb_file_include * const got = &darray_item(section->includes, k);
  198. assert_eq("Valid", expected->valid, got->valid, "%d");
  199. assert_eq("Merge mode", expected->merge, got->merge, "%d");
  200. char *path = test_get_path(expected->path);
  201. assert(path);
  202. assert_streq_not_null("File", path,
  203. xkb_file_section_get_string(section, got->path));
  204. free(path);
  205. assert_streq_not_null("File", expected->file,
  206. xkb_file_section_get_string(section, got->file));
  207. assert_streq_not_null("Section", expected->section,
  208. xkb_file_section_get_string(section, got->section));
  209. assert_eq("Explicit section",
  210. expected->explicit_section, got->explicit_section, "%d");
  211. assert_streq_not_null("Modifier", expected->modifier,
  212. xkb_file_section_get_string(section, got->modifier));
  213. assert_eq("Section flags", expected->flags, got->flags, "%#x");
  214. }
  215. return true;
  216. }
  217. static void
  218. test_file_section_parse(struct xkb_context *ctx)
  219. {
  220. char *path = test_get_path("symbols/pc");
  221. assert(path);
  222. struct xkb_file_section section = {0};
  223. struct section_test_data tests[] = {
  224. {
  225. .name = "editing",
  226. .file_type = FILE_TYPE_SYMBOLS,
  227. .flags = MAP_IS_HIDDEN | MAP_IS_PARTIAL | MAP_HAS_ALPHANUMERIC,
  228. .includes = {},
  229. .num_includes = 0
  230. },
  231. {
  232. .name = "pc105",
  233. .file_type = FILE_TYPE_SYMBOLS,
  234. .flags = MAP_IS_DEFAULT | MAP_IS_PARTIAL | MAP_HAS_ALPHANUMERIC
  235. | MAP_HAS_MODIFIER,
  236. .num_include_groups = 1,
  237. .include_groups = {
  238. { .start = 0, .end = 0 },
  239. },
  240. .num_includes = 1,
  241. .includes = {
  242. {
  243. .valid = true,
  244. .merge = MERGE_DEFAULT,
  245. .path = "symbols/pc",
  246. .file = "pc",
  247. .section = "pc105-pure-virtual-modifiers",
  248. .explicit_section = true,
  249. .modifier = "",
  250. .flags = 0
  251. }
  252. },
  253. },
  254. {
  255. .name = "pc105-pure-virtual-modifiers",
  256. .file_type = FILE_TYPE_SYMBOLS,
  257. .flags = 0,
  258. .num_include_groups = 3,
  259. .include_groups = {
  260. { .start = 0, .end = 0 },
  261. { .start = 1, .end = 1 },
  262. { .start = 2, .end = 2 },
  263. },
  264. .num_includes = 3,
  265. .includes = {
  266. {
  267. .valid = true,
  268. .merge = MERGE_DEFAULT,
  269. .path = "symbols/srvr_ctrl",
  270. .file = "srvr_ctrl",
  271. .section = "fkey2vt",
  272. .explicit_section = true,
  273. .modifier = "",
  274. .flags = MAP_IS_PARTIAL | MAP_HAS_FN,
  275. },
  276. {
  277. .valid = true,
  278. .merge = MERGE_DEFAULT,
  279. .path = "symbols/pc",
  280. .file = "pc",
  281. .section = "editing",
  282. .explicit_section = true,
  283. .modifier = "",
  284. .flags = MAP_IS_HIDDEN | MAP_IS_PARTIAL
  285. | MAP_HAS_ALPHANUMERIC,
  286. },
  287. {
  288. .valid = true,
  289. .merge = MERGE_DEFAULT,
  290. .path = "symbols/keypad",
  291. .file = "keypad",
  292. .section = "x11",
  293. .explicit_section = true,
  294. .modifier = "",
  295. .flags = MAP_IS_DEFAULT | MAP_IS_HIDDEN | MAP_IS_PARTIAL
  296. | MAP_HAS_KEYPAD,
  297. }
  298. },
  299. }
  300. };
  301. for (size_t k = 0; k < ARRAY_SIZE(tests); k++) {
  302. fprintf(stderr, "------\n*** %s: #%zu ***\n", __func__, k);
  303. xkb_file_section_init(&section);
  304. const bool is_default = tests[k].flags & MAP_IS_DEFAULT;
  305. assert(xkb_file_section_parse(
  306. ctx, XKB_FILE_ITERATOR_FAIL_ON_INCLUDE_ERROR,
  307. XKB_KEYMAP_FORMAT_TEXT_V2, TEST_KEYMAP_COMPILE_FLAGS,
  308. 0, path, (is_default ? NULL : tests[k].name), &section
  309. ));
  310. test_section(&tests[k], &section);
  311. xkb_file_section_free(&section);
  312. }
  313. free(path);
  314. }
  315. struct iterator_test_data {
  316. const char *string;
  317. const char *map;
  318. struct section_test_data sections[3];
  319. unsigned int num_sections;
  320. bool error;
  321. };
  322. static void
  323. test_file_iterator(struct xkb_context *ctx)
  324. {
  325. static const struct iterator_test_data tests[] = {
  326. {
  327. .string = "",
  328. .map = NULL,
  329. .num_sections = 0,
  330. .sections = {},
  331. .error = false,
  332. },
  333. {
  334. .string = "xkb_symbols \"1\" {};",
  335. .map = NULL,
  336. .num_sections = 1,
  337. .sections = {
  338. {
  339. .name = "1",
  340. .file_type = FILE_TYPE_SYMBOLS,
  341. .flags = 0,
  342. .includes = {},
  343. .num_includes = 0,
  344. }
  345. },
  346. .error = false,
  347. },
  348. {
  349. .string =
  350. "xkb_symbols \"1\" {\n"
  351. " include \"pc\"\n"
  352. " replace \"+de:1|cz:2\"\n"
  353. "};",
  354. .map = NULL,
  355. .num_sections = 1,
  356. .sections = {
  357. {
  358. .name = "1",
  359. .file_type = FILE_TYPE_SYMBOLS,
  360. .flags = 0,
  361. .num_include_groups = 2,
  362. .include_groups = {
  363. { .start = 0, .end = 0 },
  364. { .start = 1, .end = 2 },
  365. },
  366. .num_includes = 3,
  367. .includes = {
  368. {
  369. .valid = true,
  370. .merge = MERGE_DEFAULT,
  371. .path = "symbols/pc",
  372. .file = "pc",
  373. .section = "pc105",
  374. .explicit_section = false,
  375. .modifier = "",
  376. .flags = MAP_IS_DEFAULT | MAP_IS_PARTIAL
  377. | MAP_HAS_ALPHANUMERIC | MAP_HAS_MODIFIER,
  378. },
  379. {
  380. .valid = true,
  381. /* First include uses the merge mode of the statement */
  382. .merge = MERGE_REPLACE,
  383. .path = "symbols/de",
  384. .file = "de",
  385. .section = "basic",
  386. .explicit_section = false,
  387. .modifier = "1",
  388. .flags = MAP_IS_DEFAULT
  389. },
  390. {
  391. .valid = true,
  392. .merge = MERGE_AUGMENT,
  393. .path = "symbols/cz",
  394. .file = "cz",
  395. .section = "basic",
  396. .explicit_section = false,
  397. .modifier = "2",
  398. .flags = MAP_IS_DEFAULT | MAP_IS_PARTIAL
  399. | MAP_HAS_ALPHANUMERIC,
  400. }
  401. },
  402. }
  403. },
  404. .error = false,
  405. },
  406. {
  407. .string =
  408. "xkb_symbols \"1\" {};\n"
  409. "xkb_symbols \"2\" {};\n",
  410. .map = NULL,
  411. .num_sections = 2,
  412. .sections = {
  413. {
  414. .name = "1",
  415. .file_type = FILE_TYPE_SYMBOLS,
  416. .flags = 0,
  417. .num_include_groups = 0,
  418. .include_groups = {},
  419. .num_includes = 0,
  420. .includes = {},
  421. },
  422. {
  423. .name = "2",
  424. .file_type = FILE_TYPE_SYMBOLS,
  425. .flags = 0,
  426. .num_include_groups = 0,
  427. .include_groups = {},
  428. .num_includes = 0,
  429. .includes = {},
  430. },
  431. },
  432. .error = false,
  433. },
  434. {
  435. .string =
  436. "xkb_keymap \"1\" {\n"
  437. " xkb_types \"2\" {};\n"
  438. " xkb_symbols \"3\" {};\n"
  439. "};",
  440. .map = NULL,
  441. .num_sections = 3,
  442. .sections = {
  443. {
  444. .name = "1",
  445. .file_type = FILE_TYPE_KEYMAP,
  446. .flags = 0,
  447. .num_include_groups = 0,
  448. .include_groups = {},
  449. .num_includes = 0,
  450. .includes = {},
  451. },
  452. {
  453. .name = "2",
  454. .file_type = FILE_TYPE_TYPES,
  455. .flags = 0,
  456. .num_include_groups = 0,
  457. .include_groups = {},
  458. .num_includes = 0,
  459. .includes = {},
  460. },
  461. {
  462. .name = "3",
  463. .file_type = FILE_TYPE_SYMBOLS,
  464. .flags = 0,
  465. .num_include_groups = 0,
  466. .include_groups = {},
  467. .num_includes = 0,
  468. .includes = {},
  469. },
  470. },
  471. .error = false,
  472. },
  473. {
  474. .string =
  475. "xkb_keymap \"10\" {\n"
  476. " xkb_types \"11\" {};\n"
  477. " xkb_symbols \"12\" {};\n"
  478. "};\n"
  479. "default xkb_keymap \"20\" {\n"
  480. " xkb_types \"21\" {};\n"
  481. " xkb_symbols \"22\" {};\n"
  482. "};",
  483. .map = NULL,
  484. .sections = {},
  485. .num_sections = 0,
  486. .error = true,
  487. // TODO: enable multiple keymap per file
  488. // .num_sections = 6,
  489. // .sections = {
  490. // {
  491. // .path = NULL,
  492. // .name = "10",
  493. // .file_type = FILE_TYPE_KEYMAP,
  494. // .flags = 0,
  495. // .includes = {},
  496. // .num_include = 0
  497. // },
  498. // {
  499. // .path = NULL,
  500. // .name = "11",
  501. // .file_type = FILE_TYPE_TYPES,
  502. // .flags = 0,
  503. // .includes = {},
  504. // .num_include = 0
  505. // },
  506. // {
  507. // .path = NULL,
  508. // .name = "12",
  509. // .file_type = FILE_TYPE_SYMBOLS,
  510. // .flags = 0,
  511. // .includes = {},
  512. // .num_include = 0
  513. // },
  514. // {
  515. // .path = NULL,
  516. // .name = "20",
  517. // .file_type = FILE_TYPE_KEYMAP,
  518. // .flags = MAP_IS_DEFAULT,
  519. // .includes = {},
  520. // .num_include = 0
  521. // },
  522. // {
  523. // .path = NULL,
  524. // .name = "21",
  525. // .file_type = FILE_TYPE_TYPES,
  526. // .flags = 0,
  527. // .includes = {},
  528. // .num_include = 0
  529. // },
  530. // {
  531. // .path = NULL,
  532. // .name = "22",
  533. // .file_type = FILE_TYPE_SYMBOLS,
  534. // .flags = 0,
  535. // .includes = {},
  536. // .num_include = 0
  537. // },
  538. // },
  539. // .error = false,
  540. },
  541. };
  542. for (size_t k = 0; k < ARRAY_SIZE(tests); k++) {
  543. fprintf(stderr, "------\n*** %s: #%zu ***\n", __func__, k);
  544. struct xkb_file_iterator * const iter =
  545. xkb_file_iterator_new_from_buffer(
  546. ctx, XKB_FILE_ITERATOR_FAIL_ON_INCLUDE_ERROR,
  547. XKB_KEYMAP_FORMAT_TEXT_V2, TEST_KEYMAP_COMPILE_FLAGS,
  548. "(string)", tests[k].map, FILE_TYPE_INVALID,
  549. tests[k].string, strlen(tests[k].string)
  550. );
  551. assert(iter);
  552. const struct xkb_file_section *section;
  553. unsigned int s = 0;
  554. bool ok = true;
  555. while ((ok = xkb_file_iterator_next(iter, &section)) && section) {
  556. fprintf(stderr, "section #%u\n", s);
  557. if (s >= tests[k].num_sections) {
  558. assert_eq("Section count", tests[k].num_sections, s, "%u");
  559. break;
  560. }
  561. assert(test_section(&tests[k].sections[s], section));
  562. s++;
  563. }
  564. assert_eq("Error", tests[k].error, !ok, "%d");
  565. assert_eq("Section count", tests[k].num_sections, s, "%u");
  566. xkb_file_iterator_free(iter);
  567. }
  568. }
  569. int
  570. main(void)
  571. {
  572. test_init();
  573. struct xkb_context * const context = test_get_context(CONTEXT_NO_FLAG);
  574. test_resolve_file(context);
  575. test_file_section_parse(context);
  576. test_file_iterator(context);
  577. xkb_context_unref(context);
  578. return EXIT_SUCCESS;
  579. }