rdg 0.0.2 → 0.1.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/RELEASES.md +10 -0
- data/TODO.md +68 -0
- data/bin/ast +5 -4
- data/bin/cfg +5 -4
- data/lib/rdg/analysis/analyser.rb +30 -0
- data/lib/rdg/analysis/composite.rb +23 -0
- data/lib/rdg/analysis/context.rb +17 -0
- data/lib/rdg/analysis/equivalences.rb +25 -0
- data/lib/rdg/analysis/propagater.rb +51 -0
- data/lib/rdg/analysis/registry.rb +35 -0
- data/lib/rdg/cfg.rb +12 -41
- data/lib/rdg/control/begin.rb +6 -4
- data/lib/rdg/control/break.rb +19 -0
- data/lib/rdg/control/case.rb +25 -0
- data/lib/rdg/control/{while.rb → conditional_loop.rb} +8 -7
- data/lib/rdg/control/def.rb +21 -7
- data/lib/rdg/control/ensure.rb +30 -0
- data/lib/rdg/control/for.rb +29 -0
- data/lib/rdg/control/handler.rb +25 -0
- data/lib/rdg/control/if.rb +7 -6
- data/lib/rdg/control/jump.rb +33 -0
- data/lib/rdg/control/jump_to_start.rb +11 -0
- data/lib/rdg/control/next.rb +9 -0
- data/lib/rdg/control/none.rb +5 -3
- data/lib/rdg/control/redo.rb +9 -0
- data/lib/rdg/control/rescue.rb +31 -0
- data/lib/rdg/control/rescue_body.rb +29 -0
- data/lib/rdg/control/retry.rb +13 -0
- data/lib/rdg/control/return.rb +5 -6
- data/lib/rdg/control/when.rb +27 -0
- data/lib/rdg/graph/bidirected_adjacency_graph.rb +19 -0
- data/lib/rdg/graph/rgl/allow_duplicates.rb +45 -0
- data/lib/rdg/tree/ast.rb +15 -4
- data/lib/rdg/version.rb +1 -1
- data/rdg.gemspec +3 -2
- data/spec/integration/cfg/conditionals/case_spec.rb +66 -0
- data/spec/integration/cfg/{if_spec.rb → conditionals/if_spec.rb} +24 -9
- data/spec/integration/cfg/conditionals/unless_spec.rb +53 -0
- data/spec/integration/cfg/exceptions_spec.rb +131 -0
- data/spec/integration/cfg/loops/loop_control_spec.rb +90 -0
- data/spec/integration/cfg/loops/loop_spec.rb +70 -0
- data/spec/integration/cfg/sequence_spec.rb +7 -1
- data/spec/support/doubles/fake_ast.rb +15 -0
- data/spec/support/matchers/flow_between_matcher.rb +1 -1
- data/spec/unit/analysis/composite_spec.rb +47 -0
- data/spec/unit/analysis/equivalences_spec.rb +29 -0
- data/spec/unit/{control/analyser_spec.rb → analysis/propagater_spec.rb} +23 -12
- data/spec/unit/analysis/registry_spec.rb +61 -0
- data/spec/unit/control/begin_spec.rb +3 -6
- data/spec/unit/control/break_spec.rb +26 -0
- data/spec/unit/control/case_spec.rb +66 -0
- data/spec/unit/control/conditional_loop_spec.rb +22 -0
- data/spec/unit/control/ensure_spec.rb +33 -0
- data/spec/unit/control/for_spec.rb +26 -0
- data/spec/unit/control/handler_spec.rb +27 -0
- data/spec/unit/control/if_spec.rb +26 -18
- data/spec/unit/control/jump_spec.rb +43 -0
- data/spec/unit/control/jump_to_start_spec.rb +22 -0
- data/spec/unit/control/rescue_body_spec.rb +26 -0
- data/spec/unit/control/rescue_spec.rb +62 -0
- data/spec/unit/control/return_spec.rb +1 -10
- data/spec/unit/control/when_spec.rb +59 -0
- data/spec/unit/graph/bidirected_adjacency_graph_spec.rb +49 -0
- metadata +91 -18
- data/lib/rdg/control/analyser.rb +0 -44
- data/spec/integration/cfg/methods_spec.rb +0 -39
- data/spec/unit/control/def_spec.rb +0 -28
- data/spec/unit/control/while_spec.rb +0 -25
@@ -0,0 +1,70 @@
|
|
1
|
+
require "rdg/cfg"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
describe CFG do
|
5
|
+
%w(while until).each do |kind|
|
6
|
+
context "for simple #{kind} expressions" do
|
7
|
+
it "should show control flowing between test and body, and out to successor" do
|
8
|
+
cfg = CFG.from_source("a = 1; #{kind} true do; a += 1; end; z = 1")
|
9
|
+
|
10
|
+
expect(cfg).to contain("a = 1", "true", "a += 1", "z = 1")
|
11
|
+
|
12
|
+
expect(cfg).to flow_between("a = 1", "true")
|
13
|
+
expect(cfg).to flow_between("true", "a += 1")
|
14
|
+
expect(cfg).to flow_between("true", "z = 1")
|
15
|
+
expect(cfg).to flow_between("a += 1", "true")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should show control flowing through longer body" do
|
19
|
+
cfg = CFG.from_source("a = 1; #{kind} true do; a += 1; b += 1; c += 1; end; z = 1")
|
20
|
+
|
21
|
+
expect(cfg).to contain("a = 1", "true", "a += 1", "b += 1", "c += 1", "z = 1")
|
22
|
+
|
23
|
+
expect(cfg).to flow_between("a = 1", "true")
|
24
|
+
expect(cfg).to flow_between("true", "a += 1")
|
25
|
+
expect(cfg).to flow_between("true", "z = 1")
|
26
|
+
expect(cfg).to flow_between("a += 1", "b += 1")
|
27
|
+
expect(cfg).to flow_between("b += 1", "c += 1")
|
28
|
+
expect(cfg).to flow_between("c += 1", "true")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should show control flowing between test and body for modifier #{kind}" do
|
32
|
+
cfg = CFG.from_source("a = 1; b += 1 #{kind} true; z = 1")
|
33
|
+
|
34
|
+
expect(cfg).to contain("a = 1", "b += 1", "true", "z = 1")
|
35
|
+
|
36
|
+
expect(cfg).to flow_between("a = 1", "true")
|
37
|
+
expect(cfg).to flow_between("true", "b +=1")
|
38
|
+
expect(cfg).to flow_between("true", "z = 1")
|
39
|
+
expect(cfg).to flow_between("b += 1", "true")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "for simple for expressions" do
|
45
|
+
it "should show control flowing between test and body, and out to successor" do
|
46
|
+
cfg = CFG.from_source("a = 1; for i in [1,2,3] do; a += 1; end; z = 1")
|
47
|
+
|
48
|
+
expect(cfg).to contain("a = 1", "[1,2,3]", "a += 1", "z = 1")
|
49
|
+
|
50
|
+
expect(cfg).to flow_between("a = 1", "[1,2,3]")
|
51
|
+
expect(cfg).to flow_between("[1,2,3]", "a += 1")
|
52
|
+
expect(cfg).to flow_between("[1,2,3]", "z = 1")
|
53
|
+
expect(cfg).to flow_between("a += 1", "[1,2,3]")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should show control flowing through longer body" do
|
57
|
+
cfg = CFG.from_source("a = 1; for i in [1,2,3] do; a += 1; b += 1; c += 1; end; z = 1")
|
58
|
+
|
59
|
+
expect(cfg).to contain("a = 1", "[1,2,3]", "a += 1", "b += 1", "c += 1", "z = 1")
|
60
|
+
|
61
|
+
expect(cfg).to flow_between("a = 1", "[1,2,3]")
|
62
|
+
expect(cfg).to flow_between("[1,2,3]", "a += 1")
|
63
|
+
expect(cfg).to flow_between("[1,2,3]", "z = 1")
|
64
|
+
expect(cfg).to flow_between("a += 1", "b += 1")
|
65
|
+
expect(cfg).to flow_between("b += 1", "c += 1")
|
66
|
+
expect(cfg).to flow_between("c += 1", "[1,2,3]")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -3,7 +3,13 @@ require "rdg/cfg"
|
|
3
3
|
module RDG
|
4
4
|
describe CFG do
|
5
5
|
context "for a sequence of statements" do
|
6
|
-
|
6
|
+
it "should show no control flow for a lone integer" do
|
7
|
+
cfg = CFG.from_source("1")
|
8
|
+
|
9
|
+
expect(cfg).to contain("1")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should show no control flow for a lone assignment" do
|
7
13
|
cfg = CFG.from_source("a = 1")
|
8
14
|
|
9
15
|
expect(cfg).to contain("a = 1")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class FakeAst
|
2
|
+
attr_accessor :type, :children, :ancestors, :siblings
|
3
|
+
|
4
|
+
def initialize(type, children: [], ancestors: [], siblings: [])
|
5
|
+
@type, @children, @ancestors, @siblings = type, children, ancestors, siblings
|
6
|
+
end
|
7
|
+
|
8
|
+
def empty?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def parent
|
13
|
+
ancestors.first
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "rdg/analysis/composite"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Analysis
|
5
|
+
describe Composite do
|
6
|
+
subject { Composite.compose(FirstAnalyser, SecondAnalyser).new(:node, :context) }
|
7
|
+
|
8
|
+
it "delegates analysis to each composed analyser in turn" do
|
9
|
+
subject.analyse
|
10
|
+
|
11
|
+
expect(FirstAnalyser.called).to be_truthy
|
12
|
+
expect(SecondAnalyser.called).to be_truthy
|
13
|
+
end
|
14
|
+
|
15
|
+
it "makes node available to each composed analyser" do
|
16
|
+
subject.analyse
|
17
|
+
|
18
|
+
expect(FirstAnalyser.node).to eq(:node)
|
19
|
+
expect(SecondAnalyser.node).to eq(:node)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "makes context available to each composed analyser" do
|
23
|
+
subject.analyse
|
24
|
+
|
25
|
+
expect(FirstAnalyser.context).to eq(:context)
|
26
|
+
expect(SecondAnalyser.context).to eq(:context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FakeAnalyser
|
31
|
+
def initialize(node, context)
|
32
|
+
self.class.node, self.class.context = node, context
|
33
|
+
end
|
34
|
+
|
35
|
+
def analyse
|
36
|
+
self.class.called = true
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_accessor :called, :node, :context
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FirstAnalyser < FakeAnalyser; end
|
45
|
+
class SecondAnalyser < FakeAnalyser; end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rdg/analysis/equivalences"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Analysis
|
5
|
+
describe Equivalences do
|
6
|
+
let(:ast) { FakeAst.new(:begin, children: [1, 2, 3]) }
|
7
|
+
|
8
|
+
context "all" do
|
9
|
+
it "returns direct equivalents" do
|
10
|
+
subject.add(:if, [:predicate, :consequence])
|
11
|
+
|
12
|
+
expect(subject.all(:if)).to eq([:predicate, :consequence])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns indirect equivalents transitively" do
|
16
|
+
subject.add(:if, [:predicate, :consequence])
|
17
|
+
subject.add(:predicate, [:send, :float])
|
18
|
+
subject.add(:consequence, [:return])
|
19
|
+
|
20
|
+
expect(subject.all(:if)).to eq([:send, :float, :return])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the original when there is no equivalent" do
|
24
|
+
expect(subject.first(:if)).to eq(:if)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,27 +1,33 @@
|
|
1
|
-
require "rdg/
|
1
|
+
require "rdg/analysis/propagater"
|
2
2
|
|
3
3
|
module RDG
|
4
|
-
module
|
5
|
-
describe
|
6
|
-
let(:ast) {
|
4
|
+
module Analysis
|
5
|
+
describe Propagater do
|
6
|
+
let(:ast) { FakeAst.new(:some_type) }
|
7
7
|
let(:graph) { spy("graph") }
|
8
|
+
let(:equivalences) { spy("equivalences") }
|
9
|
+
let(:context) { Context.new(graph, equivalences) }
|
8
10
|
|
9
11
|
subject do
|
10
|
-
class
|
12
|
+
class DummyPropagater < Propagater
|
11
13
|
def internal_flow_edges
|
12
|
-
[
|
14
|
+
[%i(s1 e1), %i(s2 e2)]
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
16
|
-
|
17
|
+
def start_node
|
18
|
+
:s1
|
17
19
|
end
|
18
20
|
|
19
21
|
def end_nodes
|
20
|
-
|
22
|
+
%i(e1 e2)
|
23
|
+
end
|
24
|
+
|
25
|
+
def nodes
|
26
|
+
%i(s1 s2 e1 e2)
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
24
|
-
|
30
|
+
DummyPropagater.new(ast, context)
|
25
31
|
end
|
26
32
|
|
27
33
|
it "should add a CFG edge for every internal flow edge" do
|
@@ -31,14 +37,13 @@ module RDG
|
|
31
37
|
expect(graph).to have_received(:add_edge).with(:s2, :e2)
|
32
38
|
end
|
33
39
|
|
34
|
-
it "should move any incoming edges to start
|
40
|
+
it "should move any incoming edges to start node" do
|
35
41
|
allow(graph).to receive(:each_predecessor).and_yield(:predecessor)
|
36
42
|
|
37
43
|
subject.analyse
|
38
44
|
|
39
45
|
expect(graph).to have_received(:remove_edge).with(:predecessor, ast)
|
40
46
|
expect(graph).to have_received(:add_edge).with(:predecessor, :s1)
|
41
|
-
expect(graph).to have_received(:add_edge).with(:predecessor, :s2)
|
42
47
|
end
|
43
48
|
|
44
49
|
it "should move any outgoing edges to end nodes" do
|
@@ -56,6 +61,12 @@ module RDG
|
|
56
61
|
|
57
62
|
expect(graph).to have_received(:remove_vertex).with(ast)
|
58
63
|
end
|
64
|
+
|
65
|
+
it "should add equivalences" do
|
66
|
+
subject.analyse
|
67
|
+
|
68
|
+
expect(equivalences).to have_received(:add).with(ast, %i(s1 s2 e1 e2))
|
69
|
+
end
|
59
70
|
end
|
60
71
|
end
|
61
72
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "rdg/analysis/registry"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Analysis
|
5
|
+
describe Registry do
|
6
|
+
let(:ast) { FakeAst.new(:if) }
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
Registry.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should allow analysers to be added by type" do
|
13
|
+
Registry.register_by_type FakeIfAnalyser, :if
|
14
|
+
analyser = subject.analyser_for(ast, :context)
|
15
|
+
|
16
|
+
expect(analyser.class).to eq(FakeIfAnalyser)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should select the correct analyser based on the AST's type" do
|
20
|
+
Registry.register_by_type FakeIfAnalyser, :if
|
21
|
+
Registry.register_by_type FakeForAnalyser, :for
|
22
|
+
analyser = subject.analyser_for(ast, :context)
|
23
|
+
|
24
|
+
expect(analyser.class).to eq(FakeIfAnalyser)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should select the default analyser if the AST's type is not recognised" do
|
28
|
+
Registry.register_by_type FakeForAnalyser, :for
|
29
|
+
Registry.register_default FakeDefaultAnalyser
|
30
|
+
analyser = subject.analyser_for(ast, :context)
|
31
|
+
|
32
|
+
expect(analyser.class).to eq(FakeDefaultAnalyser)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "customisation" do
|
36
|
+
before(:each) do
|
37
|
+
Registry.register_by_type FakeIfAnalyser, :if
|
38
|
+
subject.prepend_for(ast, FakeExtraAnalyser)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow an additional handler to be prepended for a specific AST node" do
|
42
|
+
analyser = subject.analyser_for(ast, :context)
|
43
|
+
|
44
|
+
expect(analyser.class.superclass).to eq(Composite)
|
45
|
+
expect(analyser.types).to eq([FakeExtraAnalyser, FakeIfAnalyser])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not run a prepended analyser for a different AST node of the same type" do
|
49
|
+
analyser = subject.analyser_for(FakeAst.new(:if), :context)
|
50
|
+
|
51
|
+
expect(analyser.class).to eq(FakeIfAnalyser)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class FakeDefaultAnalyser < Analyser; end
|
56
|
+
class FakeIfAnalyser < Analyser; end
|
57
|
+
class FakeForAnalyser < Analyser; end
|
58
|
+
class FakeExtraAnalyser < Analyser; end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -3,14 +3,11 @@ require "rdg/control/begin"
|
|
3
3
|
module RDG
|
4
4
|
module Control
|
5
5
|
describe Begin do
|
6
|
-
|
7
|
-
|
8
|
-
allow(ast).to receive(:children) { [1, 2, 3] }
|
9
|
-
Begin.new(ast, nil, nil)
|
10
|
-
end
|
6
|
+
let(:ast) { FakeAst.new(:begin, children: [1, 2, 3]) }
|
7
|
+
subject { Begin.new(ast) }
|
11
8
|
|
12
9
|
it "should have control flow start at the first child" do
|
13
|
-
expect(subject.
|
10
|
+
expect(subject.start_node).to eq(1)
|
14
11
|
end
|
15
12
|
|
16
13
|
it "should have control flow end at the last child" do
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rdg/control/break"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe Break do
|
6
|
+
let(:graph) { spy("graph") }
|
7
|
+
let(:equivalences) { spy("equivalences") }
|
8
|
+
let(:context) { Analysis::Context.new(graph, equivalences) }
|
9
|
+
|
10
|
+
let(:inside_ast) { FakeAst.new(:send, ancestors: [loop_ast]) }
|
11
|
+
let(:outside_ast) { FakeAst.new(:send) }
|
12
|
+
|
13
|
+
let(:loop_ast) { FakeAst.new(:while, children: [:abstract_test]) }
|
14
|
+
let(:ast) { FakeAst.new(:thing, ancestors: [loop_ast]) }
|
15
|
+
|
16
|
+
subject { Break.new(ast, context) }
|
17
|
+
|
18
|
+
it "should use successor of test that are outside the loop as new successors" do
|
19
|
+
allow(equivalences).to receive(:first).with(:abstract_test).and_return(:test)
|
20
|
+
allow(graph).to receive(:each_successor).with(:test).and_return([inside_ast, outside_ast])
|
21
|
+
|
22
|
+
expect(subject.new_successors).to eq([outside_ast])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "rdg/control/case"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe Case do
|
6
|
+
context "sole (when) part" do
|
7
|
+
let(:ast) { FakeAst.new(:case, children: [:expression, :when, :""]) }
|
8
|
+
subject { Case.new(ast) }
|
9
|
+
|
10
|
+
it "should have control flow start at the expression" do
|
11
|
+
expect(subject.start_node).to eq(:expression)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have control flow end at the when part" do
|
15
|
+
expect(subject.end_nodes).to eq([:when])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have control flow edge between expression and when" do
|
19
|
+
expect(subject.internal_flow_edges).to eq([[:expression, :when]])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with else part" do
|
24
|
+
let(:ast) { FakeAst.new(:case, children: [:expression, :when, :alt]) }
|
25
|
+
subject { Case.new(ast) }
|
26
|
+
|
27
|
+
it "should have control flow start at the expression" do
|
28
|
+
expect(subject.start_node).to eq(:expression)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have control flow end at the when and alternative" do
|
32
|
+
expect(subject.end_nodes).to eq([:when, :alt])
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have control flow edge from expression to when and then to alternative" do
|
36
|
+
expect(subject.internal_flow_edges).to eq([
|
37
|
+
[:expression, :when],
|
38
|
+
[:when, :alt]
|
39
|
+
])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with several alternatives" do
|
44
|
+
let(:ast) { FakeAst.new(:case, children: [:expression, :when1, :when2, :when3, :alt]) }
|
45
|
+
subject { Case.new(ast) }
|
46
|
+
|
47
|
+
it "should have control flow start at the expression" do
|
48
|
+
expect(subject.start_node).to eq(:expression)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have control flow end at the whens and alternative" do
|
52
|
+
expect(subject.end_nodes).to eq([:when1, :when2, :when3, :alt])
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have control flow edge from expression through whens to alternative" do
|
56
|
+
expect(subject.internal_flow_edges).to eq([
|
57
|
+
[:expression, :when1],
|
58
|
+
[:when1, :when2],
|
59
|
+
[:when2, :when3],
|
60
|
+
[:when3, :alt]
|
61
|
+
])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rdg/control/conditional_loop"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe ConditionalLoop do
|
6
|
+
let(:ast) { FakeAst.new(:while, children: [:predicate, :body]) }
|
7
|
+
subject { ConditionalLoop.new(ast) }
|
8
|
+
|
9
|
+
it "should have control flow start at the predicate" do
|
10
|
+
expect(subject.start_node).to eq(:predicate)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have control flow end at the predicate" do
|
14
|
+
expect(subject.end_nodes).to eq([:predicate])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have control flow edges between predicate and body, and vice-versa" do
|
18
|
+
expect(subject.internal_flow_edges).to eq([[:predicate, :body], [:body, :predicate]])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|