reflekt 0.9.8 → 1.0.3

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.
@@ -1,3 +1,12 @@
1
+ ################################################################################
2
+ # A shadow execution.
3
+ #
4
+ # @hierachy
5
+ # 1. Execution <- YOU ARE HERE
6
+ # 2. Reflection
7
+ # 3. Meta
8
+ ################################################################################
9
+
1
10
  class Execution
2
11
 
3
12
  attr_accessor :unique_id
@@ -17,12 +26,12 @@ class Execution
17
26
  ##
18
27
  # Create Execution.
19
28
  #
20
- # @param Object object - The calling object.
21
- # @param Symbol method - The calling method.
22
- # @param Integer number - The number of reflections to create per execution.
23
- # @param ShadowStack stack - The shadow execution call stack.
29
+ # @param object [Object] The calling object.
30
+ # @param method [Symbol] The calling method.
31
+ # @param reflect_amount [Integer] The number of reflections to create per execution.
32
+ # @param stack [ShadowStack] The shadow execution call stack.
24
33
  ##
25
- def initialize(caller_object, method, number, stack)
34
+ def initialize(caller_object, method, reflect_amount, stack)
26
35
 
27
36
  @time = Time.now.to_i
28
37
  @unique_id = @time + rand(1..99999)
@@ -42,7 +51,7 @@ class Execution
42
51
 
43
52
  # Reflections.
44
53
  @control = nil
45
- @reflections = Array.new(number)
54
+ @reflections = Array.new(reflect_amount)
46
55
 
47
56
  # State.
48
57
  if @stack.peek() == nil
@@ -1,12 +1,24 @@
1
1
  ################################################################################
2
- # Meta for input and output. All meta behave the same.
2
+ # Metadata for input and output.
3
3
  #
4
- # @pattern Abstract class.
4
+ # @pattern Abstract class
5
5
  # @see lib/meta for each meta.
6
+ #
7
+ # @hierachy
8
+ # 1. Execution
9
+ # 2. Reflection
10
+ # 3. Meta <- YOU ARE HERE
6
11
  ################################################################################
7
12
 
8
13
  class Meta
9
14
 
15
+ ##
16
+ # Each meta defines its type.
17
+ ##
18
+ def initialize()
19
+ @type = nil
20
+ end
21
+
10
22
  ##
11
23
  # Each meta loads values.
12
24
  #
@@ -16,12 +28,44 @@ class Meta
16
28
  end
17
29
 
18
30
  ##
19
- # Each meta provides metadata.
31
+ # Each meta serializes metadata.
20
32
  #
21
33
  # @return [Hash]
22
34
  ##
23
- def result()
35
+ def serialize()
24
36
  {}
25
37
  end
26
38
 
39
+ ##############################################################################
40
+ # CLASS
41
+ ##############################################################################
42
+
43
+ ##
44
+ # Deserialize metadata.
45
+ #
46
+ # @todo Deserialize should create a Meta object.
47
+ # @todo Require each Meta type to handle its own deserialization.
48
+ #
49
+ # @param meta [Hash] The metadata to deserialize.
50
+ # @param meta [Hash]
51
+ ##
52
+ def self.deserialize(meta)
53
+
54
+ # Convert nil meta into NullMeta.
55
+ # Meta is nil when there are no @inputs or @output on the method.
56
+ if meta.nil?
57
+ return NullMeta.new().serialize()
58
+ end
59
+
60
+ # Symbolize keys.
61
+ # TODO: Remove once "Fix Rowdb.get(path)" bug fixed.
62
+ meta = meta.transform_keys(&:to_sym)
63
+
64
+ # Symbolize type value.
65
+ meta[:type] = meta[:type].to_sym
66
+
67
+ return meta
68
+
69
+ end
70
+
27
71
  end
@@ -1,13 +1,13 @@
1
1
  ################################################################################
2
- # Create Meta.
2
+ # Create metadata.
3
3
  #
4
- # @pattern Builder.
4
+ # @pattern Builder
5
5
  # @see lib/meta for each meta.
6
6
  ################################################################################
7
7
 
8
8
  require 'Meta'
9
- require_relative './meta/IntegerMeta'
10
- require_relative './meta/StringMeta'
9
+ # Require all meta.
10
+ Dir[File.join(__dir__, 'meta', '*.rb')].each { |file| require file }
11
11
 
12
12
  class MetaBuilder
13
13
 
@@ -19,9 +19,16 @@ class MetaBuilder
19
19
  def self.create(value)
20
20
 
21
21
  meta = nil
