reflekt 0.9.7 → 1.0.2

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,12 +1,17 @@
1
1
  ################################################################################
2
- # REFLEKT - By Maedi Prichard.
2
+ # Reflective testing.
3
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.
4
+ # @author Maedi Prichard
8
5
  #
9
- # Usage:
6
+ # @flow
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.
13
+ #
14
+ # @usage
10
15
  # class ExampleClass
11
16
  # prepend Reflekt
12
17
  ################################################################################
@@ -15,92 +20,127 @@ require 'set'
15
20
  require 'erb'
16
21
  require 'rowdb'
17
22
  require 'Accessor'
23
+ require 'Aggregator'
24
+ require 'Config'
18
25
  require 'Control'
19
26
  require 'Execution'
20
27
  require 'Reflection'
21
28
  require 'Renderer'
22
- require 'Ruler'
23
29
  require 'ShadowStack'
30
+ # Require all rules.
31
+ Dir[File.join(__dir__, 'rules', '*.rb')].each { |file| require file }
24
32
 
25
33
  module Reflekt
26
34
 
27
35
  def initialize(*args)
28
36
 
37
+ # TODO: Store counts on @@reflekt and key by instance ID.
29
38
  @reflekt_counts = {}
30
39
 
31
- # Get instance methods.
32
- # TODO: Include parent methods like "Array.include?".
33
- 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
34
44
 
35
- # Don't process skipped methods.
36
- next if self.class.reflekt_skipped?(method)
45
+ # TODO: Include core methods like "Array.include?".
46
+ instance_methods.each do |method|
37
47
 
38
48
  @reflekt_counts[method] = 0
39
49
 
40
50
  # When method called in flow.
41
51
  self.define_singleton_method(method) do |*args|
42
52
 
43
- # Don't reflect when limit reached.
44
- 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
45
55
 
46
56
  # Get current execution.
47
57
  execution = @@reflekt.stack.peek()
48
58
 
49
- # When stack empty or past execution done reflecting.
50
- 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)
51
61
 
52
- # Create execution.
53
- 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?
54
64
 
55
- @@reflekt.stack.push(execution)
65
+ # Create execution.
66
+ execution = Execution.new(self, method, @@reflekt.config.reflect_amount, @@reflekt.stack)
56
67
 
57
- 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
84
+
85
+ # Execute control.
86
+ control.reflect(*args)
87
+
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
58
93
 
59
- # Reflect.
60
- # The first method call in the Execution creates a Reflection.
61
- # Subsequent method calls are shadow executions on cloned objects.
62
- if execution.has_empty_reflections? && !execution.is_reflecting?
63
- execution.is_reflecting = true
94
+ # Save control as reflection.
95
+ @@reflekt.db.get("reflections").push(control.serialize())
64
96
 
65
- # Create control.
66
- control = Control.new(execution, 1, @@reflekt.ruler)
67
- execution.control = control
97
+ # Multiple reflections per execution.
98
+ execution.reflections.each_with_index do |value, index|
68
99
 
69
- # Execute control.
70
- control.reflect(*args)
100
+ # Create reflection.
101
+ reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
102
+ execution.reflections[index] = reflection
71
103
 
72
- # Save control.
73
- @@reflekt.db.get("controls").push(control.result())
104
+ # Execute reflection.
105
+ reflection.reflect(*args)
106
+ @reflekt_counts[method] = @reflekt_counts[method] + 1
74
107
 
75
- # Multiple reflections per execution.
76
- execution.reflections.each_with_index do |value, index|
108
+ # Save reflection.
109
+ @@reflekt.db.get("reflections").push(reflection.serialize())
77
110
 
78
- # Create reflection.
79
- reflection = Reflection.new(execution, index + 1, @@reflekt.ruler)
80
- execution.reflections[index] = reflection
111
+ end
81
112
 
82
- # Execute reflection.
83
- reflection.reflect(*args)
84
- @reflekt_counts[method] = @reflekt_counts[method] + 1
113
+ # Save control.
114
+ @@reflekt.db.get("controls").push(control.serialize())
85
115
 
