reflekt 0.7.2 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/Accessor.rb +37 -0
- data/lib/Control.rb +33 -0
- data/lib/Execution.rb +47 -0
- data/lib/Reflection.rb +163 -0
- data/lib/Reflekt.rb +223 -0
- data/lib/Renderer.rb +38 -0
- data/lib/Rule.rb +32 -0
- data/lib/RulePool.rb +44 -0
- data/lib/Ruler.rb +124 -0
- data/lib/ShadowStack.rb +56 -0
- data/lib/rules/FloatRule.rb +24 -0
- data/lib/rules/IntegerRule.rb +27 -0
- data/lib/rules/StringRule.rb +24 -0
- data/lib/web/style.css +13 -1
- data/lib/web/template.html.erb +71 -15
- metadata +16 -4
- data/lib/reflekt.rb +0 -277
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2048b61fdb7acc0deede9ee5bcae644e6d51fa6e2dab2842f748433ce5ba949
|
4
|
+
data.tar.gz: dce2b1bd92e2eaf90c16e1cad6c27d9b9fc686001c958124f184a5d11a2ebe8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4bdebf8083aef0a50806c1970b25f90736dfe68b8373f5a742ef6c7f2e4139e4ca0d273d683d826230069de2039c5fda7428c4ed8147471da00f1e93704f59a
|
7
|
+
data.tar.gz: afbe22e05eb10efe8ead84240c4296546e10570c19e73efea912494e55a85538c8fbed2872c42485ea21ba696a101347e73ec5feee37a568636a154dcae8bcf6
|
data/lib/Accessor.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
################################################################################
|
2
|
+
# ACCESSOR
|
3
|
+
#
|
4
|
+
# Access variables via one object to avoid polluting the caller class.
|
5
|
+
#
|
6
|
+
# Only 2 variables are not accessed via Accessor:
|
7
|
+
# - @reflection_counts on the instance
|
8
|
+
# - @@reflekt_skipped_methods on the instance's singleton class
|
9
|
+
################################################################################
|
10
|
+
|
11
|
+
class Accessor
|
12
|
+
|
13
|
+
attr_accessor :setup
|
14
|
+
attr_accessor :db
|
15
|
+
attr_accessor :stack
|
16
|
+
attr_accessor :renderer
|
17
|
+
attr_accessor :rules
|
18
|
+
attr_accessor :path
|
19
|
+
attr_accessor :output_path
|
20
|
+
attr_accessor :reflect_amount
|
21
|
+
attr_accessor :reflection_limit
|
22
|
+
|
23
|
+
def initialize()
|
24
|
+
|
25
|
+
@setup = nil
|
26
|
+
@db = nil
|
27
|
+
@stack = nil
|
28
|
+
@renderer = nil
|
29
|
+
@rules = nil
|
30
|
+
@path = nil
|
31
|
+
@output_path = nil
|
32
|
+
@reflect_amount = nil
|
33
|
+
@reflection_limit = nil
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/Control.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'Reflection'
|
2
|
+
|
3
|
+
class Control < Reflection
|
4
|
+
|
5
|
+
##
|
6
|
+
# Reflect on a method.
|
7
|
+
#
|
8
|
+
# Creates a shadow execution stack.
|
9
|
+
#
|
10
|
+
# @param method - The name of the method.
|
11
|
+
# @param *args - The method arguments.
|
12
|
+
#
|
13
|
+
# @return - A reflection hash.
|
14
|
+
##
|
15
|
+
def reflect(*args)
|
16
|
+
|
17
|
+
@inputs = *args
|
18
|
+
|
19
|
+
# Action method with new arguments.
|
20
|
+
begin
|
21
|
+
@output = @clone.send(@method, *@inputs)
|
22
|
+
# When fail.
|
23
|
+
rescue StandardError => message
|
24
|
+
@status = FAIL
|
25
|
+
@message = message
|
26
|
+
# When pass.
|
27
|
+
else
|
28
|
+
@status = PASS
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/Execution.rb
ADDED
@@ -0,0 +1,47 @@
|
|
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 :control
|
9
|
+
attr_accessor :reflections
|
10
|
+
attr_accessor :is_reflecting
|
11
|
+
|
12
|
+
def initialize(object, reflection_count)
|
13
|
+
|
14
|
+
@object = object
|
15
|
+
@caller_id = object.object_id
|
16
|
+
@caller_class = object.class
|
17
|
+
@parent = nil
|
18
|
+
@child = nil
|
19
|
+
|
20
|
+
@control = []
|
21
|
+
@reflections = Array.new(reflection_count)
|
22
|
+
@is_reflecting = false
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_empty_reflections?
|
27
|
+
@reflections.include? nil
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Is the Execution currently reflecting methods?
|
32
|
+
##
|
33
|
+
def is_reflecting?
|
34
|
+
@is_reflecting
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_finished_reflecting?
|
38
|
+
if is_reflecting?
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
if has_empty_reflections?
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/Reflection.rb
ADDED
@@ -0,0 +1,163 @@
|
|
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, ruler)
|
19
|
+
|
20
|
+
@execution = execution
|
21
|
+
@method = method
|
22
|
+
@ruler = ruler
|
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
|
+
@inputs = []
|
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
|
+
# Create deviated arguments.
|
49
|
+
args.each do |arg|
|
50
|
+
case arg
|
51
|
+
when Integer
|
52
|
+
@inputs << rand(999)
|
53
|
+
else
|
54
|
+
@inputs << arg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Action method with new arguments.
|
59
|
+
begin
|
60
|
+
|
61
|
+
# Validate input with controls.
|
62
|
+
unless @ruler.nil?
|
63
|
+
if @ruler.validate_inputs(@inputs)
|
64
|
+
@status = PASS
|
65
|
+
else
|
66
|
+
@status = FAIL
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Run reflection.
|
71
|
+
@output = @clone.send(@method, *@inputs)
|
72
|
+
|
73
|
+
# Validate output with controls.
|
74
|
+
unless @ruler.nil?
|
75
|
+
if @ruler.validate_output(@output)
|
76
|
+
@status = PASS
|
77
|
+
else
|
78
|
+
@status = FAIL
|
79
|
+
end
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
# When fail.
|
84
|
+
rescue StandardError => message
|
85
|
+
@status = FAIL
|
86
|
+
@message = message
|
87
|
+
|
88
|
+
# When no validation and execution fails.
|
89
|
+
else
|
90
|
+
@status = PASS
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def result()
|
96
|
+
# Build reflection.
|
97
|
+
reflection = {
|
98
|
+
TIME => @time,
|
99
|
+
STATUS => @status,
|
100
|
+
INPUT => normalize_input(@inputs),
|
101
|
+
OUTPUT => normalize_output(@output),
|
102
|
+
MESSAGE => @message
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Normalize inputs.
|
108
|
+
#
|
109
|
+
# @param args - The actual inputs.
|
110
|
+
# @return - A generic inputs representation.
|
111
|
+
##
|
112
|
+
def normalize_input(args)
|
113
|
+
inputs = []
|
114
|
+
args.each do |arg|
|
115
|
+
input = {
|
116
|
+
TYPE => arg.class.to_s,
|
117
|
+
VALUE => normalize_value(arg)
|
118
|
+
}
|
119
|
+
if (arg.class == Array)
|
120
|
+
input[COUNT] = arg.count
|
121
|
+
end
|
122
|
+
inputs << input
|
123
|
+
end
|
124
|
+
inputs
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Normalize output.
|
129
|
+
#
|
130
|
+
# @param input - The actual output.
|
131
|
+
# @return - A generic output representation.
|
132
|
+
##
|
133
|
+
def normalize_output(input)
|
134
|
+
|
135
|
+
output = {
|
136
|
+
TYPE => input.class.to_s,
|
137
|
+
VALUE => normalize_value(input)
|
138
|
+
}
|
139
|
+
|
140
|
+
if (input.class == Array || input.class == Hash)
|
141
|
+
output[COUNT] = input.count
|
142
|
+
elsif (input.class == TrueClass || input.class == FalseClass)
|
143
|
+
output[TYPE] = :Boolean
|
144
|
+
end
|
145
|
+
|
146
|
+
return output
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
def normalize_value(value)
|
151
|
+
|
152
|
+
unless value.nil?
|
153
|
+
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
154
|
+
if value.length >= 30
|
155
|
+
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
return value
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/lib/Reflekt.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
################################################################################
|
2
|
+
# REFLEKT - By Maedi Prichard.
|
3
|
+
#
|
4
|
+
# An Execution is created each time a method is called.
|
5
|
+
# Many Refections are created per Execution.
|
6
|
+
# Each Reflection executes on a ShadowStack on cloned data.
|
7
|
+
# Then flow is returned to the original method and normal execution continues.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# class ExampleClass
|
11
|
+
# prepend Reflekt
|
12
|
+
################################################################################
|
13
|
+
|
14
|
+
require 'set'
|
15
|
+
require 'erb'
|
16
|
+
require 'rowdb'
|
17
|
+
require 'Accessor'
|
18
|
+
require 'Control'
|
19
|
+
require 'Execution'
|
20
|
+
require 'Reflection'
|
21
|
+
require 'Renderer'
|
22
|
+
require 'Ruler'
|
23
|
+
require 'ShadowStack'
|
24
|
+
|
25
|
+
module Reflekt
|
26
|
+
|
27
|
+
def initialize(*args)
|
28
|
+
|
29
|
+
@reflection_counts = {}
|
30
|
+
|
31
|
+
# Get instance methods.
|
32
|
+
# TODO: Include parent methods like "Array.include?".
|
33
|
+
self.class.instance_methods(false).each do |method|
|
34
|
+
|
35
|
+
# Don't process skipped methods.
|
36
|
+
next if self.class.reflekt_skipped?(method)
|
37
|
+
|
38
|
+
@reflection_counts[method] = 0
|
39
|
+
|
40
|
+
# When method called in flow.
|
41
|
+
self.define_singleton_method(method) do |*args|
|
42
|
+
|
43
|
+
# Don't reflect when limit reached.
|
44
|
+
unless @reflection_counts[method] >= @@reflekt.reflection_limit
|
45
|
+
|
46
|
+
# Get current execution.
|
47
|
+
execution = @@reflekt.stack.peek()
|
48
|
+
|
49
|
+
# When stack empty or past execution done reflecting.
|
50
|
+
if execution.nil? || execution.has_finished_reflecting?
|
51
|
+
|
52
|
+
# Create execution.
|
53
|
+
execution = Execution.new(self, @@reflekt.reflect_amount)
|
54
|
+
@@reflekt.stack.push(execution)
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get ruler.
|
59
|
+
# The method's ruler will not exist the first time the db generated.
|
60
|
+
if @@reflekt.rules.key? execution.caller_class.to_s.to_sym
|
61
|
+
ruler = @@reflekt.rules[execution.caller_class.to_s.to_sym][method.to_s]
|
62
|
+
else
|
63
|
+
ruler = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# Reflect.
|
67
|
+
# The first method call in the Execution creates a Reflection.
|
68
|
+
# Subsequent method calls are shadow executions on cloned objects.
|
69
|
+
if execution.has_empty_reflections? && !execution.is_reflecting?
|
70
|
+
execution.is_reflecting = true
|
71
|
+
|
72
|
+
class_name = execution.caller_class.to_s
|
73
|
+
method_name = method.to_s
|
74
|
+
|
75
|
+
# Create control.
|
76
|
+
control = Control.new(execution, method, ruler)
|
77
|
+
execution.control = control
|
78
|
+
|
79
|
+
# Execute control.
|
80
|
+
control.reflect(*args)
|
81
|
+
|
82
|
+
# Save control.
|
83
|
+
@@reflekt.db.get("#{class_name}.#{method_name}.controls").push(control.result())
|
84
|
+
|
85
|
+
# Multiple reflections per execution.
|
86
|
+
execution.reflections.each_with_index do |value, index|
|
87
|
+
|
88
|
+
# Create reflection.
|
89
|
+
reflection = Reflection.new(execution, method, ruler)
|
90
|
+
execution.reflections[index] = reflection
|
91
|
+
|
92
|
+
# Execute reflection.
|
93
|
+
reflection.reflect(*args)
|
94
|
+
@reflection_counts[method] = @reflection_counts[method] + 1
|
95
|
+
|
96
|
+
# Save reflection.
|
97
|
+
@@reflekt.db.get("#{class_name}.#{method_name}.reflections").push(reflection.result())
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# Save results.
|
102
|
+
@@reflekt.db.write()
|
103
|
+
|
104
|
+
# Render results.
|
105
|
+
@@reflekt.renderer.render()
|
106
|
+
|
107
|
+
execution.is_reflecting = false
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Continue execution / shadow execution.
|
113
|
+
super *args
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Continue initialization.
|
120
|
+
super
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def self.prepended(base)
|
127
|
+
|
128
|
+
# Prepend class methods to the instance's singleton class.
|
129
|
+
base.singleton_class.prepend(SingletonClassMethods)
|
130
|
+
|
131
|
+
# Setup class.
|
132
|
+
@@reflekt = Accessor.new()
|
133
|
+
@@reflekt.setup ||= reflekt_setup_class
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
# Setup class.
|
138
|
+
def self.reflekt_setup_class()
|
139
|
+
|
140
|
+
# Receive configuration.
|
141
|
+
$ENV ||= {}
|
142
|
+
$ENV[:reflekt] ||= $ENV[:reflekt] = {}
|
143
|
+
|
144
|
+
# Set configuration.
|
145
|
+
@@reflekt.path = File.dirname(File.realpath(__FILE__))
|
146
|
+
|
147
|
+
# Build reflections directory.
|
148
|
+
if $ENV[:reflekt][:output_path]
|
149
|
+
@@reflekt.output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
|
150
|
+
# Build reflections directory in current execution path.
|
151
|
+
else
|
152
|
+
@@reflekt.output_path = File.join(Dir.pwd, 'reflections')
|
153
|
+
end
|
154
|
+
# Create reflections directory.
|
155
|
+
unless Dir.exist? @@reflekt.output_path
|
156
|
+
Dir.mkdir(@@reflekt.output_path)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Create database.
|
160
|
+
@@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.json')
|
161
|
+
@@reflekt.db.defaults({ :reflekt => { :api_version => 1 }})
|
162
|
+
|
163
|
+
# Create shadow execution stack.
|
164
|
+
@@reflekt.stack = ShadowStack.new()
|
165
|
+
|
166
|
+
# Define rules.
|
167
|
+
# TODO: Fix Rowdb.get(path) not returning data at path after Rowdb.push()?
|
168
|
+
@@reflekt.rules = {}
|
169
|
+
db = @@reflekt.db.value()
|
170
|
+
db.each do |class_name, class_values|
|
171
|
+
@@reflekt.rules[class_name] = {}
|
172
|
+
class_values.each do |method_name, method_values|
|
173
|
+
next if method_values.nil?
|
174
|
+
next unless method_values.class == Hash
|
175
|
+
if method_values.key? "controls"
|
176
|
+
|
177
|
+
ruler = Ruler.new()
|
178
|
+
ruler.load(method_values['controls'])
|
179
|
+
ruler.train()
|
180
|
+
|
181
|
+
@@reflekt.rules[class_name][method_name] = ruler
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# The amount of reflections to create per method call.
|
187
|
+
@@reflekt.reflect_amount = 2
|
188
|
+
|
189
|
+
# Limit the amount of reflections that can be created per instance method.
|
190
|
+
# A method called thousands of times doesn't need that many reflections.
|
191
|
+
@@reflekt.reflection_limit = 10
|
192
|
+
|
193
|
+
# Create renderer.
|
194
|
+
@@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
|
195
|
+
|
196
|
+
return true
|
197
|
+
end
|
198
|
+
|
199
|
+
module SingletonClassMethods
|
200
|
+
|
201
|
+
@@reflekt_skipped_methods = Set.new
|
202
|
+
|
203
|
+
##
|
204
|
+
# Skip a method.
|
205
|
+
#
|
206
|
+
# @param method - A symbol representing the method name.
|
207
|
+
##
|
208
|
+
def reflekt_skip(method)
|
209
|
+
@@reflekt_skipped_methods.add(method)
|
210
|
+
end
|
211
|
+
|
212
|
+
def reflekt_skipped?(method)
|
213
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
214
|
+
false
|
215
|
+
end
|
216
|
+
|
217
|
+
def reflekt_limit(amount)
|
218
|
+
@@reflekt.reflection_limit = amount
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|