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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d92d12a27556a25c258f83cf2983ffc2758a70c5cd359f4e7beafb862e88e68c
4
- data.tar.gz: be5fa824eb0e6cd3bd15e03da504c40a65e471687881a5fbfcebc0d0906399fd
3
+ metadata.gz: f2048b61fdb7acc0deede9ee5bcae644e6d51fa6e2dab2842f748433ce5ba949
4
+ data.tar.gz: dce2b1bd92e2eaf90c16e1cad6c27d9b9fc686001c958124f184a5d11a2ebe8d
5
5
  SHA512:
6
- metadata.gz: 9ffb39bc278932fd985731c7e509767e79c3356da41d7114a57cfe47a774bb97742ae283837859e2ff158ff84121e2b565c2bf25aff1ae0e8345671b9bd8da22
7
- data.tar.gz: 3db51deab75140780e718e3536ab06696a1d0ef5308b1cbfcf2fb8a56dc9144905f22f2f53e918afcd7d1e962416134fc41a07676e17c4a16b935c70aece19fb
6
+ metadata.gz: b4bdebf8083aef0a50806c1970b25f90736dfe68b8373f5a742ef6c7f2e4139e4ca0d273d683d826230069de2039c5fda7428c4ed8147471da00f1e93704f59a
7
+ data.tar.gz: afbe22e05eb10efe8ead84240c4296546e10570c19e73efea912494e55a85538c8fbed2872c42485ea21ba696a101347e73ec5feee37a568636a154dcae8bcf6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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