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.
- checksums.yaml +4 -4
- data/lib/Accessor.rb +9 -10
- data/lib/Aggregator.rb +238 -0
- data/lib/Clone.rb +31 -0
- data/lib/Config.rb +41 -0
- data/lib/Control.rb +53 -14
- data/lib/Execution.rb +48 -7
- data/lib/Meta.rb +39 -0
- data/lib/MetaBuilder.rb +83 -0
- data/lib/Reflection.rb +124 -106
- data/lib/Reflekt.rb +120 -96
- data/lib/Renderer.rb +20 -19
- data/lib/Rule.rb +37 -17
- data/lib/RuleSet.rb +66 -42
- data/lib/ShadowStack.rb +9 -21
- 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/StringMeta.rb +26 -0
- data/lib/rules/ArrayRule.rb +88 -0
- data/lib/rules/BooleanRule.rb +45 -0
- data/lib/rules/FloatRule.rb +40 -11
- data/lib/rules/IntegerRule.rb +36 -10
- data/lib/rules/StringRule.rb +60 -8
- data/lib/web/README.md +11 -0
- data/lib/web/bundle.js +30 -0
- data/lib/web/gitignore.txt +7 -0
- data/lib/web/index.html +26 -0
- data/lib/web/package-lock.json +1347 -0
- data/lib/web/package.json +26 -0
- data/lib/web/server.js +118 -0
- metadata +26 -11
- data/lib/Ruler.rb +0 -197
- data/lib/web/script.js +0 -8
- data/lib/web/style.css +0 -151
- data/lib/web/template.html.erb +0 -266
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,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
|
-
|
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
|
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
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
71
|
-
|
72
|
-
execution.control = control
|
97
|
+
# Multiple reflections per execution.
|
98
|
+
execution.reflections.each_with_index do |value, index|
|
73
99
|
|
74
|
-
|
75
|
-
|
100
|
+
# Create reflection.
|
101
|
+
reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
|
102
|
+
execution.reflections[index] = reflection
|
76
103
|
|
77
|
-
|
78
|
-
|
104
|
+
# Execute reflection.
|
105
|
+
reflection.reflect(*args)
|
106
|
+
@reflekt_counts[method] = @reflekt_counts[method] + 1
|
79
107
|
|
80
|
-
|
81
|
-
|
108
|
+
# Save reflection.
|
109
|
+
@@reflekt.db.get("reflections").push(reflection.result())
|
82
110
|
|
83
|
-
|
84
|
-
reflection = Reflection.new(execution, klass, method, @@reflekt.ruler)
|
85
|
-
execution.reflections[index] = reflection
|
111
|
+
end
|
86
112
|
|
87
|
-
|
88
|
-
|
89
|
-
@reflekt_counts[method] = @reflekt_counts[method] + 1
|
113
|
+
# Save control.
|
114
|
+
@@reflekt.db.get("controls").push(control.result())
|
90
115
|
|
91
|
-
|
92
|
-
|
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
|
-
|
97
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
# Don't execute skipped methods when reflecting.
|
130
|
+
unless execution.is_reflecting? && self.class.reflekt_skipped?(method)
|
98
131
|
|
99
|
-
#
|
100
|
-
|
132
|
+
# Continue execution / shadow execution.
|
133
|
+
super *args
|
101
134
|
|
102
|
-
execution.is_reflecting = false
|
103
135
|
end
|
104
136
|
|
105
|
-
|
137
|
+
# When Reflekt disabled or control reflection failed.
|
138
|
+
else
|
139
|
+
|
140
|
+
# Continue execution.
|
141
|
+
super *args
|
106
142
|
|
107
|
-
|
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
|
-
|
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
|
-
#
|
143
|
-
if
|
144
|
-
@@reflekt.output_path = File.join(
|
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,
|
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.
|
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
|
201
|
+
# Create shadow stack.
|
159
202
|
@@reflekt.stack = ShadowStack.new()
|
160
203
|
|
161
|
-
# Create
|
162
|
-
@@reflekt.
|
163
|
-
|
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
|
-
# @
|
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
|
-
|
215
|
-
end
|
237
|
+
#def reflekt_limit(amount)
|
238
|
+
# @@reflekt.reflect_limit = amount
|
239
|
+
#end
|
216
240
|
|
217
241
|
end
|
218
242
|
|
data/lib/Renderer.rb
CHANGED
@@ -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
|
-
#
|
11
|
+
# Place files in output path.
|
10
12
|
##
|
11
13
|
def render()
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
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,103 @@
|
|
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
|
-
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
|
-
|
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
|
-
|
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
|
-
|
27
|
-
@types << type
|
44
|
+
unless meta.nil? || meta[:type].nil?
|
28
45
|
|
29
|
-
|
30
|
-
|
46
|
+
meta_type = meta[:type]
|
47
|
+
@meta_types << meta_type
|
31
48
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
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.
|
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
|
-
|
67
|
-
|
85
|
+
##
|
86
|
+
# Get the results of the rules.
|
87
|
+
#
|
88
|
+
# @return [Array] The rules.
|
89
|
+
##
|
90
|
+
def result()
|
68
91
|
|
69
|
-
|
92
|
+
rules = {}
|
70
93
|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
94
|
+
@rules.each do |key, rule|
|
95
|
+
rules[rule.class] = rule.result()
|
74
96
|
end
|
75
97
|
|
76
|
-
return
|
98
|
+
return rules
|
99
|
+
|
77
100
|
end
|
78
101
|
|
102
|
+
|
79
103
|
end
|