reflekt 0.9.6 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,97 +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, @@reflekt.reflect_amount)
54
- @@reflekt.stack.push(execution)
62
+ # When stack empty or past execution done reflecting.
63
+ if execution.nil? || execution.has_finished_reflecting?
55
64
 
56
- end
65
+ # Create execution.
66
+ execution = Execution.new(self, method, @@reflekt.config.reflect_amount, @@reflekt.stack)
67
+
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)
57
87
 
58
- # Reflect.
59
- # The first method call in the Execution creates a Reflection.
60
- # Subsequent method calls are shadow executions on cloned objects.
61
- if execution.has_empty_reflections? && !execution.is_reflecting?
62
- execution.is_reflecting = true
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
63
93
 
64
- # Use symbols "klass" and "method" as keys in hashes.
65
- # Use strings "class_name" and "method_name" to query the database.
66
- class_name = execution.caller_class.to_s
67
- method_name = method.to_s
68
- klass = class_name.to_sym
94
+ # Save control as reflection.
95
+ @@reflekt.db.get("reflections").push(control.result())
69
96
 
70
- # Create control.
71
- control = Control.new(execution, klass, method, @@reflekt.ruler)
72
- execution.control = control
97
+ # Multiple reflections per execution.
98
+ execution.reflections.each_with_index do |value, index|
73
99
 
74
- # Execute control.
75
- control.reflect(*args)
100
+ # Create reflection.
101
+ reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
102
+ execution.reflections[index] = reflection
76
103
 
77
- # Save control.
78
- @@reflekt.db.get("#{class_name}.#{method_name}.controls").push(control.result())
104
+ # Execute reflection.
105
+ reflection.reflect(*args)
106
+ @reflekt_counts[method] = @reflekt_counts[method] + 1
79
107
 
80
- # Multiple reflections per execution.
81
- execution.reflections.each_with_index do |value, index|
108
+ # Save reflection.
109
+ @@reflekt.db.get("reflections").push(reflection.result())
82
110
 
83
- # Create reflection.
84
- reflection = Reflection.new(execution, klass, method, @@reflekt.ruler)
85
- execution.reflections[index] = reflection
111
+ end
86
112
 
87
- # Execute reflection.
88
- reflection.reflect(*args)
89
- @reflekt_counts[method] = @reflekt_counts[method] + 1
113
+ # Save control.
114
+ @@reflekt.db.get("controls").push(control.result())
90
115
 
91
- # Save reflection.
92
- @@reflekt.db.get("#{class_name}.#{method_name}.reflections").push(reflection.result())
116
+ # Save results.
117
+ @@reflekt.db.write()
93
118
 
119
+ # Render results.
120
+ @@reflekt.renderer.render()
121
+
122
+ end
123
+
124
+ execution.is_reflecting = false
94
125
  end
95
126
 
96
- # Save results.
97
- @@reflekt.db.write()
127
+ end
128
+
129
+ # Don't execute skipped methods when reflecting.
130
+ unless execution.is_reflecting? && self.class.reflekt_skipped?(method)
98
131
 
99
- # Render results.
100
- @@reflekt.renderer.render()
132
+ # Continue execution / shadow execution.
133
+ super *args
101
134
 
102
- execution.is_reflecting = false
103
135
  end
104
136
 
105
- end
137
+ # When Reflekt disabled or control reflection failed.
138
+ else
139
+
140
+ # Continue execution.
141
+ super *args
106
142
 
107
- # Continue execution / shadow execution.
108
- super *args
143
+ end
109
144
 
110
145
  end
111
146
 
@@ -116,6 +151,13 @@ module Reflekt
116
151
 
117
152
  end
118
153
 
154
+ ##
155
+ # Provide Config instance to block.
156
+ ##
157
+ def self.configure
158
+ yield(@@reflekt.config)
159
+ end
160
+
119
161
  private
120
162
 
121
163
  def self.prepended(base)
@@ -133,73 +175,55 @@ module Reflekt
133
175
  def self.reflekt_setup_class()
134
176
 
135
177
  # Receive configuration.
136
- $ENV ||= {}
137
- $ENV[:reflekt] ||= $ENV[:reflekt] = {}
178
+ @@reflekt.config = Config.new()
138
179
 
139
180
  # Set configuration.
140
181
  @@reflekt.path = File.dirname(File.realpath(__FILE__))
141
182
 
142
- # Build reflections directory.
143
- if $ENV[:reflekt][:output_path]
144
- @@reflekt.output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
145
- # Build reflections directory in 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)
146
186
  else
147
- @@reflekt.output_path = File.join(Dir.pwd, 'reflections')
187
+ @@reflekt.output_path = File.join(Dir.pwd, @@reflekt.config.output_directory)
148
188
  end
189
+
149
190
  # Create reflections directory.
150
191
  unless Dir.exist? @@reflekt.output_path
151
192
  Dir.mkdir(@@reflekt.output_path)
152
193
  end
153
194
 
154
195
  # Create database.
155
- @@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.json')
196
+ @@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.js')
156
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()
157
200
 
158
- # Create shadow execution stack.
201
+ # Create shadow stack.
159
202
  @@reflekt.stack = ShadowStack.new()
160
203
 
