reflekt 0.9.2 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -1,38 +1,36 @@
1
- require 'set'
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
- # A parameter can accept multiple input types.
24
- # Duplicates will not be added to the set.
11
+ # Each rule trains on metadata to determine its boundaries.
12
+ #
13
+ # @param meta [Meta]
25
14
  ##
26
- def add_type(type)
27
- @types.add(type)
15
+ def train(meta)
28
16
  end
29
17
 
30
- def add_value(value)
31
- @values.add(value)
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
- def is_number?
35
- @types.include? Integer
27
+ ##
28
+ # Each rule provides metadata.
29
+ #
30
+ # @return [Hash]
31
+ ##
32
+ def result()
33
+ {}
36
34
  end
37
35
 
38
36
  end
@@ -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
@@ -1,7 +1,5 @@
1
1
  ################################################################################
2
- # SHADOW STACK
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
- # Push Execution.
21
+ # Place Execution at the top of stack.
24
22
  #
25
- # @param object - The object being executed.
26
- # @param args - The arguments being executed.
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
- # Reference previous execution.
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 new execution at the top of the stack.
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