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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d92d12a27556a25c258f83cf2983ffc2758a70c5cd359f4e7beafb862e88e68c
4
- data.tar.gz: be5fa824eb0e6cd3bd15e03da504c40a65e471687881a5fbfcebc0d0906399fd
3
+ metadata.gz: e4d3c0c32672fd8813482a3f546032904d3d6f65e592fd4435eb7ada2bb1b201
4
+ data.tar.gz: d48f2b100986c4408db5cb055f4ebf1e6a50d99fa99dc6d98e8af8e5d8bcd66e
5
5
  SHA512:
6
- metadata.gz: 9ffb39bc278932fd985731c7e509767e79c3356da41d7114a57cfe47a774bb97742ae283837859e2ff158ff84121e2b565c2bf25aff1ae0e8345671b9bd8da22
7
- data.tar.gz: 3db51deab75140780e718e3536ab06696a1d0ef5308b1cbfcf2fb8a56dc9144905f22f2f53e918afcd7d1e962416134fc41a07676e17c4a16b935c70aece19fb
6
+ metadata.gz: 1422110bc34f7b88f8b4ba71359c408d38f3ebf72235735e51864fd0bd16c32fd5abc20e5cfa8a516478932800590f80f7f1b7b4d0dac3c0ebd18a3d8dfffd54
7
+ data.tar.gz: 4019f5868ca20d1aebfa9e4ddb2c5136782aa52185588cb44558f4725f51ea59f90bc75896e8bfac383151d7346bb534d98bc00a11a569c350d0c460fe3b31ab
@@ -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
@@ -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
@@ -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
@@ -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
- # Usage. Prepend to the class like so:
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
- # Reflection keys.
17
- REFLEKT_TIME = "t"
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
- def initialize(*args)
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
- @reflekt_forked = false
34
- @reflekt_clones = []
31
+ def initialize(*args)
35
32
 
36
- # Limit the amount of clones that can be created per instance.
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
- # Override methods.
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
- # When method called in flow.
46
- if @reflekt_forked
39
+ # Don't process skipped methods.
40
+ next if self.class.reflekt_skipped?(method)
47
41
 
48
- if @reflekt_count < @reflekt_limit
49
- unless self.class.deflekted?(method)
42
+ @reflection_counts[method] = 0
50
43
 
51
- # Reflekt on method.
52
- @reflekt_clones.each do |clone|
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
- # Save results.
57
- @@reflekt_db.write()
47
+ # Don't reflect when limit reached.
48
+ unless @reflection_counts[method] >= @@reflection_limit
58
49
 
59
- reflekt_render()
50
+ # Get current execution.
51
+ execution = @@reflekt_stack.peek()
60
52
 
61
- end
62
- @reflekt_count = @reflekt_count + 1
63
- end
53
+ # When stack empty or past execution done reflecting.
54
+ if execution.nil? || execution.has_finished_reflecting?
64
55
 
65
- end
56
+ # Create execution.
57
+ execution = Execution.new(self, @@reflekt_reflect_amount)
58
+ @@reflekt_stack.push(execution)
66
59
 
67
- # Continue method flow.
68
- super *args
69
- end
60
+ end
70
61
 
71
- end
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
- # Continue contructor flow.
74
- super
68
+ # Multiple reflections per execution.
69
+ execution.reflections.each_with_index do |value, index|
75
70
 
76
- # Create forks.
77
- reflekt_fork()
71
+ # Flag first reflection is a control.
72
+ is_control = false
73
+ is_control = true if index == 0
78
74
 
79
- end
75
+ # Create reflection.
76
+ reflection = Reflection.new(execution, method, is_control)
77
+ execution.reflections[index] = reflection
80
78
 
81
- def reflekt_fork()
79
+ # Execute reflection.
80
+ reflection.reflect(*args)
82
81
 
83
- @@reflekt_clone_count.times do |clone|
84
- @reflekt_clones << self.clone
85
- end
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
- @reflekt_forked = true
87
+ end
88
88
 
89
- end
89
+ # Save results.
90
+ @@reflekt_db.write()
91
+
92
+ # Render results.
93
+ reflekt_render()
90
94
 
91
- def reflekt_action(clone, method, *args)
95
+ execution.is_reflecting = false
96
+ end
92
97
 
93
- class_name = clone.class.to_s
94
- method_name = method.to_s
98
+ end
95
99
 
96
- # TODO: Create control fork. Get good value. Check against it.
100
+ @reflection_counts[method] = @reflection_counts[method] + 1
97
101
 
98
- # Create new arguments that are deviations on inputted type.
99
- input = []
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
- # Save reflection.
131
- @@reflekt_db.get("#{class_name}.#{method_name}").push(reflection)
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
- # Normalize output.
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
- # Render results.
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 from host application.
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 "reflections" directory in configured path.
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
- # Create "reflections" directory in current execution path.
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
- @@deflekted_methods = Set.new
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
- @@deflekted_methods.add(method)
194
+ @@reflekt_skipped_methods.add(method)
264
195
  end
265
196
 
266
- def deflekted?(method)
267
- return true if @@deflekted_methods.include?(method)
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
- @reflekt_limit = amount
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.7.2
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/maedi/reflekt
40
+ homepage: https://github.com/refIekt/reflekt
38
41
  licenses:
39
42
  - MPL-2.0
40
43
  metadata: {}