Skip to content

Commit cc405b0

Browse files
committed
Add unit tests for YANG versioning extensions
Split tests into separate modules for ietf-yang-semver and ietf-yang-revisions extensions: test_yang_semver.c: - Tests for ysv:version extension * Valid formats (basic, with modifiers, pre-release, metadata) * Invalid formats (wrong placement, malformed versions) * Version uniqueness across revisions - Tests for ysv:recommended-min-version extension * Valid MAJOR.MINOR.PATCH format * Invalid formats (with modifiers, pre-release) * Duplicate prevention test_yang_revisions.c: - Tests for rev:non-backwards-compatible extension * Valid usage in revision statements * Invalid placement and duplicates - Tests for rev:recommended-min-date extension * Valid date formats (YYYY-MM-DD) * Invalid date formats * Duplicate prevention Update CMakeLists.txt to register both test suites.
1 parent 5f5c0a2 commit cc405b0

File tree

3 files changed

+362
-0
lines changed

3 files changed

+362
-0
lines changed

tests/utests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,5 @@ ly_add_utest(NAME nacm SOURCES extensions/test_nacm.c)
7777
ly_add_utest(NAME yangdata SOURCES extensions/test_yangdata.c)
7878
ly_add_utest(NAME schema_mount SOURCES extensions/test_schema_mount.c)
7979
ly_add_utest(NAME structure SOURCES extensions/test_structure.c)
80+
ly_add_utest(NAME yang_revisions SOURCES extensions/test_yang_revisions.c)
81+
ly_add_utest(NAME yang_semver SOURCES extensions/test_yang_semver.c)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* @file test_yang_revisions.c
3+
* @author Joe Clarke <[email protected]>
4+
* @brief unit tests for YANG Revisions extensions support
5+
*
6+
* Copyright (c) 2025 Cisco Systems, Inc.
7+
*
8+
* This source code is licensed under BSD 3-Clause License (the "License").
9+
* You may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://opensource.org/licenses/BSD-3-Clause
13+
*/
14+
#define _UTEST_MAIN_
15+
#include "utests.h"
16+
17+
#include "libyang.h"
18+
19+
static int
20+
setup(void **state)
21+
{
22+
UTEST_SETUP;
23+
24+
assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_DIR_MODULES_YANG));
25+
assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-yang-revisions", "2025-01-28", NULL));
26+
27+
return 0;
28+
}
29+
30+
static void
31+
test_non_backwards_compatible_valid(void **state)
32+
{
33+
struct lys_module *mod;
34+
const char *data;
35+
36+
/* Valid: no argument */
37+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:revisions:a; prefix a;"
38+
"import ietf-yang-revisions { prefix rev; }"
39+
"revision 2025-01-01 { rev:non-backwards-compatible; }}";
40+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
41+
}
42+
43+
static void
44+
test_non_backwards_compatible_invalid(void **state)
45+
{
46+
const char *data;
47+
48+
/* Invalid: not in revision statement */
49+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:revisions:a; prefix a;"
50+
"import ietf-yang-revisions { prefix rev; }"
51+
"rev:non-backwards-compatible;}";
52+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
53+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
54+
"Extension rev:non-backwards-compatible is allowed only in a revision statement, but it is placed in \"module\" statement.",
55+
"/a:{extension='rev:non-backwards-compatible'}", 0);
56+
57+
/* Invalid: duplicate in same revision */
58+
data = "module c {yang-version 1.1; namespace urn:tests:extensions:revisions:c; prefix c;"
59+
"import ietf-yang-revisions { prefix rev; }"
60+
"revision 2025-01-01 { rev:non-backwards-compatible; rev:non-backwards-compatible; }}";
61+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
62+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
63+
"Extension rev:non-backwards-compatible is instantiated multiple times in the same revision.",
64+
"/c:{extension='rev:non-backwards-compatible'}", 0);
65+
}
66+
67+
static void
68+
test_recommended_min_date_valid(void **state)
69+
{
70+
struct lys_module *mod;
71+
const char *data;
72+
73+
/* Valid: proper date format */
74+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:revisions:a; prefix a;"
75+
"import ietf-yang-revisions { prefix rev; }"
76+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2013-07-15\"; }}";
77+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
78+
79+
/* Valid: different date */
80+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:revisions:b; prefix b;"
81+
"import ietf-yang-revisions { prefix rev; }"
82+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2020-12-31\"; }}";
83+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
84+
}
85+
86+
static void
87+
test_recommended_min_date_invalid(void **state)
88+
{
89+
const char *data;
90+
91+
/* Invalid: not in import statement */
92+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:revisions:a; prefix a;"
93+
"import ietf-yang-revisions { prefix rev; }"
94+
"rev:recommended-min-date \"2013-07-15\";}";
95+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
96+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
97+
"Extension rev:recommended-min-date is allowed only in an import statement, but it is placed in \"module\" statement.",
98+
"/a:{extension='rev:recommended-min-date'}/2013-07-15", 0);
99+
100+
/* Invalid: bad date format - wrong separator */
101+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:revisions:b; prefix b;"
102+
"import ietf-yang-revisions { prefix rev; }"
103+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2013/07/15\"; }}";
104+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
105+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
106+
"Extension rev:recommended-min-date has invalid revision-date format: 2013/07/15 (expected YYYY-MM-DD)",
107+
"/b:{extension='rev:recommended-min-date'}/2013/07/15", 0);
108+
109+
/* Invalid: bad date format - too short */
110+
data = "module c {yang-version 1.1; namespace urn:tests:extensions:revisions:c; prefix c;"
111+
"import ietf-yang-revisions { prefix rev; }"
112+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2013-07\"; }}";
113+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
114+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
115+
"Extension rev:recommended-min-date has invalid revision-date format: 2013-07 (expected YYYY-MM-DD)",
116+
"/c:{extension='rev:recommended-min-date'}/2013-07", 0);
117+
118+
/* Invalid: bad date - invalid month */
119+
data = "module d {yang-version 1.1; namespace urn:tests:extensions:revisions:d; prefix d;"
120+
"import ietf-yang-revisions { prefix rev; }"
121+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2013-13-15\"; }}";
122+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
123+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
124+
"Extension rev:recommended-min-date has invalid revision-date format: 2013-13-15 (expected YYYY-MM-DD)",
125+
"/d:{extension='rev:recommended-min-date'}/2013-13-15", 0);
126+
127+
/* Invalid: duplicate in same import */
128+
data = "module e {yang-version 1.1; namespace urn:tests:extensions:revisions:e; prefix e;"
129+
"import ietf-yang-revisions { prefix rev; }"
130+
"import ietf-yang-types { prefix yt; rev:recommended-min-date \"2013-07-15\"; rev:recommended-min-date \"2014-01-01\"; }}";
131+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
132+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-revisions v1\": "
133+
"Extension rev:recommended-min-date is instantiated multiple times in the same import.",
134+
"/e:{extension='rev:recommended-min-date'}/2013-07-15", 0);
135+
}
136+
137+
int
138+
main(void)
139+
{
140+
const struct CMUnitTest tests[] = {
141+
UTEST(test_non_backwards_compatible_valid, setup),
142+
UTEST(test_non_backwards_compatible_invalid, setup),
143+
UTEST(test_recommended_min_date_valid, setup),
144+
UTEST(test_recommended_min_date_invalid, setup),
145+
};
146+
147+
return cmocka_run_group_tests(tests, NULL, NULL);
148+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/**
2+
* @file test_yang_semver.c
3+
* @author Joe Clarke <[email protected]>
4+
* @brief unit tests for YANG Semantic Versioning extensions support
5+
*
6+
* Copyright (c) 2025 Cisco Systems, Inc.
7+
*
8+
* This source code is licensed under BSD 3-Clause License (the "License").
9+
* You may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://opensource.org/licenses/BSD-3-Clause
13+
*/
14+
#define _UTEST_MAIN_
15+
#include "utests.h"
16+
17+
#include "libyang.h"
18+
19+
static int
20+
setup(void **state)
21+
{
22+
UTEST_SETUP;
23+
24+
assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_DIR_MODULES_YANG));
25+
assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-yang-semver", "2025-09-29", NULL));
26+
27+
return 0;
28+
}
29+
30+
static void
31+
test_version_valid(void **state)
32+
{
33+
struct lys_module *mod;
34+
const char *data;
35+
36+
/* Valid: basic semantic version */
37+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:semver:a; prefix a;"
38+
"import ietf-yang-semver { prefix ysv; }"
39+
"revision 2025-01-01 { ysv:version \"1.0.0\"; }}";
40+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
41+
42+
/* Valid: version with compatible modifier */
43+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:semver:b; prefix b;"
44+
"import ietf-yang-semver { prefix ysv; }"
45+
"revision 2025-01-01 { ysv:version \"1.0.0_compatible\"; }}";
46+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
47+
48+
/* Valid: version with non_compatible modifier */
49+
data = "module c {yang-version 1.1; namespace urn:tests:extensions:semver:c; prefix c;"
50+
"import ietf-yang-semver { prefix ysv; }"
51+
"revision 2025-01-01 { ysv:version \"2.1.3_non_compatible\"; }}";
52+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
53+
54+
/* Valid: version with pre-release */
55+
data = "module d {yang-version 1.1; namespace urn:tests:extensions:semver:d; prefix d;"
56+
"import ietf-yang-semver { prefix ysv; }"
57+
"revision 2025-01-01 { ysv:version \"1.0.0-alpha.1\"; }}";
58+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
59+
60+
/* Valid: version with metadata */
61+
data = "module e {yang-version 1.1; namespace urn:tests:extensions:semver:e; prefix e;"
62+
"import ietf-yang-semver { prefix ysv; }"
63+
"revision 2025-01-01 { ysv:version \"1.0.0+build.123\"; }}";
64+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
65+
66+
/* Valid: complex version */
67+
data = "module f {yang-version 1.1; namespace urn:tests:extensions:semver:f; prefix f;"
68+
"import ietf-yang-semver { prefix ysv; }"
69+
"revision 2025-01-01 { ysv:version \"1.2.3_compatible-beta.2+build.456\"; }}";
70+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
71+
}
72+
73+
static void
74+
test_version_invalid(void **state)
75+
{
76+
const char *data;
77+
78+
/* Invalid: not in revision statement */
79+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:semver:a; prefix a;"
80+
"import ietf-yang-semver { prefix ysv; }"
81+
"ysv:version \"1.0.0\";}";
82+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
83+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
84+
"Extension ysv:version is allowed only in a revision statement, but it is placed in \"module\" statement.",
85+
"/a:{extension='ysv:version'}/1.0.0", 0);
86+
87+
/* Invalid: bad format - missing patch */
88+
data = "module c {yang-version 1.1; namespace urn:tests:extensions:semver:c; prefix c;"
89+
"import ietf-yang-semver { prefix ysv; }"
90+
"revision 2025-01-01 { ysv:version \"1.0\"; }}";
91+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
92+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
93+
"Extension ysv:version has invalid semantic version format: 1.0",
94+
"/c:{extension='ysv:version'}/1.0", 0);
95+
96+
/* Invalid: bad format - too many dots */
97+
data = "module d {yang-version 1.1; namespace urn:tests:extensions:semver:d; prefix d;"
98+
"import ietf-yang-semver { prefix ysv; }"
99+
"revision 2025-01-01 { ysv:version \"1.0.0.1\"; }}";
100+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
101+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
102+
"Extension ysv:version has invalid semantic version format: 1.0.0.1",
103+
"/d:{extension='ysv:version'}/1.0.0.1", 0);
104+
105+
/* Invalid: duplicate in same revision */
106+
data = "module e {yang-version 1.1; namespace urn:tests:extensions:semver:e; prefix e;"
107+
"import ietf-yang-semver { prefix ysv; }"
108+
"revision 2025-01-01 { ysv:version \"1.0.0\"; ysv:version \"1.0.1\"; }}";
109+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
110+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
111+
"Extension ysv:version is instantiated multiple times in the same revision.",
112+
"/e:{extension='ysv:version'}/1.0.0", 0);
113+
}
114+
115+
static void
116+
test_version_uniqueness(void **state)
117+
{
118+
struct lys_module *mod;
119+
const char *data;
120+
121+
/* Invalid: same version in different revisions */
122+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:semver:a; prefix a;"
123+
"import ietf-yang-semver { prefix ysv; }"
124+
"revision 2025-01-01 { ysv:version \"1.0.0\"; }"
125+
"revision 2025-02-01 { ysv:version \"1.0.0\"; }}";
126+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
127+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
128+
"Version 1.0.0 is not unique - it appears in multiple revisions.",
129+
"/a:{extension='ysv:version'}/1.0.0", 0);
130+
131+
/* Valid: different versions in different revisions */
132+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:semver:b; prefix b;"
133+
"import ietf-yang-semver { prefix ysv; }"
134+
"revision 2025-01-01 { ysv:version \"1.0.0\"; }"
135+
"revision 2025-02-01 { ysv:version \"1.0.1\"; }}";
136+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
137+
}
138+
139+
static void
140+
test_recommended_min_version_valid(void **state)
141+
{
142+
struct lys_module *mod;
143+
const char *data;
144+
145+
/* Valid: basic MAJOR.MINOR.PATCH */
146+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:semver:a; prefix a;"
147+
"import ietf-yang-semver { prefix ysv; }"
148+
"import ietf-yang-types { prefix yt; ysv:recommended-min-version \"1.0.0\"; }}";
149+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
150+
151+
/* Valid: different version format */
152+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:semver:b; prefix b;"
153+
"import ietf-yang-semver { prefix ysv; }"
154+
"import ietf-yang-types { prefix yt; ysv:recommended-min-version \"2.15.3\"; }}";
155+
UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod);
156+
}
157+
158+
static void
159+
test_recommended_min_version_invalid(void **state)
160+
{
161+
const char *data;
162+
163+
/* Invalid: not in import statement */
164+
data = "module a {yang-version 1.1; namespace urn:tests:extensions:semver:a; prefix a;"
165+
"import ietf-yang-semver { prefix ysv; }"
166+
"ysv:recommended-min-version \"1.0.0\";}";
167+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
168+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
169+
"Extension ysv:recommended-min-version is allowed only in an import statement, but it is placed in \"module\" statement.",
170+
"/a:{extension='ysv:recommended-min-version'}/1.0.0", 0);
171+
172+
/* Invalid: has modifiers (not allowed) */
173+
data = "module b {yang-version 1.1; namespace urn:tests:extensions:semver:b; prefix b;"
174+
"import ietf-yang-semver { prefix ysv; }"
175+
"import ietf-yang-types { prefix yt; ysv:recommended-min-version \"1.0.0_compatible\"; }}";
176+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
177+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
178+
"Extension ysv:recommended-min-version argument must be MAJOR.MINOR.PATCH format (no modifiers): 1.0.0_compatible",
179+
"/b:{extension='ysv:recommended-min-version'}/1.0.0_compatible", 0);
180+
181+
/* Invalid: has pre-release (not allowed) */
182+
data = "module c {yang-version 1.1; namespace urn:tests:extensions:semver:c; prefix c;"
183+
"import ietf-yang-semver { prefix ysv; }"
184+
"import ietf-yang-types { prefix yt; ysv:recommended-min-version \"1.0.0-alpha\"; }}";
185+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
186+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
187+
"Extension ysv:recommended-min-version argument must be MAJOR.MINOR.PATCH format (no modifiers): 1.0.0-alpha",
188+
"/c:{extension='ysv:recommended-min-version'}/1.0.0-alpha", 0);
189+
190+
/* Invalid: duplicate in same import */
191+
data = "module d {yang-version 1.1; namespace urn:tests:extensions:semver:d; prefix d;"
192+
"import ietf-yang-semver { prefix ysv; }"
193+
"import ietf-yang-types { prefix yt; ysv:recommended-min-version \"1.0.0\"; ysv:recommended-min-version \"2.0.0\"; }}";
194+
assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
195+
CHECK_LOG_CTX("Ext plugin \"ly2 ietf-yang-semver v1\": "
196+
"Extension ysv:recommended-min-version is instantiated multiple times in the same import.",
197+
"/d:{extension='ysv:recommended-min-version'}/1.0.0", 0);
198+
}
199+
200+
int
201+
main(void)
202+
{
203+
const struct CMUnitTest tests[] = {
204+
UTEST(test_version_valid, setup),
205+
UTEST(test_version_invalid, setup),
206+
UTEST(test_version_uniqueness, setup),
207+
UTEST(test_recommended_min_version_valid, setup),
208+
UTEST(test_recommended_min_version_invalid, setup),
209+
};
210+
211+
return cmocka_run_group_tests(tests, NULL, NULL);
212+
}

0 commit comments

Comments
 (0)