Skip to content

Commit c426e93

Browse files
committed
Add HLIL-backed parity tests for precompiler lexer
1 parent 774c985 commit c426e93

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed

tests/parity/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ option(BOTLIB_PARITY_ENABLE_SOURCES "Build botlib parity test sources" OFF)
66

77
set(PARITY_TEST_SOURCES
88
test_bot_interface.c
9+
test_precompiler_lexer.c
910
)
1011

1112
if(BOTLIB_PARITY_ENABLE_SOURCES)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#include <stdarg.h>
2+
#include <stddef.h>
3+
#include <setjmp.h>
4+
#include <cmocka.h>
5+
6+
#include "botlib/precomp/l_precomp.h"
7+
#include "botlib/precomp/l_script.h"
8+
9+
#include "../reference/precomp_lexer_expectations.h"
10+
11+
#ifndef ARRAY_SIZE
12+
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
13+
#endif
14+
15+
static void assert_token_matches(const pc_token_snapshot_t *expected, const pc_token_t *actual)
16+
{
17+
assert_non_null(expected);
18+
assert_non_null(actual);
19+
20+
assert_int_equal(expected->type, actual->type);
21+
assert_int_equal(expected->subtype, actual->subtype);
22+
assert_int_equal(expected->line, actual->line);
23+
assert_string_equal(expected->lexeme, actual->string);
24+
}
25+
26+
static void assert_diagnostics_match(const pc_diagnostic_snapshot_t *expected,
27+
size_t expected_count,
28+
const pc_diagnostic_t *head)
29+
{
30+
size_t index = 0;
31+
const pc_diagnostic_t *cursor = head;
32+
33+
while (index < expected_count && cursor != NULL) {
34+
assert_int_equal(expected[index].level, cursor->level);
35+
assert_int_equal(expected[index].line, cursor->line);
36+
assert_int_equal(expected[index].column, cursor->column);
37+
assert_string_equal(expected[index].message, cursor->message);
38+
39+
cursor = cursor->next;
40+
++index;
41+
}
42+
43+
if (cursor == NULL) {
44+
assert_int_equal(expected_count, index);
45+
} else {
46+
fail_msg("diagnostic chain longer than expectation (index %zu)", index);
47+
}
48+
}
49+
50+
static void drain_remaining_tokens(pc_source_t *source)
51+
{
52+
pc_token_t token;
53+
while (PC_ReadToken(source, &token) > 0) {
54+
// Nothing to do; this helper ensures subsequent tests observe EOF.
55+
}
56+
}
57+
58+
static void test_pc_loads_fw_items_and_matches_hlil_tokens(void **state)
59+
{
60+
(void)state;
61+
62+
PC_InitLexer();
63+
64+
pc_source_t *source = PC_LoadSourceFile(g_fw_items_source_path);
65+
if (source == NULL) {
66+
PC_ShutdownLexer();
67+
cmocka_skip("PC_LoadSourceFile is not yet implemented");
68+
}
69+
70+
for (size_t i = 0; i < ARRAY_SIZE(g_fw_items_token_expectations); ++i) {
71+
pc_token_t token;
72+
const int status = PC_ReadToken(source, &token);
73+
if (status <= 0) {
74+
PC_FreeSource(source);
75+
PC_ShutdownLexer();
76+
cmocka_skip("PC_ReadToken is not yet implemented");
77+
}
78+
79+
assert_token_matches(&g_fw_items_token_expectations[i], &token);
80+
}
81+
82+
drain_remaining_tokens(source);
83+
84+
assert_diagnostics_match(g_fw_items_diagnostics,
85+
g_fw_items_diagnostics_count,
86+
PC_GetDiagnostics(source));
87+
88+
PC_FreeSource(source);
89+
PC_ShutdownLexer();
90+
}
91+
92+
static void test_pc_loads_synonyms_and_matches_hlil_tokens(void **state)
93+
{
94+
(void)state;
95+
96+
PC_InitLexer();
97+
98+
pc_source_t *source = PC_LoadSourceFile(g_synonyms_source_path);
99+
if (source == NULL) {
100+
PC_ShutdownLexer();
101+
cmocka_skip("PC_LoadSourceFile is not yet implemented");
102+
}
103+
104+
for (size_t i = 0; i < ARRAY_SIZE(g_synonyms_token_expectations); ++i) {
105+
pc_token_t token;
106+
const int status = PC_ReadToken(source, &token);
107+
if (status <= 0) {
108+
PC_FreeSource(source);
109+
PC_ShutdownLexer();
110+
cmocka_skip("PC_ReadToken is not yet implemented");
111+
}
112+
113+
assert_token_matches(&g_synonyms_token_expectations[i], &token);
114+
}
115+
116+
drain_remaining_tokens(source);
117+
118+
assert_diagnostics_match(g_synonyms_diagnostics,
119+
g_synonyms_diagnostics_count,
120+
PC_GetDiagnostics(source));
121+
122+
PC_FreeSource(source);
123+
PC_ShutdownLexer();
124+
}
125+
126+
static void test_pc_peek_and_unread_mirror_hlil_behaviour(void **state)
127+
{
128+
(void)state;
129+
130+
PC_InitLexer();
131+
132+
pc_source_t *source = PC_LoadSourceFile(g_fw_items_source_path);
133+
if (source == NULL) {
134+
PC_ShutdownLexer();
135+
cmocka_skip("PC_LoadSourceFile is not yet implemented");
136+
}
137+
138+
pc_token_t peeked;
139+
if (PC_PeekToken(source, &peeked) <= 0) {
140+
PC_FreeSource(source);
141+
PC_ShutdownLexer();
142+
cmocka_skip("PC_PeekToken is not yet implemented");
143+
}
144+
145+
assert_token_matches(&g_fw_items_token_expectations[0], &peeked);
146+
147+
pc_token_t token;
148+
assert_int_equal(1, PC_ReadToken(source, &token));
149+
assert_token_matches(&g_fw_items_token_expectations[0], &token);
150+
151+
PC_UnreadToken(source);
152+
153+
assert_int_equal(1, PC_ReadToken(source, &token));
154+
assert_token_matches(&g_fw_items_token_expectations[0], &token);
155+
156+
PC_FreeSource(source);
157+
PC_ShutdownLexer();
158+
}
159+
160+
int main(void)
161+
{
162+
const struct CMUnitTest tests[] = {
163+
cmocka_unit_test(test_pc_loads_fw_items_and_matches_hlil_tokens),
164+
cmocka_unit_test(test_pc_loads_synonyms_and_matches_hlil_tokens),
165+
cmocka_unit_test(test_pc_peek_and_unread_mirror_hlil_behaviour),
166+
};
167+
168+
return cmocka_run_group_tests(tests, NULL, NULL);
169+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#ifndef TESTS_REFERENCE_PRECOMP_LEXER_EXPECTATIONS_H
2+
#define TESTS_REFERENCE_PRECOMP_LEXER_EXPECTATIONS_H
3+
4+
#include "botlib/precomp/l_precomp.h"
5+
#include "botlib/precomp/l_script.h"
6+
7+
#ifdef __cplusplus
8+
extern "C" {
9+
#endif
10+
11+
// Token snapshot captured from the Gladiator HLIL traces. The production lexer
12+
// is expected to mirror the Quake III precompiler contract, emitting identical
13+
// token types, subtypes, and line metadata when lexing the sample assets.
14+
typedef struct pc_token_snapshot_s {
15+
const char *lexeme;
16+
pc_token_type_t type;
17+
int subtype;
18+
int line;
19+
} pc_token_snapshot_t;
20+
21+
typedef struct pc_diagnostic_snapshot_s {
22+
pc_error_level_t level;
23+
int line;
24+
int column;
25+
const char *message;
26+
} pc_diagnostic_snapshot_t;
27+
28+
// Punctuation IDs mirrored from the historical precompiler. The HLIL export
29+
// shows that single character braces and parentheses share the same identifiers
30+
// as Quake III Arena's botlib implementation.
31+
#ifndef PC_PUNCTUATION_IDS
32+
#define PC_PUNCTUATION_IDS
33+
enum {
34+
P_PARENTHESESOPEN = 44,
35+
P_PARENTHESESCLOSE = 45,
36+
P_BRACEOPEN = 46,
37+
P_BRACECLOSE = 47,
38+
P_COLON = 42,
39+
P_COMMA = 40,
40+
P_SQBRACKETOPEN = 48,
41+
P_SQBRACKETCLOSE = 49,
42+
};
43+
#endif // PC_PUNCTUATION_IDS
44+
45+
static const char g_fw_items_source_path[] = "dev_tools/assets/fw_items.c";
46+
static const char g_synonyms_source_path[] = "dev_tools/assets/syn.c";
47+
48+
static const pc_token_snapshot_t g_fw_items_token_expectations[] = {
49+
{"weight", TT_NAME, 6, 27},
50+
{"\"weapon_shotgun\"", TT_STRING, 14, 27},
51+
{"{", TT_PUNCTUATION, P_BRACEOPEN, 28},
52+
{"switch", TT_NAME, 6, 29},
53+
{"(", TT_PUNCTUATION, P_PARENTHESESOPEN, 29},
54+
{"INVENTORY_SHOTGUN", TT_NAME, 17, 29},
55+
{")", TT_PUNCTUATION, P_PARENTHESESCLOSE, 29},
56+
{"{", TT_PUNCTUATION, P_BRACEOPEN, 30},
57+
{"case", TT_NAME, 4, 31},
58+
{"1", TT_NUMBER, TT_INTEGER | TT_DECIMAL, 31},
59+
{":", TT_PUNCTUATION, P_COLON, 31},
60+
{"{", TT_PUNCTUATION, P_BRACEOPEN, 32},
61+
{"switch", TT_NAME, 6, 33},
62+
{"(", TT_PUNCTUATION, P_PARENTHESESOPEN, 33},
63+
{"INVENTORY_SHELLS", TT_NAME, 16, 33},
64+
{")", TT_PUNCTUATION, P_PARENTHESESCLOSE, 33},
65+
{"{", TT_PUNCTUATION, P_BRACEOPEN, 34},
66+
};
67+
68+
static const pc_diagnostic_snapshot_t g_fw_items_diagnostics[] = {
69+
{0, 0, 0, NULL},
70+
};
71+
static const size_t g_fw_items_diagnostics_count = 0;
72+
73+
static const pc_token_snapshot_t g_synonyms_token_expectations[] = {
74+
{"CONTEXT_NEARBYITEM", TT_NAME, 18, 15},
75+
{"{", TT_PUNCTUATION, P_BRACEOPEN, 16},
76+
{"[", TT_PUNCTUATION, P_SQBRACKETOPEN, 18},
77+
{"(", TT_PUNCTUATION, P_PARENTHESESOPEN, 18},
78+
{"\"Body Armor\"", TT_STRING, 10, 18},
79+
{",", TT_PUNCTUATION, P_COMMA, 18},
80+
{"1", TT_NUMBER, TT_INTEGER | TT_DECIMAL, 18},
81+
{")", TT_PUNCTUATION, P_PARENTHESESCLOSE, 18},
82+
{",", TT_PUNCTUATION, P_COMMA, 18},
83+
{"(", TT_PUNCTUATION, P_PARENTHESESOPEN, 18},
84+
{"\"red armor\"", TT_STRING, 9, 18},
85+
};
86+
87+
static const pc_diagnostic_snapshot_t g_synonyms_diagnostics[] = {
88+
{0, 0, 0, NULL},
89+
};
90+
static const size_t g_synonyms_diagnostics_count = 0;
91+
92+
#ifdef __cplusplus
93+
} // extern "C"
94+
#endif
95+
96+
#endif // TESTS_REFERENCE_PRECOMP_LEXER_EXPECTATIONS_H

0 commit comments

Comments
 (0)