rgl 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,179 @@
1
+ require 'enumerator'
2
+
3
+ require 'rgl/adjacency'
4
+ require 'rgl/base'
5
+ require 'rgl/condensation'
6
+
7
+ module RGL
8
+ module Graph
9
+ # Returns an RGL::DirectedAdjacencyGraph which is the transitive closure of
10
+ # this graph. Meaning, for each path u -> ... -> v in this graph, the path
11
+ # is copied and the edge u -> v is added. This method supports working with
12
+ # cyclic graphs by ensuring that edges are created between every pair of
13
+ # vertices in the cycle, including self-referencing edges.
14
+ #
15
+ # This method should run in O(|V||E|) time, where |V| and |E| are the number
16
+ # of vertices and edges respectively.
17
+ #
18
+ # Raises RGL::NotDirectedError if run on an undirected graph.
19
+ def transitive_closure
20
+ raise NotDirectedError,
21
+ "transitive_closure only supported for directed graphs" unless directed?
22
+
23
+ # Compute a condensation graph in order to hide cycles.
24
+ cg = condensation_graph
25
+
26
+ # Use a depth first search to calculate the transitive closure over the
27
+ # condensation graph. This ensures that as we traverse up the graph we
28
+ # know the transitive closure of each subgraph rooted at each node
29
+ # starting at the leaves. Subsequent root nodes which consume these
30
+ # subgraphs by way of the nodes' immediate successors can then immediately
31
+ # add edges to the roots of the subgraphs and to every successor of those
32
+ # roots.
33
+ tc_cg = DirectedAdjacencyGraph.new
34
+ cg.depth_first_search do |v|
35
+ # For each vertex v, w, and x where the edges v -> w and w -> x exist in
36
+ # the source graph, add edges v -> w and v -> x to the target graph.
37
+ cg.each_adjacent(v) do |w|
38
+ tc_cg.add_edge(v, w)
39
+ tc_cg.each_adjacent(w) do |x|
40
+ tc_cg.add_edge(v, x)
41
+ end
42
+ end
43
+ # Ensure that a vertex with no in or out edges is added to the graph.
44
+ tc_cg.add_vertex(v)
45
+ end
46
+
47
+ # Expand the condensed transitive closure.
48
+ #
49
+ # For each trivial strongly connected component in the condensed graph,
50
+ # add the single node it contains to the new graph and add edges for each
51
+ # edge the node begins in the original graph.
52
+ # For each NON-trivial strongly connected component in the condensed
53
+ # graph, add each node it contains to the new graph and add edges to
54
+ # every node in the strongly connected component, including self
55
+ # referential edges. Then for each edge of the original graph from any
56
+ # of the contained nodes, add edges from each of the contained nodes to
57
+ # all the edge targets.
58
+ g = DirectedAdjacencyGraph.new
59
+ tc_cg.each_vertex do |scc|
60
+ scc.each do |v|
61
+ # Add edges between all members of non-trivial strongly connected
62
+ # components (size > 1) and ensure that self referential edges are
63
+ # added when necessary for trivial strongly connected components.
64
+ if scc.size > 1 || has_edge?(v, v) then
65
+ scc.each do |w|
66
+ g.add_edge(v, w)
67
+ end
68
+ end
69
+ # Ensure that a vertex with no in or out edges is added to the graph.
70
+ g.add_vertex(v)
71
+ end
72
+ # Add an edge from every member of a strongly connected component to
73
+ # every member of each strongly connected component to which the former
74
+ # points.
75
+ tc_cg.each_adjacent(scc) do |scc2|
76
+ scc.each do |v|
77
+ scc2.each do |w|
78
+ g.add_edge(v, w)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Finally, the transitive closure...
85
+ g
86
+ end
87
+
88
+ # Returns an RGL::DirectedAdjacencyGraph which is the transitive reduction
89
+ # of this graph. Meaning, that each edge u -> v is omitted if path
90
+ # u -> ... -> v exists. This method supports working with cyclic graphs;
91
+ # however, cycles are arbitrarily simplified which may lead to variant,
92
+ # although equally valid, results on equivalent graphs.
93
+ #
94
+ # This method should run in O(|V||E|) time, where |V| and |E| are the number
95
+ # of vertices and edges respectively.
96
+ #
97
+ # Raises RGL::NotDirectedError if run on an undirected graph.
98
+ def transitive_reduction
99
+ raise NotDirectedError,
100
+ "transitive_reduction only supported for directed graphs" unless directed?
101
+
102
+ # Compute a condensation graph in order to hide cycles.
103
+ cg = condensation_graph
104
+
105
+ # Use a depth first search to compute the transitive reduction over the
106
+ # condensed graph. This is similar to the computation of the transitive
107
+ # closure over the graph in that for any node of the graph all nodes
108
+ # reachable from the node are tracked. Using a depth first search ensures
109
+ # that all nodes reachable from a target node are known when considering
110
+ # whether or not to add an edge pointing to that target.
111
+ tr_cg = DirectedAdjacencyGraph.new
112
+ paths_from = {}
113
+ cg.depth_first_search do |v|
114
+ paths_from[v] = Set.new
115
+ cg.each_adjacent(v) do |w|
116
+ # Only add the edge v -> w if there is no other edge v -> x such that
117
+ # w is reachable from x. Make sure to completely skip the case where
118
+ # x == w.
119
+ unless Enumerable::Enumerator.new(cg, :each_adjacent, v).any? do |x|
120
+ x != w && paths_from[x].include?(w)
121
+ end then
122
+ tr_cg.add_edge(v, w)
123
+
124
+ # For each vertex v, track all nodes reachable from v by adding node
125
+ # w to the list as well as all the nodes readable from w.
126
+ paths_from[v] << w
127
+ paths_from[v].merge(paths_from[w])
128
+ end
129
+ end
130
+ # Ensure that a vertex with no in or out edges is added to the graph.
131
+ tr_cg.add_vertex(v)
132
+ end
133
+
134
+ # Expand the condensed transitive reduction.
135
+ #
136
+ # For each trivial strongly connected component in the condensed graph,
137
+ # add the single node it contains to the new graph and add edges for each
138
+ # edge the node begins in the original graph.
139
+ # For each NON-trivial strongly connected component in the condensed
140
+ # graph, add each node it contains to the new graph and add arbitrary
141
+ # edges between the nodes to form a simple cycle. Then for each strongly
142
+ # connected component adjacent to the current one, find and add the first
143
+ # edge which exists in the original graph, starts in the first strongly
144
+ # connected component, and ends in the second strongly connected
145
+ # component.
146
+ g = DirectedAdjacencyGraph.new
147
+ tr_cg.each_vertex do |scc|
148
+ # Make a cycle of the contents of non-trivial strongly connected
149
+ # components.
150
+ scc_arr = scc.to_a
151
+ if scc.size > 1 || has_edge?(scc_arr.first, scc_arr.first) then
152
+ 0.upto(scc_arr.size - 2) do |idx|
153
+ g.add_edge(scc_arr[idx], scc_arr[idx + 1])
154
+ end
155
+ g.add_edge(scc_arr.last, scc_arr.first)
156
+ end
157
+
158
+ # Choose a single edge between the members of two different strongly
159
+ # connected component to add to the graph.
160
+ edges = Enumerable::Enumerator.new(self, :each_edge)
161
+ tr_cg.each_adjacent(scc) do |scc2|
162
+ g.add_edge(
163
+ *edges.find do |v, w|
164
+ scc.member?(v) && scc2.member?(w)
165
+ end
166
+ )
167
+ end
168
+
169
+ # Ensure that a vertex with no in or out edges is added to the graph.
170
+ scc.each do |v|
171
+ g.add_vertex(v)
172
+ end
173
+ end
174
+
175
+ # Finally, the transitive reduction...
176
+ g
177
+ end
178
+ end # module Graph
179
+ end # module RGL
@@ -11,40 +11,56 @@ class TestGraph < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def setup
14
- @dg = DirectedAdjacencyGraph.new
14
+ @dg1 = DirectedAdjacencyGraph.new
15
15
  @edges = [[1,2],[2,3],[2,4],[4,5],[1,6],[6,4]]