86
- # Save reflection.
87
- @@reflekt.db.get("reflections").push(reflection.result())
116
+ # Save results.
117
+ @@reflekt.db.write()
88
118
 
119
+ # Render results.
120
+ @@reflekt.renderer.render()
121
+
122
+ end
123
+
124
+ execution.is_reflecting = false
89
125
  end
90
126
 
91
- # Save results.
92
- @@reflekt.db.write()
127
+ end
128
+
129
+ # Don't execute skipped methods when reflecting.
130
+ unless execution.is_reflecting? && self.class.reflekt_skipped?(method)
93
131
 
94
- # Render results.
95
- @@reflekt.renderer.render()
132
+ # Continue execution / shadow execution.
133
+ super *args
96
134
 
97
- execution.is_reflecting = false
98
135
  end
99
136
 
100
- end
137
+ # When Reflekt disabled or control reflection failed.
138
+ else
139
+
140
+ # Continue execution.
141
+ super *args
101
142
 
102
- # Continue execution / shadow execution.
103
- super *args
143
+ end
104
144
 
105
145
  end
106
146
 
@@ -111,6 +151,13 @@ module Reflekt
111
151
 
112
152
  end
113
153
 
154
+ ##
155
+ # Provide Config instance to block.
156
+ ##
157
+ def self.configure
158
+ yield(@@reflekt.config)
159
+ end
160
+
114
161
  private
115
162
 
116
163
  def self.prepended(base)
@@ -128,18 +175,16 @@ module Reflekt
128
175
  def self.reflekt_setup_class()
129
176
 
130
177
  # Receive configuration.
131
- $ENV ||= {}
132
- $ENV[:reflekt] ||= $ENV[:reflekt] = {}
178
+ @@reflekt.config = Config.new()
133
179
 
134
180
  # Set configuration.
135
181
  @@reflekt.path = File.dirname(File.realpath(__FILE__))
136
182
 
137
- # Get reflections directory path from config.
138
- if $ENV[:reflekt][:output_path]
139
- @@reflekt.output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
140
- # Get reflections directory path from current execution path.
183
+ # Get reflections directory path from config or current execution path.
184
+ if @@reflekt.config.output_path
185
+ @@reflekt.output_path = File.join(@@reflekt.config.output_path, @@reflekt.config.output_directory)
141
186
  else
142
- @@reflekt.output_path = File.join(Dir.pwd, 'reflections')
187
+ @@reflekt.output_path = File.join(Dir.pwd, @@reflekt.config.output_directory)
143
188
  end
144
189
 
145
190
  # Create reflections directory.
@@ -150,52 +195,35 @@ module Reflekt
150
195
  # Create database.
151
196
  @@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.js')
152
197
  @@reflekt.db.defaults({ :reflekt => { :api_version => 1 }})
198
+ # @TODO Fix Rowdb.get(path) not returning values at path after Rowdb.push()
199
+ db = @@reflekt.db.value()
153
200
 
154
- # Create shadow execution stack.
201
+ # Create shadow stack.
155
202
  @@reflekt.stack = ShadowStack.new()
156
203
 
157
- # Create rules.
158
- @@reflekt.ruler = Ruler.new()
159
- # TODO: Fix Rowdb.get(path) not returning values at path after Rowdb.push()?
160
- values = @@reflekt.db.value()
161
- values.each do |klass, class_values|
162
-
163
- class_values.each do |method_name, method_items|
164
- next if method_items.nil?
165
- next unless method_items.class == Hash
166
- if method_items.key? "controls"
167
-
168
- method = method_name.to_sym
169
-
170
- @@reflekt.ruler.load(klass, method, method_items['controls'])
171
- @@reflekt.ruler.train(klass, method)
172
-
173
- end
174
- end
175
-
176
- end
177
-
178
- # The amount of reflections to create per method call.
179
- @@reflekt.reflect_amount = 2
180
-
181
- # Limit the amount of reflections that can be created per instance method.
182
- # A method called thousands of times doesn't need that many reflections.
183
- @@reflekt.reflect_limit = 10
204
+ # Create aggregated rule sets.
205
+ @@reflekt.aggregator = Aggregator.new(@@reflekt.config.meta_map)
206
+ @@reflekt.aggregator.train(db[:controls])
184
207
 
