reflekt 0.9.6 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|