16
- @edges.each do |(src,target)|
17
- @dg.add_edge(src, target)
16
+ @edges.each do |(src,target)|
17
+ @dg1.add_edge(src, target)
18
+ end
19
+ @loan_vertices = [7, 8, 9]
20
+ @loan_vertices.each do |vertex|
21
+ @dg1.add_vertex(vertex)
22
+ end
23
+
24
+ @dg2 = DirectedAdjacencyGraph[*@edges.flatten]
25
+ @loan_vertices.each do |vertex|
26
+ @dg2.add_vertex(vertex)
18
27
  end
19
28
 
20
29
  @ug = AdjacencyGraph.new(Array)
21
30
  @ug.add_edges(*@edges)
31
+ @ug.add_vertices(*@loan_vertices)
22
32
  end
23
33
 
24
34
  def test_equality
25
- assert @dg == @dg
26
- assert @dg == @dg.dup
27
- assert @ug == @ug.dup
28
- assert @ug != @dg
29
- assert @dg != @ug
30
- assert @dg != 42
31
- dup = DirectedAdjacencyGraph[*@edges.flatten]
32
- assert @dg == dup
33
- @dg.add_vertex 42
34
- assert @dg != dup
35
+ assert_equal @dg1, @dg1
36
+ assert_equal @dg1, @dg1.dup
37
+ assert_equal @ug, @ug.dup
38
+ assert_not_equal @ug, @dg1
39
+ assert_not_equal @dg1, @ug
40
+ assert_not_equal @dg1, 42
41
+ assert_equal @dg1, @dg2
42
+ @dg1.add_vertex 42
43
+ assert_not_equal @dg1, @dg2
44
+ end
45
+
46
+ def test_to_adjacency
47
+ assert_equal @dg1, @dg1.to_adjacency
48
+ assert_equal @ug, @ug.to_adjacency
35
49
  end
36
50
 
37
51
  def test_merge
38
- merge = DirectedAdjacencyGraph.new(Array, @dg, @ug)
39
- assert merge.num_edges == 12
40
- merge = DirectedAdjacencyGraph.new(Set, @dg, @dg)
41
- assert merge.num_edges == 6
52
+ merge = DirectedAdjacencyGraph.new(Array, @dg1, @ug)
53
+ assert_equal merge.num_edges, 12
54
+ assert_equal merge.num_vertices, 9
55
+ merge = DirectedAdjacencyGraph.new(Set, @dg1, @dg1)
56
+ assert_equal merge.num_edges, 6
57
+ assert_equal merge.num_vertices, 9
42
58
  end
43
59
 
44
60
  def test_set_edgelist_class
45
- edges = @dg.edges
46
- @dg.edgelist_class=Array
47
- assert_equal edges, @dg.edges
61
+ edges = @dg1.edges
62
+ @dg1.edgelist_class=Array
63
+ assert_equal edges, @dg1.edges
48
64
  end
49
65
 
50
66
  def test_not_implemented
@@ -1,7 +1,7 @@
1
1
  require 'test/unit'
2
2
  require 'rgl/rdot'
3
3
 
4
- include DOT
4
+ include RGL
5
5
 
6
6
  # Add some helper methods to TestCase
7
7
  class Test::Unit::TestCase
@@ -18,476 +18,500 @@ class Test::Unit::TestCase
18
18
 
19
19
  end
20
20
 
21
- # Tests for DOTPort
21
+ # Tests for DOT::Port
22
22
  class TestDotPort < Test::Unit::TestCase
23
23
  def test_name
24
- port = DOTPort.new()
24
+ port = DOT::Port.new()
25
25
  assert_equal('', port.to_s)
26
26
 
27
- port = DOTPort.new('test_name')
27
+ port = DOT::Port.new('test_name')
28
28
  assert_equal('<test_name>', port.to_s)
29
29
  end
30
30
 
31
31
  def test_label
32
- port = DOTPort.new(nil, 'test_label')
32
+ port = DOT::Port.new(nil, 'test_label')
33
33
  assert_equal('test_label', port.to_s)
34
34
  end
35
35
 
36
36
  def test_name_and_label
37
- port = DOTPort.new('test_name', 'test_label')
37
+ port = DOT::Port.new('test_name', 'test_label')
38
38
  assert_equal('<test_name> test_label', port.to_s)
39
39
  end
40
40
 
41
41
  def test_nested_ports
42
- port = DOTPort.new([DOTPort.new(nil, 'a'), DOTPort.new(nil, 'b')])
42
+ port = DOT::Port.new([DOT::Port.new(nil, 'a'), DOT::Port.new(nil, 'b')])
43
43
  assert_equal('{a | b}', port.to_s)
44
44
  end
45
45
 
46
46
  def test_name_label_and_nested_ports
47
- port = DOTPort.new('test_name', 'test_label')
48
- port.ports = [DOTPort.new(nil, 'a'), DOTPort.new(nil, 'b')]
47
+ port = DOT::Port.new('test_name', 'test_label')
48
+ port.ports = [DOT::Port.new(nil, 'a'), DOT::Port.new(nil, 'b')]
49
49
  assert_equal('{a | b}', port.to_s)
50
50
  end
51
51
  end
52
52
 
53
- # Tests for DOTNode
53
+ # Tests for DOT::Node
54
54
  class TestDotNode < Test::Unit::TestCase
55
55
 
56
56
  def test_no_name
57
- node = DOTNode.new()
57
+ node = DOT::Node.new()
58
58
  dot = node.to_s
59
59
  assert_nil(dot)
60
60
  end
61
61
 
62
62
  # bug 16125
63
63
  def test_1prop_0comma
64
- node = DOTNode.new({"label"=>"the_label"})
64
+ node = DOT::Node.new({"label"=>"the_label"})
65
65
  dot = node.to_s
66
66
  assert_no_match(dot, /,/)
67
67
  end
68
68
 
69
69
  def test_2prop_1comma
70
- node = DOTNode.new({"label"=>"the_label", "shape"=>"ellipse"})
70
+ node = DOT::Node.new({"label"=>"the_label", "shape"=>"ellipse"})
71
71
  dot = node.to_s
72
72
  assert_match(dot, /\[[^,]*,[^,]*\]/)
73
73
  end
74
74
 
75
75
  def test_name_without_label
76
- node = DOTNode.new({"name"=>"test_name"})
76
+ node = DOT::Node.new({"name"=>"test_name"})
77
77
  dot = node.to_s
78
78
  assert_no_match(dot, /label/)
79
79
  end
80
80
 
81
81
  def test_no_label
82
- node = DOTNode.new({"shape"=>"ellipse"})
82
+ node = DOT::Node.new({"shape"=>"ellipse"})
83
83
  dot = node.to_s
84
84
  assert_no_match(dot, /label/)
85
85
  end
86
86
 
87
87
  def test_Mrecord_no_label_no_ports
88
- node = DOTNode.new({"name" => "test_name", "shape"=>"Mrecord"})
88
+ node = DOT::Node.new({"name" => "test_name", "shape"=>"Mrecord"})
89
89
  dot = node.to_s
90
90
  assert_match(dot, /shape\s*=\s*Mrecord/)
91
91
  assert_no_match(dot, /label/)
92
92
  end
93
93
 
94
94
  def test_Mrecord_label_no_ports
95
- node = DOTNode.new({"name" => "test_name", "label" => "test_label", "shape"=>"Mrecord"})
95
+ node = DOT::Node.new({"name" => "test_name", "label" => "test_label", "shape"=>"Mrecord"})
96
96
  dot = node.to_s
