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.
- checksums.yaml +4 -4
- data/lib/Accessor.rb +9 -10
- data/lib/Aggregator.rb +251 -0
- data/lib/Clone.rb +31 -0
- data/lib/Config.rb +42 -0
- data/lib/Control.rb +62 -14
- data/lib/Execution.rb +15 -6
- data/lib/Meta.rb +71 -0
- data/lib/MetaBuilder.rb +84 -0
- data/lib/Reflection.rb +109 -89
- data/lib/Reflekt.rb +118 -90
- data/lib/Renderer.rb +5 -0
- data/lib/Rule.rb +37 -17
- data/lib/RuleSet.rb +72 -42
- data/lib/ShadowStack.rb +9 -10
- data/lib/meta/ArrayMeta.rb +34 -0
- data/lib/meta/BooleanMeta.rb +26 -0
- data/lib/meta/FloatMeta.rb +26 -0
- data/lib/meta/IntegerMeta.rb +26 -0
- data/lib/meta/NullMeta.rb +34 -0
- data/lib/meta/StringMeta.rb +26 -0
- data/lib/rules/ArrayRule.rb +88 -0
- data/lib/rules/BooleanRule.rb +47 -0
- data/lib/rules/FloatRule.rb +40 -11
- data/lib/rules/IntegerRule.rb +36 -10
- data/lib/rules/NullRule.rb +36 -0
- data/lib/rules/StringRule.rb +60 -8
- data/lib/web/README.md +3 -3
- data/lib/web/bundle.js +5 -5
- data/lib/web/gitignore.txt +7 -0
- data/lib/web/index.html +3 -0
- data/lib/web/server.js +45 -49
- metadata +22 -8
- data/lib/Ruler.rb +0 -192
data/lib/Reflekt.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
################################################################################
|
2
|
-
#
|
2
|
+
# Reflective testing.
|
3
3
|
#
|
4
|
-
#
|
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
|
-
#
|
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
|
-
|
33
|
-
self.class.instance_methods(false)
|
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
|
-
|
36
|
-
|
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
|
-
#
|
44
|
-
|
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
|
-
#
|
50
|
-
|
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
|
-
#
|
53
|
-
execution
|
62
|
+
# When stack empty or past execution done reflecting.
|
63
|
+
if execution.nil? || execution.has_finished_reflecting?
|
54
64
|
|
55
|
-
|
65
|
+
# Create execution.
|
66
|
+
execution = Execution.new(self, method, @@reflekt.config.reflect_amount, @@reflekt.stack)
|
56
67
|
|
57
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
66
|
-
|
67
|
-
execution.control = control
|
97
|
+
# Multiple reflections per execution.
|
98
|
+
execution.reflections.each_with_index do |value, index|
|
68
99
|
|
69
|
-
|
70
|
-
|
100
|
+
# Create reflection.
|
101
|
+
reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
|
102
|
+
execution.reflections[index] = reflection
|
71
103
|
|
72
|
-
|
73
|
-
|
104
|
+
# Execute reflection.
|
105
|
+
reflection.reflect(*args)
|
106
|
+
@reflekt_counts[method] = @reflekt_counts[method] + 1
|
74
107
|
|
75
|
-
|
76
|
-
|
108
|
+
# Save reflection.
|
109
|
+
@@reflekt.db.get("reflections").push(reflection.serialize())
|
77
110
|
|
78
|
-
|
79
|
-
reflection = Reflection.new(execution, index + 1, @@reflekt.ruler)
|
80
|
-
execution.reflections[index] = reflection
|
111
|
+
end
|
81
112
|
|
82
|
-
|
83
|
-
|
84
|
-
@reflekt_counts[method] = @reflekt_counts[method] + 1
|
113
|
+
# Save control.
|
114
|
+
@@reflekt.db.get("controls").push(control.serialize())
|
85
115
|
|
86
|
-
|
87
|
-
|
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
|
-
|
92
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
# Don't execute skipped methods when reflecting.
|
130
|
+
unless execution.is_reflecting? && self.class.reflekt_skipped?(method)
|
93
131
|
|
94
|
-
#
|
95
|
-
|
132
|
+
# Continue execution / shadow execution.
|
133
|
+
super *args
|
96
134
|
|
97
|
-
execution.is_reflecting = false
|
98
135
|
end
|
99
136
|
|
100
|
-
|
137
|
+
# When Reflekt disabled or control reflection failed.
|
138
|
+
else
|
139
|
+
|
140
|
+
# Continue execution.
|
141
|
+
super *args
|
101
142
|
|
102
|
-
|
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
|
-
|
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
|
139
|
-
@@reflekt.output_path = File.join(
|
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,
|
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
|
201
|
+
# Create shadow stack.
|
155
202
|
@@reflekt.stack = ShadowStack.new()
|
156
203
|
|
157
|
-
# Create
|
158
|
-
@@reflekt.
|
159
|
-
|
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
|
-
# @
|
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
|
-
|
211
|
-
end
|
237
|
+
#def reflekt_limit(amount)
|
238
|
+
# @@reflekt.reflect_limit = amount
|
239
|
+
#end
|
212
240
|
|
213
241
|
end
|
214
242
|
|
data/lib/Renderer.rb
CHANGED
data/lib/Rule.rb
CHANGED
@@ -1,32 +1,52 @@
|
|
1
1
|
################################################################################
|
2
|
-
#
|
2
|
+
# A pattern that metadata follows.
|
3
3
|
#
|
4
|
-
# Abstract class
|
5
|
-
#
|
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
|
-
|
13
|
-
def initialize()
|
14
|
-
@values = []
|
15
|
-
end
|
16
|
+
attr_reader :type
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
23
|
-
|
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
|
-
|
28
|
-
|
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
|
data/lib/RuleSet.rb
CHANGED
@@ -1,79 +1,109 @@
|
|
1
1
|
################################################################################
|
2
|
-
#
|
2
|
+
# A collection of rules that validate metadata.
|
3
3
|
#
|
4
|
-
#
|
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 '
|
9
|
-
|
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
|
-
|
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
|
-
|
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
|
27
|
-
|
45
|
+
# Track supported meta types.
|
46
|
+
meta_type = meta[:type]
|
47
|
+
@meta_types << meta_type
|
28
48
|
|
29
|
-
# Get rule for this
|
30
|
-
|
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
|
-
|
50
|
-
unless rule.nil?
|
51
|
-
rule.load(value)
|
52
|
-
end
|
64
|
+
end
|
53
65
|
|
54
|
-
|
66
|
+
##
|
67
|
+
# @param value [Dynamic]
|
68
|
+
##
|
69
|
+
def test(value)
|
55
70
|
|
56
|
-
|
71
|
+
result = true
|
72
|
+
meta_type = MetaBuilder.data_type_to_meta_type(value)
|
57
73
|
|
58
|
-
|
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.
|
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
|
-
|
67
|
-
|
91
|
+
##
|
92
|
+
# Get the results of the rules.
|
93
|
+
#
|
94
|
+
# @return [Array] The rules.
|
95
|
+
##
|
96
|
+
def result()
|
68
97
|
|
69
|
-
|
98
|
+
rules = {}
|
70
99
|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
100
|
+
@rules.each do |key, rule|
|
101
|
+
rules[rule.class] = rule.result()
|
74
102
|
end
|
75
103
|
|
76
|
-
return
|
104
|
+
return rules
|
105
|
+
|
77
106
|
end
|
78
107
|
|
108
|
+
|
79
109
|
end
|