22
+ data_type = value.class.to_s
22
23
 
23
- # Creates values for matching data type.
24
- case value.class.to_s
24
+ # Create meta type for matching data type.
25
+ case data_type
26
+ when "Array"
27
+ meta = ArrayMeta.new()
28
+ when "TrueClass", "FalseClass"
29
+ meta = BooleanMeta.new()
30
+ when "Float"
31
+ meta = FloatMeta.new()
25
32
  when "Integer"
26
33
  meta = IntegerMeta.new()
27
34
  when "String"
@@ -53,4 +60,25 @@ class MetaBuilder
53
60
 
54
61
  end
55
62
 
63
+ ##
64
+ # @param data_type [Type]
65
+ ##
66
+ def self.data_type_to_meta_type(value)
67
+
68
+ data_type = value.class
69
+
70
+ meta_types = {
71
+ Array => :array,
72
+ TrueClass => :bool,
73
+ FalseClass => :bool,
74
+ Float => :float,
75
+ Integer => :int,
76
+ NilClass => :null,
77
+ String => :string
78
+ }
79
+
80
+ return meta_types[data_type]
81
+
82
+ end
83
+
56
84
  end
@@ -1,20 +1,30 @@
1
1
  ################################################################################
2
2
  # A snapshot of simulated data.
3
3
  #
4
+ # @note
5
+ # A reflection's random value is within the bounds of aggregated control rule sets
6
+ # as well as as the arg type being inputted into the current control reflection.
7
+ #
4
8
  # @nomenclature
5
- # args/inputs/values are the same thing but at a different stage of lifecycle.
9
+ # args, inputs/output and meta represent different stages of a value.
6
10
  #
7
11
  # @hierachy
8
12
  # 1. Execution
9
- # 2. Reflection
10
- # 3. RuleSet
13
+ # 2. Reflection <- YOU ARE HERE
14
+ # 3. Meta
15
+ #
16
+ # @status
17
+ # - :pass [Symbol] The reflection passes the rules.
18
+ # - :fail [Symbol] The reflection fails the rules or produces a system error.
19
+ # - :error [Symbol] The control reflection produces a system error.
11
20
  ################################################################################
12
21
 
22
+ require 'Clone'
13
23
  require 'MetaBuilder'
14
24
 
15
25
  class Reflection
16
26
 
17
- attr_accessor :clone
27
+ attr_reader :status
18
28
 
19
29
  ##
20
30
  # Create a Reflection.
@@ -37,83 +47,100 @@ class Reflection
37
47
  @method = execution.method
38
48
 
39
49
  # Metadata.
40
- @inputs = []
50
+ @inputs = nil
41
51
  @output = nil
42
52
 
43
53
  # Clone the execution's calling object.
54
+ # TODO: Abstract away into Clone class.
44
55
  @clone = execution.caller_object.clone
45
- @clone_id = nil
46
56
 
47
57
  # Result.
48
58
  @status = :pass
49
59
  @time = Time.now.to_i
60
+ @message = nil
50
61
 
51
62
  end
52
63
 
53
64
  ##
54
65
  # Reflect on a method.
55
66
  #
56
- # Creates a shadow execution stack.
67
+ # Creates a shadow execution.
57
68
  # @param *args [Dynamic] The method's arguments.
58
69
  ##
59
70
  def reflect(*args)
60
71
 
61
- # Get aggregated RuleSets.
62
- agg_input_rule_sets = @aggregator.get_input_rule_sets(@klass, @method)
63
- agg_output_rule_set = @aggregator.get_output_rule_set(@klass, @method)
72
+ # Get aggregated rule sets.
73
+ input_rule_sets = @aggregator.get_input_rule_sets(@klass, @method)
74
+ output_rule_set = @aggregator.get_output_rule_set(@klass, @method)
64
75
 
65
- # Create random arguments.
66
- new_args = randomize(args)
76
+ # When arguments exist.
77
+ unless args.size == 0
67
78
 
68
- # Create metadata for each argument.
69
- @inputs = MetaBuilder.create_many(new_args)
79
+ # Create random arguments from aggregated rule sets.
80
+ unless input_rule_sets.nil?
70
81
 
71
- # Action method with new arguments.
72
- begin
82
+ # Base random arguments on the types of the current arguments.
83
+ if Aggregator.testable?(args, input_rule_sets)
73
84
 
74
- # Validate input with aggregated control RuleSets.
75
- unless agg_input_rule_sets.nil?
76
- unless @aggregator.validate_inputs(new_args, agg_input_rule_sets)
85
+ args = randomize(args, input_rule_sets)
86
+
87
+ # TODO: Fallback to argument types from aggregated control rule sets
88
+ # when arg types not testable or reflect_amount above 3.
89
+ else
77
90
  @status = :fail
