13 #include <catch2/catch.hpp>
19 struct DOTParsingTest {
20 std::string input_description;
26 template<
typename Test>
27 void do_parseDot_test(
const Test& test);
31 std::string graph_name = {},
ConfigStore graph_attrs = {}
33 ConfigGraph cg(std::move(graph_name), std::move(graph_attrs));
34 cg.insert(std::move(vertex_name), std::move(attrs));
40 TEST_CASE(
"DOT Parsing Tests -- Basic Functionality") {
41 const auto test = GENERATE(values<DOTParsingTest>({
58 {
"has graph name",
true,
60 singleNodeCG(
"v", {},
"G"),
62 {
"has graph no name",
true,
64 singleNodeCG(
"v", {},
""),
66 {
"has graph name and attributes",
true,
67 "digraph G { graph[a=b] v }",
70 {
"has graph name and attributes",
true,
71 "digraph G { graph[a=b] v }",
74 {
"has graph name and attributes, semicolon",
true,
75 "digraph G { graph[a=b]; v }",
76 singleNodeCG(
"v", {},
"G", {{
"a",
"b"}}),
78 {
"with 1 node, no attributes",
true,
82 {
"with 1 node, no attributes, with semicolon",
true,
86 {
"with 1 node, no attributes, quoted",
true,
90 {
"with 1 node, 1 attribute",
true,
91 "digraph { v [a=b] }",
92 singleNodeCG(
"v", {{
"a",
"b"}}),
94 {
"with 1 node, 1 attribute, with semicolon",
true,
95 "digraph { v [a=b]; }",
96 singleNodeCG(
"v", {{
"a",
"b"}}),
98 {
"with 1 node, 2 attributes across 2 declarations, no override",
true,
99 "digraph { v [a=b] v [c=d] }",
100 singleNodeCG(
"v", {{
"a",
"b"},{
"c",
"d"}}),
102 {
"with 1 node, 2 attributes across 2 declarations, with override",
true,
103 "digraph { v [a=b] v [a=c] }",
104 singleNodeCG(
"v", {{
"a",
"c"}}),
106 {
"with 1 keyword node, quoted",
true,
107 "digraph { \"node\" }",
108 singleNodeCG(
"node"),
110 {
"with 1 edge, no nodes, no attributes",
true,
114 {
"with 1 edge, 1 node declared before, no attributes",
true,
115 "digraph { a a->b }",
118 {
"with 1 edge, 1 node declared before, 1 after, no attributes",
true,
119 "digraph { a a->b b }",
122 {
"with 1 edge, 1 node declared before, 1 after, no attributes, with semicolons",
true,
123 "digraph { a; a->b; b; }",
126 {
"with 1 edge, no nodes, with attributes",
true,
127 "digraph { a->b [c=d]}",
130 {
"with 2 edges in continuation, no nodes, with attributes",
true,
131 "digraph { a->b->c [d=e]}",
135 cg.link(cg.target(ab), cg.insert(
"c").first, {{
"d",
"e"}});
139 {
"duplicate edge, no nodes, with attributes, not strict",
true,
140 "digraph { a->b [c=d]; a->b [c=e]}",
160 do_parseDot_test(test);
163 const auto test = GENERATE(values<DOTParsingTest>({
164 {
"default node attrs -- one node",
true,
165 "digraph { node[a=b] v }",
168 {
"default node attrs -- one node, with semicolon",
true,
169 "digraph { node[a=b]; v }",
172 {
"default node attrs -- two nodes",
true,
173 "digraph { node[a=b] v w }",
174 ConfigGraph(
"", {}, {{
"v",{{
"a",
"b"}}}, {
"w",{{
"a",
"b"}}}}),
176 {
"default node attrs -- two nodes, one before attrs",
true,
177 "digraph { v node[a=b] w }",
178 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{{
"a",
"b"}}}}),
180 {
"default node attrs -- two nodes, interleaved defaults, adding more",
true,
181 "digraph { node[a=a] v node[b=b] w }",
182 ConfigGraph(
"", {}, {{
"v",{{
"a",
"a"}}}, {
"w",{{
"a",
"a"}, {
"b",
"b"}}}}),
184 {
"default node attrs -- two nodes, interleaved defaults, overriding",
true,
185 "digraph { node[a=a] v node[a=b] w }",
186 ConfigGraph(
"", {}, {{
"v",{{
"a",
"a"}}}, {
"w",{{
"a",
"b"}}}}),
188 {
"default node attrs -- nodes declared in edge",
true,
189 "digraph { node[a=b] v->w }",
190 ConfigGraph(
"", {}, {{
"v",{{
"a",
"b"}}}, {
"w",{{
"a",
"b"}}}}, {{
"v",
"w"}}),
192 {
"default node attrs -- nodes declared in chained edge",
true,
193 "digraph { node[a=b] v->w->x }",
194 ConfigGraph(
"", {}, {{
"v",{{
"a",
"b"}}}, {
"w",{{
"a",
"b"}}}, {
"x",{{
"a",
"b"}}}}, {{
"v",
"w"}, {
"w",
"x"}}),
196 {
"default edge attrs -- one edge",
true,
197 "digraph { edge[a=b] v->w }",
198 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"b"}}}}),
200 {
"default edge attrs -- one edge, with semicolon",
true,
201 "digraph { edge[a=b]; v->w }",
202 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"b"}}}}),
204 {
"default edge attrs -- two edges",
true,
205 "digraph { edge[a=b] v->w x->y }",
206 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"b"}}}, {
"x",
"y",{{
"a",
"b"}}}}),
208 {
"default edge attrs -- two edges, one before attrs",
true,
209 "digraph { v->w edge[a=b] x->y }",
210 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w"}, {
"x",
"y",{{
"a",
"b"}}}}),
212 {
"default edge attrs -- two edges, interleaved defaults, adding more",
true,
213 "digraph { edge[a=a] v->w edge[b=b] x->y }",
214 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"a"}}}, {
"x",
"y",{{
"a",
"a"}, {
"b",
"b"}}}}),
216 {
"default edge attrs -- two edges, interleaved defaults, overriding",
true,
217 "digraph { edge[a=a] v->w edge[a=b] x->y }",
218 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"a"}}}, {
"x",
"y",{{
"a",
"b"}}}}),
220 {
"default edge attrs -- chain edge",
true,
221 "digraph { edge[a=b] v->w->x }",
222 ConfigGraph(
"", {}, {{
"v",{}}, {
"w",{}}}, {{
"v",
"w",{{
"a",
"b"}}}, {
"w",
"x",{{
"a",
"b"}}}}),
226 do_parseDot_test(test);
230 const auto test = GENERATE(values<DOTParsingTest>({
231 {
"empty with extra }",
false,
235 {
"empty but has semicolon",
false,
239 {
"with 1 node, no attributes, incorrectly quoted",
false,
240 "digraph { \"v\"\" }",
243 {
"with 1 keyword node, not quoted",
false,
249 do_parseDot_test(test);
253 const auto test = GENERATE(values<DOTParsingTest>({
254 {
"empty string node",
true,
258 {
"node with escaped quote",
true,
259 "digraph { \"\\\"\" }",
266 {
"node with escaped closing quote",
true,
267 "digraph { \"\\\" }",
270 {
"node with escaped closing quote, with other chars",
true,
271 "digraph { \"aaa\\\" }",
272 singleNodeCG(
"aaa\\"),
274 {
"node with backslash and escaped quote",
true,
275 "digraph { \"\\\\\"\" }",
276 singleNodeCG(
"\\\""),
278 {
"node with newline",
true,
279 "digraph { \"\n\" }",
282 {
"node that ends in a number",
true,
283 "digraph { abc123 }",
284 singleNodeCG(
"abc123"),
286 {
"node that starts with a number",
false,
287 "digraph { 123abc }",
290 {
"node that starts with a number -- quoted",
true,
291 "digraph { \"123abc\" }",
292 singleNodeCG(
"123abc"),
294 {
"node is real number -- normal case",
true,
298 {
"node is real number -- negative",
true,
300 singleNodeCG(
"-4.5"),
302 {
"node is real number -- no whole part",
true,
306 {
"node is real number -- no whole part, negative",
true,
310 {
"node is real number -- no fractional part",
true,
314 {
"node is real number -- no fractional part, negative",
true,
318 {
"node is . -- unquoted",
false,
322 {
"node is . -- quoted",
true,
328 {
"node is <>",
true,
"digraph { <> }", singleNodeCG(
"<>") },
329 {
"node is <a>",
true,
"digraph { <a> }", singleNodeCG(
"<a>") },
330 {
"node is <<>",
false,
"digraph { <>> }", {} },
331 {
"node is <>>",
false,
"digraph { <>> }", {} },
332 {
"node is <<>>",
true,
"digraph { <<>> }", singleNodeCG(
"<<>>") },
333 {
"node is <<a/>>",
true,
"digraph { <<a/>> }", singleNodeCG(
"<<a/>>") },
334 {
"node is <<a><a/>>",
true,
"digraph { <<a><a/>> }", singleNodeCG(
"<<a><a/>>") },
335 {
"node is <<a>b<a/>>",
true,
"digraph { <<a>b<a/>> }", singleNodeCG(
"<<a>b<a/>>") },
336 {
"node is <<a>b<a/><c>d<c/>>",
true,
"digraph { <<a>b<a/><c>d<c/>> }", singleNodeCG(
"<<a>b<a/><c>d<c/>>") },
337 {
"node is <<a>b<a/><c/>>",
true,
"digraph { <<a>b<a/><c/>> }", singleNodeCG(
"<<a>b<a/><c/>>") },
338 {
"node is <<a/><c/>>",
true,
"digraph { <<a/><c/>> }", singleNodeCG(
"<<a/><c/>>") },
339 {
"node is <<a/>\n<c/>>",
true,
"digraph { <<a/><c/>> }", singleNodeCG(
"<<a/><c/>>") },
340 {
"node is <<a/></\nc>>",
true,
"digraph { <<a/><c/>> }", singleNodeCG(
"<<a/><c/>>") },
341 {
"node is <<a>b\n<a/>>",
true,
"digraph { <<a>b<a/>> }", singleNodeCG(
"<<a>b<a/>>") },
342 {
"node is <<a\n>b<a/>>",
true,
"digraph { <<a>b<a/>> }", singleNodeCG(
"<<a>b<a/>>") },
343 {
"node is <<a>b<a/>>",
true,
"digraph { <<a>b<a/>> }", singleNodeCG(
"<<a>b<a/>>") },
346 do_parseDot_test(test);
350 const auto test = GENERATE(values<DOTParsingTest>({
351 {
"// comment -- at beginning of line",
true,
352 "digraph {\n// abcd \n}",
355 {
"// comment -- not beginning of line",
true,
356 "digraph {\n a // abcd \n}",
359 {
"// comment -- comments out closing brace",
false,
363 {
"# comment -- at beginning of line",
true,
364 "digraph\n# ababaa\n{ }",
367 {
"# comment -- not a beginning of line",
false,
371 {
"block comment middle of graph",
true,
375 {
"block comment -- comments out opening brace",
false,
379 {
"block comment -- no closing sequence",
false,
383 {
"block comment -- multi-line",
true,
384 "digraph { /*\n\nabcd*/ a }",
389 do_parseDot_test(test);
395 template<
typename T,
typename U>
396 ConfigGraph do_dotParse(
const T& input,
const U& input_description) {
397 auto instr = std::istringstream(input);
399 return parseDot(instr, input_description);
400 }
catch (
const std::exception& e) {
401 std::ostringstream s;
402 s <<
"Exception when parsing: " << e.what()
403 <<
"\nInput was:\n" << input;
404 std::throw_with_nested(std::logic_error(s.str()));
408 template<
typename Test>
409 void do_parseDot_test(
const Test& test) {
410 GIVEN(
"A '" << test.input_description <<
"' input graph") {
411 WHEN(
"Parsing the input") {
412 THEN(
"The result should " << (test.expect_to_parse ?
"be as expected" :
"fail to parse")) {
413 if (test.expect_to_parse) {
414 const auto actual = do_dotParse(test.input, test.input_description);
415 REQUIRE(actual == test.expected);
417 AND_WHEN(
"Parsing the DOT output of the result") {
419 auto dot_dump = std::ostringstream{};
420 actual.printDot(dot_dump);
421 const auto actual2 = do_dotParse(dot_dump.str(),
"re-parse: " + test.input_description);
423 THEN(
"The graph should survive intact") {
424 REQUIRE(actual2 == test.expected);
429 const auto actual = do_dotParse(test.input, test.input_description);
431 <<
"Test should have thrown (ie. failed to parse). Dumping return value:\n"
432 << actual << std::endl;