reflekt 0.7.2 → 0.9.5
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/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
|