161
- # Create rules.
162
- @@reflekt.ruler = Ruler.new()
163
- # TODO: Fix Rowdb.get(path) not returning values at path after Rowdb.push()?
164
- values = @@reflekt.db.value()
165
- values.each do |klass, class_values|
166
-
167
- class_values.each do |method_name, method_items|
168
- next if method_items.nil?
169
- next unless method_items.class == Hash
170
- if method_items.key? "controls"
171
-
172
- method = method_name.to_sym
173
-
174
- @@reflekt.ruler.load(klass, method, method_items['controls'])
175
- @@reflekt.ruler.train(klass, method)
176
-
177
- end
178
- end
179
-
180
- end
181
-
182
- # The amount of reflections to create per method call.
183
- @@reflekt.reflect_amount = 2
184
-
185
- # Limit the amount of reflections that can be created per instance method.
186
- # A method called thousands of times doesn't need that many reflections.
187
- @@reflekt.reflect_limit = 10
204
+ # Create aggregated rule sets.
205
+ @@reflekt.aggregator = Aggregator.new(@@reflekt.config.meta_map)
206
+ @@reflekt.aggregator.train(db[:controls])
188
207
 
189
208
  # Create renderer.
190
209
  @@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
191
210
 
192
211
  return true
212
+
193
213
  end
194
214
 
195
215
  module SingletonClassMethods
196
216
 
197
- @@reflekt_skipped_methods = Set.new
217
+ @@reflekt_skipped_methods = Set.new()
198
218
 
199
219
  ##
200
220
  # Skip a method.
201
221
  #
202
- # @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.
203
227
  ##
204
228
  def reflekt_skip(method)
205
229
  @@reflekt_skipped_methods.add(method)
@@ -210,9 +234,9 @@ module Reflekt
210
234
  false
211
235
  end
212
236
 
213
- def reflekt_limit(amount)
214
- @@reflekt.reflect_limit = amount
215
- end
237
+ #def reflekt_limit(amount)
238
+ # @@reflekt.reflect_limit = amount
239
+ #end
216
240
 
217
241
  end
218
242
 
@@ -1,35 +1,36 @@
1
1
  class Renderer
2
2
 
3
3
  def initialize(path, output_path)
4
+
4
5
  @path = path
5
6
  @output_path = output_path
7
+
6
8
  end
7
9
 
8
10
  ##
9
- # Render results.
11
+ # Place files in output path.
10
12
  ##
11
13
  def render()
12
14
 
13
- # Get JSON.
14
- json = File.read("#{@output_path}/db.json")
15
-
16
- # Save HTML.
17
- template = File.read("#{@path}/web/template.html.erb")
18
- rendered = ERB.new(template).result(binding)
19
- File.open("#{@output_path}/index.html", 'w+') do |f|
20
- f.write rendered
21
- end
22
-
23
- # Add JS.
24
- javascript = File.read("#{@path}/web/script.js")
25
- File.open("#{@output_path}/script.js", 'w+') do |f|
26
- f.write javascript
15
+ filenames = [
16
+ "bundle.js",
17
+ "index.html",
18
+ "package-lock.json",
19
+ "package.json",
20
+ "README.md",
21
+ "server.js"
22
+ ]
23
+
24
+ filenames.each do |filename|
25
+ file = File.read(File.join(@path, "web", filename))
26
+ File.open(File.join(@output_path, filename), 'w+') do |f|
27
+ f.write file
28
+ end
27
29
  end
28
30
 
29
- # Add CSS.
30
- stylesheet = File.read("#{@path}/web/style.css")
31
- File.open("#{@output_path}/style.css", 'w+') do |f|
32
- f.write stylesheet
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
33
34
  end
34
35
 
35
36
  end
@@ -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,103 @@
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'
11
16
 
12
17
  class RuleSet
13
18
 
14
- attr_accessor :types
15
19
  attr_accessor :rules
16
20
 
17
- def initialize()
21
+ ##
22
+ # @param meta_map [Hash] The rules to apply to each data type.
23
+ ##
24
+ def initialize(meta_map)
25
+
26
+ # The rules that apply to meta types.
27
+ @meta_map = meta_map
28
+
29
+ # The types of meta this rule set applies to.
30
+ # Rules are only validated on their supported meta type.
31
+ @meta_types = Set.new()
18
32
 
19
- @types = Set.new
20
33
  @rules = {}
21
34
 
22
35
  end
23
36
 
24
- def load(type, value)
37
+ ##
38
+ # Train rule set on metadata.
39
+ #
40
+ # @param meta [Meta] The metadata to train on.
41
+ ##
42
+ def train(meta)
25
43
 
26
- # Track data type.
27
- @types << type
44
+ unless meta.nil? || meta[:type].nil?
28
45
 
29
- # Get rule for this data type.
30
- rule = nil
46
+ meta_type = meta[:type]
47
+ @meta_types << meta_type
31
48
 
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]
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)
60
+
61
+ end
46
62
  end
47
- end
48
63
 
49
- # Add value to rule.
50
- unless rule.nil?
51
- rule.load(value)
52
64
  end
53
65
 
54
- return self
55
-
56
66
  end
57
67
 
58
- def train()
68
+ def test(value)
69
+ result = true
70
+
71
+ # Only test data type on rule of matching meta type.
72
+ meta_type = MetaBuilder.data_type_to_meta_type(value)
59
73
 
60
74
  @rules.each do |klass, rule|
61
- rule.train()
75
+ if (rule.type == meta_type)
76
+ unless rule.test(value)
77
+ result = false
78
+ end
79
+ end
62
80
  end
63
81
 
82
+ return result
64
83
  end
65
84
 
66
- def validate_rule(value)
67
- result = true
85
+ ##
86
+ # Get the results of the rules.
87
+ #
88
+ # @return [Array] The rules.
89
+ ##
90
+ def result()
68
91
 
69
- @rules.each do |klass, rule|
92
+ rules = {}
70
93
 
71
- unless rule.validate(value)
72
- result = false
73
- end
94
+ @rules.each do |key, rule|
95
+ rules[rule.class] = rule.result()
74
96
  end
75
97
 
76
- return result
98
+ return rules
99
+
77
100
  end
78
101
 
102
+
79
103
  end