reflekt 0.9.8 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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