seafoam 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/rubocop.yml +10 -0
  4. data/.github/workflows/specs.yml +19 -0
  5. data/.gitignore +7 -0
  6. data/.rubocop.yml +34 -0
  7. data/.ruby-version +1 -0
  8. data/.seafoam/config +1 -0
  9. data/CODE_OF_CONDUCT.md +128 -0
  10. data/CONTRIBUTING.md +5 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE.md +7 -0
  13. data/README.md +298 -0
  14. data/bin/bgv2isabelle +53 -0
  15. data/bin/bgv2json +42 -0
  16. data/bin/seafoam +24 -0
  17. data/docs/annotators.md +43 -0
  18. data/docs/bgv.md +284 -0
  19. data/docs/getting-graphs.md +47 -0
  20. data/examples/Fib.java +24 -0
  21. data/examples/MatMult.java +39 -0
  22. data/examples/fib-java.bgv +0 -0
  23. data/examples/fib-js.bgv +0 -0
  24. data/examples/fib-ruby.bgv +0 -0
  25. data/examples/fib.js +15 -0
  26. data/examples/fib.rb +15 -0
  27. data/examples/identity.bgv +0 -0
  28. data/examples/identity.rb +13 -0
  29. data/examples/java/Irreducible.j +35 -0
  30. data/examples/java/IrreducibleDecompiled.java +21 -0
  31. data/examples/java/JavaExamples.java +418 -0
  32. data/examples/java/exampleArithOperator.bgv +0 -0
  33. data/examples/java/exampleArithOperator.cfg +925 -0
  34. data/examples/java/exampleArrayAllocation.bgv +0 -0
  35. data/examples/java/exampleArrayAllocation.cfg +5268 -0
  36. data/examples/java/exampleArrayRead.bgv +0 -0
  37. data/examples/java/exampleArrayRead.cfg +2263 -0
  38. data/examples/java/exampleArrayWrite.bgv +0 -0
  39. data/examples/java/exampleArrayWrite.cfg +2315 -0
  40. data/examples/java/exampleCatch.bgv +0 -0
  41. data/examples/java/exampleCatch.cfg +4150 -0
  42. data/examples/java/exampleCompareOperator.bgv +0 -0
  43. data/examples/java/exampleCompareOperator.cfg +1109 -0
  44. data/examples/java/exampleDoubleSynchronized.bgv +0 -0
  45. data/examples/java/exampleDoubleSynchronized.cfg +26497 -0
  46. data/examples/java/exampleExactArith.bgv +0 -0
  47. data/examples/java/exampleExactArith.cfg +1888 -0
  48. data/examples/java/exampleFieldRead.bgv +0 -0
  49. data/examples/java/exampleFieldRead.cfg +1228 -0
  50. data/examples/java/exampleFieldWrite.bgv +0 -0
  51. data/examples/java/exampleFieldWrite.cfg +1102 -0
  52. data/examples/java/exampleFor.bgv +0 -0
  53. data/examples/java/exampleFor.cfg +3936 -0
  54. data/examples/java/exampleFullEscape.bgv +0 -0
  55. data/examples/java/exampleFullEscape.cfg +5893 -0
  56. data/examples/java/exampleIf.bgv +0 -0
  57. data/examples/java/exampleIf.cfg +2462 -0
  58. data/examples/java/exampleIfNeverTaken.bgv +0 -0
  59. data/examples/java/exampleIfNeverTaken.cfg +2476 -0
  60. data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
  61. data/examples/java/exampleInstanceOfManyImpls.cfg +6391 -0
  62. data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
  63. data/examples/java/exampleInstanceOfOneImpl.cfg +2604 -0
  64. data/examples/java/exampleIntSwitch.bgv +0 -0
  65. data/examples/java/exampleIntSwitch.cfg +3121 -0
  66. data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
  67. data/examples/java/exampleInterfaceCallManyImpls.cfg +1358 -0
  68. data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
  69. data/examples/java/exampleInterfaceCallOneImpl.cfg +3859 -0
  70. data/examples/java/exampleLocalInstanceOf.bgv +0 -0
  71. data/examples/java/exampleLocalInstanceOf.cfg +5276 -0
  72. data/examples/java/exampleLocalSynchronized.bgv +0 -0
  73. data/examples/java/exampleLocalSynchronized.cfg +1364 -0
  74. data/examples/java/exampleLocalVariables.bgv +0 -0
  75. data/examples/java/exampleLocalVariables.cfg +1195 -0
  76. data/examples/java/exampleLocalVariablesState.bgv +0 -0
  77. data/examples/java/exampleLocalVariablesState.cfg +1673 -0
  78. data/examples/java/exampleNestedWhile.bgv +0 -0
  79. data/examples/java/exampleNestedWhile.cfg +15499 -0
  80. data/examples/java/exampleNestedWhileBreak.bgv +0 -0
  81. data/examples/java/exampleNestedWhileBreak.cfg +11162 -0
  82. data/examples/java/exampleNoEscape.bgv +0 -0
  83. data/examples/java/exampleNoEscape.cfg +974 -0
  84. data/examples/java/exampleObjectAllocation.bgv +0 -0
  85. data/examples/java/exampleObjectAllocation.cfg +5287 -0
  86. data/examples/java/examplePartialEscape.bgv +0 -0
  87. data/examples/java/examplePartialEscape.cfg +7042 -0
  88. data/examples/java/examplePhi.bgv +0 -0
  89. data/examples/java/examplePhi.cfg +3227 -0
  90. data/examples/java/exampleReducible.bgv +0 -0
  91. data/examples/java/exampleReducible.cfg +5578 -0
  92. data/examples/java/exampleSimpleCall.bgv +0 -0
  93. data/examples/java/exampleSimpleCall.cfg +1435 -0
  94. data/examples/java/exampleStamp.bgv +0 -0
  95. data/examples/java/exampleStamp.cfg +913 -0
  96. data/examples/java/exampleStaticCall.bgv +0 -0
  97. data/examples/java/exampleStaticCall.cfg +1154 -0
  98. data/examples/java/exampleStringSwitch.bgv +0 -0
  99. data/examples/java/exampleStringSwitch.cfg +15377 -0
  100. data/examples/java/exampleSynchronized.bgv +0 -0
  101. data/examples/java/exampleSynchronized.cfg +26027 -0
  102. data/examples/java/exampleThrow.bgv +0 -0
  103. data/examples/java/exampleThrow.cfg +780 -0
  104. data/examples/java/exampleThrowCatch.bgv +0 -0
  105. data/examples/java/exampleThrowCatch.cfg +744 -0
  106. data/examples/java/exampleUnsafeRead.bgv +0 -0
  107. data/examples/java/exampleUnsafeRead.cfg +912 -0
  108. data/examples/java/exampleUnsafeWrite.bgv +0 -0
  109. data/examples/java/exampleUnsafeWrite.cfg +962 -0
  110. data/examples/java/exampleWhile.bgv +0 -0
  111. data/examples/java/exampleWhile.cfg +3936 -0
  112. data/examples/java/exampleWhileBreak.bgv +0 -0
  113. data/examples/java/exampleWhileBreak.cfg +5963 -0
  114. data/examples/matmult-java.bgv +0 -0
  115. data/examples/matmult-ruby.bgv +0 -0
  116. data/examples/matmult.rb +29 -0
  117. data/examples/overflow.bgv +0 -0
  118. data/examples/overflow.rb +13 -0
  119. data/lib/seafoam.rb +13 -0
  120. data/lib/seafoam/annotators.rb +54 -0
  121. data/lib/seafoam/annotators/fallback.rb +27 -0
  122. data/lib/seafoam/annotators/graal.rb +376 -0
  123. data/lib/seafoam/bgv/bgv_parser.rb +602 -0
  124. data/lib/seafoam/binary/binary_reader.rb +21 -0
  125. data/lib/seafoam/binary/io_binary_reader.rb +88 -0
  126. data/lib/seafoam/colors.rb +18 -0
  127. data/lib/seafoam/commands.rb +447 -0
  128. data/lib/seafoam/config.rb +34 -0
  129. data/lib/seafoam/graph.rb +91 -0
  130. data/lib/seafoam/graphviz_writer.rb +213 -0
  131. data/lib/seafoam/spotlight.rb +28 -0
  132. data/lib/seafoam/version.rb +5 -0
  133. data/seafoam.gemspec +20 -0
  134. data/spec/seafoam/annotators/fallback_spec.rb +69 -0
  135. data/spec/seafoam/annotators/graal_spec.rb +96 -0
  136. data/spec/seafoam/annotators_spec.rb +61 -0
  137. data/spec/seafoam/bgv/bgv_parser_spec.rb +144 -0
  138. data/spec/seafoam/bgv/fixtures/not.bgv +1 -0
  139. data/spec/seafoam/bgv/fixtures/unsupported.bgv +1 -0
  140. data/spec/seafoam/binary/io_binary_reader_spec.rb +176 -0
  141. data/spec/seafoam/command_spec.rb +252 -0
  142. data/spec/seafoam/graph_spec.rb +172 -0
  143. data/spec/seafoam/graphviz_writer_spec.rb +63 -0
  144. data/spec/seafoam/spec_helpers.rb +30 -0
  145. data/spec/seafoam/spotlight_spec.rb +38 -0
  146. data/tools/render-all +36 -0
  147. metadata +238 -0