185
208
  # Create renderer.
186
209
  @@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
187
210
 
188
211
  return true
212
+
189
213
  end
190
214
 
191
215
  module SingletonClassMethods
192
216
 
193
- @@reflekt_skipped_methods = Set.new
217
+ @@reflekt_skipped_methods = Set.new()
194
218
 
195
219
  ##
196
220
  # Skip a method.
197
221
  #
198
- # @param method - A symbol representing the method name.
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
+ #
226
+ # @param method [Symbol] The method name.
199
227
  ##
200
228
  def reflekt_skip(method)
201
229
  @@reflekt_skipped_methods.add(method)
@@ -206,9 +234,9 @@ module Reflekt
206
234
  false
207
235
  end
208
236
 
209
- def reflekt_limit(amount)
210
- @@reflekt.reflect_limit = amount
211
- end
237
+ #def reflekt_limit(amount)
238
+ # @@reflekt.reflect_limit = amount
239
+ #end
212
240
 
213
241
  end
214
242
 
@@ -28,6 +28,11 @@ class Renderer
28
28
  end
29
29
  end
30
30
 
31
+ file = File.read(File.join(@path, "web", "gitignore.txt"))
32
+ File.open(File.join(@output_path, ".gitignore"), 'w+') do |f|
33
+ f.write file
34
+ end
35
+
31
36
  end
32
37
 
33
38
 
@@ -1,32 +1,52 @@
1
1
  ################################################################################
2
- # RULES
2
+ # A pattern that metadata follows.
3
3
  #
4
- # Abstract class.
5
- # See lib/rules for rules.
4
+ # @pattern Abstract class
5
+ #
6
+ # @hierachy
7
+ # 1. Aggregator
8
+ # 2. RuleSet
9
+ # 3. Rule <- YOU ARE HERE
10
+ #
11
+ # @see lib/rules for rules.
6
12
  ################################################################################
7
13
 
8
- require 'set'
9
-
10
14
  class Rule
11
15
 
12
- # Each rule intitalises itself.
13
- def initialize()
14
- @values = []
15
- end
16
+ attr_reader :type
16
17
 
17
- # Each rule loads up an array of values.
18
- def load(value)
19
- @values << value
18
+ ##
19
+ # Each rule trains on metadata to determine its boundaries.
20
+ #
21
+ # @param meta [Meta]
22
+ ##
23
+ def train(meta)
20
24
  end
21
25
 
22
- # Each rule trains on values to determine its boundaries.
23
- def train()
24
-
26
+ ##
27
+ # Each rule validates a value with its boundaries.
28
+ #
29
+ # @param value [Dynamic]
30
+ # @return [Boolean] Whether the value passes or fails.
31
+ ##
32
+ def test(value)
25
33
  end
26
34
 
27
- # Each rule compares the values it's given with its boundaries.
28
- def validate(value)
35
+ ##
36
+ # Each rule provides results.
37
+ #
38
+ # @return [Hash]
39
+ ##
40
+ def result()
41
+ {}
42
+ end
29
43
 
44
+ ##
45
+ # Each rule provides a random example that matches the rule's boundaries.
46
+ #
47
+ # @return [Dynamic] A random value.
48
+ ##
49
+ def random()
30
50
  end
31
51
 
32
52
  end
@@ -1,79 +1,109 @@
1
1
  ################################################################################
2
- # RULE POOL
2
+ # A collection of rules that validate metadata.
3
3
  #
