reflekt 0.9.6 → 1.0.1

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,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