97
97
  assert_match(dot, /shape\s*=\s*Mrecord/)
98
98
  assert_match(dot, /label\s*=\s*test_label/)
99
99
  end
100
100
 
101
101
  def test_Mrecord_label_with_ports
102
- node = DOTNode.new({"name" => "test_name", "label" => "test_label", "shape"=>"Mrecord"})
103
- node.ports << DOTPort.new(nil, "a")
104
- node.ports << DOTPort.new(nil, "b")
102
+ node = DOT::Node.new({"name" => "test_name", "label" => "test_label", "shape"=>"Mrecord"})
103
+ node.ports << DOT::Port.new(nil, "a")
104
+ node.ports << DOT::Port.new(nil, "b")
105
105
  dot = node.to_s
106
106
  assert_match(dot, /shape\s*=\s*Mrecord/)
107
107
  assert_match(dot, /label\s*=\s*"a\s*|\s*b"/)
108
108
  end
109
109
 
110
110
  def test_Mrecord_no_label_with_ports
111
- node = DOTNode.new({"name" => "test_name", "shape"=>"Mrecord"})
112
- node.ports << DOTPort.new(nil, "a")
113
- node.ports << DOTPort.new(nil, "b")
111
+ node = DOT::Node.new({"name" => "test_name", "shape"=>"Mrecord"})
112
+ node.ports << DOT::Port.new(nil, "a")
113
+ node.ports << DOT::Port.new(nil, "b")
114
114
  dot = node.to_s
115
115
  assert_match(dot, /shape\s*=\s*Mrecord/)
116
116
  assert_match(dot, /label\s*=\s*"a\s*|\s*b"/)
117
117
  end
118
118
 
119
119
  def test_record_no_label_no_ports
120
- node = DOTNode.new({"name" => "test_name", "shape"=>"record"})
120
+ node = DOT::Node.new({"name" => "test_name", "shape"=>"record"})
121
121
  dot = node.to_s
122
122
  assert_match(dot, /shape\s*=\s*record/)
123
123
  assert_no_match(dot, /label/)
124
124
  end
125
125
 
126
126
  def test_record_label_no_ports
127
- node = DOTNode.new({"name" => "test_name", "label" => "test_label", "shape"=>"record"})
127
+ node = DOT::Node.new({"name" => "test_name", "label" => "test_label", "shape"=>"record"})
128
128
  dot = node.to_s
129
129
  assert_match(dot, /shape\s*=\s*record/)
130
130
  assert_match(dot, /label\s*=\s*test_label/)
131
131
  end
132
132
 
133
133
  def test_record_label_with_ports
134
- node = DOTNode.new({"name" => "test_name", "label" => "test_label", "shape"=>"record"})
135
- node.ports << DOTPort.new(nil, "a")
136
- node.ports << DOTPort.new(nil, "b")
134
+ node = DOT::Node.new({"name" => "test_name", "label" => "test_label", "shape"=>"record"})
135
+ node.ports << DOT::Port.new(nil, "a")
136
+ node.ports << DOT::Port.new(nil, "b")
137
137
  dot = node.to_s
138
138
  assert_match(dot, /shape\s*=\s*record/)
139
139
  assert_match(dot, /label\s*=\s*"a\s*|\s*b"/)
140
140
  end
141
141
 
142
142
  def test_record_no_label_with_ports
143
- node = DOTNode.new({"name" => "test_name", "shape"=>"record"})
144
- node.ports << DOTPort.new(nil, "a")
145
- node.ports << DOTPort.new(nil, "b")
143
+ node = DOT::Node.new({"name" => "test_name", "shape"=>"record"})
144
+ node.ports << DOT::Port.new(nil, "a")
145
+ node.ports << DOT::Port.new(nil, "b")
146
146
  dot = node.to_s
147
147
  assert_match(dot, /shape\s*=\s*record/)
148
148
  assert_match(dot, /label\s*=\s*"a\s*|\s*b"/)
149
149
  end
150
150
 
151
151
  def test_no_shape_no_label_no_ports
152
- node = DOTNode.new({"name" => "test_name"})
153
- node.ports << DOTPort.new(nil, "a")
154
- node.ports << DOTPort.new(nil, "b")
152
+ node = DOT::Node.new({"name" => "test_name"})
153
+ node.ports << DOT::Port.new(nil, "a")
154
+ node.ports << DOT::Port.new(nil, "b")
155
155
  dot = node.to_s
156
156
  assert_no_match(dot, /shape\s*=\s/)
157
157
  assert_no_match(dot, /label\s*=\s*/)
158
158
  end
159
159
 
160
160
  def test_no_shape_no_label_with_ports
161
- node = DOTNode.new({"name" => "test_name"})
162
- node.ports << DOTPort.new(nil, "a")
163
- node.ports << DOTPort.new(nil, "b")
161
+ node = DOT::Node.new({"name" => "test_name"})
162
+ node.ports << DOT::Port.new(nil, "a")
163
+ node.ports << DOT::Port.new(nil, "b")
164
164
  dot = node.to_s
165
165
  assert_no_match(dot, /shape\s*=\s*record/)
166
166
  assert_no_match(dot, /label\s*=\s*/)
167
167
  end
168
168
 
169
169
  def test_name_quoting
170
- node = DOTNode.new({"name" => "Name with spaces"})
170
+ node = DOT::Node.new({"name" => "Name with spaces"})
171
171
  dot = node.to_s
172
172
  assert_match(dot, /^"Name with spaces"$/)
173
173
 
174
- node = DOTNode.new({"name" => "Name with \"quotes\""})
174
+ node = DOT::Node.new({"name" => "Name with \"quotes\""})
175
175
  dot = node.to_s
176
176
  assert_match(dot, /^"Name with \\"quotes\\""$/)
177
177
 
178
- node = DOTNode.new({"name" => "Name with \\backslashes\\"})
178
+ node = DOT::Node.new({"name" => "Name with \\backslashes\\"})
179
179
  dot = node.to_s
180
180
  assert_match(dot, /^"Name with \\\\backslashes\\\\"$/)
181
181
 
182
- node = DOTNode.new({"name" => "123.456"})
182
+ node = DOT::Node.new({"name" => "Name with\nembedded\nnewlines"})
183
+ dot = node.to_s
184
+ assert_match(dot, /\A.*"Name with\nembedded\nnewlines".*\Z/m)
185
+
186
+ node = DOT::Node.new({"name" => "Name_with_trailing_newline\n"})
187
+ dot = node.to_s
188
+ assert_match(dot, /\A.*"Name_with_trailing_newline\n".*\Z/m)
189
+
190
+ node = DOT::Node.new({"name" => "123.456"})
183
191
  dot = node.to_s
184
192
  assert_match(dot, /^123.456$/)
185
193
 
186
- node = DOTNode.new({"name" => ".456"})
194
+ node = DOT::Node.new({"name" => ".456"})
187
195
  dot = node.to_s
188
196
  assert_match(dot, /^.456$/)
189
197
 
190
- node = DOTNode.new({"name" => "-.456"})
198
+ node = DOT::Node.new({"name" => "-.456"})
191
199
  dot = node.to_s
192
200
  assert_match(dot, /^-.456$/)
193
201
 
194
- node = DOTNode.new({"name" => "-456"})
202
+ node = DOT::Node.new({"name" => "-456"})
195
203
  dot = node.to_s
196
204
  assert_match(dot, /^-456$/)
197
205
 
198
- node = DOTNode.new({"name" => "-123.456"})
206
+ node = DOT::Node.new({"name" => "-123.456"})
199
207
  dot = node.to_s
200
208
  assert_match(dot, /^-123.456$/)
201
209
 