78
91
  end
92
+
79
93
  end
80
94
 
95
+ # Create metadata for each argument.
96
+ # TODO: Create metadata for other inputs such as properties on the instance.
97
+ @inputs = MetaBuilder.create_many(args)
98
+
99
+ end
100
+
101
+ # Action method with new/old arguments.
102
+ begin
103
+
81
104
  # Run reflection.
82
- output = @clone.send(@method, *new_args)
105
+ output = @clone.send(@method, *args)
83
106
  @output = MetaBuilder.create(output)
84
107
 
85
- # Validate output with aggregated control RuleSets.
86
- unless agg_output_rule_set.nil?
87
- unless @aggregator.validate_output(output, agg_output_rule_set)
108
+ # Validate output against aggregated control rule sets.
109
+ unless output_rule_set.nil?
110
+ unless @aggregator.test_output(output, output_rule_set)
88
111
  @status = :fail
89
112
  end
90
113
  end
91
114
 
92
- # When fail.
115
+ # When a system error occurs.
93
116
  rescue StandardError => message
117
+
94
118
  @status = :fail
95
119
  @message = message
120
+
96
121
  end
97
122
 
98
123
  end
99
124
 
100
125
  ##
101
- # Create random values for each argument.
126
+ # Create random values for each argument from control reflections.
102
127
  #
103
128
  # @param args [Dynamic] The arguments to create random values for.
129
+ # @param input_rule_sets [Array] Aggregated rule sets for each argument.
130
+ #
104
131
  # @return [Dynamic] Random arguments.
105
132
  ##
106
- def randomize(args)
133
+ def randomize(args, input_rule_sets)
107
134
 
108
135
  random_args = []
109
136
 
110
- args.each do |arg|
111
- case arg
112
- when Integer
113
- random_args << rand(999)
114
- else
115
- random_args << arg
116
- end
137
+ args.each_with_index do |arg, arg_num|
138
+
139
+ rule_type = Aggregator.value_to_rule_type(arg)
140
+ agg_rule = input_rule_sets[arg_num].rules[rule_type]
141
+
142
+ random_args << agg_rule.random()
143
+
117
144
  end
118
145
 
119
146
  return random_args
@@ -125,7 +152,7 @@ class Reflection
125
152
  #
126
153
  # @return [Hash] Reflection metadata.
127
154
  ##
128
- def result()
155
+ def serialize()
129
156
 
130
157
  # The ID of the first execution in the ShadowStack.
131
158
  base_id = nil
@@ -144,11 +171,19 @@ class Reflection
144
171
  :method => @method,
145
172
  :status => @status,
146
173
  :message => @message,
147
- :inputs => [],
148
- :output => @output,
174
+ :inputs => nil,
175
+ :output => nil,
149
176
  }
150
- @inputs.each do |meta|
151
- reflection[:inputs] << meta.result()
177
+
178
+ unless @inputs.nil?
179
+ reflection[:inputs] = []
180
+ @inputs.each do |meta|
181
+ reflection[:inputs] << meta.serialize()
182
+ end
183
+ end
184
+
185
+ unless @output.nil?
186
+ reflection[:output] = @output.serialize()
152
187
  end
153
188
 
154
189
  return reflection
@@ -1,14 +1,15 @@
1
1
  ################################################################################
2
2
  # Reflective testing.
3
3
  #
4
- # @author
5
- # Maedi Prichard
4
+ # @author Maedi Prichard
6
5
  #
7
6
  # @flow
8
- # 1. An Execution is created on method call.
9
- # 2. Many Refections are created per Execution.
10
- # 3. Each Reflection executes on cloned data.
11
- # 4. Flow is returned to the original method.
7
+ # 1. Reflekt is prepended to a class and setup.
8
+ # 2. When a class insantiates so does Reflekt.
9
+ # 3. An Execution is created on method call.
10
+ # 4. Many Refections are created per Execution.
11
+ # 5. Each Reflection executes on cloned data.
12
+ # 6. Flow is returned to the original method.
12
13
  #
13
14
  # @usage
14
15
  # class ExampleClass
@@ -20,91 +21,126 @@ require 'erb'
20
21
  require 'rowdb'
21
22
  require 'Accessor'
22
23
  require 'Aggregator'
24
+ require 'Config'
23
25
  require 'Control'
24
26
  require 'Execution'
