package bird import ( "strings" "testing" ) // FuzzRender drives the bird template with a wide range of inputs and // confirms two safety properties: // // 1. Render never panics. // 2. On nil-error return, the output is deterministic (calling Render // twice with the same input yields byte-identical output) and contains // no unbalanced braces (a smoke test for malformed template branches). func FuzzRender(f *testing.F) { type seed struct { routerID string asn uint32 peerAddr string peerASN uint32 cidr6 string cidr4 string anycast6 string anycast4 string localV6 string localV4 string } seeds := []seed{ {routerID: "10.0.0.1", asn: 65101, peerAddr: "2001:db8::1", peerASN: 65000, cidr6: "2001:db8:f001::/64"}, {routerID: "172.25.25.101", asn: 65101, peerAddr: "172.25.25.1", peerASN: 65000, cidr4: "172.25.210.0/24"}, {routerID: "10.0.0.1", asn: 65101, peerAddr: "2001:db8::1", peerASN: 65000, cidr6: "2001:db8:f001::/64", anycast6: "2001:db8:a::1"}, {routerID: "10.0.0.1", asn: 65101, peerAddr: "10.0.0.2", peerASN: 65000, cidr4: "10.0.0.0/24", anycast4: "10.255.0.1"}, {routerID: "10.0.0.1", asn: 65101}, // no peer, no cidrs {routerID: "", asn: 65101, peerAddr: "10.0.0.2", peerASN: 1}, // empty routerID → expect error {routerID: "10.0.0.1", asn: 0, peerAddr: "10.0.0.2", peerASN: 1}, // zero ASN → expect error // Backtick-bearing inputs to defend the template against accidental // closure of the raw-string literal. {routerID: "10.0.0.1`", asn: 65101}, // Newlines and template-meta in user-supplied addresses {routerID: "10.0.0.1", asn: 65101, peerAddr: "2001:db8::1\n{{kaboom}}", peerASN: 65000, cidr6: "2001:db8:f001::/64"}, } for _, s := range seeds { f.Add(s.routerID, s.asn, s.peerAddr, s.peerASN, s.cidr6, s.cidr4, s.anycast6, s.anycast4, s.localV6, s.localV4) } f.Fuzz(func(t *testing.T, routerID string, asn uint32, peerAddr string, peerASN uint32, cidr6, cidr4, anycast6, anycast4, localV6, localV4 string) { in := NodeBGP{ RouterID: routerID, LocalASN: asn, LocalV6: localV6, LocalV4: localV4, } // Add the peer in whichever family it belongs to, if any. FamilyOf // returns "" for non-IPs; that test exercises the "skip unknown // family" branch in the bird agent code path. if fam := FamilyOf(peerAddr); fam != "" { in.Peers = []Peer{{Family: fam, Address: peerAddr, ASN: peerASN}} } if cidr6 != "" { in.CIDR6 = []string{cidr6} } if cidr4 != "" { in.CIDR4 = []string{cidr4} } if anycast6 != "" { in.Anycast6 = []string{anycast6} } if anycast4 != "" { in.Anycast4 = []string{anycast4} } out, err := Render(in) if err != nil { return } // Determinism. out2, err := Render(in) if err != nil { t.Fatalf("Render became flaky: first ok, second %v", err) } if out != out2 { t.Fatalf("Render not deterministic on identical input") } // Smoke test for balanced braces. The template uses `{` and `}` // as BIRD's block delimiters; if our template engine ever // produced an unbalanced output we'd catch it here. if got := strings.Count(out, "{") - strings.Count(out, "}"); got != 0 { t.Fatalf("unbalanced braces: %d", got) } }) }