202
- node = DOTNode.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
210
+ node = DOT::Node.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
203
211
  dot = node.to_s
204
212
  assert_match(dot, /^<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>$/)
205
213
  end
206
214
 
207
215
  def test_label_quoting
208
- node = DOTNode.new({"name" => "test_name", "label" => "Label with spaces"})
216
+ node = DOT::Node.new({"name" => "test_name", "label" => "Label with spaces"})
209
217
  dot = node.to_s
210
218
  assert_match(dot, /label\s*=\s*"Label with spaces"/)
211
219
 
212
- node = DOTNode.new({"name" => "test_name", "label" => "Label with \"quotes\""})
220
+ node = DOT::Node.new({"name" => "test_name", "label" => "Label with \"quotes\""})
213
221
  dot = node.to_s
214
222
  assert_match(dot, /label\s*=\s*"Label with \\"quotes\\""/)
215
223
 
216
- node = DOTNode.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
224
+ node = DOT::Node.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
217
225
  dot = node.to_s
218
226
  assert_match(dot, /label\s*=\s*"Label with \\\\backslashes\\\\"/)
219
227
 
220
- node = DOTNode.new({"name" => "test_name", "label" => "Label with\nembedded newline"})
228
+ node = DOT::Node.new({"name" => "test_name", "label" => "Label with\nembedded\nnewlines"})
229
+ dot = node.to_s
230
+ assert_match(dot, /label\s*=\s*"Label with\\nembedded\\nnewlines"/)
231
+
232
+ node = DOT::Node.new({"name" => "test_name", "label" => "Label_with_a_trailing_newline\n"})
221
233
  dot = node.to_s
222
- assert_match(dot, /label\s*=\s*"Label with\\nembedded newline"/)
234
+ assert_match(dot, /label\s*=\s*"Label_with_a_trailing_newline\\n"/)
223
235
 
224
- node = DOTNode.new({"name" => "test_name", "label" => "Left justified label\\l"})
236
+ node = DOT::Node.new({"name" => "test_name", "label" => "Left justified label\\l"})
225
237
  dot = node.to_s
226
238
  assert_match(dot, /label\s*=\s*"Left justified label\\l"/)
227
239
 
228
- node = DOTNode.new({"name" => "test_name", "label" => "Right justified label\\r"})
240
+ node = DOT::Node.new({"name" => "test_name", "label" => "Right justified label\\r"})
229
241
  dot = node.to_s
230
242
  assert_match(dot, /label\s*=\s*"Right justified label\\r"/)
231
243
 
232
- node = DOTNode.new({"name" => "test_name", "label" => "123.456"})
244
+ node = DOT::Node.new({"name" => "test_name", "label" => "123.456"})
233
245
  dot = node.to_s
234
246
  assert_match(dot, /label\s*=\s*123.456/)
235
247
 
236
- node = DOTNode.new({"name" => "test_name", "label" => ".456"})
248
+ node = DOT::Node.new({"name" => "test_name", "label" => ".456"})
237
249
  dot = node.to_s
238
250
  assert_match(dot, /label\s*=\s*.456/)
239
251
 
240
- node = DOTNode.new({"name" => "test_name", "label" => "-.456"})
252
+ node = DOT::Node.new({"name" => "test_name", "label" => "-.456"})
241
253
  dot = node.to_s
242
254
  assert_match(dot, /label\s*=\s*-.456/)
243
255
 
244
- node = DOTNode.new({"name" => "test_name", "label" => "-456"})
256
+ node = DOT::Node.new({"name" => "test_name", "label" => "-456"})
245
257
  dot = node.to_s
246
258
  assert_match(dot, /label\s*=\s*-456/)
247
259
 
248
- node = DOTNode.new({"name" => "test_name", "label" => "-123.456"})
260
+ node = DOT::Node.new({"name" => "test_name", "label" => "-123.456"})
249
261
  dot = node.to_s
250
262
  assert_match(dot, /label\s*=\s*-123.456/)
251
263
 
252
- node = DOTNode.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
264
+ node = DOT::Node.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
253
265
  dot = node.to_s
