reflekt 0.9.2 → 0.9.8
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 +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
|