CGRA-ME
dotparser_tests.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * The software programs comprising "CGRA-ME" and the documentation provided
3  * with them are copyright by its authors and the University of Toronto. Only
4  * non-commercial, not-for-profit use of this software is permitted without ex-
5  * plicit permission. This software is provided "as is" with no warranties or
6  * guarantees of support. See the LICENCE for more details. You should have re-
7  * ceived a copy of the full licence along with this software. If not, see
8  * <http://cgra-me.ece.utoronto.ca/license/>.
9  ******************************************************************************/
10 
11 #include <CGRA/dotparse.h>
12 
13 #include <catch2/catch.hpp>
14 
15 #include <sstream>
16 
17 namespace {
18 
19 struct DOTParsingTest {
20  std::string input_description;
21  bool expect_to_parse;
22  std::string input;
23  ConfigGraph expected;
24 };
25 
26 template<typename Test>
27 void do_parseDot_test(const Test& test);
28 
29 ConfigGraph singleNodeCG(
30  std::string vertex_name, ConfigStore attrs = {},
31  std::string graph_name = {}, ConfigStore graph_attrs = {}
32 ) {
33  ConfigGraph cg(std::move(graph_name), std::move(graph_attrs));
34  cg.insert(std::move(vertex_name), std::move(attrs));
35  return cg;
36 }
37 
38 } // end anon namespace
39 
40 TEST_CASE("DOT Parsing Tests -- Basic Functionality") {
41  const auto test = GENERATE(values<DOTParsingTest>({
42  { "empty", true,
43  "digraph { }",
44  { },
45  },
46  { "EMPTY", true,
47  "DIGRAPH { }",
48  { },
49  },
50  { "EmPtY", true,
51  "DiGrApH { }",
52  { },
53  },
54  { "eMpTy", true,
55  "dIgRaPh { }",
56  { },
57  },
58  { "has graph name", true,
59  "digraph G { v }",
60  singleNodeCG("v", {}, "G"),
61  },
62  { "has graph no name", true,
63  "digraph { v }",
64  singleNodeCG("v", {}, ""),
65  },
66  { "has graph name and attributes", true,
67  "digraph G { graph[a=b] v }",
68  ConfigGraph("G", {{"a","b"}}, {{"v",{}}}),
69  },
70  { "has graph name and attributes", true,
71  "digraph G { graph[a=b] v }",
72  ConfigGraph("G", {{"a","b"}}, {{"v",{}}}),
73  },
74  { "has graph name and attributes, semicolon", true,
75  "digraph G { graph[a=b]; v }",
76  singleNodeCG("v", {}, "G", {{"a","b"}}),
77  },
78  { "with 1 node, no attributes", true,
79  "digraph { v }",
80  singleNodeCG("v"),
81  },
82  { "with 1 node, no attributes, with semicolon", true,
83  "digraph { v; }",
84  singleNodeCG("v"),
85  },
86  { "with 1 node, no attributes, quoted", true,
87  "digraph { \"v\" }",
88  singleNodeCG("v"),
89  },
90  { "with 1 node, 1 attribute", true,
91  "digraph { v [a=b] }",
92  singleNodeCG("v", {{"a","b"}}),
93  },
94  { "with 1 node, 1 attribute, with semicolon", true,
95  "digraph { v [a=b]; }",
96  singleNodeCG("v", {{"a","b"}}),
97  },
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"}}),
101  },
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"}}),
105  },
106  { "with 1 keyword node, quoted", true,
107  "digraph { \"node\" }",
108  singleNodeCG("node"),
109  },
110  { "with 1 edge, no nodes, no attributes", true,
111  "digraph { a->b }",
112  ConfigGraph("", {}, {}, {{"a","b",{}}}),
113  },
114  { "with 1 edge, 1 node declared before, no attributes", true,
115  "digraph { a a->b }",
116  ConfigGraph("", {}, {}, {{"a","b",{}}}),
117  },
118  { "with 1 edge, 1 node declared before, 1 after, no attributes", true,
119  "digraph { a a->b b }",
120  ConfigGraph("", {}, {}, {{"a","b",{}}}),
121  },
122  { "with 1 edge, 1 node declared before, 1 after, no attributes, with semicolons", true,
123  "digraph { a; a->b; b; }",
124  ConfigGraph("", {}, {}, {{"a","b",{}}}),
125  },
126  { "with 1 edge, no nodes, with attributes", true,
127  "digraph { a->b [c=d]}",
128  ConfigGraph("", {}, {}, {{"a","b",{{"c","d"}}}}),
129  },
130  { "with 2 edges in continuation, no nodes, with attributes", true,
131  "digraph { a->b->c [d=e]}",
132  [&]{
133  ConfigGraph cg;
134  auto ab = cg.link(cg.insert("a").first, cg.insert("b").first, {{"d","e"}});
135  cg.link(cg.target(ab), cg.insert("c").first, {{"d","e"}});
136  return cg;
137  }(),
138  },
139  { "duplicate edge, no nodes, with attributes, not strict", true,
140  "digraph { a->b [c=d]; a->b [c=e]}",
141  [&]{
142  ConfigGraph cg;
143  cg.link(cg.insert("a").first, cg.insert("b").first, {{"c","d"}});
144  cg.link(cg.insert("a").first, cg.insert("b").first, {{"c","e"}});
145  CHECK(cg.numEdges() == 2);
146  return cg;
147  }(),
148  },
149  // requires 'strict' support
150  // { "duplicate edge, no nodes, with attribute override, strict", true,
151  // "strict digraph { a->b [c=d]; a->b [c=e]}",
152  // [&]{
153  // ConfigGraph cg;
154  // cg.link(cg.insert("a").first, cg.insert("b").first, {{"c","e"}});
155  // return cg;
156  // }(),
157  // },
158  }));
159 
160  do_parseDot_test(test);
161 }
162 TEST_CASE("DOT Parsing Tests -- Default Attributes") {
163  const auto test = GENERATE(values<DOTParsingTest>({
164  { "default node attrs -- one node", true,
165  "digraph { node[a=b] v }",
166  ConfigGraph("", {}, {{"v",{{"a","b"}}}}),
167  },
168  { "default node attrs -- one node, with semicolon", true,
169  "digraph { node[a=b]; v }",
170  ConfigGraph("", {}, {{"v",{{"a","b"}}}}),
171  },
172  { "default node attrs -- two nodes", true,
173  "digraph { node[a=b] v w }",
174  ConfigGraph("", {}, {{"v",{{"a","b"}}}, {"w",{{"a","b"}}}}),
175  },
176  { "default node attrs -- two nodes, one before attrs", true,
177  "digraph { v node[a=b] w }",
178  ConfigGraph("", {}, {{"v",{}}, {"w",{{"a","b"}}}}),
179  },
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"}}}}),
183  },
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"}}}}),
187  },
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"}}),
191  },
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"}}),
195  },
196  { "default edge attrs -- one edge", true,
197  "digraph { edge[a=b] v->w }",
198  ConfigGraph("", {}, {{"v",{}}, {"w",{}}}, {{"v","w",{{"a","b"}}}}),
199  },
200  { "default edge attrs -- one edge, with semicolon", true,
201  "digraph { edge[a=b]; v->w }",
202  ConfigGraph("", {}, {{"v",{}}, {"w",{}}}, {{"v","w",{{"a","b"}}}}),
203  },
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"}}}}),
207  },
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"}}}}),
211  },
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"}}}}),
215  },
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"}}}}),
219  },
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"}}}}),
223  },
224  }));
225 
226  do_parseDot_test(test);
227 }
228 
229 TEST_CASE("DOT Parsing Tests -- Simple Failures") {
230  const auto test = GENERATE(values<DOTParsingTest>({
231  { "empty with extra }", false,
232  "digraph { }}",
233  { },
234  },
235  { "empty but has semicolon", false,
236  "digraph { ; }",
237  { },
238  },
239  { "with 1 node, no attributes, incorrectly quoted", false,
240  "digraph { \"v\"\" }",
241  { },
242  },
243  { "with 1 keyword node, not quoted", false,
244  "digraph { node }",
245  { },
246  },
247  }));
248 
249  do_parseDot_test(test);
250 }
251 
252 TEST_CASE("DOT Parsing Tests -- ID Tests") {
253  const auto test = GENERATE(values<DOTParsingTest>({
254  { "empty string node", true,
255  "digraph { \"\" }",
256  singleNodeCG(""),
257  },
258  { "node with escaped quote", true,
259  "digraph { \"\\\"\" }",
260  singleNodeCG("\""),
261  },
262  // honestly, this (and the following) is mildly ambiguous, according to my reading of the spec.
263  // I think accepting makes slightly more sense because it allows IDs to end in backslashes,
264  // and therefore the only thing that needs to be done to quote an arbitrary string is
265  // to replace internal quotes.
266  { "node with escaped closing quote", true,
267  "digraph { \"\\\" }",
268  singleNodeCG("\\"),
269  },
270  { "node with escaped closing quote, with other chars", true,
271  "digraph { \"aaa\\\" }",
272  singleNodeCG("aaa\\"),
273  },
274  { "node with backslash and escaped quote", true,
275  "digraph { \"\\\\\"\" }",
276  singleNodeCG("\\\""),
277  },
278  { "node with newline", true,
279  "digraph { \"\n\" }",
280  singleNodeCG("\n"),
281  },
282  { "node that ends in a number", true,
283  "digraph { abc123 }",
284  singleNodeCG("abc123"),
285  },
286  { "node that starts with a number", false,
287  "digraph { 123abc }",
288  { },
289  },
290  { "node that starts with a number -- quoted", true,
291  "digraph { \"123abc\" }",
292  singleNodeCG("123abc"),
293  },
294  { "node is real number -- normal case", true,
295  "digraph { 4.5 }",
296  singleNodeCG("4.5"),
297  },
298  { "node is real number -- negative", true,
299  "digraph { -4.5 }",
300  singleNodeCG("-4.5"),
301  },
302  { "node is real number -- no whole part", true,
303  "digraph { .5 }",
304  singleNodeCG(".5"),
305  },
306  { "node is real number -- no whole part, negative", true,
307  "digraph { -.5 }",
308  singleNodeCG("-.5"),
309  },
310  { "node is real number -- no fractional part", true,
311  "digraph { 4. }",
312  singleNodeCG("4."),
313  },
314  { "node is real number -- no fractional part, negative", true,
315  "digraph { -4. }",
316  singleNodeCG("-4."),
317  },
318  { "node is . -- unquoted", false,
319  "digraph { . }",
320  { },
321  },
322  { "node is . -- quoted", true,
323  "digraph { \".\" }",
324  singleNodeCG("."),
325  },
326  // Some HTML ID tests. Note that the parser is quite permissive (will accept any <...> balanced string),
327  // but it is only required to accept valid XML.
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/>>") },
344  }));
345 
346  do_parseDot_test(test);
347 }
348 
349 TEST_CASE("DOT Parsing Tests -- Comments") {
350  const auto test = GENERATE(values<DOTParsingTest>({
351  { "// comment -- at beginning of line", true,
352  "digraph {\n// abcd \n}",
353  { },
354  },
355  { "// comment -- not beginning of line", true,
356  "digraph {\n a // abcd \n}",
357  singleNodeCG("a"),
358  },
359  { "// comment -- comments out closing brace", false,
360  "digraph { // }",
361  { },
362  },
363  { "# comment -- at beginning of line", true,
364  "digraph\n# ababaa\n{ }",
365  { },
366  },
367  { "# comment -- not a beginning of line", false,
368  "digraph { # }",
369  { },
370  },
371  { "block comment middle of graph", true,
372  "digraph { /* */ }",
373  { },
374  },
375  { "block comment -- comments out opening brace", false,
376  "digraph /* { */ }",
377  { },
378  },
379  { "block comment -- no closing sequence", false,
380  "digraph /* { }",
381  { },
382  },
383  { "block comment -- multi-line", true,
384  "digraph { /*\n\nabcd*/ a }",
385  singleNodeCG("a"),
386  },
387  }));
388 
389  do_parseDot_test(test);
390 }
391 
392 
393 namespace {
394 
395 template<typename T, typename U>
396 ConfigGraph do_dotParse(const T& input, const U& input_description) {
397  auto instr = std::istringstream(input);
398  try {
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()));
405  }
406 }
407 
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);
416 
417  AND_WHEN("Parsing the DOT output of the result") {
418 
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);
422 
423  THEN("The graph should survive intact") {
424  REQUIRE(actual2 == test.expected);
425  }
426  }
427  } else {
428  REQUIRE_THROWS([&]{
429  const auto actual = do_dotParse(test.input, test.input_description);
430  std::cout
431  << "Test should have thrown (ie. failed to parse). Dumping return value:\n"
432  << actual << std::endl;
433  }());
434  }
435  }
436  }
437  }
438 }
439 
440 } // end anon namespace
dotparse.h
ConfigGraph
Definition: ConfigGraph.h:72
ConfigStore
Definition: ConfigStore.h:76
ConfigGraph::insert
std::pair< VertexID, bool > insert(std::string name, ConfigStore vertex_attrs={})
Definition: ConfigGraph.h:173
ConfigGraph::link
EdgeID link(VertexID source, VertexID target, ConfigStore edge_attrs={})
Definition: ConfigGraph.h:190
parseDot
ConfigGraph parseDot(std::istream &input, std::string file_name)
TEST_CASE
TEST_CASE("DOT Parsing Tests -- Basic Functionality")
Definition: dotparser_tests.cpp:40
ConfigGraph::numEdges
std::ptrdiff_t numEdges() const
Definition: ConfigGraph.h:242