254
266
  assert_match(dot, /label\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
255
267
  end
256
268
 
257
269
  def test_option_quoting
258
- node = DOTNode.new({"name" => "test_name", "comment" => "Comment with spaces"})
270
+ node = DOT::Node.new({"name" => "test_name", "comment" => "Comment with spaces"})
259
271
  dot = node.to_s
260
272
  assert_match(dot, /comment\s*=\s*"Comment with spaces"/)
261
273
 
262
- node = DOTNode.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
274
+ node = DOT::Node.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
263
275
  dot = node.to_s
264
276
  assert_match(dot, /comment\s*=\s*"Comment with \\"quotes\\""/)
265
277
 
266
- node = DOTNode.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
278
+ node = DOT::Node.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
267
279
  dot = node.to_s
268
280
  assert_match(dot, /comment\s*=\s*"Comment with \\\\backslashes\\\\"/)
269
281
 
270
- node = DOTNode.new({"name" => "test_name", "comment" => "123.456"})
282
+ node = DOT::Node.new({"name" => "test_name", "comment" => "123.456"})
271
283
  dot = node.to_s
272
284
  assert_match(dot, /comment\s*=\s*123.456/)
273
285
 
274
- node = DOTNode.new({"name" => "test_name", "comment" => ".456"})
286
+ node = DOT::Node.new({"name" => "test_name", "comment" => ".456"})
275
287
  dot = node.to_s
276
288
  assert_match(dot, /comment\s*=\s*.456/)
277
289
 
278
- node = DOTNode.new({"name" => "test_name", "comment" => "-.456"})
290
+ node = DOT::Node.new({"name" => "test_name", "comment" => "-.456"})
279
291
  dot = node.to_s
280
292
  assert_match(dot, /comment\s*=\s*-.456/)
281
293
 
282
- node = DOTNode.new({"name" => "test_name", "comment" => "-456"})
294
+ node = DOT::Node.new({"name" => "test_name", "comment" => "-456"})
283
295
  dot = node.to_s
284
296
  assert_match(dot, /comment\s*=\s*-456/)
285
297
 
286
- node = DOTNode.new({"name" => "test_name", "comment" => "-123.456"})
298
+ node = DOT::Node.new({"name" => "test_name", "comment" => "-123.456"})
287
299
  dot = node.to_s
288
300
  assert_match(dot, /comment\s*=\s*-123.456/)
289
301
 
290
- node = DOTNode.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
302
+ node = DOT::Node.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
291
303
  dot = node.to_s
292
304
  assert_match(dot, /comment\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
293
305
  end
294
306
  end
295
307
 
296
- # Tests for DOTEdge
308
+ # Tests for DOT::Edge
297
309
  class TestDotEdge < Test::Unit::TestCase
298
310
 
299
311
  def test_0prop
300
- edge = DOTEdge.new({'from' => 'a', 'to' => 'b'})
312
+ edge = DOT::Edge.new({'from' => 'a', 'to' => 'b'})
301
313
  dot = edge.to_s
302
314
  assert_equal('a -- b', dot)
303
315
  end
304
316
 
305
317
  def test_1prop_0comma
306
- edge = DOTEdge.new({"label"=>"the_label"})
318
+ edge = DOT::Edge.new({"label"=>"the_label"})
307
319
  dot = edge.to_s
308
320
  assert_no_match(dot, /,/)
309
321
  end
310
322
 
311
323
  def test_2prop_1comma
312
- edge = DOTEdge.new({"label"=>"the_label", "weight"=>"2"})
324
+ edge = DOT::Edge.new({"label"=>"the_label", "weight"=>"2"})
313
325
  dot = edge.to_s
314
326
  assert_match(dot, /\[[^,]*,[^,]*\]/)
315
327
  end
316
328
 
317
329
  def test_no_label
318
- edge = DOTEdge.new({"weight"=>"2"})
330
+ edge = DOT::Edge.new({"weight"=>"2"})
319
331
  dot = edge.to_s
320
332
  assert_no_match(dot, /label/)
321
333
  end
322
334
  end
323
335
 
324
- # Tests for DOTDirectedEdge
336
+ # Tests for DOT::DirectedEdge
325
337
  class TestDotDirectedEdge < Test::Unit::TestCase
326
338
 
327
339
  def test_0prop
328
- edge = DOTDirectedEdge.new({'from' => 'a', 'to' => 'b'})
340
+ edge = DOT::DirectedEdge.new({'from' => 'a', 'to' => 'b'})
329
341
  dot = edge.to_s
330
342
  assert_equal('a -> b', dot)
331
343
  end
332
344
 
333
345
  def test_1prop_0comma
334
- edge = DOTDirectedEdge.new({"label"=>"the_label"})
346
+ edge = DOT::DirectedEdge.new({"label"=>"the_label"})
335
347
  dot = edge.to_s
336
348
  assert_no_match(dot, /,/)
337
349
  end
338
350
 
339
351
  def test_2prop_1comma
340
- edge = DOTDirectedEdge.new({"label"=>"the_label", "weight"=>"2"})
352
+ edge = DOT::DirectedEdge.new({"label"=>"the_label", "weight"=>"2"})
341
353
  dot = edge.to_s
342
354
  assert_match(dot, /\[[^,]*,[^,]*\]/)
343
355
  end
344
356
 
345
357
  def test_no_label
346
- edge = DOTDirectedEdge.new({"weight"=>"2"})
358
+ edge = DOT::DirectedEdge.new({"weight"=>"2"})
347
359
  dot = edge.to_s
348
360
  assert_no_match(dot, /label/)
349
361
  end
350
362
  end
351
363
 
352
- # Tests for DOTGraph
364
+ # Tests for DOT::Graph
353
365
  class TestDotGraph < Test::Unit::TestCase
354
366
  def test_graph_statement
355
- graph = DOTGraph.new()
367
+ graph = DOT::Graph.new()
356
368
  dot = graph.to_s
357
369
  assert_match(dot, /^\s*graph /)
358
370
  end
359
371
 
360
372
  def test_name_quoting
361
- node = DOTGraph.new({"name" => "Name with spaces"})
373
+ node = DOT::Graph.new({"name" => "Name with spaces"})
362
374
  dot = node.to_s
363
375
  assert_match(dot, /^graph "Name with spaces" \{$/)
364
376
 
365
- node = DOTGraph.new({"name" => "Name with \"quotes\""})
377
+ node = DOT::Graph.new({"name" => "Name with \"quotes\""})
366
378
  dot = node.to_s
367
379
  assert_match(dot, /^graph "Name with \\"quotes\\"" \{$/)
368
380
 
369
- node = DOTGraph.new({"name" => "Name with \\backslashes\\"})
381
+ node = DOT::Graph.new({"name" => "Name with \\backslashes\\"})
370
382
  dot = node.to_s
371
383
  assert_match(dot, /^graph "Name with \\\\backslashes\\\\" \{$/)
372
384
 
373
- node = DOTGraph.new({"name" => "123.456"})
385
+ node = DOT::Graph.new({"name" => "Name with\nembedded\nnewlines"})
386
+ dot = node.to_s
387
+ assert_match(dot, /\A.*"Name with\nembedded\nnewlines".*\Z/m)
388
+
389
+ node = DOT::Graph.new({"name" => "Name_with_trailing_newline\n"})
390
+ dot = node.to_s
391
+ assert_match(dot, /\A.*"Name_with_trailing_newline\n".*\Z/m)
392
+
393
+ node = DOT::Graph.new({"name" => "123.456"})
374
394
  dot = node.to_s
375
395
  assert_match(dot, /^graph 123.456 \{$/)
376
396
 
377
- node = DOTGraph.new({"name" => ".456"})
397
+ node = DOT::Graph.new({"name" => ".456"})
378
398
  dot = node.to_s
379
399
  assert_match(dot, /^graph .456 \{$/)
380
400
 
381
- node = DOTGraph.new({"name" => "-.456"})
401
+ node = DOT::Graph.new({"name" => "-.456"})
382
402
  dot = node.to_s
383
403
  assert_match(dot, /^graph -.456 \{$/)
384
404
 
385
- node = DOTGraph.new({"name" => "-456"})
405
+ node = DOT::Graph.new({"name" => "-456"})
386
406
  dot = node.to_s
387
407
  assert_match(dot, /^graph -456 \{$/)
388
408
 
389
- node = DOTGraph.new({"name" => "-123.456"})
409
+ node = DOT::Graph.new({"name" => "-123.456"})
390
410
  dot = node.to_s
391
411
  assert_match(dot, /^graph -123.456 \{$/)
392
412
 
393
- node = DOTGraph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
413
+ node = DOT::Graph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
394
414
  dot = node.to_s
395
415
  assert_match(dot, /^graph <html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html> \{$/)
396
416
  end
397
417
 
398
418
  def test_label_quoting
399
- node = DOTGraph.new({"name" => "test_name", "label" => "Label with spaces"})
419
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Label with spaces"})
400
420
  dot = node.to_s
401
421
  assert_match(dot, /label\s*=\s*"Label with spaces"/)
402
422
 
403
- node = DOTGraph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
423
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
404
424
  dot = node.to_s
405
425
  assert_match(dot, /label\s*=\s*"Label with \\"quotes\\""/)
406
426
 
407
- node = DOTGraph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
427
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
408
428
  dot = node.to_s
409
429
  assert_match(dot, /label\s*=\s*"Label with \\\\backslashes\\\\"/)
410
430
 
411
- node = DOTGraph.new({"name" => "test_name", "label" => "Label with\nembedded newline"})
431
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Label with\nembedded\nnewlines"})
432
+ dot = node.to_s
433
+ assert_match(dot, /label\s*=\s*"Label with\\nembedded\\nnewlines"/)
434
+
435
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Label_with_a_trailing_newline\n"})
412
436
  dot = node.to_s
413
- assert_match(dot, /label\s*=\s*"Label with\\nembedded newline"/)
437
+ assert_match(dot, /label\s*=\s*"Label_with_a_trailing_newline\\n"/)
414
438
 
415
- node = DOTGraph.new({"name" => "test_name", "label" => "Left justified label\\l"})
439
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Left justified label\\l"})
416
440
  dot = node.to_s
417
441
  assert_match(dot, /label\s*=\s*"Left justified label\\l"/)
418
442
 
419
- node = DOTGraph.new({"name" => "test_name", "label" => "Right justified label\\r"})
443
+ node = DOT::Graph.new({"name" => "test_name", "label" => "Right justified label\\r"})
420
444
  dot = node.to_s
421
445
  assert_match(dot, /label\s*=\s*"Right justified label\\r"/)
422
446
 
423
- node = DOTGraph.new({"name" => "test_name", "label" => "123.456"})
447
+ node = DOT::Graph.new({"name" => "test_name", "label" => "123.456"})
424
448
  dot = node.to_s
425
449
  assert_match(dot, /label\s*=\s*123.456/)
426
450
 
427
- node = DOTGraph.new({"name" => "test_name", "label" => ".456"})
451
+ node = DOT::Graph.new({"name" => "test_name", "label" => ".456"})
428
452
  dot = node.to_s
429
453
  assert_match(dot, /label\s*=\s*.456/)
430
454
 
431
- node = DOTGraph.new({"name" => "test_name", "label" => "-.456"})
455
+ node = DOT::Graph.new({"name" => "test_name", "label" => "-.456"})
432
456
  dot = node.to_s
433
457
  assert_match(dot, /label\s*=\s*-.456/)
434
458
 
435
- node = DOTGraph.new({"name" => "test_name", "label" => "-456"})
459
+ node = DOT::Graph.new({"name" => "test_name", "label" => "-456"})
436
460
  dot = node.to_s
437
461
  assert_match(dot, /label\s*=\s*-456/)
438
462
 
439
- node = DOTGraph.new({"name" => "test_name", "label" => "-123.456"})
463
+ node = DOT::Graph.new({"name" => "test_name", "label" => "-123.456"})
440
464
  dot = node.to_s
441
465
  assert_match(dot, /label\s*=\s*-123.456/)
442
466
 
443
- node = DOTGraph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
467
+ node = DOT::Graph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
444
468
  dot = node.to_s
445
469
  assert_match(dot, /label\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
446
470
  end
447
471
 
448
472
  def test_option_quoting
449
- node = DOTGraph.new({"name" => "test_name", "comment" => "Comment with spaces"})
473
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "Comment with spaces"})
450
474
  dot = node.to_s
451
475
  assert_match(dot, /comment\s*=\s*"Comment with spaces"/)
452
476
 
453
- node = DOTGraph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
477
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
454
478
  dot = node.to_s
455
479
  assert_match(dot, /comment\s*=\s*"Comment with \\"quotes\\""/)
456
480
 
457
- node = DOTGraph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
481
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
458
482
  dot = node.to_s
459
483
  assert_match(dot, /comment\s*=\s*"Comment with \\\\backslashes\\\\"/)
460
484
 
461
- node = DOTGraph.new({"name" => "test_name", "comment" => "123.456"})
485
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "123.456"})
462
486
  dot = node.to_s
463
487
  assert_match(dot, /comment\s*=\s*123.456/)
464
488
 
465
- node = DOTGraph.new({"name" => "test_name", "comment" => ".456"})
489
+ node = DOT::Graph.new({"name" => "test_name", "comment" => ".456"})
466
490
  dot = node.to_s
467
491
  assert_match(dot, /comment\s*=\s*.456/)
468
492
 
469
- node = DOTGraph.new({"name" => "test_name", "comment" => "-.456"})
493
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "-.456"})
470
494
  dot = node.to_s
471
495
  assert_match(dot, /comment\s*=\s*-.456/)
472
496
 
473
- node = DOTGraph.new({"name" => "test_name", "comment" => "-456"})
497
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "-456"})
474
498
  dot = node.to_s
475
499
  assert_match(dot, /comment\s*=\s*-456/)
476
500
 
477
- node = DOTGraph.new({"name" => "test_name", "comment" => "-123.456"})
501
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "-123.456"})
478
502
  dot = node.to_s
479
503
  assert_match(dot, /comment\s*=\s*-123.456/)
480
504
 
481
- node = DOTGraph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
505
+ node = DOT::Graph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
482
506
  dot = node.to_s
483
507
  assert_match(dot, /comment\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
484
508
  end
485
509
 
486
510
  def test_element_containment
487
- node1 = DOTNode.new('name' => 'test_node1')
488
- node2 = DOTNode.new('name' => 'test_node2')
511
+ node1 = DOT::Node.new('name' => 'test_node1')
512
+ node2 = DOT::Node.new('name' => 'test_node2')
489
513
 
490
- graph = DOTGraph.new('name' => 'test_graph')
514
+ graph = DOT::Graph.new('name' => 'test_graph')
491
515
  assert_nil(graph.pop)
492
516
  assert_equal(graph, graph.push(node1))
493
517
  assert_equal(graph, graph << node2)
@@ -498,151 +522,163 @@ class TestDotGraph < Test::Unit::TestCase
498
522
  assert_equal(node1, graph.pop)
499
523
  assert_nil(graph.pop)
500
524
 
501
- graph = DOTGraph.new('name' => 'test_graph', 'elements' => [node1])
525
+ graph = DOT::Graph.new('name' => 'test_graph', 'elements' => [node1])
502
526
  assert_equal(node1, graph.pop)
503
527
  assert_nil(graph.pop)
504
528
  end
505
529
  end
506
530
 
507
- # Tests for DOTDigraph
531
+ # Tests for DOT::Digraph
508
532
  class TestDotDigraph < Test::Unit::TestCase
509
533
  def test_digraph_statement
510
- digraph = DOTDigraph.new()
534
+ digraph = DOT::Digraph.new()
511
535
  dot = digraph.to_s
512
536
  assert_match(dot, /^\s*digraph /)
513
537
  end
514
538
 
515
539
  def test_name_quoting
516
- node = DOTDigraph.new({"name" => "Name with spaces"})
540
+ node = DOT::Digraph.new({"name" => "Name with spaces"})
517
541
  dot = node.to_s
518
542
  assert_match(dot, /^digraph "Name with spaces" \{$/)
519
543
 
520
- node = DOTDigraph.new({"name" => "Name with \"quotes\""})
544
+ node = DOT::Digraph.new({"name" => "Name with \"quotes\""})
521
545
  dot = node.to_s
522
546
  assert_match(dot, /^digraph "Name with \\"quotes\\"" \{$/)
523
547
 
524
- node = DOTDigraph.new({"name" => "Name with \\backslashes\\"})
548
+ node = DOT::Digraph.new({"name" => "Name with \\backslashes\\"})
525
549
  dot = node.to_s
526
550
  assert_match(dot, /^digraph "Name with \\\\backslashes\\\\" \{$/)
527
551
 
528
- node = DOTDigraph.new({"name" => "123.456"})
552
+ node = DOT::Digraph.new({"name" => "Name with\nembedded\nnewlines"})
553
+ dot = node.to_s
554
+ assert_match(dot, /\A.*"Name with\nembedded\nnewlines".*\Z/m)
555
+
556
+ node = DOT::Digraph.new({"name" => "Name_with_trailing_newline\n"})
557
+ dot = node.to_s
558
+ assert_match(dot, /\A.*"Name_with_trailing_newline\n".*\Z/m)
559
+
560
+ node = DOT::Digraph.new({"name" => "123.456"})
529
561
  dot = node.to_s
530
562
  assert_match(dot, /^digraph 123.456 \{$/)
531
563
 
532
- node = DOTDigraph.new({"name" => ".456"})
564
+ node = DOT::Digraph.new({"name" => ".456"})
533
565
  dot = node.to_s
534
566
  assert_match(dot, /^digraph .456 \{$/)
535
567
 
536
- node = DOTDigraph.new({"name" => "-.456"})
568
+ node = DOT::Digraph.new({"name" => "-.456"})
537
569
  dot = node.to_s
538
570
  assert_match(dot, /^digraph -.456 \{$/)
539
571
 
540
- node = DOTDigraph.new({"name" => "-456"})
572
+ node = DOT::Digraph.new({"name" => "-456"})
541
573
  dot = node.to_s
542
574
  assert_match(dot, /^digraph -456 \{$/)
543
575
 
544
- node = DOTDigraph.new({"name" => "-123.456"})
576
+ node = DOT::Digraph.new({"name" => "-123.456"})
545
577
  dot = node.to_s
546
578
  assert_match(dot, /^digraph -123.456 \{$/)
547
579
 
548
- node = DOTDigraph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
580
+ node = DOT::Digraph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
549
581
  dot = node.to_s
550
582
  assert_match(dot, /^digraph <html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html> \{$/)
551
583
  end
552
584
 
553
585
  def test_label_quoting
554
- node = DOTDigraph.new({"name" => "test_name", "label" => "Label with spaces"})
586
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Label with spaces"})
555
587
  dot = node.to_s
556
588
  assert_match(dot, /label\s*=\s*"Label with spaces"/)
557
589
 
558
- node = DOTDigraph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
590
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
559
591
  dot = node.to_s
560
592
  assert_match(dot, /label\s*=\s*"Label with \\"quotes\\""/)
561
593
 
562
- node = DOTDigraph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
594
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
563
595
  dot = node.to_s
564
596
  assert_match(dot, /label\s*=\s*"Label with \\\\backslashes\\\\"/)
565
597
 
566
- node = DOTDigraph.new({"name" => "test_name", "label" => "Label with\nembedded newline"})
598
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Label with\nembedded\nnewlines"})
599
+ dot = node.to_s
600
+ assert_match(dot, /label\s*=\s*"Label with\\nembedded\\nnewlines"/)
601
+
602
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Label_with_a_trailing_newline\n"})
567
603
  dot = node.to_s
568
- assert_match(dot, /label\s*=\s*"Label with\\nembedded newline"/)
604
+ assert_match(dot, /label\s*=\s*"Label_with_a_trailing_newline\\n"/)
569
605
 
570
- node = DOTDigraph.new({"name" => "test_name", "label" => "Left justified label\\l"})
606
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Left justified label\\l"})
571
607
  dot = node.to_s
572
608
  assert_match(dot, /label\s*=\s*"Left justified label\\l"/)
573
609
 
574
- node = DOTDigraph.new({"name" => "test_name", "label" => "Right justified label\\r"})
610
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "Right justified label\\r"})
575
611
  dot = node.to_s
576
612
  assert_match(dot, /label\s*=\s*"Right justified label\\r"/)
577
613
 
578
- node = DOTDigraph.new({"name" => "test_name", "label" => "123.456"})
614
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "123.456"})
579
615
  dot = node.to_s
580
616
  assert_match(dot, /label\s*=\s*123.456/)
581
617
 
582
- node = DOTDigraph.new({"name" => "test_name", "label" => ".456"})
618
+ node = DOT::Digraph.new({"name" => "test_name", "label" => ".456"})
583
619
  dot = node.to_s
584
620
  assert_match(dot, /label\s*=\s*.456/)
585
621
 
586
- node = DOTDigraph.new({"name" => "test_name", "label" => "-.456"})
622
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "-.456"})
587
623
  dot = node.to_s
588
624
  assert_match(dot, /label\s*=\s*-.456/)
589
625
 
590
- node = DOTDigraph.new({"name" => "test_name", "label" => "-456"})
626
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "-456"})
591
627
  dot = node.to_s
592
628
  assert_match(dot, /label\s*=\s*-456/)
593
629
 
594
- node = DOTDigraph.new({"name" => "test_name", "label" => "-123.456"})
630
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "-123.456"})
595
631
  dot = node.to_s
596
632
  assert_match(dot, /label\s*=\s*-123.456/)
597
633
 
598
- node = DOTDigraph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
634
+ node = DOT::Digraph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
599
635
  dot = node.to_s
600
636
  assert_match(dot, /label\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
601
637
  end
602
638
 
603
639
  def test_option_quoting
604
- node = DOTDigraph.new({"name" => "test_name", "comment" => "Comment with spaces"})
640
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "Comment with spaces"})
605
641
  dot = node.to_s
606
642
  assert_match(dot, /comment\s*=\s*"Comment with spaces"/)
607
643
 
608
- node = DOTDigraph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
644
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
609
645
  dot = node.to_s
610
646
  assert_match(dot, /comment\s*=\s*"Comment with \\"quotes\\""/)
611
647
 
612
- node = DOTDigraph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
648
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
613
649
  dot = node.to_s
614
650
  assert_match(dot, /comment\s*=\s*"Comment with \\\\backslashes\\\\"/)
615
651
 
616
- node = DOTDigraph.new({"name" => "test_name", "comment" => "123.456"})
652
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "123.456"})
617
653
  dot = node.to_s