4
- # A collection of unique rules that validate an argument.
4
+ # @patterns
5
+ # - Dependency Injection
6
+ # - Builder
7
+ #
8
+ # @hierachy
9
+ # 1. Aggregator
10
+ # 2. RuleSet <- YOU ARE HERE
11
+ # 3. Rule
5
12
  ################################################################################
6
13
 
7
14
  require 'set'
8
- require 'rules/FloatRule'
9
- require 'rules/IntegerRule'
10
- require 'rules/StringRule'
15
+ require 'MetaBuilder'
16
+ require_relative './meta/NullMeta.rb'
11
17
 
12
18
  class RuleSet
13
19
 
14
- attr_accessor :types
15
20
  attr_accessor :rules
16
21
 
17
- def initialize()
22
+ ##
23
+ # @param meta_map [Hash] The rules to apply to each data type.
24
+ ##
25
+ def initialize(meta_map)
26
+
27
+ # The rules that apply to meta types.
28
+ @meta_map = meta_map
29
+
30
+ # The types of meta this rule set applies to.
31
+ # Rules are only validated on their supported meta type.
32
+ @meta_types = Set.new()
18
33
 
19
- @types = Set.new
20
34
  @rules = {}
21
35
 
22
36
  end
23
37
 
24
- def load(type, value)
38
+ ##
39
+ # Train rule set on metadata.
40
+ #
41
+ # @param meta [Hash] The metadata to train on.
42
+ ##
43
+ def train(meta)
25
44
 
26
- # Track data type.
27
- @types << type
45
+ # Track supported meta types.
46
+ meta_type = meta[:type]
47
+ @meta_types << meta_type
28
48
 
29
- # Get rule for this data type.
30
- rule = nil
49
+ # Get rule types for this meta type.
50
+ if @meta_map.key? meta_type
51
+ @meta_map[meta_type].each do |rule_type|
52
+
53
+ # Ensure rule exists.
54
+ if @rules[rule_type].nil?
55
+ @rules[rule_type] = rule_type.new()
56
+ end
57
+
58
+ # Train rule.
59
+ @rules[rule_type].train(meta)
31
60
 
32
- case type
33
- when "Integer"
34
- unless @rules.key? IntegerRule
35
- rule = IntegerRule.new()
36
- @rules[IntegerRule] = rule
37
- else
38
- rule = @rules[IntegerRule]
39
- end
40
- when "String"
41
- unless @rules.key? StringRule
42
- rule = StringRule.new()
43
- @rules[StringRule] = rule
44
- else
45
- rule = @rules[IntegerRule]
46
61
  end
47
62
  end
48
63
 
49
- # Add value to rule.
50
- unless rule.nil?
51
- rule.load(value)
52
- end
64
+ end
53
65
 
54
- return self
66
+ ##
67
+ # @param value [Dynamic]
68
+ ##
69
+ def test(value)
55
70
 
56
- end
71
+ result = true
72
+ meta_type = MetaBuilder.data_type_to_meta_type(value)
57
73
 
58
- def train()
74
+ # Fail if value's meta type not testable by rule set.
75
+ unless @meta_types.include? meta_type
76
+ return false
77
+ end
59
78
 
60
79
  @rules.each do |klass, rule|
61
- rule.train()
80
+ if (rule.type == meta_type)
81
+ unless rule.test(value)
82
+ result = false
83
+ end
84
+ end
62
85
  end
63
86
 
87
+ return result
88
+
64
89
  end
65
90
 
66
- def validate_rule(value)
67
- result = true
91
+ ##
92
+ # Get the results of the rules.
93
+ #
94
+ # @return [Array] The rules.
95
+ ##
96
+ def result()
68
97
 
69
- @rules.each do |klass, rule|
98
+ rules = {}
70
99
 
71
- unless rule.validate(value)
72
- result = false
73
- end
100
+ @rules.each do |key, rule|
101
+ rules[rule.class] = rule.result()
74
102
  end
75
103
 
76
- return result
104
+ return rules
105
+
77
106
  end
78
107
 
108
+
79
109
  end