reflekt 0.7.2 → 0.9.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/lib/Execution.rb +45 -0
- data/lib/Reflection.rb +145 -0
- data/lib/ShadowStack.rb +56 -0
- data/lib/reflekt.rb +83 -152
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4d3c0c32672fd8813482a3f546032904d3d6f65e592fd4435eb7ada2bb1b201
|
4
|
+
data.tar.gz: d48f2b100986c4408db5cb055f4ebf1e6a50d99fa99dc6d98e8af8e5d8bcd66e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1422110bc34f7b88f8b4ba71359c408d38f3ebf72235735e51864fd0bd16c32fd5abc20e5cfa8a516478932800590f80f7f1b7b4d0dac3c0ebd18a3d8dfffd54
|
7
|
+
data.tar.gz: 4019f5868ca20d1aebfa9e4ddb2c5136782aa52185588cb44558f4725f51ea59f90bc75896e8bfac383151d7346bb534d98bc00a11a569c350d0c460fe3b31ab
|
data/lib/Execution.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Execution
|
2
|
+
|
3
|
+
attr_accessor :object
|
4
|
+
attr_accessor :caller_id
|
5
|
+
attr_accessor :caller_class
|
6
|
+
attr_accessor :parent
|
7
|
+
attr_accessor :child
|
8
|
+
attr_accessor :reflections
|
9
|
+
attr_accessor :is_reflecting
|
10
|
+
|
11
|
+
def initialize(object, reflection_count)
|
12
|
+
|
13
|
+
@object = object
|
14
|
+
@caller_id = object.object_id
|
15
|
+
@caller_class = object.class
|
16
|
+
@parent = nil
|
17
|
+
@child = nil
|
18
|
+
|
19
|
+
@reflections = Array.new(reflection_count)
|
20
|
+
@is_reflecting = false
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_empty_reflections?
|
25
|
+
@reflections.include? nil
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Is the Execution currently reflecting methods?
|
30
|
+
##
|
31
|
+
def is_reflecting?
|
32
|
+
@is_reflecting
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_finished_reflecting?
|
36
|
+
if is_reflecting?
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
if has_empty_reflections?
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/Reflection.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
class Reflection
|
2
|
+
|
3
|
+
# Keys.
|
4
|
+
TIME = "t"
|
5
|
+
INPUT = "i"
|
6
|
+
OUTPUT = "o"
|
7
|
+
TYPE = "T"
|
8
|
+
COUNT = "C"
|
9
|
+
VALUE = "V"
|
10
|
+
STATUS = "s"
|
11
|
+
MESSAGE = "m"
|
12
|
+
# Values.
|
13
|
+
PASS = "p"
|
14
|
+
FAIL = "f"
|
15
|
+
|
16
|
+
attr_accessor :clone
|
17
|
+
|
18
|
+
def initialize(execution, method, is_control)
|
19
|
+
|
20
|
+
@execution = execution
|
21
|
+
@method = method
|
22
|
+
@is_control = is_control
|
23
|
+
|
24
|
+
# Clone the execution's object.
|
25
|
+
@clone = execution.object.clone
|
26
|
+
@clone_id = nil
|
27
|
+
|
28
|
+
# Result.
|
29
|
+
@status = nil
|
30
|
+
@time = Time.now.to_i
|
31
|
+
@input = []
|
32
|
+
@output = nil
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Reflect on a method.
|
38
|
+
#
|
39
|
+
# Creates a shadow execution stack.
|
40
|
+
#
|
41
|
+
# @param method - The name of the method.
|
42
|
+
# @param *args - The method arguments.
|
43
|
+
#
|
44
|
+
# @return - A reflection hash.
|
45
|
+
##
|
46
|
+
def reflect(*args)
|
47
|
+
|
48
|
+
# Reflect on real world arguments.
|
49
|
+
if @is_control
|
50
|
+
@input = *args
|
51
|
+
# Reflect on deviated arguments.
|
52
|
+
else
|
53
|
+
args.each do |arg|
|
54
|
+
case arg
|
55
|
+
when Integer
|
56
|
+
@input << rand(9999)
|
57
|
+
else
|
58
|
+
@input << arg
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Action method with new arguments.
|
64
|
+
begin
|
65
|
+
@output = @clone.send(@method, *@input)
|
66
|
+
# When fail.
|
67
|
+
rescue StandardError => message
|
68
|
+
@status = MESSAGE
|
69
|
+
@message = message
|
70
|
+
# When pass.
|
71
|
+
else
|
72
|
+
@status = PASS
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def result()
|
78
|
+
# Build reflection.
|
79
|
+
reflection = {
|
80
|
+
TIME => @time,
|
81
|
+
STATUS => @status,
|
82
|
+
INPUT => normalize_input(@input),
|
83
|
+
OUTPUT => normalize_output(@output),
|
84
|
+
MESSAGE => @message
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Normalize inputs.
|
90
|
+
#
|
91
|
+
# @param args - The actual inputs.
|
92
|
+
# @return - A generic inputs representation.
|
93
|
+
##
|
94
|
+
def normalize_input(args)
|
95
|
+
inputs = []
|
96
|
+
args.each do |arg|
|
97
|
+
input = {
|
98
|
+
TYPE => arg.class.to_s,
|
99
|
+
VALUE => normalize_value(arg)
|
100
|
+
}
|
101
|
+
if (arg.class == Array)
|
102
|
+
input[COUNT] = arg.count
|
103
|
+
end
|
104
|
+
inputs << input
|
105
|
+
end
|
106
|
+
inputs
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Normalize output.
|
111
|
+
#
|
112
|
+
# @param input - The actual output.
|
113
|
+
# @return - A generic output representation.
|
114
|
+
##
|
115
|
+
def normalize_output(input)
|
116
|
+
|
117
|
+
output = {
|
118
|
+
TYPE => input.class.to_s,
|
119
|
+
VALUE => normalize_value(input)
|
120
|
+
}
|
121
|
+
|
122
|
+
if (input.class == Array || input.class == Hash)
|
123
|
+
output[COUNT] = input.count
|
124
|
+
elsif (input.class == TrueClass || input.class == FalseClass)
|
125
|
+
output[TYPE] = :Boolean
|
126
|
+
end
|
127
|
+
|
128
|
+
return output
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def normalize_value(value)
|
133
|
+
|
134
|
+
unless value.nil?
|
135
|
+
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
136
|
+
if value.length >= 30
|
137
|
+
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return value
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/ShadowStack.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
################################################################################
|
2
|
+
# SHADOW STACK
|
3
|
+
#
|
4
|
+
# Track the executions in a call stack.
|
5
|
+
################################################################################
|
6
|
+
|
7
|
+
class ShadowStack
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@bottom = nil
|
11
|
+
@top = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def peek()
|
15
|
+
@top
|
16
|
+
end
|
17
|
+
|
18
|
+
def base()
|
19
|
+
@bottom
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Push Execution.
|
24
|
+
#
|
25
|
+
# @param object - The object being executed.
|
26
|
+
# @param args - The arguments being executed.
|
27
|
+
#
|
28
|
+
# @return Execution - The new execution.
|
29
|
+
##
|
30
|
+
def push(execution)
|
31
|
+
|
32
|
+
# Reference previous execution.
|
33
|
+
if @bottom.nil?
|
34
|
+
@bottom = execution
|
35
|
+
else
|
36
|
+
execution.child = @top
|
37
|
+
@top.parent = execution
|
38
|
+
end
|
39
|
+
|
40
|
+
# Place new execution at the top of the stack.
|
41
|
+
@top = execution
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def display
|
46
|
+
display_execution_tree(@bottom)
|
47
|
+
end
|
48
|
+
|
49
|
+
def display_execution_tree(execution)
|
50
|
+
p execution
|
51
|
+
unless execution.parent == nil
|
52
|
+
display_execution_tree(execution.parent)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/reflekt.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'erb'
|
3
3
|
require 'rowdb'
|
4
|
+
require 'Execution'
|
5
|
+
require 'Reflection'
|
6
|
+
require 'ShadowStack'
|
4
7
|
|
5
8
|
################################################################################
|
6
9
|
# REFLEKT
|
7
10
|
#
|
8
|
-
#
|
11
|
+
# An Execution is created each time a method is called.
|
12
|
+
# Multiple Refections are created per Execution.
|
13
|
+
# These Reflections execute on a ShadowStack on cloned objects.
|
14
|
+
# Then flow is returned to the original method and normal execution continues.
|
15
|
+
#
|
16
|
+
# Usage:
|
9
17
|
#
|
10
18
|
# class ExampleClass
|
11
19
|
# prepend Reflekt
|
@@ -13,186 +21,105 @@ require 'rowdb'
|
|
13
21
|
|
14
22
|
module Reflekt
|
15
23
|
|
16
|
-
#
|
17
|
-
|
18
|
-
REFLEKT_INPUT = "i"
|
19
|
-
REFLEKT_OUTPUT = "o"
|
20
|
-
REFLEKT_TYPE = "T"
|
21
|
-
REFLEKT_COUNT = "C"
|
22
|
-
REFLEKT_VALUE = "V"
|
23
|
-
REFLEKT_STATUS = "s"
|
24
|
-
REFLEKT_MESSAGE = "m"
|
25
|
-
# Reflection values.
|
26
|
-
REFLEKT_PASS = "p"
|
27
|
-
REFLEKT_FAIL = "f"
|
28
|
-
|
29
|
-
@@reflekt_clone_count = 5
|
24
|
+
# The amount of reflections to create per method call.
|
25
|
+
@@reflekt_reflect_amount = 2
|
30
26
|
|
31
|
-
|
27
|
+
# Limit the amount of reflections that can be created per instance method.
|
28
|
+
# A method called thousands of times doesn't need that many reflections.
|
29
|
+
@@reflection_limit = 10
|
32
30
|
|
33
|
-
|
34
|
-
@reflekt_clones = []
|
31
|
+
def initialize(*args)
|
35
32
|
|
36
|
-
|
37
|
-
# A method called thousands of times doesn't need that many reflections.
|
38
|
-
@reflekt_limit = 5
|
39
|
-
@reflekt_count = 0
|
33
|
+
@reflection_counts = {}
|
40
34
|
|
41
|
-
#
|
35
|
+
# Get instance methods.
|
36
|
+
# TODO: Include parent methods like "Array.include?".
|
42
37
|
self.class.instance_methods(false).each do |method|
|
43
|
-
self.define_singleton_method(method) do |*args|
|
44
38
|
|
45
|
-
|
46
|
-
|
39
|
+
# Don't process skipped methods.
|
40
|
+
next if self.class.reflekt_skipped?(method)
|
47
41
|
|
48
|
-
|
49
|
-
unless self.class.deflekted?(method)
|
42
|
+
@reflection_counts[method] = 0
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
reflekt_action(clone, method, *args)
|
54
|
-
end
|
44
|
+
# When method called in flow.
|
45
|
+
self.define_singleton_method(method) do |*args|
|
55
46
|
|
56
|
-
|
57
|
-
|
47
|
+
# Don't reflect when limit reached.
|
48
|
+
unless @reflection_counts[method] >= @@reflection_limit
|
58
49
|
|
59
|
-
|
50
|
+
# Get current execution.
|
51
|
+
execution = @@reflekt_stack.peek()
|
60
52
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
53
|
+
# When stack empty or past execution done reflecting.
|
54
|
+
if execution.nil? || execution.has_finished_reflecting?
|
64
55
|
|
65
|
-
|
56
|
+
# Create execution.
|
57
|
+
execution = Execution.new(self, @@reflekt_reflect_amount)
|
58
|
+
@@reflekt_stack.push(execution)
|
66
59
|
|
67
|
-
|
68
|
-
super *args
|
69
|
-
end
|
60
|
+
end
|
70
61
|
|
71
|
-
|
62
|
+
# Reflect.
|
63
|
+
# The first method call in the Execution creates a Reflection.
|
64
|
+
# Subsequent method calls are shadow executions on cloned objects.
|
65
|
+
if execution.has_empty_reflections? && !execution.is_reflecting?
|
66
|
+
execution.is_reflecting = true
|
72
67
|
|
73
|
-
|
74
|
-
|
68
|
+
# Multiple reflections per execution.
|
69
|
+
execution.reflections.each_with_index do |value, index|
|
75
70
|
|
76
|
-
|
77
|
-
|
71
|
+
# Flag first reflection is a control.
|
72
|
+
is_control = false
|
73
|
+
is_control = true if index == 0
|
78
74
|
|
79
|
-
|
75
|
+
# Create reflection.
|
76
|
+
reflection = Reflection.new(execution, method, is_control)
|
77
|
+
execution.reflections[index] = reflection
|
80
78
|
|
81
|
-
|
79
|
+
# Execute reflection.
|
80
|
+
reflection.reflect(*args)
|
82
81
|
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
# Add result.
|
83
|
+
class_name = execution.caller_class.to_s
|
84
|
+
method_name = method.to_s
|
85
|
+
@@reflekt_db.get("#{class_name}.#{method_name}").push(reflection.result())
|
86
86
|
|
87
|
-
|
87
|
+
end
|
88
88
|
|
89
|
-
|
89
|
+
# Save results.
|
90
|
+
@@reflekt_db.write()
|
91
|
+
|
92
|
+
# Render results.
|
93
|
+
reflekt_render()
|
90
94
|
|
91
|
-
|
95
|
+
execution.is_reflecting = false
|
96
|
+
end
|
92
97
|
|
93
|
-
|
94
|
-
method_name = method.to_s
|
98
|
+
end
|
95
99
|
|
96
|
-
|
100
|
+
@reflection_counts[method] = @reflection_counts[method] + 1
|
97
101
|
|
98
|
-
|
99
|
-
|
102
|
+
# Continue execution / shadow execution.
|
103
|
+
super *args
|
100
104
|
|
101
|
-
args.each do |arg|
|
102
|
-
case arg
|
103
|
-
when Integer
|
104
|
-
input << rand(9999)
|
105
|
-
else
|
106
|
-
input << arg
|
107
105
|
end
|
108
|
-
end
|
109
106
|
|
110
|
-
# Action method with new arguments.
|
111
|
-
begin
|
112
|
-
output = clone.send(method, *input)
|
113
|
-
|
114
|
-
# Build reflection.
|
115
|
-
reflection = {
|
116
|
-
REFLEKT_TIME => Time.now.to_i,
|
117
|
-
REFLEKT_INPUT => reflekt_normalize_input(input),
|
118
|
-
REFLEKT_OUTPUT => reflekt_normalize_output(output)
|
119
|
-
}
|
120
|
-
|
121
|
-
# When fail.
|
122
|
-
rescue StandardError => message
|
123
|
-
reflection[REFLEKT_STATUS] = REFLEKT_MESSAGE
|
124
|
-
reflection[REFLEKT_MESSAGE] = message
|
125
|
-
# When pass.
|
126
|
-
else
|
127
|
-
reflection[REFLEKT_STATUS] = REFLEKT_PASS
|
128
107
|
end
|
129
108
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
end
|
109
|
+
# Continue initialization.
|
110
|
+
super
|
134
111
|
|
135
|
-
##
|
136
|
-
# Normalize inputs.
|
137
|
-
#
|
138
|
-
# @param args - The actual inputs.
|
139
|
-
# @return - A generic inputs representation.
|
140
|
-
##
|
141
|
-
def reflekt_normalize_input(args)
|
142
|
-
inputs = []
|
143
|
-
args.each do |arg|
|
144
|
-
input = {
|
145
|
-
REFLEKT_TYPE => arg.class.to_s,
|
146
|
-
REFLEKT_VALUE => reflekt_normalize_value(arg)
|
147
|
-
}
|
148
|
-
if (arg.class == Array)
|
149
|
-
input[REFLEKT_COUNT] = arg.count
|
150
|
-
end
|
151
|
-
inputs << input
|
152
|
-
end
|
153
|
-
inputs
|
154
112
|
end
|
155
113
|
|
156
114
|
##
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# @param output - The actual output.
|
160
|
-
# @return - A generic output representation.
|
115
|
+
# Render results.
|
161
116
|
##
|
162
|
-
def reflekt_normalize_output(output)
|
163
|
-
|
164
|
-
o = {
|
165
|
-
REFLEKT_TYPE => output.class.to_s,
|
166
|
-
REFLEKT_VALUE => reflekt_normalize_value(output)
|
167
|
-
}
|
168
|
-
|
169
|
-
if (output.class == Array || output.class == Hash)
|
170
|
-
o[REFLEKT_COUNT] = output.count
|
171
|
-
elsif (output.class == TrueClass || output.class == FalseClass)
|
172
|
-
o[REFLEKT_TYPE] = :Boolean
|
173
|
-
end
|
174
|
-
|
175
|
-
return o
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def reflekt_normalize_value(value)
|
180
|
-
|
181
|
-
unless value.nil?
|
182
|
-
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
183
|
-
if value.length >= 30
|
184
|
-
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
return value
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
117
|
def reflekt_render()
|
193
118
|
|
194
|
-
#
|
119
|
+
# Get JSON.
|
195
120
|
@@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
|
121
|
+
|
122
|
+
# Save HTML.
|
196
123
|
template = File.read("#{@@reflekt_path}/web/template.html.erb")
|
197
124
|
rendered = ERB.new(template).result(binding)
|
198
125
|
File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
|
@@ -225,20 +152,24 @@ module Reflekt
|
|
225
152
|
# Setup class.
|
226
153
|
def self.reflekt_setup_class()
|
227
154
|
|
228
|
-
# Receive configuration
|
155
|
+
# Receive configuration.
|
229
156
|
$ENV ||= {}
|
230
157
|
$ENV[:reflekt] ||= $ENV[:reflekt] = {}
|
231
158
|
|
159
|
+
# Set configuration.
|
232
160
|
@@reflekt_path = File.dirname(File.realpath(__FILE__))
|
233
161
|
|
234
|
-
# Create
|
162
|
+
# Create reflection tree.
|
163
|
+
@@reflekt_stack = ShadowStack.new()
|
164
|
+
|
165
|
+
# Build reflections directory.
|
235
166
|
if $ENV[:reflekt][:output_path]
|
236
167
|
@@reflekt_output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
|
237
|
-
#
|
168
|
+
# Build reflections directory in current execution path.
|
238
169
|
else
|
239
170
|
@@reflekt_output_path = File.join(Dir.pwd, 'reflections')
|
240
171
|
end
|
241
|
-
|
172
|
+
# Create reflections directory.
|
242
173
|
unless Dir.exist? @@reflekt_output_path
|
243
174
|
Dir.mkdir(@@reflekt_output_path)
|
244
175
|
end
|
@@ -252,7 +183,7 @@ module Reflekt
|
|
252
183
|
|
253
184
|
module SingletonClassMethods
|
254
185
|
|
255
|
-
@@
|
186
|
+
@@reflekt_skipped_methods = Set.new
|
256
187
|
|
257
188
|
##
|
258
189
|
# Skip a method.
|
@@ -260,16 +191,16 @@ module Reflekt
|
|
260
191
|
# @param method - A symbol representing the method name.
|
261
192
|
##
|
262
193
|
def reflekt_skip(method)
|
263
|
-
@@
|
194
|
+
@@reflekt_skipped_methods.add(method)
|
264
195
|
end
|
265
196
|
|
266
|
-
def
|
267
|
-
return true if @@
|
197
|
+
def reflekt_skipped?(method)
|
198
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
268
199
|
false
|
269
200
|
end
|
270
201
|
|
271
202
|
def reflekt_limit(amount)
|
272
|
-
|
203
|
+
@@reflection_limit = amount
|
273
204
|
end
|
274
205
|
|
275
206
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reflekt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maedi Prichard
|
@@ -30,11 +30,14 @@ executables: []
|
|
30
30
|
extensions: []
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
|
+
- lib/Execution.rb
|
34
|
+
- lib/Reflection.rb
|
35
|
+
- lib/ShadowStack.rb
|
33
36
|
- lib/reflekt.rb
|
34
37
|
- lib/web/script.js
|
35
38
|
- lib/web/style.css
|
36
39
|
- lib/web/template.html.erb
|
37
|
-
homepage: https://github.com/
|
40
|
+
homepage: https://github.com/refIekt/reflekt
|
38
41
|
licenses:
|
39
42
|
- MPL-2.0
|
40
43
|
metadata: {}
|