618
654
  assert_match(dot, /comment\s*=\s*123.456/)
619
655
 
620
- node = DOTDigraph.new({"name" => "test_name", "comment" => ".456"})
656
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => ".456"})
621
657
  dot = node.to_s
622
658
  assert_match(dot, /comment\s*=\s*.456/)
623
659
 
624
- node = DOTDigraph.new({"name" => "test_name", "comment" => "-.456"})
660
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "-.456"})
625
661
  dot = node.to_s
626
662
  assert_match(dot, /comment\s*=\s*-.456/)
627
663
 
628
- node = DOTDigraph.new({"name" => "test_name", "comment" => "-456"})
664
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "-456"})
629
665
  dot = node.to_s
630
666
  assert_match(dot, /comment\s*=\s*-456/)
631
667
 
632
- node = DOTDigraph.new({"name" => "test_name", "comment" => "-123.456"})
668
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "-123.456"})
633
669
  dot = node.to_s
634
670
  assert_match(dot, /comment\s*=\s*-123.456/)
635
671
 
636
- node = DOTDigraph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
672
+ node = DOT::Digraph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
637
673
  dot = node.to_s
638
674
  assert_match(dot, /comment\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
639
675
  end
640
676
 
641
677
  def test_element_containment
642
- node1 = DOTNode.new('name' => 'test_node1')
643
- node2 = DOTNode.new('name' => 'test_node2')
678
+ node1 = DOT::Node.new('name' => 'test_node1')
679
+ node2 = DOT::Node.new('name' => 'test_node2')
644
680
 
645
- graph = DOTDigraph.new('name' => 'test_graph')
681
+ graph = DOT::Digraph.new('name' => 'test_graph')
646
682
  assert_nil(graph.pop)
647
683
  assert_equal(graph, graph.push(node1))
648
684
  assert_equal(graph, graph << node2)
@@ -653,151 +689,163 @@ class TestDotDigraph < Test::Unit::TestCase
653
689
  assert_equal(node1, graph.pop)
654
690
  assert_nil(graph.pop)
655
691
 
656
- graph = DOTDigraph.new('name' => 'test_graph', 'elements' => [node1])
692
+ graph = DOT::Digraph.new('name' => 'test_graph', 'elements' => [node1])
657
693
  assert_equal(node1, graph.pop)
658
694
  assert_nil(graph.pop)
659
695
  end
660
696
  end
661
697
 
662
- # Tests for DOTSubgraph
698
+ # Tests for DOT::Subgraph
663
699
  class TestDotSubgraph < Test::Unit::TestCase
664
700
  def test_subgraph_statement
665
- subgraph = DOTSubgraph.new()
701
+ subgraph = DOT::Subgraph.new()
666
702
  dot = subgraph.to_s
667
703
  assert_match(dot, /^\s*subgraph /)
668
704
  end
669
705
 
670
706
  def test_name_quoting
671
- node = DOTSubgraph.new({"name" => "Name with spaces"})
707
+ node = DOT::Subgraph.new({"name" => "Name with spaces"})
672
708
  dot = node.to_s
673
709
  assert_match(dot, /^subgraph "Name with spaces" \{$/)
674
710
 
675
- node = DOTSubgraph.new({"name" => "Name with \"quotes\""})
711
+ node = DOT::Subgraph.new({"name" => "Name with \"quotes\""})
676
712
  dot = node.to_s
677
713
  assert_match(dot, /^subgraph "Name with \\"quotes\\"" \{$/)
678
714
 
679
- node = DOTSubgraph.new({"name" => "Name with \\backslashes\\"})
715
+ node = DOT::Subgraph.new({"name" => "Name with \\backslashes\\"})
680
716
  dot = node.to_s
681
717
  assert_match(dot, /^subgraph "Name with \\\\backslashes\\\\" \{$/)
682
718
 
683
- node = DOTSubgraph.new({"name" => "123.456"})
719
+ node = DOT::Subgraph.new({"name" => "Name with\nembedded\nnewlines"})
720
+ dot = node.to_s
721
+ assert_match(dot, /\A.*"Name with\nembedded\nnewlines".*\Z/m)
722
+
723
+ node = DOT::Subgraph.new({"name" => "Name_with_trailing_newline\n"})
724
+ dot = node.to_s
725
+ assert_match(dot, /\A.*"Name_with_trailing_newline\n".*\Z/m)
726
+
727
+ node = DOT::Subgraph.new({"name" => "123.456"})
684
728
  dot = node.to_s
685
729
  assert_match(dot, /^subgraph 123.456 \{$/)
686
730
 
687
- node = DOTSubgraph.new({"name" => ".456"})
731
+ node = DOT::Subgraph.new({"name" => ".456"})
688
732
  dot = node.to_s
689
733
  assert_match(dot, /^subgraph .456 \{$/)
690
734
 
691
- node = DOTSubgraph.new({"name" => "-.456"})
735
+ node = DOT::Subgraph.new({"name" => "-.456"})
692
736
  dot = node.to_s
693
737
  assert_match(dot, /^subgraph -.456 \{$/)
694
738
 
695
- node = DOTSubgraph.new({"name" => "-456"})
739
+ node = DOT::Subgraph.new({"name" => "-456"})
696
740
  dot = node.to_s
697
741
  assert_match(dot, /^subgraph -456 \{$/)
698
742
 
699
- node = DOTSubgraph.new({"name" => "-123.456"})
743
+ node = DOT::Subgraph.new({"name" => "-123.456"})
700
744
  dot = node.to_s
701
745
  assert_match(dot, /^subgraph -123.456 \{$/)
702
746
 
703
- node = DOTSubgraph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
747
+ node = DOT::Subgraph.new({"name" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
704
748
  dot = node.to_s
705
749
  assert_match(dot, /^subgraph <html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html> \{$/)
706
750
  end
707
751
 
708
752
  def test_label_quoting
709
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Label with spaces"})
753
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Label with spaces"})
710
754
  dot = node.to_s
711
755
  assert_match(dot, /label\s*=\s*"Label with spaces"/)
712
756
 
713
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
757
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Label with \"quotes\""})
714
758
  dot = node.to_s
715
759
  assert_match(dot, /label\s*=\s*"Label with \\"quotes\\""/)
716
760
 
717
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
761
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Label with \\backslashes\\"})
718
762
  dot = node.to_s
719
763
  assert_match(dot, /label\s*=\s*"Label with \\\\backslashes\\\\"/)
720
764
 
721
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Label with\nembedded newline"})
765
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Label with\nembedded\nnewlines"})
766
+ dot = node.to_s
767
+ assert_match(dot, /label\s*=\s*"Label with\\nembedded\\nnewlines"/)
768
+
769
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Label_with_a_trailing_newline\n"})
722
770
  dot = node.to_s
723
- assert_match(dot, /label\s*=\s*"Label with\\nembedded newline"/)
771
+ assert_match(dot, /label\s*=\s*"Label_with_a_trailing_newline\\n"/)
724
772
 
725
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Left justified label\\l"})
773
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Left justified label\\l"})
726
774
  dot = node.to_s
727
775
  assert_match(dot, /label\s*=\s*"Left justified label\\l"/)
728
776
 
729
- node = DOTSubgraph.new({"name" => "test_name", "label" => "Right justified label\\r"})
777
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "Right justified label\\r"})
730
778
  dot = node.to_s
731
779
  assert_match(dot, /label\s*=\s*"Right justified label\\r"/)
732
780
 
733
- node = DOTSubgraph.new({"name" => "test_name", "label" => "123.456"})
781
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "123.456"})
734
782
  dot = node.to_s
735
783
  assert_match(dot, /label\s*=\s*123.456/)
736
784
 
737
- node = DOTSubgraph.new({"name" => "test_name", "label" => ".456"})
785
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => ".456"})
738
786
  dot = node.to_s
739
787
  assert_match(dot, /label\s*=\s*.456/)
740
788
 
741
- node = DOTSubgraph.new({"name" => "test_name", "label" => "-.456"})
789
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "-.456"})
742
790
  dot = node.to_s
743
791
  assert_match(dot, /label\s*=\s*-.456/)
744
792
 
745
- node = DOTSubgraph.new({"name" => "test_name", "label" => "-456"})
793
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "-456"})
746
794
  dot = node.to_s