25
27
  require 'Reflection'
26
28
  require 'Renderer'
27
29
  require 'ShadowStack'
30
+ # Require all rules.
31
+ Dir[File.join(__dir__, 'rules', '*.rb')].each { |file| require file }
28
32
 
29
33
  module Reflekt
30
34
 
31
35
  def initialize(*args)
32
36
 
37
+ # TODO: Store counts on @@reflekt and key by instance ID.
33
38
  @reflekt_counts = {}
34
39
 
35
- # Get instance methods.
36
- # TODO: Include parent methods like "Array.include?".
37
- self.class.instance_methods(false).each do |method|
40
+ # Get child and parent instance methods.
41
+ parent_instance_methods = self.class.superclass.instance_methods(false)
42
+ child_instance_methods = self.class.instance_methods(false)
43
+ instance_methods = parent_instance_methods + child_instance_methods
38
44
 
39
- # Don't process skipped methods.
40
- next if self.class.reflekt_skipped?(method)
45
+ # TODO: Include core methods like "Array.include?".
46
+ instance_methods.each do |method|
41
47
 
42
48
  @reflekt_counts[method] = 0
43
49
 
44
50
  # When method called in flow.
45
51
  self.define_singleton_method(method) do |*args|
46
52
 
47
- # Don't reflect when limit reached.
48
- unless @reflekt_counts[method] >= @@reflekt.reflect_limit
53
+ # When Reflekt enabled and control reflection has executed without error.
54
+ if @@reflekt.config.enabled && !@@reflekt.error
49
55
 
50
56
  # Get current execution.
51
57
  execution = @@reflekt.stack.peek()
52
58
 
53
- # When stack empty or past execution done reflecting.
54
- if execution.nil? || execution.has_finished_reflecting?
59
+ # Don't reflect when reflect limit reached or method skipped.
60
+ unless (@reflekt_counts[method] >= @@reflekt.config.reflect_limit) || self.class.reflekt_skipped?(method)
55
61
 
56
- # Create execution.
57
- execution = Execution.new(self, method, @@reflekt.reflect_amount, @@reflekt.stack)
62
+ # When stack empty or past execution done reflecting.
63
+ if execution.nil? || execution.has_finished_reflecting?
58
64
 
59
- @@reflekt.stack.push(execution)
65
+ # Create execution.
66
+ execution = Execution.new(self, method, @@reflekt.config.reflect_amount, @@reflekt.stack)
60
67
 
61
- end
68
+ @@reflekt.stack.push(execution)
69
+
70
+ end
71
+
72
+ ##
73
+ # Reflect the execution.
74
+ #
75
+ # The first method call in the execution creates a reflection.
76
+ # Then method calls are shadow executions which return to the reflection.
77
+ ##
78
+ if execution.has_empty_reflections? && !execution.is_reflecting?
79
+ execution.is_reflecting = true
80
+
81
+ # Create control.
82
+ control = Control.new(execution, 0, @@reflekt.aggregator)
83
+ execution.control = control
62
84
 
63
- # Reflect.
64
- # The first method call in the Execution creates a Reflection.
65
- # Subsequent method calls are shadow executions on cloned objects.
66
- if execution.has_empty_reflections? && !execution.is_reflecting?
67
- execution.is_reflecting = true
85
+ # Execute control.
86
+ control.reflect(*args)
68
87
 
69
- # Create control.
70
- control = Control.new(execution, 1, @@reflekt.aggregator)
71
- execution.control = control
88
+ # Stop reflecting when control fails to execute.
89
+ if control.status == :error
90
+ @@reflekt.error = true
91
+ # Continue reflecting when control executes succesfully.
92
+ else
72
93
 
73
- # Execute control.
74
- control.reflect(*args)
94
+ # Save control as reflection.
95
+ @@reflekt.db.get("reflections").push(control.serialize())
75
96
 
76
- # Save control.
77
- @@reflekt.db.get("controls").push(control.result())
97
+ # Multiple reflections per execution.
98
+ execution.reflections.each_with_index do |value, index|
78
99
 
79
- # Multiple reflections per execution.
80
- execution.reflections.each_with_index do |value, index|
100
+ # Create reflection.
101
+ reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
102
+ execution.reflections[index] = reflection
81
103
 
82
- # Create reflection.
83
- reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
84
- execution.reflections[index] = reflection
104
+ # Execute reflection.
105
+ reflection.reflect(*args)
106
+ @reflekt_counts[method] = @reflekt_counts[method] + 1
85
107
 
