reflekt 0.9.2 → 0.9.8
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 +37 -0
- data/lib/Aggregator.rb +193 -0
- data/lib/Control.rb +18 -8
- data/lib/Execution.rb +39 -7
- data/lib/Meta.rb +27 -0
- data/lib/MetaBuilder.rb +56 -0
- data/lib/Reflection.rb +104 -104
- data/lib/Reflekt.rb +205 -0
- data/lib/Renderer.rb +39 -0
- data/lib/Rule.rb +24 -26
- data/lib/RuleSet.rb +105 -0
- data/lib/ShadowStack.rb +8 -22
- data/lib/meta/IntegerMeta.rb +27 -0
- data/lib/meta/StringMeta.rb +27 -0
- data/lib/rules/IntegerRule.rb +51 -0
- data/lib/rules/StringRule.rb +57 -0
- 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 +122 -0
- metadata +21 -8
- data/lib/Ruler.rb +0 -112
- data/lib/reflekt.rb +0 -244
- 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
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Reflective testing.
|
3
|
+
#
|
4
|
+
# @author
|
5
|
+
# Maedi Prichard
|
6
|
+
#
|
7
|
+
# @flow
|
8
|
+
# 1. An Execution is created on method call.
|
9
|
+
# 2. Many Refections are created per Execution.
|
10
|
+
# 3. Each Reflection executes on cloned data.
|
11
|
+
# 4. Flow is returned to the original method.
|
12
|
+
#
|
13
|
+
# @usage
|
14
|
+
# class ExampleClass
|
15
|
+
# prepend Reflekt
|
16
|
+
################################################################################
|
17
|
+
|
18
|
+
require 'set'
|
19
|
+
require 'erb'
|
20
|
+
require 'rowdb'
|
21
|
+
require 'Accessor'
|
22
|
+
require 'Aggregator'
|
23
|
+
require 'Control'
|
24
|
+
require 'Execution'
|
25
|
+
require 'Reflection'
|
26
|
+
require 'Renderer'
|
27
|
+
require 'ShadowStack'
|
28
|
+
|
29
|
+
module Reflekt
|
30
|
+
|
31
|
+
def initialize(*args)
|
32
|
+
|
33
|
+
@reflekt_counts = {}
|
34
|
+
|
35
|
+
# Get instance methods.
|
36
|
+
# TODO: Include parent methods like "Array.include?".
|
37
|
+
self.class.instance_methods(false).each do |method|
|
38
|
+
|
39
|
+
# Don't process skipped methods.
|
40
|
+
next if self.class.reflekt_skipped?(method)
|
41
|
+
|
42
|
+
@reflekt_counts[method] = 0
|
43
|
+
|
44
|
+
# When method called in flow.
|
45
|
+
self.define_singleton_method(method) do |*args|
|
46
|
+
|
47
|
+
# Don't reflect when limit reached.
|
48
|
+
unless @reflekt_counts[method] >= @@reflekt.reflect_limit
|
49
|
+
|
50
|
+
# Get current execution.
|
51
|
+
execution = @@reflekt.stack.peek()
|
52
|
+
|
53
|
+
# When stack empty or past execution done reflecting.
|
54
|
+
if execution.nil? || execution.has_finished_reflecting?
|
55
|
+
|
56
|
+
# Create execution.
|
57
|
+
execution = Execution.new(self, method, @@reflekt.reflect_amount, @@reflekt.stack)
|
58
|
+
|
59
|
+
@@reflekt.stack.push(execution)
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# Reflect.
|
64
|
+
# The first method call in the Execution creates a Reflection.
|
65
|
+
# Subsequent method calls are shadow executions on cloned objects.
|
66
|
+
if execution.has_empty_reflections? && !execution.is_reflecting?
|
67
|
+
execution.is_reflecting = true
|
68
|
+
|
69
|
+
# Create control.
|
70
|
+
control = Control.new(execution, 1, @@reflekt.aggregator)
|
71
|
+
execution.control = control
|
72
|
+
|
73
|
+
# Execute control.
|
74
|
+
control.reflect(*args)
|
75
|
+
|
76
|
+
# Save control.
|
77
|
+
@@reflekt.db.get("controls").push(control.result())
|
78
|
+
|
79
|
+
# Multiple reflections per execution.
|
80
|
+
execution.reflections.each_with_index do |value, index|
|
81
|
+
|
82
|
+
# Create reflection.
|
83
|
+
reflection = Reflection.new(execution, index + 1, @@reflekt.aggregator)
|
84
|
+
execution.reflections[index] = reflection
|
85
|
+
|
86
|
+
# Execute reflection.
|
87
|
+
reflection.reflect(*args)
|
88
|
+
@reflekt_counts[method] = @reflekt_counts[method] + 1
|
89
|
+
|
90
|
+
# Save reflection.
|
91
|
+
@@reflekt.db.get("reflections").push(reflection.result())
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# Save results.
|
96
|
+
@@reflekt.db.write()
|
97
|
+
|
98
|
+
# Render results.
|
99
|
+
@@reflekt.renderer.render()
|
100
|
+
|
101
|
+
execution.is_reflecting = false
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# Continue execution / shadow execution.
|
107
|
+
super *args
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
# Continue initialization.
|
114
|
+
super
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def self.prepended(base)
|
121
|
+
|
122
|
+
# Prepend class methods to the instance's singleton class.
|
123
|
+
base.singleton_class.prepend(SingletonClassMethods)
|
124
|
+
|
125
|
+
# Setup class.
|
126
|
+
@@reflekt = Accessor.new()
|
127
|
+
@@reflekt.setup ||= reflekt_setup_class
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
# Setup class.
|
132
|
+
def self.reflekt_setup_class()
|
133
|
+
|
134
|
+
# Receive configuration.
|
135
|
+
$ENV ||= {}
|
136
|
+
$ENV[:reflekt] ||= $ENV[:reflekt] = {}
|
137
|
+
$ENV[:reflekt][:output_directory] = "reflections"
|
138
|
+
|
139
|
+
# Set configuration.
|
140
|
+
@@reflekt.path = File.dirname(File.realpath(__FILE__))
|
141
|
+
|
142
|
+
# Get reflections directory path from config or current execution path.
|
143
|
+
if $ENV[:reflekt][:output_path]
|
144
|
+
@@reflekt.output_path = File.join($ENV[:reflekt][:output_path], $ENV[:reflekt][:output_directory])
|
145
|
+
else
|
146
|
+
@@reflekt.output_path = File.join(Dir.pwd, $ENV[:reflekt][:output_directory])
|
147
|
+
end
|
148
|
+
|
149
|
+
# Create reflections directory.
|
150
|
+
unless Dir.exist? @@reflekt.output_path
|
151
|
+
Dir.mkdir(@@reflekt.output_path)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Create database.
|
155
|
+
@@reflekt.db = Rowdb.new(@@reflekt.output_path + '/db.js')
|
156
|
+
@@reflekt.db.defaults({ :reflekt => { :api_version => 1 }})
|
157
|
+
# @TODO Fix Rowdb.get(path) not returning values at path after Rowdb.push()
|
158
|
+
db = @@reflekt.db.value()
|
159
|
+
|
160
|
+
# Create shadow stack.
|
161
|
+
@@reflekt.stack = ShadowStack.new()
|
162
|
+
|
163
|
+
# Create aggregated rule sets.
|
164
|
+
@@reflekt.aggregator = Aggregator.new()
|
165
|
+
@@reflekt.aggregator.train(db[:controls])
|
166
|
+
|
167
|
+
# The amount of reflections to create per method call.
|
168
|
+
@@reflekt.reflect_amount = 2
|
169
|
+
|
170
|
+
# Limit the amount of reflections that can be created per instance method.
|
171
|
+
# A method called thousands of times doesn't need that many reflections.
|
172
|
+
@@reflekt.reflect_limit = 10
|
173
|
+
|
174
|
+
# Create renderer.
|
175
|
+
@@reflekt.renderer = Renderer.new(@@reflekt.path, @@reflekt.output_path)
|
176
|
+
|
177
|
+
return true
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
module SingletonClassMethods
|
182
|
+
|
183
|
+
@@reflekt_skipped_methods = Set.new()
|
184
|
+
|
185
|
+
##
|
186
|
+
# Skip a method.
|
187
|
+
#
|
188
|
+
# @param method [Symbol] The method name.
|
189
|
+
##
|
190
|
+
def reflekt_skip(method)
|
191
|
+
@@reflekt_skipped_methods.add(method)
|
192
|
+
end
|
193
|
+
|
194
|
+
def reflekt_skipped?(method)
|
195
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
196
|
+
false
|
197
|
+
end
|
198
|
+
|
199
|
+
def reflekt_limit(amount)
|
200
|
+
@@reflekt.reflect_limit = amount
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
data/lib/Renderer.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Renderer
|
2
|
+
|
3
|
+
def initialize(path, output_path)
|
4
|
+
|
5
|
+
@path = path
|
6
|
+
@output_path = output_path
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Place files in output path.
|
12
|
+
##
|
13
|
+
def render()
|
14
|
+
|
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
|
29
|
+
end
|
30
|
+
|
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
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
data/lib/Rule.rb
CHANGED
@@ -1,38 +1,36 @@
|
|
1
|
-
|
1
|
+
################################################################################
|
2
|
+
# All rules behave the same.
|
3
|
+
#
|
4
|
+
# @pattern Abstract class.
|
5
|
+
# @see lib/rules for rules.
|
6
|
+
################################################################################
|
2
7
|
|
3
8
|
class Rule
|
4
9
|
|
5
|
-
attr_accessor :type
|
6
|
-
attr_accessor :min
|
7
|
-
attr_accessor :max
|
8
|
-
attr_accessor :length
|
9
|
-
attr_accessor :types
|
10
|
-
attr_accessor :values
|
11
|
-
|
12
|
-
def initialize()
|
13
|
-
|
14
|
-
@types = Set.new
|
15
|
-
@values = Set.new
|
16
|
-
@min = nil
|
17
|
-
@max = nil
|
18
|
-
@length = nil
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
10
|
##
|
23
|
-
#
|
24
|
-
#
|
11
|
+
# Each rule trains on metadata to determine its boundaries.
|
12
|
+
#
|
13
|
+
# @param meta [Meta]
|
25
14
|
##
|
26
|
-
def
|
27
|
-
@types.add(type)
|
15
|
+
def train(meta)
|
28
16
|
end
|
29
17
|
|
30
|
-
|
31
|
-
|
18
|
+
##
|
19
|
+
# Each rule validates a value with its boundaries.
|
20
|
+
#
|
21
|
+
# @param value [Dynamic]
|
22
|
+
# @return [Boolean] Whether the value passes or fails.
|
23
|
+
##
|
24
|
+
def test(value)
|
32
25
|
end
|
33
26
|
|
34
|
-
|
35
|
-
|
27
|
+
##
|
28
|
+
# Each rule provides metadata.
|
29
|
+
#
|
30
|
+
# @return [Hash]
|
31
|
+
##
|
32
|
+
def result()
|
33
|
+
{}
|
36
34
|
end
|
37
35
|
|
38
36
|
end
|
data/lib/RuleSet.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
################################################################################
|
2
|
+
# A collection of rules that validate a value.
|
3
|
+
################################################################################
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
class RuleSet
|
8
|
+
|
9
|
+
attr_accessor :rules
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
|
13
|
+
@rules = {}
|
14
|
+
@types = Set.new()
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_sets(args)
|
19
|
+
|
20
|
+
rule_sets = []
|
21
|
+
|
22
|
+
args.each do |arg|
|
23
|
+
rule_sets << self.create_set(arg)
|
24
|
+
end
|
25
|
+
|
26
|
+
rule_sets
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create_set(value)
|
30
|
+
|
31
|
+
rule_set = RuleSet.new()
|
32
|
+
value_type = value.class.to_s
|
33
|
+
|
34
|
+
# Creates values for matching data type.
|
35
|
+
case value_type
|
36
|
+
when "Integer"
|
37
|
+
rule = IntegerRule.new()
|
38
|
+
rule.train(arg)
|
39
|
+
rule_set.rules[IntegerRule] = rule
|
40
|
+
when "String"
|
41
|
+
rule = StringRule.new()
|
42
|
+
rule.train(arg)
|
43
|
+
rule_set.rules[StringRule] = rule
|
44
|
+
end
|
45
|
+
|
46
|
+
rule_set
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Train rule set on metadata.
|
51
|
+
#
|
52
|
+
# @param meta [Meta] The metadata to train on.
|
53
|
+
##
|
54
|
+
def train(meta)
|
55
|
+
|
56
|
+
# Track data type.
|
57
|
+
@types << meta.class
|
58
|
+
type = meta.class.to_s
|
59
|
+
|
60
|
+
# Get rule for this data type.
|
61
|
+
rule = nil
|
62
|
+
case type
|
63
|
+
when "Integer"
|
64
|
+
unless @rules.key? IntegerRule
|
65
|
+
rule = IntegerRule.new()
|
66
|
+
@rules[IntegerRule] = rule
|
67
|
+
else
|
68
|
+
rule = @rules[IntegerRule]
|
69
|
+
end
|
70
|
+
when "String"
|
71
|
+
unless @rules.key? StringRule
|
72
|
+
rule = StringRule.new()
|
73
|
+
@rules[StringRule] = rule
|
74
|
+
else
|
75
|
+
rule = @rules[IntegerRule]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Train rule.
|
80
|
+
unless rule.nil?
|
81
|
+
rule.train(meta)
|
82
|
+
end
|
83
|
+
|
84
|
+
return self
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Get the results of the rules.
|
90
|
+
#
|
91
|
+
# @return [Array] The rules.
|
92
|
+
##
|
93
|
+
def result()
|
94
|
+
|
95
|
+
rules = {}
|
96
|
+
|
97
|
+
@rules.each do |key, rule|
|
98
|
+
rules[rule.class] = rule.result()
|
99
|
+
end
|
100
|
+
|
101
|
+
return rules
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/lib/ShadowStack.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
################################################################################
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# Track the executions in a call stack.
|
2
|
+
# Track the executions in a shadow call stack.
|
5
3
|
################################################################################
|
6
4
|
|
7
5
|
class ShadowStack
|
@@ -20,37 +18,25 @@ class ShadowStack
|
|
20
18
|
end
|
21
19
|
|
22
20
|
##
|
23
|
-
#
|
21
|
+
# Place Execution at the top of stack.
|
24
22
|
#
|
25
|
-
# @param
|
26
|
-
# @
|
27
|
-
#
|
28
|
-
# @return Execution - The new execution.
|
23
|
+
# @param execution [Execution] The execution to place.
|
24
|
+
# @return [Execution] The placed execution.
|
29
25
|
##
|
30
26
|
def push(execution)
|
31
27
|
|
32
|
-
#
|
28
|
+
# Place first execution at bottom of stack.
|
33
29
|
if @bottom.nil?
|
34
30
|
@bottom = execution
|
31
|
+
# Connect subsequent executions to each other.
|
35
32
|
else
|
36
|
-
execution.child = @top
|
37
33
|
@top.parent = execution
|
34
|
+
execution.child = @top
|
38
35
|
end
|
39
36
|
|
40
|
-
# Place
|
37
|
+
# Place execution at top of stack.
|
41
38
|
@top = execution
|
42
39
|
|
43
40
|
end
|
44
41
|
|
45
|
-
def display
|
46
|
-
display_execution_tree(@bottom)
|
47
|
-
end
|
48
|
-
|
49
|
-
def display_execution_tree(execution)
|
50
|
-
p execution
|
51
|
-
unless execution.parent == nil
|
52
|
-
display_execution_tree(execution.parent)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
42
|
end
|