rgl 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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