86
- # Execute reflection.
87
- reflection.reflect(*args)
88
- @reflekt_counts[method] = @reflekt_counts[method] + 1
108
+ # Save reflection.
109
+ @@reflekt.db.get("reflections").push(reflection.serialize())
89
110
 
90
- # Save reflection.
91
- @@reflekt.db.get("reflections").push(reflection.result())
111
+ end
92
112
 
113
+ # Save control.
114
+ @@reflekt.db.get("controls").push(control.serialize())
115
+
116
+ # Save results.
117
+ @@reflekt.db.write()
118
+
119
+ # Render results.
120
+ @@reflekt.renderer.render()
121
+
122
+ end
123
+
124
+ execution.is_reflecting = false
93
125
  end
94
126
 
95
- # Save results.
96
- @@reflekt.db.write()
127
+ end
128
+
129
+ # Don't execute skipped methods when reflecting.
130
+ unless execution.is_reflecting? && self.class.reflekt_skipped?(method)
97
131
 
98
- # Render results.
99
- @@reflekt.renderer.render()
132
+ # Continue execution / shadow execution.
133
+ super *args
100
134
 
101
- execution.is_reflecting = false
102
135
  end
103
136
 
104
- end
137
+ # When Reflekt disabled or control reflection failed.
138
+ else
105
139
 
106
- # Continue execution / shadow execution.
107
- super *args
140
+ # Continue execution.
141
+ super *args
142
+
143
+ end
108
144
 
109
145
  end
110
146
 
@@ -115,6 +151,13 @@ module Reflekt
115
151
 
116
152
  end
117
153
 
154
+ ##
155
+ # Provide Config instance to block.
156
+ ##
157
+ def self.configure
158
+ yield(@@reflekt.config)
159
+ end
160
+
118
161
  private
119
162
 
120
163
  def self.prepended(base)
@@ -132,18 +175,16 @@ module Reflekt
132
175
  def self.reflekt_setup_class()
133
176
 
134
177
  # Receive configuration.
135
- $ENV ||= {}
136
- $ENV[:reflekt] ||= $ENV[:reflekt] = {}
137
- $ENV[:reflekt][:output_directory] = "reflections"
178
+ @@reflekt.config = Config.new()
138
179
 
139
180
  # Set configuration.
140
181
  @@reflekt.path = File.dirname(File.realpath(__FILE__))
141
182
 
142
183
  # Get reflections directory path from config or current execution path.
143
- if $ENV[:reflekt][:output_path]
144
- @@reflekt.output_path = File.join($ENV[:reflekt][:output_path], $ENV[:reflekt][:output_directory])
184
+ if @@reflekt.config.output_path
185
+ @@reflekt.output_path = File.join(@@reflekt.config.output_path, @@reflekt.config.output_directory)
145
186
  else
146
- @@reflekt.output_path = File.join(Dir.pwd, $ENV[:reflekt][:output_directory])
187
+ @@reflekt.output_path = File.join(Dir.pwd, @@reflekt.config.output_directory)
147
188
  end
148
189
 
149
190
  # Create reflections directory.
@@ -161,16 +202,9 @@ module Reflekt
161
202
  @@reflekt.stack = ShadowStack.new()
162
203
 
163
204
  # Create aggregated rule sets.
164
- @@reflekt.aggregator = Aggregator.new()
205
+ @@reflekt.aggregator = Aggregator.new(@@reflekt.config.meta_map)
165
206
  @@reflekt.aggregator.train(db[:controls])
166
207
 
167
- # The amount of reflections to create per method call.
168
- @@reflekt.reflect_amount = 2
169
-
170
- # Limit the amount of reflections that can be created per instance method.
171
- # A method called thousands of times doesn't need that many reflections.
172
- @@reflekt.reflect_limit = 10
173
-
174
208
  # Create renderer.
175
209
  @@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
176
210
 
@@ -185,6 +219,10 @@ module Reflekt
185
219
  ##
186
220
  # Skip a method.
187
221
  #
222
+ # @note
223
+ # Class variables cascade to child classes.
224
+ # So a reflekt_skip on the parent class will persist to the child class.
225
+ #
188
226
  # @param method [Symbol] The method name.
189
227
  ##
190
228
  def reflekt_skip(method)
@@ -196,9 +234,9 @@ module Reflekt
196
234
  false
197
235
  end
198
236
 
199
- def reflekt_limit(amount)
200
- @@reflekt.reflect_limit = amount
201
- end
237
+ #def reflekt_limit(amount)
238
+ # @@reflekt.reflect_limit = amount
239
+ #end
202
240
 
203
241
  end
204
242