Skip to content

Commit 8d26a66

Browse files
authored
Merge pull request #567 from Becheler/test/graphviz-round-trip
test: adding missing round-trip test for graphviz format
2 parents cd0a3be + f6b5d98 commit 8d26a66

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

test/Jamfile.v2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ alias graph_test_regular :
7878
[ run graph.cpp : : : <define>TEST=8 : graph_8 ]
7979
[ run graph.cpp : : : <define>TEST=9 : graph_9 ]
8080
[ compile graph_concepts.cpp ]
81-
[ run graphviz_test.cpp
82-
/boost/graph//boost_graph : --log_level=all ]
81+
[ run graphviz_test.cpp /boost/graph//boost_graph : --log_level=all ]
82+
[ run graphviz_round_trip_test.cpp /boost/graph//boost_graph ]
8383
[ run metis_test.cpp : $(METIS_INPUT_FILE) ]
8484
[ run gursoy_atun_layout_test.cpp : : : [ requires cxx11_noexcept cxx11_rvalue_references sfinae_expr cxx11_auto_declarations cxx11_lambdas cxx11_unified_initialization_syntax cxx11_hdr_tuple cxx11_hdr_initializer_list cxx11_hdr_chrono cxx11_thread_local cxx11_constexpr cxx11_nullptr cxx11_numeric_limits cxx11_decltype cxx11_hdr_array cxx11_hdr_atomic cxx11_hdr_type_traits cxx11_allocator cxx11_explicit_conversion_operators ] ]
8585
[ run layout_test.cpp : : : <test-info>always_show_run_output <toolset>intel:<debug-symbols>off [ requires cxx11_noexcept cxx11_rvalue_references sfinae_expr cxx11_auto_declarations cxx11_lambdas cxx11_unified_initialization_syntax cxx11_hdr_tuple cxx11_hdr_initializer_list cxx11_hdr_chrono cxx11_thread_local cxx11_constexpr cxx11_nullptr cxx11_numeric_limits cxx11_decltype cxx11_hdr_array cxx11_hdr_atomic cxx11_hdr_type_traits cxx11_allocator cxx11_explicit_conversion_operators ] ]

test/graphviz_round_trip_test.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (C) 2026 Arnaud Becheler
2+
//
3+
// Use, modification and distribution is subject to the Boost Software
4+
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
5+
// http://www.boost.org/LICENSE_1_0.txt)
6+
7+
// Round-trips a graph through write_graphviz_dp and read_graphviz. The vertex
8+
// and edge string values are chosen to straddle the DOT quoting boundary in
9+
// escape_dot_string, so a writer that mis-quotes produces DOT that re-reads
10+
// into different values and fails the comparison.
11+
12+
#include <boost/graph/adjacency_list.hpp>
13+
#include <boost/graph/graphviz.hpp>
14+
#include <boost/core/lightweight_test.hpp>
15+
#include <map>
16+
#include <set>
17+
#include <sstream>
18+
#include <string>
19+
#include <vector>
20+
21+
namespace
22+
{
23+
24+
struct vertex_props
25+
{
26+
std::string node_id;
27+
std::string label;
28+
};
29+
30+
struct edge_props
31+
{
32+
std::string elabel;
33+
};
34+
35+
using graph_t = boost::adjacency_list< boost::vecS, boost::vecS,
36+
boost::directedS, vertex_props, edge_props >;
37+
38+
// First four are valid unquoted DOT ids/numerals
39+
// the rest force quoting or escaping (space, embedded quote, digit-led identifier, multi-dot numeral).
40+
const std::vector< std::string >& boundary_labels()
41+
{
42+
static const std::vector< std::string > labels = { "plain", "id_123", "42",
43+
"-3.14", "has space", "has\"quote", "9lives", "1.2.3" };
44+
return labels;
45+
}
46+
47+
graph_t make_graph()
48+
{
49+
const std::vector< std::string >& labels = boundary_labels();
50+
graph_t g;
51+
std::vector< graph_t::vertex_descriptor > added;
52+
added.reserve(labels.size());
53+
for (std::size_t i = 0; i < labels.size(); ++i)
54+
{
55+
graph_t::vertex_descriptor vd = boost::add_vertex(g);
56+
g[vd].node_id = "n" + std::to_string(i);
57+
g[vd].label = labels[i];
58+
added.push_back(vd);
59+
}
60+
for (std::size_t i = 1; i < added.size(); ++i)
61+
{
62+
graph_t::edge_descriptor ed
63+
= boost::add_edge(added[i - 1], added[i], g).first;
64+
g[ed].elabel = "e " + std::to_string(i); // space forces quoting
65+
}
66+
return g;
67+
}
68+
69+
std::map< std::string, std::string > labels_by_id(const graph_t& g)
70+
{
71+
std::map< std::string, std::string > m;
72+
graph_t::vertex_iterator it, end;
73+
for (boost::tie(it, end) = boost::vertices(g); it != end; ++it)
74+
m[g[*it].node_id] = g[*it].label;
75+
return m;
76+
}
77+
78+
std::multiset< std::string > edges_repr(const graph_t& g)
79+
{
80+
std::multiset< std::string > s;
81+
graph_t::edge_iterator it, end;
82+
for (boost::tie(it, end) = boost::edges(g); it != end; ++it)
83+
{
84+
const std::string src = g[boost::source(*it, g)].node_id;
85+
const std::string tgt = g[boost::target(*it, g)].node_id;
86+
s.insert(src + "->" + tgt + ":" + g[*it].elabel);
87+
}
88+
return s;
89+
}
90+
91+
boost::dynamic_properties make_properties(graph_t& g)
92+
{
93+
boost::dynamic_properties dp;
94+
dp.property("node_id", boost::get(&vertex_props::node_id, g));
95+
dp.property("label", boost::get(&vertex_props::label, g));
96+
dp.property("elabel", boost::get(&edge_props::elabel, g));
97+
return dp;
98+
}
99+
100+
} // namespace
101+
102+
int main()
103+
{
104+
graph_t g = make_graph();
105+
boost::dynamic_properties dp = make_properties(g);
106+
107+
std::ostringstream out;
108+
boost::write_graphviz_dp(out, g, dp, std::string("node_id"));
109+
const std::string text = out.str();
110+
111+
graph_t g2;
112+
boost::dynamic_properties dp2 = make_properties(g2);
113+
const bool ok = boost::read_graphviz(text.begin(), text.end(), g2, dp2, "node_id");
114+
BOOST_TEST(ok);
115+
116+
BOOST_TEST(boost::num_vertices(g) == boost::num_vertices(g2));
117+
BOOST_TEST(boost::num_edges(g) == boost::num_edges(g2));
118+
BOOST_TEST(labels_by_id(g) == labels_by_id(g2));
119+
BOOST_TEST(edges_repr(g) == edges_repr(g2));
120+
121+
return boost::report_errors();
122+
}

0 commit comments

Comments
 (0)