747
795
  assert_match(dot, /label\s*=\s*-456/)
748
796
 
749
- node = DOTSubgraph.new({"name" => "test_name", "label" => "-123.456"})
797
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "-123.456"})
750
798
  dot = node.to_s
751
799
  assert_match(dot, /label\s*=\s*-123.456/)
752
800
 
753
- node = DOTSubgraph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
801
+ node = DOT::Subgraph.new({"name" => "test_name", "label" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
754
802
  dot = node.to_s
755
803
  assert_match(dot, /label\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
756
804
  end
757
805
 
758
806
  def test_option_quoting
759
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "Comment with spaces"})
807
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "Comment with spaces"})
760
808
  dot = node.to_s
761
809
  assert_match(dot, /comment\s*=\s*"Comment with spaces"/)
762
810
 
763
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
811
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "Comment with \"quotes\""})
764
812
  dot = node.to_s
765
813
  assert_match(dot, /comment\s*=\s*"Comment with \\"quotes\\""/)
766
814
 
767
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
815
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "Comment with \\backslashes\\"})
768
816
  dot = node.to_s
769
817
  assert_match(dot, /comment\s*=\s*"Comment with \\\\backslashes\\\\"/)
770
818
 
771
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "123.456"})
819
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "123.456"})
772
820
  dot = node.to_s
