The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
pair_tokenize_tests.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Tests for fr_pair_ctx_afrom_substr()
18 *
19 * @file src/lib/util/test/pair_tokenize_tests.c
20 *
21 * @copyright 2026 The FreeRADIUS server project
22 */
23
24static void test_init(void);
25#define TEST_GLOBAL_INIT test_init()
26
27#include "acutest_common_init.h"
28#include "acutest_helpers.h"
29
30#include <freeradius-devel/util/dict_test.h>
31#include <freeradius-devel/util/pair.h>
32
33static TALLOC_CTX *autofree;
35
36/** Global initialisation
37 */
38static void test_init(void)
39{
41 if (!autofree) {
42 error:
43 fr_perror("pair_tokenize_tests");
44 fr_exit_now(EXIT_FAILURE);
45 }
46
47 /*
48 * Mismatch between the binary and the libraries it depends on
49 */
50 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) goto error;
51
52 if (fr_dict_test_init(autofree, &test_dict, NULL) < 0) goto error;
53}
54
55/** Start a parsing context at the root of the test dictionary
56 */
57static void pair_ctx_init(fr_pair_ctx_t *pair_ctx, fr_pair_list_t *list)
58{
60 pair_ctx->ctx = autofree;
61 pair_ctx->list = list;
63}
64
65static void test_leaf_quoted(void)
66{
67 fr_pair_ctx_t pair_ctx;
68 fr_pair_list_t list;
70 char const in[] = "Test-String-0 = \"hello world\"";
71 fr_slen_t slen;
72
73 pair_ctx_init(&pair_ctx, &list);
74
75 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN(in, sizeof(in) - 1));
76 TEST_CHECK_SLEN(slen, (fr_slen_t)(sizeof(in) - 1));
77
79 vp = fr_pair_list_head(&list);
80 TEST_ASSERT(vp != NULL);
82 TEST_CHECK(vp->op == T_OP_EQ);
83 TEST_CHECK_STRCMP(vp->vp_strvalue, "hello world");
84
85 TEST_CASE("A leaf parsed from the root leaves the context at the root");
87
88 fr_pair_list_free(&list);
89}
90
92{
93 fr_pair_ctx_t pair_ctx;
94 fr_pair_list_t list;
96 char const in[] = "Test-Uint32-0 == 42, Test-String-0 = bob";
97 fr_sbuff_t sbuff = FR_SBUFF_IN(in, sizeof(in) - 1);
98 fr_slen_t slen;
99
100 pair_ctx_init(&pair_ctx, &list);
101
102 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &sbuff);
103 TEST_CHECK_SLEN(slen, 19);
104 TEST_CHECK(fr_sbuff_is_char(&sbuff, ','));
105
106 TEST_CASE("The caller consumes the comma");
107 fr_sbuff_advance(&sbuff, 1);
108 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &sbuff);
109 TEST_CHECK_SLEN(slen, 20);
110 TEST_CHECK(fr_sbuff_remaining(&sbuff) == 0);
111
113
114 vp = fr_pair_list_head(&list);
115 TEST_ASSERT(vp != NULL);
117 TEST_CHECK(vp->op == T_OP_CMP_EQ);
118 TEST_CHECK(vp->vp_uint32 == 42);
119
120 vp = fr_pair_list_next(&list, vp);
121 TEST_ASSERT(vp != NULL);
123 TEST_CHECK(vp->op == T_OP_EQ);
124 TEST_CHECK_STRCMP(vp->vp_strvalue, "bob");
125
126 fr_pair_list_free(&list);
127}
128
129/** The sbuff bounds the input, so skipping leading whitespace cannot run past the end of the input
130 */
132{
133 fr_pair_ctx_t pair_ctx;
134 fr_pair_list_t list;
135 fr_pair_t *vp;
136 char const in[] = " Test-String-0 = bob";
137 fr_slen_t slen;
138
139 pair_ctx_init(&pair_ctx, &list);
140
141 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN(in, sizeof(in) - 1));
142 TEST_CHECK_SLEN(slen, (fr_slen_t)(sizeof(in) - 1));
143
144 vp = fr_pair_list_head(&list);
145 TEST_ASSERT(vp != NULL);
146 TEST_CHECK_STRCMP(vp->vp_strvalue, "bob");
147
148 fr_pair_list_free(&list);
149}
150
151static void test_whitespace_only(void)
152{
153 fr_pair_ctx_t pair_ctx;
154 fr_pair_list_t list;
155 char const in[] = " \t ";
156 fr_slen_t slen;
157
158 pair_ctx_init(&pair_ctx, &list);
159
160 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN(in, sizeof(in) - 1));
161 TEST_CHECK_SLEN(slen, (fr_slen_t)(sizeof(in) - 1));
163}
164
165static void test_context_structural(void)
166{
167 fr_pair_ctx_t pair_ctx;
168 fr_pair_list_t list;
169 fr_pair_t *vp;
170 fr_slen_t slen;
171
172 pair_ctx_init(&pair_ctx, &list);
173
174 TEST_CASE("A bare structural attribute changes the context");
175 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-TLV-0"));
176 TEST_CHECK_SLEN(slen, 10);
178
179 TEST_CASE("After a leading '.', the leaf is a child of the current context");
180 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR(".String = 'child'"));
181 TEST_CHECK_SLEN(slen, 17);
183
184 vp = fr_pair_list_head(&list);
185 TEST_ASSERT(vp != NULL);
187 TEST_CHECK_STRCMP(vp->vp_strvalue, "child");
188
189 TEST_CASE("A structural attribute followed by a comma leaves the comma for the caller");
190 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-Group-0,"));
191 TEST_CHECK_SLEN(slen, 12);
193
194 fr_pair_list_free(&list);
195}
196
197static void test_context_nested(void)
198{
199 fr_pair_ctx_t pair_ctx;
200 fr_pair_list_t list;
201 fr_pair_t *vp;
202 fr_slen_t slen;
203
204 pair_ctx_init(&pair_ctx, &list);
205
206 TEST_CASE("A dotted reference descends from the root");
207 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-Nested-Top-TLV-0.Child-TLV"));
208 TEST_CHECK_SLEN(slen, 31);
210
211 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR(".Leaf-Int32 = -7"));
212 TEST_CHECK_SLEN(slen, 16);
213
214 vp = fr_pair_list_head(&list);
215 TEST_ASSERT(vp != NULL);
217 TEST_CHECK(vp->vp_int32 == -7);
218
219 TEST_CASE("'..' is the parent of the current context");
220 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR(".."));
221 TEST_CHECK_SLEN(slen, 2);
223
224 TEST_CASE("After a leading '.', a structural attribute is a child of the current context");
225 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR(".Child-TLV"));
226 TEST_CHECK_SLEN(slen, 10);
228
229 TEST_CASE("'..' followed by a leaf parses the leaf under the parent of the current context");
230 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-TLV-0"));
231 TEST_CHECK_SLEN(slen, 10);
232 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("..Test-String-0 = up"));
233 TEST_CHECK_SLEN(slen, 20);
235
236 vp = fr_pair_list_tail(&list);
237 TEST_ASSERT(vp != NULL);
239 TEST_CHECK_STRCMP(vp->vp_strvalue, "up");
240
241 TEST_CASE("'..' followed by a reference descends from the parent of the current context");
242 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-Nested-Top-TLV-0.Child-TLV"));
243 TEST_CHECK_SLEN(slen, 31);
244 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("..Child-TLV"));
245 TEST_CHECK_SLEN(slen, 11);
247
248 TEST_CASE("Each further '.' walks one level further up");
249 slen = fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("...Test-TLV-0"));
250 TEST_CHECK_SLEN(slen, 13);
252
253 fr_pair_list_free(&list);
254}
255
256/** Check that a parse fails at the expected offset with the expected message
257 */
258static void pair_ctx_check_error(fr_pair_ctx_t *pair_ctx, char const *in, fr_slen_t offset, char const *msg)
259{
260 fr_slen_t slen;
261
262 TEST_CASE(in);
264 slen = fr_pair_ctx_afrom_substr(pair_ctx, &FR_SBUFF_IN(in, strlen(in)));
265 TEST_CHECK_SLEN(slen, -(offset + 1));
267}
268
269static void test_errors(void)
270{
271 fr_pair_ctx_t pair_ctx;
272 fr_pair_list_t list;
273
274 pair_ctx_init(&pair_ctx, &list);
275
276 pair_ctx_check_error(&pair_ctx, "Test-Nope = 1", 0,
277 "Attribute 'Test-Nope' not found in namespace 'test'");
278
279 TEST_CASE("The error offset counts leading whitespace");
280 pair_ctx_check_error(&pair_ctx, " Test-Nope = 1", 2,
281 "Attribute 'Test-Nope' not found in namespace 'test'");
282
283 pair_ctx_check_error(&pair_ctx, "Test-String-0 \"x\"", 14,
284 "Expected operator");
285
286 pair_ctx_check_error(&pair_ctx, "Test-String-0 = `ls`", 16,
287 "Invalid string quotation");
288
289 pair_ctx_check_error(&pair_ctx, "Test-String-0 = \"abc", 20,
290 "Unterminated string");
291
292 pair_ctx_check_error(&pair_ctx, "Test-TLV-0 foo", 10,
293 "Unexpected text after attribute");
294
295 pair_ctx_check_error(&pair_ctx, "Test-TLV-0.Nope", 11,
296 "Attribute 'Nope' not found in namespace 'Test-TLV-0'");
297
298 TEST_CASE("A trailing '.' has no attribute to set the context from");
300 TEST_CHECK(fr_pair_ctx_afrom_substr(&pair_ctx, &FR_SBUFF_IN_STR("Test-TLV-0.")) < 0);
301 TEST_CHECK(fr_strerror()[0] != '\0');
302
303 TEST_CASE("The dictionary root has no parent, so '..' at the root fails");
304 fr_pair_ctx_reset(&pair_ctx, test_dict);
305 pair_ctx_check_error(&pair_ctx, "..Test-String-0", 1,
306 "No parent above the dictionary root");
307
309}
310
312 { "leaf_quoted", test_leaf_quoted },
313 { "leaf_bareword_stops_at_comma", test_leaf_bareword_stops_at_comma },
314 { "leaf_leading_whitespace", test_leaf_leading_whitespace },
315 { "whitespace_only", test_whitespace_only },
316 { "context_structural", test_context_structural },
317 { "context_nested", test_context_nested },
318 { "errors", test_errors },
319
321};
#define TEST_CHECK(cond)
Definition acutest.h:101
#define TEST_CASE(name)
Definition acutest.h:203
#define TEST_ASSERT(cond)
Definition acutest.h:125
log_entry msg
Definition acutest.h:947
#define TEST_TERMINATOR
Definition acutest.h:77
Common initialisation for acutest binaries.
#define TEST_CHECK_SLEN(_got, _exp)
#define TEST_CHECK_STRCMP(_got, _exp)
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition debug.h:272
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2717
fr_dict_attr_t const * fr_dict_attr_test_tlv_string
Definition dict_test.c:65
fr_dict_attr_t const * fr_dict_attr_test_tlv
Definition dict_test.c:64
fr_dict_attr_t const * fr_dict_attr_test_nested_top_tlv
Definition dict_test.c:76
fr_dict_attr_t const * fr_dict_attr_test_uint32
Definition dict_test.c:47
fr_dict_attr_t const * fr_dict_attr_test_string
Definition dict_test.c:30
fr_dict_attr_t const * fr_dict_attr_test_nested_child_tlv
Definition dict_test.c:77
int fr_dict_test_init(TALLOC_CTX *ctx, fr_dict_t **dict_p, fr_dict_test_attr_t const *test_defs)
Initialise a test dictionary and add our test_defs to it.
Definition dict_test.c:248
fr_dict_attr_t const * fr_dict_attr_test_group
Definition dict_test.c:74
fr_dict_attr_t const * fr_dict_attr_test_nested_leaf_int32
Definition dict_test.c:79
ssize_t fr_slen_t
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
static void test_whitespace_only(void)
static TALLOC_CTX * autofree
static void pair_ctx_init(fr_pair_ctx_t *pair_ctx, fr_pair_list_t *list)
Start a parsing context at the root of the test dictionary.
static void test_leaf_quoted(void)
static void test_context_structural(void)
static fr_dict_t * test_dict
static void test_context_nested(void)
static void test_leaf_bareword_stops_at_comma(void)
static void pair_ctx_check_error(fr_pair_ctx_t *pair_ctx, char const *in, fr_slen_t offset, char const *msg)
Check that a parse fails at the expected offset with the expected message.
static void test_init(void)
Global initialisation.
static void test_errors(void)
static void test_leaf_leading_whitespace(void)
The sbuff bounds the input, so skipping leading whitespace cannot run past the end of the input.
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_is_char(_sbuff_or_marker, _c)
#define FR_SBUFF_IN_STR(_start)
#define fr_sbuff_advance(_sbuff_or_marker, _len)
#define fr_sbuff_remaining(_sbuff_or_marker)
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
@ T_OP_EQ
Definition token.h:81
@ T_OP_CMP_EQ
Definition token.h:104
TALLOC_CTX * ctx
to allocate VPs in
Definition pair.h:891
fr_slen_t fr_pair_ctx_afrom_substr(fr_pair_ctx_t *pair_ctx, fr_sbuff_t *in)
Parse one pair, or one context change, from a string.
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
Definition pair_inline.c:69
fr_pair_t * fr_pair_list_tail(fr_pair_list_t const *list)
Get the tail of a valuepair list.
Definition pair_inline.c:55
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
Reset a pair_ctx to the dictionary root.
fr_pair_list_t * list
of VPs to add
Definition pair.h:893
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition pair.h:892
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition pair_inline.c:42
size_t fr_pair_list_num_elements(fr_pair_list_t const *list)
Get the length of a list of fr_pair_t.
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition strerror.c:737
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:581
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition version.h:81
static fr_sbuff_err_t char size_t fr_sbuff_t * in
Definition value.h:1061