@@ -0,0 +1,34 @@
1
+ module Seafoam
2
+ # Finds and loads configuration.
3
+ class Config
4
+ def initialize
5
+ @dot_dir = find_dot_dir
6
+ end
7
+
8
+ # Load the configuration.
9
+ def load_config
10
+ config_file = File.expand_path('config', @dot_dir)
11
+ if File.exist?(config_file)
12
+ puts "loading config #{config_file}" if $DEBUG
13
+ load config_file
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Walk up the directory chain from the current directory to root, looking
20
+ # for .seafoam.
21
+ def find_dot_dir
22
+ dir = Dir.getwd
23
+ loop do
24
+ dot_dir = File.expand_path('.seafoam', dir)
25
+ return dot_dir if Dir.exist?(dot_dir)
26
+
27
+ new_dir = File.expand_path('..', dir)
28
+ break if new_dir == dir
29
+
30
+ dir = new_dir
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,91 @@
1
+ module Seafoam
2
+ # A graph, with properties, nodes, and edges. We don't encapsulate the graph
3
+ # too much - be careful.
4
+ class Graph
5
+ attr_reader :props, :nodes, :edges
6
+
7
+ def initialize(props = nil)
8
+ @props = props || {}
9
+ @nodes = {}
10
+ @edges = []
11
+ end
12
+
13
+ # Create a node.
14
+ def create_node(id, props = nil)
15
+ props ||= {}
16
+ node = Node.new(id, props)
17
+ @nodes[id] = node
18
+ node
19
+ end
20
+
21
+ # Create an edge between two nodes.
22
+ def create_edge(from, to, props = nil)
23
+ props ||= {}
24
+ edge = Edge.new(from, to, props)
25
+ @edges.push edge
26
+ from.outputs.push edge
27
+ to.inputs.push edge
28
+ edge
29
+ end
30
+ end
31
+
32
+ # A node, with properties, input edges, and output edges.
33
+ class Node
34
+ attr_reader :id, :inputs, :outputs, :props
35
+
36
+ def initialize(id, props = nil)
37
+ props ||= {}
38
+ @id = id
39
+ @inputs = []
40
+ @outputs = []
41
+ @props = props
42
+ end
43
+
44
+ # All edges - input and output.
45
+ def edges
46
+ inputs + outputs
47
+ end
48
+
49
+ # All adjacent nodes - from input and output edges.
50
+ def adjacent
51
+ (inputs.map(&:from) + outputs.map(&:to)).uniq
52
+ end
53
+
54
+ # id (label)
55
+ def id_and_label
56
+ if props[:label]
57
+ "#{id} (#{props[:label]})"
58
+ else
59
+ id.to_s
60
+ end
61
+ end
62
+
63
+ # Inspect.
64
+ def inspect
65
+ "<Node #{id}>"
66
+ end
67
+ end
68
+
69
+ # A directed edge, with a node it's from and a node it's going to, and
70
+ # properties.
71
+ class Edge
72
+ attr_reader :from, :to, :props
73
+
74
+ def initialize(from, to, props = nil)
75
+ props ||= {}
76
+ @from = from
77
+ @to = to
78
+ @props = props
79
+ end
80
+
81
+ # Both nodes - from and to.
82
+ def nodes
83
+ [@from, @to]
84
+ end
85
+
86
+ # Inspect.
87
+ def inspect
88
+ "<Edge #{from.id} -> #{to.id}>"
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,213 @@
1
+ module Seafoam
2
+ # A writer from graphs to the Graphviz DOT format, including all the
3
+ # formatting.
4
+ class GraphvizWriter
5
+ def initialize(stream)
6
+ @stream = stream
7
+ end
8
+
9
+ # Write a graph.
10
+ def write_graph(graph, hidpi = false)
11
+ inline_attrs = {}
12
+ attrs = {}
13
+ attrs[:dpi] = 200 if hidpi
14
+ attrs[:bgcolor] = 'transparent'
15
+ @stream.puts 'digraph G {'
16
+ @stream.puts " graph #{write_attrs(attrs)};"
17
+ write_nodes inline_attrs, graph
18
+ write_edges inline_attrs, graph
19
+ @stream.puts '}'
20
+ end
21
+
22
+ private
23
+
24
+ # Write node declarations.
25
+ def write_nodes(inline_attrs, graph)
26
+ graph.nodes.each_value do |node|
27
+ write_node inline_attrs, node
28
+ end
29
+ end
30
+
31
+ def write_node(inline_attrs, node)
32
+ # We're going to build up a hash of Graphviz drawing attributes.
33
+ attrs = {}
34
+
35
+ # The node is hidden, and it's not going to be inlined above any
36
+ # other node.
37
+ if node.props[:hidden] && !node.props[:inlined]
38
+ # If the node has any adjacent nodes that are not hidden, and are
39
+ # shaded, then we need to declare the node but make it invisible so
40
+ # the edge appears, pointing off into space, but the node does not.
41
+ if node.adjacent.any? { |a| !a.props[:hidden] && a.props[:spotlight] == 'shaded' }
42
+ attrs[:style] = 'invis'
43
+ attrs[:label] = ''
44
+ @stream.puts " node#{node.id} #{write_attrs(attrs)};"
45
+ end
46
+ else
47
+ # This is a visible node.
48
+
49
+ # Give it a label.
50
+ if node.props[:label]
51
+ attrs[:label] = "#{node.id} #{node.props[:label]}"
52
+ else
53
+ # If we really still don't have a label, just use the ID.
54
+ attrs[:label] = node.id.to_s
55
+ end
56
+
57
+ # Basic attributes for a node.
58
+ attrs[:shape] = 'rectangle'
59
+ attrs[:fontname] = 'Arial'
60
+ attrs[:style] = 'filled'
61
+ attrs[:color] = 'black'
62
+
63
+ # Color depends on the kind of node.
64
+ back_color, fore_color = NODE_COLORS[node.props[:kind]]
65
+ attrs[:fillcolor] = back_color
66
+ attrs[:fontcolor] = fore_color
67
+
68
+ if node.props[:inlined]
69
+ # If the node is to be inlined then draw it smaller and a different
70
+ # shape.
71
+ attrs[:shape] = 'oval'
72
+ attrs[:fontsize] = '8'
73
+
74
+ # Just record these attributes for where it's used by other nodes
75
+ # so it can be drawn above them - don't actually declare a node.
76
+ inline_attrs[node.id] = attrs
77
+ else
78
+ attrs[:shape] = 'diamond' if node.props[:kind] == 'calc'
79
+
80
+ # If the node is shaded, convert the attributes to the shaded
81
+ # version.
82
+ attrs = shade(attrs) if node.props[:spotlight] == 'shaded'
83
+
84
+ # Declare the node.
85
+ @stream.puts " node#{node.id} #{write_attrs(attrs)};"
86
+ end
87
+ end
88
+ end
89
+
90
+ # Write edge declarations.
91
+
92
+ def write_edges(inline_attrs, graph)
93
+ graph.edges.each do |edge|
94
+ # Skip the edge if it's from a node that is hidden and it doesn't point
95
+ # to a shaded node.
96
+ next if edge.from.props[:hidden] && edge.to.props[:spotlight] != 'shaded'
97
+
98
+ # Skip the edge if it's to a node that is hidden and it doesn't come
99
+ # from a shaded node.
100
+ next if edge.to.props[:hidden] && edge.from.props[:spotlight] != 'shaded'
101
+
102
+ write_edge inline_attrs, edge
103
+ end
104
+ end
105
+
106
+ def write_edge(inline_attrs, edge)
107
+ # We're going to build up a hash of Graphviz drawing attributes.
108
+ attrs = {}
109
+
110
+ label = edge.props[:label]
111
+ if edge.from.props[:out_annotation]
112
+ label = "#{label} #{edge.from.props[:out_annotation]}"
113
+ end
114
+ attrs[:label] = label
115
+
116
+ # Basic edge attributes.
117
+ attrs[:fontname] = 'arial'
118
+ color = EDGE_COLORS[edge.props[:kind]]
119
+ attrs[:color] = EDGE_COLORS[edge.props[:kind]]
120
+ attrs[:fontcolor] = color
121
+
122
+ # Properties depending on the kind of edge.
123
+ case edge.props[:kind]
124
+ when 'control'
125
+ attrs[:penwidth] = 2
126
+ when 'loop'
127
+ attrs[:penwidth] = 4
128
+ when 'info'
129
+ attrs[:style] = 'dashed'
130
+ end
131
+
132
+ # Reversed edges.
133
+ attrs[:dir] = 'back' if edge.props[:reverse]
134
+
135
+ # Convert attributes to shaded if any edges involved are shaded.
136
+ attrs = shade(attrs) if edge.nodes.any? { |n| n.props[:spotlight] == 'shaded' }
137
+
138
+ # Does this edge come from an inlined node?
139
+
140
+ if edge.from.props[:inlined]
141
+ # An inlined edge is drawn as a new version of the from-node and an
142
+ # edge from that new version directly to the to-node. With only one
143
+ # user it's a short edge and the from-node is show directly above the
144
+ # to-node.
145
+
146
+ if edge.to.props[:spotlight] == 'shaded'
147
+ # Draw inlined edges to shaded nodes as invisible.
148
+ node_attrs = { label: '', style: 'invis' }
149
+ else
150
+ # Get attributes from when we went through nodes earlier.
151
+ node_attrs = inline_attrs[edge.from.id]
152
+ end
153
+
154
+ # Inlined nodes skip the arrow for simplicity.
155
+ attrs[:arrowhead] = 'none'
156
+ attrs[:fontsize] = '8'
157
+
158
+ # Declare a new node just for this user.
159
+ @stream.puts " inline#{edge.from.id}x#{edge.to.id} #{write_attrs(node_attrs)};"
160
+
161
+ # Declare the edge.
162
+ @stream.puts " inline#{edge.from.id}x#{edge.to.id} -> node#{edge.to.id} #{write_attrs(attrs)};"
163
+ else
164
+ # Declare the edge.
165
+ @stream.puts " node#{edge.from.id} -> node#{edge.to.id} #{write_attrs(attrs)};"
166
+ end
167
+ end
168
+
169
+ # Return attributes for a node or edge modified to 'shade' them in terms
170
+ # the spotlight functionality - so basically make them light grey.
171
+ def shade(attrs)
172
+ attrs = attrs.dup
173
+ attrs[:color] = ICE_STONE
174
+ attrs[:fontcolor] = ICE_STONE
175
+ attrs[:fillcolor] = DUST
176
+ attrs
177
+ end
178
+
179
+ # Write a hash of key-value attributes into the DOT format.
180
+ def write_attrs(attrs)
181
+ '[' + attrs.reject { |_k, v| v.nil? }.map { |k, v| "#{k}=#{quote(v)}" }.join(',') + ']'
182
+ end
183
+
184
+ # Quote and escape a string.
185
+ def quote(string)
186
+ string = string.to_s
187
+ string = string.gsub('\\', '\\\\')
188
+ string = string.gsub('"', '\\"')
189
+ "\"#{string}\""
190
+ end
191
+
192
+ # Color theme.
193
+
194
+ EDGE_COLORS = {
195
+ 'info' => BIG_STONE,
196
+ 'control' => AMARANTH,
197
+ 'loop' => AMARANTH,
198
+ 'data' => KEPPEL,
199
+ 'other' => BLACK
200
+ }
201
+
202
+ NODE_COLORS = {
203
+ 'info' => [DUST, BLACK],
204
+ 'input' => [WHITE_ICE, BLACK],
205
+ 'control' => [CARISSMA, BLACK],
206
+ 'effect' => [AMARANTH, WHITE],
207
+ 'virtual' => [BIG_STONE, WHITE],
208
+ 'guard' => [ORANGE, BLACK],
209
+ 'calc' => [KEPPEL, BLACK],
210
+ 'other' => [DUST, BLACK]
211
+ }
212
+ end
213
+ end
@@ -0,0 +1,28 @@
1
+ module Seafoam
2
+ # Spotlight can *light* nodes, which makes them visible, their adjacent nodes
3
+ # visible by grey, and other nodes invisible. Multiple nodes can be *lit*.
4
+ class Spotlight
5
+ def initialize(graph)
6
+ @graph = graph
7
+ end
8
+
9
+ # Mark a node as lit by the spotlight.
10
+ def light(node)
11
+ # This node is lit.
12
+ node.props[:spotlight] = 'lit'
13
+
14
+ # Adjacent nodes are shaded, if they haven't be lit themselvs.
15
+ node.adjacent.each do |adjacent|
16
+ adjacent.props[:spotlight] ||= 'shaded'
17
+ end
18
+ end
19
+
20
+ # Go through all other nodes and make them hidden, having lit as many nodes
21
+ # as you want.
22
+ def shade
23
+ @graph.nodes.each_value do |node|
24
+ node.props[:hidden] = true unless node.props[:spotlight]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module Seafoam
2
+ MAJOR_VERSION = 0
3
+ MINOR_VERSION = 2
4
+ VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
5
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'lib/seafoam/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'seafoam'
5
+ spec.version = Seafoam::VERSION
6
+ spec.summary = 'A tool for working with compiler graphs'
7
+ spec.authors = ['Chris Seaton']
8
+ spec.homepage = 'https://github.com/Shopify/seafoam'
9
+ spec.license = 'MIT'
10
+
11
+ spec.files = `git ls-files`.split("\n")
12
+ spec.bindir = 'bin'
13
+ spec.executables = %w[seafoam bgv2json bgv2isabelle]
14
+
15
+ spec.required_ruby_version = '>= 2.5.8'
16
+
17
+ spec.add_development_dependency 'benchmark-ips', '~> 2.7'
18
+ spec.add_development_dependency 'rspec', '~> 3.8'
19
+ spec.add_development_dependency 'rubocop', '~> 0.74'
20
+ end
@@ -0,0 +1,69 @@
1
+ require 'seafoam'
2
+
3
+ require 'rspec'
4
+
5
+ describe Seafoam::Annotators::FallbackAnnotator do
6
+ it 'always applies' do
7
+ expect(Seafoam::Annotators::FallbackAnnotator.applies?(Seafoam::Graph.new)).to be true
8
+ end
9
+
10
+ it 'adds a label annotation when there is a label property' do
11
+ graph = Seafoam::Graph.new
12
+ node = graph.create_node(0, 'label' => 'foo')
13
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
14
+ annotator.annotate graph
15
+ expect(node.props[:label]).to eq 'foo'
16
+ end
17
+
18
+ it 'does not overwrite an existing label annotation' do
19
+ graph = Seafoam::Graph.new
20
+ node = graph.create_node(0, 'label' => 'foo', :label => 'bar')
21
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
22
+ annotator.annotate graph
23
+ expect(node.props[:label]).to eq 'bar'
24
+ end
25
+
26
+ it 'adds nothing when there is no label property' do
27
+ graph = Seafoam::Graph.new
28
+ node = graph.create_node(0, 'xlabel' => 'foo')
29
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
30
+ annotator.annotate graph
31
+ expect(node.props[:label]).to be nil
32
+ end
33
+
34
+ it 'sets node kind to other when there is no kind' do
35
+ graph = Seafoam::Graph.new
36
+ node = graph.create_node(0)
37
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
38
+ annotator.annotate graph
39
+ expect(node.props[:kind]).to eq 'other'
40
+ end
41
+
42
+ it 'does not overwrite an existing node kind annotations' do
43
+ graph = Seafoam::Graph.new
44
+ node = graph.create_node(0, kind: 'control')
45
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
46
+ annotator.annotate graph
47
+ expect(node.props[:kind]).to eq 'control'
48
+ end
49
+
50
+ it 'sets edge kind to other when there is no kind' do
51
+ graph = Seafoam::Graph.new
52
+ node_a = graph.create_node(0)
53
+ node_b = graph.create_node(1)
54
+ edge = graph.create_edge(node_a, node_b)
55
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
56
+ annotator.annotate graph
57
+ expect(edge.props[:kind]).to eq 'other'
58
+ end
59
+
60
+ it 'does not overwrite an existing edge kind annotations' do
61
+ graph = Seafoam::Graph.new
62
+ node_a = graph.create_node(0)
63
+ node_b = graph.create_node(1)
64
+ edge = graph.create_edge(node_a, node_b, kind: 'control')
65
+ annotator = Seafoam::Annotators::FallbackAnnotator.new
66
+ annotator.annotate graph
67
+ expect(edge.props[:kind]).to eq 'control'
68
+ end
69
+ end