773
821
  assert_match(dot, /comment\s*=\s*123.456/)
774
822
 
775
- node = DOTSubgraph.new({"name" => "test_name", "comment" => ".456"})
823
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => ".456"})
776
824
  dot = node.to_s
777
825
  assert_match(dot, /comment\s*=\s*.456/)
778
826
 
779
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "-.456"})
827
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "-.456"})
780
828
  dot = node.to_s
781
829
  assert_match(dot, /comment\s*=\s*-.456/)
782
830
 
783
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "-456"})
831
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "-456"})
784
832
  dot = node.to_s
785
833
  assert_match(dot, /comment\s*=\s*-456/)
786
834
 
787
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "-123.456"})
835
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "-123.456"})
788
836
  dot = node.to_s
789
837
  assert_match(dot, /comment\s*=\s*-123.456/)
790
838
 
791
- node = DOTSubgraph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
839
+ node = DOT::Subgraph.new({"name" => "test_name", "comment" => "<html><head><title>test</title></head>\n<body>text</body></html>"})
792
840
  dot = node.to_s
793
841
  assert_match(dot, /comment\s*=\s*<html><head><title>test<\/title><\/head>\n<body>text<\/body><\/html>/)
794
842
  end
795
843
 
796
844
  def test_element_containment
797
- node1 = DOTNode.new('name' => 'test_node1')
798
- node2 = DOTNode.new('name' => 'test_node2')
845
+ node1 = DOT::Node.new('name' => 'test_node1')
846
+ node2 = DOT::Node.new('name' => 'test_node2')
799
847
 
800
- graph = DOTSubgraph.new('name' => 'test_graph')
848
+ graph = DOT::Subgraph.new('name' => 'test_graph')
801
849
  assert_nil(graph.pop)
802
850
  assert_equal(graph, graph.push(node1))
803
851
  assert_equal(graph, graph << node2)
@@ -808,7 +856,7 @@ class TestDotSubgraph < Test::Unit::TestCase
808
856
  assert_equal(node1, graph.pop)
809
857
  assert_nil(graph.pop)
810
858
 
811
- graph = DOTSubgraph.new('name' => 'test_graph', 'elements' => [node1])
859
+ graph = DOT::Subgraph.new('name' => 'test_graph', 'elements' => [node1])
812
860
  assert_equal(node1, graph.pop)
813
861
  assert_nil(graph.pop)
814
862
  end