rdg 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|