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.
@@ -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