delfos 0.0.1.pre.beta → 0.0.1.pre.rc1
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/lib/delfos.rb +63 -21
- data/lib/delfos/common_path.rb +1 -3
- data/lib/delfos/execution_chain.rb +15 -14
- data/lib/delfos/method_logging.rb +18 -20
- data/lib/delfos/method_logging/added_methods.rb +67 -0
- data/lib/delfos/method_logging/args.rb +24 -23
- data/lib/delfos/method_logging/code_location.rb +25 -28
- data/lib/delfos/neo4j/distance_update.rb +26 -26
- data/lib/delfos/neo4j/execution_persistence.rb +59 -24
- data/lib/delfos/neo4j/informer.rb +16 -26
- data/lib/delfos/neo4j/query_execution.rb +58 -17
- data/lib/delfos/patching/basic_object.rb +18 -0
- data/lib/delfos/patching/basic_object_remove.rb +7 -0
- data/lib/delfos/patching/method_override.rb +138 -0
- data/lib/delfos/patching/unstubbing_spec_helper.rb +58 -0
- metadata +40 -58
- data/lib/delfos/method_logging/klass_determination.rb +0 -16
- data/lib/delfos/patching.rb +0 -105
- data/lib/delfos/patching_unstubbing_spec_helper.rb +0 -44
- data/lib/delfos/perform_patching.rb +0 -16
- data/lib/delfos/remove_patching.rb +0 -10
- data/lib/delfos/version.rb +0 -4
@@ -7,24 +7,7 @@ module Delfos
|
|
7
7
|
module Neo4j
|
8
8
|
class DistanceUpdate
|
9
9
|
def perform
|
10
|
-
query
|
11
|
-
MATCH
|
12
|
-
(klass) - [:OWNS] -> (method),
|
13
|
-
(method) - [:CONTAINS] -> (call_site),
|
14
|
-
(call_site) - [:CALLS] -> (called),
|
15
|
-
(called_klass) - [:OWNS] -> (called)
|
16
|
-
|
17
|
-
RETURN
|
18
|
-
head(labels(klass)),
|
19
|
-
call_site, id(call_site),
|
20
|
-
method,
|
21
|
-
called, id(called),
|
22
|
-
head(labels(called_klass))
|
23
|
-
QUERY
|
24
|
-
|
25
|
-
results = Delfos::Neo4j::QueryExecution.execute(query)
|
26
|
-
|
27
|
-
update(results)
|
10
|
+
update Delfos::Neo4j::QueryExecution.execute(query)
|
28
11
|
end
|
29
12
|
|
30
13
|
def determine_full_path(f)
|
@@ -39,8 +22,25 @@ module Delfos
|
|
39
22
|
|
40
23
|
private
|
41
24
|
|
25
|
+
def query
|
26
|
+
<<-QUERY
|
27
|
+
MATCH
|
28
|
+
(klass:Class) - [:OWNS] -> (method:Method),
|
29
|
+
(method) - [:CONTAINS] -> (call_site:CallSite),
|
30
|
+
(call_site) - [:CALLS] -> (called:Method),
|
31
|
+
(called_klass:Class) - [:OWNS] -> (called:Method)
|
32
|
+
|
33
|
+
RETURN
|
34
|
+
head(labels(klass)),
|
35
|
+
call_site, id(call_site),
|
36
|
+
method,
|
37
|
+
called, id(called),
|
38
|
+
head(labels(called_klass))
|
39
|
+
QUERY
|
40
|
+
end
|
41
|
+
|
42
42
|
def update(results)
|
43
|
-
results.map do |
|
43
|
+
Array(results).compact.map do |_klass, call_site, call_site_id, _meth, called, called_id, _called_klass|
|
44
44
|
start = determine_full_path call_site["file"]
|
45
45
|
finish = determine_full_path called["file"]
|
46
46
|
|
@@ -51,19 +51,19 @@ module Delfos
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def perform_query(calc, call_site_id, called_id)
|
54
|
-
Delfos::Neo4j::QueryExecution.execute <<-QUERY
|
55
|
-
START call_site = node(
|
56
|
-
called = node(
|
54
|
+
Delfos::Neo4j::QueryExecution.execute <<-QUERY, {call_site_id: call_site_id, called_id: called_id, sum_traversals: calc.sum_traversals, sum_possible_traversals: calc.sum_possible_traversals}
|
55
|
+
START call_site = node({call_site_id}),
|
56
|
+
called = node({called_id})
|
57
57
|
|
58
|
-
MERGE (call_site) - #{
|
58
|
+
MERGE (call_site) - #{rel} -> (called)
|
59
59
|
QUERY
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
62
|
+
def rel
|
63
63
|
<<-REL
|
64
64
|
[:EFFERENT_COUPLING{
|
65
|
-
distance:
|
66
|
-
possible_distance:
|
65
|
+
distance: {sum_traversals},
|
66
|
+
possible_distance: {sum_possible_traversals}
|
67
67
|
}
|
68
68
|
]
|
69
69
|
REL
|
@@ -1,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative "query_execution"
|
2
3
|
|
3
4
|
module Delfos
|
4
5
|
module Neo4j
|
5
6
|
class ExecutionPersistence
|
6
|
-
def self.save!(
|
7
|
-
new(
|
7
|
+
def self.save!(chain)
|
8
|
+
new(chain.call_sites, chain.execution_count).save!
|
8
9
|
end
|
9
10
|
|
10
11
|
def save!
|
11
|
-
Neo4j::QueryExecution.execute
|
12
|
+
Neo4j::QueryExecution.execute(query, params)
|
12
13
|
end
|
13
14
|
|
14
15
|
private
|
@@ -22,42 +23,76 @@ module Delfos
|
|
22
23
|
attr_reader :call_sites, :execution_count
|
23
24
|
|
24
25
|
def query
|
25
|
-
|
26
|
+
map_call_sites do |c,i|
|
26
27
|
call_site_query(c, i)
|
27
28
|
end.join("\n")
|
28
29
|
end
|
29
30
|
|
31
|
+
def params
|
32
|
+
params = {}
|
33
|
+
|
34
|
+
map_call_sites do |c,i|
|
35
|
+
params.merge!(call_site_params(c, i))
|
36
|
+
end
|
37
|
+
|
38
|
+
params
|
39
|
+
end
|
40
|
+
|
41
|
+
def map_call_sites
|
42
|
+
call_sites.compact.map.with_index do |c, i|
|
43
|
+
yield c, i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
30
47
|
def call_site_query(cs, i)
|
31
|
-
|
32
|
-
|
48
|
+
<<-QUERY
|
49
|
+
MERGE
|
33
50
|
|
34
|
-
|
35
|
-
|
51
|
+
(
|
52
|
+
k#{i}:Class { name: {klass#{i}} }
|
53
|
+
)
|
36
54
|
|
37
|
-
|
55
|
+
- [:OWNS] ->
|
38
56
|
|
39
|
-
|
57
|
+
(
|
58
|
+
m#{i} :Method {
|
59
|
+
type: {method_type#{i}},
|
60
|
+
name: {method_name#{i}},
|
61
|
+
file: {method_definition_file#{i}},
|
62
|
+
line_number: {method_definition_line#{i}}}
|
63
|
+
)
|
40
64
|
|
41
|
-
|
42
|
-
QUERY
|
43
|
-
end
|
65
|
+
MERGE
|
44
66
|
|
45
|
-
|
46
|
-
<<-QUERY
|
47
|
-
MERGE (e#{i}:ExecutionChain{number: #{execution_count}})
|
67
|
+
(m#{i})
|
48
68
|
|
49
|
-
|
50
|
-
QUERY
|
51
|
-
end
|
69
|
+
-[:CONTAINS]->
|
52
70
|
|
53
|
-
|
54
|
-
<<-QUERY
|
55
|
-
MERGE (k#{i}:#{cs.klass})
|
56
|
-
- [:OWNS] ->
|
71
|
+
(cs#{i}:CallSite {file: {file#{i}}, line_number: {line_number#{i}}})
|
57
72
|
|
58
|
-
|
73
|
+
MERGE (e#{i}:ExecutionChain{number: {execution_count#{i}}})
|
74
|
+
|
75
|
+
MERGE (e#{i})
|
76
|
+
-
|
77
|
+
[:STEP {number: {step_number#{i}}}]
|
78
|
+
->
|
79
|
+
(cs#{i})
|
59
80
|
QUERY
|
60
81
|
end
|
82
|
+
|
83
|
+
def call_site_params(cs, i)
|
84
|
+
{
|
85
|
+
"klass#{i}" => cs.klass,
|
86
|
+
"method_name#{i}" => cs.method_name,
|
87
|
+
"method_type#{i}" => cs.method_type,
|
88
|
+
"method_definition_file#{i}" => cs.method_definition_file,
|
89
|
+
"execution_count#{i}" => execution_count,
|
90
|
+
"method_definition_line#{i}" => cs.method_definition_line,
|
91
|
+
"file#{i}" => cs.file,
|
92
|
+
"line_number#{i}" => cs.line_number,
|
93
|
+
"step_number#{i}" => i + 1
|
94
|
+
}
|
95
|
+
end
|
61
96
|
end
|
62
97
|
end
|
63
98
|
end
|
@@ -11,21 +11,29 @@ module Delfos
|
|
11
11
|
|
12
12
|
def execute_query(*args)
|
13
13
|
query = query_for(*args)
|
14
|
+
params = {}
|
14
15
|
|
15
|
-
QueryExecution.execute(query)
|
16
|
+
QueryExecution.execute(query, params)
|
16
17
|
end
|
17
18
|
|
18
19
|
def query_for(args, call_site, called_code)
|
19
20
|
assign_query_variables(args, call_site, called_code)
|
20
21
|
|
21
22
|
klasses_query = query_variables.map do |klass, name|
|
22
|
-
"MERGE (#{name}
|
23
|
+
"MERGE (#{name}:Class {name: #{klass.inspect}})"
|
23
24
|
end.join("\n")
|
24
25
|
|
25
26
|
<<-QUERY
|
26
27
|
#{klasses_query}
|
27
28
|
|
28
|
-
#{
|
29
|
+
MERGE (#{query_variable(call_site.klass)}) - [:OWNS] -> #{method_node(call_site, "m1")}
|
30
|
+
|
31
|
+
MERGE (m1) - [:CONTAINS] -> (cs:CallSite{file: "#{call_site.file}", line_number: #{call_site.line_number}})
|
32
|
+
|
33
|
+
MERGE (#{query_variable(called_code.klass)}) - [:OWNS] -> #{method_node(called_code, "m2")}
|
34
|
+
|
35
|
+
MERGE (cs) - [:CALLS] -> m2
|
36
|
+
|
29
37
|
#{args_query args}
|
30
38
|
QUERY
|
31
39
|
end
|
@@ -39,32 +47,14 @@ module Delfos
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
42
|
-
def merge_query(call_site, called_code)
|
43
|
-
<<-MERGE_QUERY
|
44
|
-
#{method_definition call_site, "m1"}
|
45
|
-
|
46
|
-
MERGE (m1) - [:CONTAINS] -> (cs:CallSite{file: "#{call_site.file}", line_number: #{call_site.line_number}})
|
47
|
-
|
48
|
-
#{method_definition called_code, "m2"}
|
49
|
-
|
50
|
-
MERGE (cs) - [:CALLS] -> m2
|
51
|
-
MERGE_QUERY
|
52
|
-
end
|
53
|
-
|
54
|
-
def method_definition(code, id)
|
55
|
-
<<-METHOD
|
56
|
-
MERGE (#{query_variable(code.klass)}) - [:OWNS] -> #{method_node(code, id)}
|
57
|
-
METHOD
|
58
|
-
end
|
59
|
-
|
60
50
|
def method_node(code, id)
|
61
|
-
if code.method_definition_file.length
|
51
|
+
if code.method_definition_file.length.positive? && code.method_definition_line.positive?
|
62
52
|
<<-NODE
|
63
|
-
(#{id}
|
53
|
+
(#{id}:Method{type: "#{code.method_type}", name: "#{code.method_name}", file: #{code.method_definition_file.inspect}, line_number: #{code.method_definition_line}})
|
64
54
|
NODE
|
65
55
|
else
|
66
56
|
<<-NODE
|
67
|
-
(#{id}
|
57
|
+
(#{id}:Method{type: "#{code.method_type}", name: "#{code.method_name}"})
|
68
58
|
NODE
|
69
59
|
end
|
70
60
|
end
|
@@ -85,7 +75,7 @@ module Delfos
|
|
85
75
|
end
|
86
76
|
|
87
77
|
def code_execution_query
|
88
|
-
Delfos::Patching.
|
78
|
+
Delfos::Patching.execution_chain
|
89
79
|
end
|
90
80
|
|
91
81
|
class QueryVariables < Hash
|
@@ -95,7 +85,7 @@ module Delfos
|
|
95
85
|
end
|
96
86
|
|
97
87
|
def assign(klass, prefix)
|
98
|
-
klass = klass.to_s
|
88
|
+
klass = klass.to_s.tr(":", "_")
|
99
89
|
val = self[klass]
|
100
90
|
return val if val
|
101
91
|
|
@@ -1,38 +1,79 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "delfos"
|
3
3
|
require "json"
|
4
|
+
require "net/http"
|
5
|
+
require "uri"
|
4
6
|
|
5
7
|
module Delfos
|
6
8
|
module Neo4j
|
7
9
|
module QueryExecution
|
10
|
+
class ExecutionError < IOError
|
11
|
+
def initialize(response, query, params)
|
12
|
+
message = response.inspect
|
13
|
+
|
14
|
+
super [message, query, params.to_json].join("\n\n ")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
class << self
|
9
|
-
def execute(query,
|
10
|
-
|
11
|
-
|
19
|
+
def execute(query, params={})
|
20
|
+
return unless query.length.positive?
|
21
|
+
|
22
|
+
parse_response(*response_for(query, params))
|
12
23
|
end
|
13
24
|
|
14
25
|
private
|
15
26
|
|
16
|
-
def response_for(query)
|
17
|
-
body = <<-BODY.gsub(/^\s+/, "").chomp
|
18
|
-
{"statements": [ { "statement": #{query.inspect} }]}
|
19
|
-
BODY
|
20
|
-
url = "http://neo4j:password@localhost:7474/db/data/transaction/commit"
|
21
27
|
|
22
|
-
|
28
|
+
def parse_response(result, query, params)
|
29
|
+
result = JSON.parse result.body
|
30
|
+
|
31
|
+
if result["errors"].length.positive?
|
32
|
+
raise ExecutionError.new(result["errors"], query, params)
|
33
|
+
end
|
34
|
+
|
35
|
+
strip_out_meta_data(result)
|
36
|
+
end
|
37
|
+
|
38
|
+
def strip_out_meta_data(result)
|
39
|
+
results = result["results"]
|
40
|
+
|
41
|
+
if results
|
42
|
+
result = results.first
|
43
|
+
if result
|
44
|
+
data = result["data"]
|
45
|
+
data &.map { |r| r["row"] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def response_for(query, params)
|
51
|
+
request = request_for(query, params)
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
53
|
+
|
54
|
+
response = http.request(request)
|
55
|
+
[response, query, params]
|
56
|
+
end
|
57
|
+
|
58
|
+
def request_for(query, params)
|
59
|
+
build_request do
|
60
|
+
"{\"statements\": [{\"statement\": #{query.inspect} , \"parameters\": #{params.to_json}}]}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_request
|
65
|
+
Net::HTTP::Post.new(uri.request_uri).tap do |request|
|
66
|
+
request.basic_auth(Delfos.neo4j.username, Delfos.neo4j.password)
|
67
|
+
request.content_type = "application/json"
|
23
68
|
|
24
|
-
|
69
|
+
request.body = yield
|
70
|
+
end
|
25
71
|
end
|
26
72
|
|
27
|
-
def
|
28
|
-
|
29
|
-
-H "User-Agent: delfos" -H "Accept: application/json; charset=UTF-8" -H "Content-Type: application/json"
|
30
|
-
HEADERS
|
73
|
+
def uri
|
74
|
+
@uri ||= URI.parse "#{Delfos.neo4j.url}/db/data/transaction/commit"
|
31
75
|
end
|
32
76
|
end
|
33
77
|
end
|
34
78
|
end
|
35
79
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "method_override"
|
3
|
+
|
4
|
+
class BasicObject
|
5
|
+
def self.method_added(name)
|
6
|
+
return if %i(to_s singleton_method_added method_added).include?(name)
|
7
|
+
|
8
|
+
::Delfos::Patching::MethodOverride.setup(self, name, private_instance_methods, class_method: false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.singleton_method_added(name)
|
12
|
+
# can't currently support to_s and name as those are used inside the method logging code
|
13
|
+
return if %i(to_s name extended included inherited method_added singleton_method_added).include?(name)
|
14
|
+
return if ::Delfos::MethodLogging.exclude?(method(name))
|
15
|
+
|
16
|
+
::Delfos::Patching::MethodOverride.setup(self, name, private_methods, class_method: true)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../method_logging"
|
4
|
+
|
5
|
+
module Delfos
|
6
|
+
module Patching
|
7
|
+
class MethodOverride
|
8
|
+
class << self
|
9
|
+
def setup(klass, name, private_methods, class_method:)
|
10
|
+
instance = new(klass, name, private_methods, class_method)
|
11
|
+
|
12
|
+
instance.ensure_method_recorded_once!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :klass, :name, :private_methods, :class_method
|
17
|
+
|
18
|
+
def initialize(klass, name, private_methods, class_method)
|
19
|
+
@klass = klass
|
20
|
+
@name = name
|
21
|
+
@private_methods = private_methods
|
22
|
+
@class_method = class_method
|
23
|
+
original_method # ensure memoized method is the original not the overridden one
|
24
|
+
end
|
25
|
+
|
26
|
+
def ensure_method_recorded_once!
|
27
|
+
record_method! { setup }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Redefine the method (only once) at runtime to enabling logging to Neo4j
|
31
|
+
def setup
|
32
|
+
processor = method(:process)
|
33
|
+
cm = class_method
|
34
|
+
|
35
|
+
method_defining_method.call(name) do |*args, **kw_args, &block|
|
36
|
+
arguments = MethodArguments.new(args, kw_args, block, cm)
|
37
|
+
|
38
|
+
processor.call(self, caller.dup, binding.dup, arguments)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def process(instance, stack, caller_binding, arguments)
|
43
|
+
method_to_call = method_selector(instance)
|
44
|
+
|
45
|
+
call_site = Delfos::MethodLogging::CodeLocation.from_call_site(stack, caller_binding)
|
46
|
+
|
47
|
+
with_logging(call_site, instance, method_to_call, class_method, arguments) do
|
48
|
+
arguments.apply_to(method_to_call)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_logging(call_site, instance, method_to_call, class_method, arguments)
|
53
|
+
Delfos.method_logging.log(call_site, instance, method_to_call, class_method, arguments) if call_site
|
54
|
+
|
55
|
+
with_stack(call_site) do
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def with_stack(call_site)
|
62
|
+
return yield unless call_site
|
63
|
+
|
64
|
+
begin
|
65
|
+
ExecutionChain.push(call_site)
|
66
|
+
yield
|
67
|
+
ensure
|
68
|
+
ExecutionChain.pop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
MethodArguments = Struct.new(:args, :keyword_args, :block, :class_method) do
|
73
|
+
def apply_to(method)
|
74
|
+
if keyword_args.empty?
|
75
|
+
method.call(*args, &block)
|
76
|
+
else
|
77
|
+
method.call(*args, **keyword_args, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def original_method
|
84
|
+
@original_method ||= if class_method
|
85
|
+
klass.method(name)
|
86
|
+
else
|
87
|
+
klass.instance_method(name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def record_method!
|
94
|
+
return true if bail?
|
95
|
+
yield
|
96
|
+
|
97
|
+
Delfos::MethodLogging::AddedMethods.append(klass, key, original_method)
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_selector(instance)
|
101
|
+
if class_method
|
102
|
+
m = Delfos::MethodLogging::AddedMethods.find(instance, "ClassMethod_#{name}")
|
103
|
+
m.receiver == instance ? m : m.unbind.bind(instance)
|
104
|
+
else
|
105
|
+
original_method.bind(instance)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_defining_method
|
110
|
+
class_method ? klass.method(:define_singleton_method) : klass.method(:define_method)
|
111
|
+
end
|
112
|
+
|
113
|
+
def bail?
|
114
|
+
method_has_been_added? || private_method? || exclude?
|
115
|
+
end
|
116
|
+
|
117
|
+
def method_has_been_added?
|
118
|
+
Delfos::MethodLogging::AddedMethods.find(klass, key)
|
119
|
+
end
|
120
|
+
|
121
|
+
def private_method?
|
122
|
+
private_methods.include?(name.to_sym)
|
123
|
+
end
|
124
|
+
|
125
|
+
def exclude?
|
126
|
+
::Delfos.method_logging.exclude?(original_method)
|
127
|
+
end
|
128
|
+
|
129
|
+
def key
|
130
|
+
"#{type}_#{name}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def type
|
134
|
+
class_method ? "ClassMethod" : "InstanceMethod"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|