Commit 7d5e5cbe04 for frr
commit 7d5e5cbe047d5d96ac40029aa66aa0794f88a1cc
Author: Srinivasan Koona Lokabiraman <srinivasan@nexthop.ai>
Date: Fri Oct 23 05:34:38 2026 +0000
tests: check color extcommunity config output re-parses
test_ecommunity: add color-format and color-format-co cases. Each
parses a color extended community, checks the COMMUNITY_LIST,
ROUTE_MAP and DISPLAY renderings ("color CC:VALUE" for the first two,
"Color:CC:VALUE" for display), and parses the COMMUNITY_LIST output
again to check it round-trips.
bgp_color_extcommunities: add
test_bgp_color_extcommunity_list_config_roundtrip, which configures
color extcommunity-lists, takes their lines from running-config,
enters them again and checks they are accepted and unchanged.
Signed-off-by: Srinivasan Koona Lokabiraman <srinivasan@nexthop.ai>
diff --git a/tests/bgpd/test_ecommunity.c b/tests/bgpd/test_ecommunity.c
index 2f550a557b..e250dc3005 100644
--- a/tests/bgpd/test_ecommunity.c
+++ b/tests/bgpd/test_ecommunity.c
@@ -183,6 +183,89 @@ static void lb_display_test(struct lb_segment *t)
}
+/* Color extended-community formatting.
+ *
+ * COMMUNITY_LIST and ROUTE_MAP render config that has to parse again, so both
+ * must emit "color CC:VALUE"; DISPLAY stays human-readable as "Color:CC:VALUE".
+ * The CO field is two binary digits, hence 11 == 3.
+ */
+static struct color_segment {
+ const char *name;
+ const char *str; /* input, extcommunity-list syntax */
+ const char *reparse; /* expected COMMUNITY_LIST and ROUTE_MAP output */
+ const char *display; /* expected DISPLAY output */
+} color_segments[] = {
+ {"color-format", "color 00:100", "color 00:100", "Color:00:100"},
+ {"color-format-co", "color 11:55555", "color 11:55555",
+ "Color:11:55555"},
+ {"color-format-max", "color 11:4294967295", "color 11:4294967295",
+ "Color:11:4294967295"},
+ {NULL, NULL, NULL, NULL}};
+
+static int check_format(const char *what, struct ecommunity *ecom, int format,
+ const char *shouldbe)
+{
+ char *str = ecommunity_ecom2str(ecom, format, 0);
+ int fails = 0;
+
+ printf(" %-14s %s\n", what, str ? str : "NULL");
+ if (!str || strcmp(str, shouldbe)) {
+ printf("shouldbe: %s\n", shouldbe);
+ fails++;
+ }
+ XFREE(MTYPE_ECOMMUNITY_STR, str);
+
+ return fails;
+}
+
+static void color_test(struct color_segment *t)
+{
+ struct ecommunity *ecom, *etmp;
+ char *str1, *str2;
+ int fails = 0;
+
+ printf("%s: %s\n", t->name, t->str);
+
+ ecom = ecommunity_str2com(t->str, 0, 1);
+ if (!ecom) {
+ printf("shouldbe: %s\nparse returned NULL\n", t->reparse);
+ failed++;
+ printf("failed\n\n");
+ return;
+ }
+
+ fails += check_format("COMMUNITY_LIST", ecom,
+ ECOMMUNITY_FORMAT_COMMUNITY_LIST, t->reparse);
+ fails += check_format("ROUTE_MAP", ecom, ECOMMUNITY_FORMAT_ROUTE_MAP,
+ t->reparse);
+ fails += check_format("DISPLAY", ecom, ECOMMUNITY_FORMAT_DISPLAY,
+ t->display);
+
+ /* the re-parseable form must survive a round trip */
+ str1 = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0);
+ etmp = str1 ? ecommunity_str2com(str1, 0, 1) : NULL;
+ if (etmp)
+ str2 = ecommunity_ecom2str(etmp,
+ ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0);
+ else
+ str2 = NULL;
+
+ if (!str2 || strcmp(str1, str2)) {
+ printf("dogfood: in %s\n"
+ " in->out %s\n",
+ str1 ? str1 : "NULL", str2 ? str2 : "NULL");
+ fails++;
+ }
+
+ ecommunity_free(&etmp);
+ ecommunity_free(&ecom);
+ XFREE(MTYPE_ECOMMUNITY_STR, str1);
+ XFREE(MTYPE_ECOMMUNITY_STR, str2);
+
+ failed += fails;
+ printf(fails ? "failed\n\n" : "OK\n\n");
+}
+
int main(void)
{
int i = 0;
@@ -192,6 +275,10 @@ int main(void)
for (i = 0; lb_segments[i].name; i++)
lb_display_test(&lb_segments[i]);
+ i = 0;
+ while (color_segments[i].name)
+ color_test(&color_segments[i++]);
+
printf("failures: %d\n", failed);
// printf ("aspath count: %ld\n", aspath_count());
return failed;
diff --git a/tests/bgpd/test_ecommunity.py b/tests/bgpd/test_ecommunity.py
index 9080d42087..a13948b353 100644
--- a/tests/bgpd/test_ecommunity.py
+++ b/tests/bgpd/test_ecommunity.py
@@ -13,3 +13,6 @@ TestEcommunity.okfail("asn4")
TestEcommunity.okfail("lb-gbps")
TestEcommunity.okfail("lb-mbps")
TestEcommunity.okfail("lb-ipv6-gbps")
+TestEcommunity.okfail("color-format")
+TestEcommunity.okfail("color-format-co")
+TestEcommunity.okfail("color-format-max")
diff --git a/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py b/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py
index 94a444d5c5..224dd6f6bd 100644
--- a/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py
+++ b/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py
@@ -121,6 +121,54 @@ def test_bgp_color_extended_communities():
), "10.10.10.0/24 ext community is correctly not installed, but SHOULD be"
+def test_bgp_color_extcommunity_list_config_roundtrip():
+ "running-config output for a color extcommunity-list must be re-enterable"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ r1.vtysh_cmd(
+ """
+ configure terminal
+ bgp extcommunity-list standard COLOR1 permit color 00:100
+ bgp extcommunity-list standard COLOR2 permit color 11:55555
+ """
+ )
+
+ def _generated_line(name):
+ for line in r1.vtysh_cmd("show running-config").splitlines():
+ if line.startswith("bgp extcommunity-list standard {} ".format(name)):
+ return line.strip()
+ return None
+
+ for name, value in (("COLOR1", "00:100"), ("COLOR2", "11:55555")):
+ line = _generated_line(name)
+ assert line is not None, "{} missing from running-config".format(name)
+ assert "permit color {}".format(value) in line, (
+ "{} rendered as {!r}, expected a re-parseable 'color {}'".format(
+ name, line, value
+ )
+ )
+
+ # Feed the generated lines back in under new names; they must parse.
+ for name in ("COLOR1", "COLOR2"):
+ line = _generated_line(name)
+ reentered = line.replace(
+ "standard {} ".format(name), "standard RT_{} ".format(name)
+ )
+ output = r1.vtysh_cmd("configure terminal\n{}".format(reentered))
+ assert "Malformed" not in output, "re-entering {!r} failed: {}".format(
+ reentered, output
+ )
+
+ assert _generated_line("RT_{}".format(name)) == reentered, (
+ "round-trip of {} is not idempotent".format(name)
+ )
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))