reflekt 0.5.0 → 0.9.1
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/Control.rb +7 -0
- data/lib/Execution.rb +47 -0
- data/lib/Reflection.rb +145 -0
- data/lib/ShadowStack.rb +56 -0
- data/lib/reflekt.rb +123 -83
- data/lib/web/{alpine.js → script.js} +0 -0
- data/lib/web/style.css +151 -0
- data/lib/web/template.html.erb +199 -31
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3589292c5c5cba2f3d7c7d5e1d7e02ee1052afe7339929cfa058b787b89c3158
|
4
|
+
data.tar.gz: d33c6774d53d4b7a0978fcfc48fa00e22f3684ccd51e5d42676f01831fb84291
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e3120f544d87057b4783a4e6e76a9689844f17ed1ad602c8b2c5e5ee241285ef83e276b56fbdb33da0d167c68077eca6f851e859a28392718a26ed129ab18e7
|
7
|
+
data.tar.gz: 47d323198d8d27585ef5fb0d493eca120d0cd2ff532a6578ef3b7490ccbc9e4fc38757bab8fb2ad4ab6c05c0a9fd721b27a5925a90c95503c0de5d4fb7483e12
|
data/lib/Control.rb
ADDED
data/lib/Execution.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Execution
|
2
|
+
|
3
|
+
attr_accessor :object
|
4
|
+
attr_accessor :caller_id
|
5
|
+
attr_accessor :caller_class
|
6
|
+
attr_accessor :parent
|
7
|
+
attr_accessor :child
|
8
|
+
attr_accessor :control
|
9
|
+
attr_accessor :reflections
|
10
|
+
attr_accessor :is_reflecting
|
11
|
+
|
12
|
+
def initialize(object, reflection_count)
|
13
|
+
|
14
|
+
@object = object
|
15
|
+
@caller_id = object.object_id
|
16
|
+
@caller_class = object.class
|
17
|
+
@parent = nil
|
18
|
+
@child = nil
|
19
|
+
|
20
|
+
@control = []
|
21
|
+
@reflections = Array.new(reflection_count)
|
22
|
+
@is_reflecting = false
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_empty_reflections?
|
27
|
+
@reflections.include? nil
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Is the Execution currently reflecting methods?
|
32
|
+
##
|
33
|
+
def is_reflecting?
|
34
|
+
@is_reflecting
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_finished_reflecting?
|
38
|
+
if is_reflecting?
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
if has_empty_reflections?
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/Reflection.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
class Reflection
|
2
|
+
|
3
|
+
# Keys.
|
4
|
+
TIME = "t"
|
5
|
+
INPUT = "i"
|
6
|
+
OUTPUT = "o"
|
7
|
+
TYPE = "T"
|
8
|
+
COUNT = "C"
|
9
|
+
VALUE = "V"
|
10
|
+
STATUS = "s"
|
11
|
+
MESSAGE = "m"
|
12
|
+
# Values.
|
13
|
+
PASS = "p"
|
14
|
+
FAIL = "f"
|
15
|
+
|
16
|
+
attr_accessor :clone
|
17
|
+
|
18
|
+
def initialize(execution, method, is_control)
|
19
|
+
|
20
|
+
@execution = execution
|
21
|
+
@method = method
|
22
|
+
@is_control = is_control
|
23
|
+
|
24
|
+
# Clone the execution's object.
|
25
|
+
@clone = execution.object.clone
|
26
|
+
@clone_id = nil
|
27
|
+
|
28
|
+
# Result.
|
29
|
+
@status = nil
|
30
|
+
@time = Time.now.to_i
|
31
|
+
@input = []
|
32
|
+
@output = nil
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Reflect on a method.
|
38
|
+
#
|
39
|
+
# Creates a shadow execution stack.
|
40
|
+
#
|
41
|
+
# @param method - The name of the method.
|
42
|
+
# @param *args - The method arguments.
|
43
|
+
#
|
44
|
+
# @return - A reflection hash.
|
45
|
+
##
|
46
|
+
def reflect(*args)
|
47
|
+
|
48
|
+
# Reflect on real world arguments.
|
49
|
+
if @is_control
|
50
|
+
@input = *args
|
51
|
+
# Reflect on deviated arguments.
|
52
|
+
else
|
53
|
+
args.each do |arg|
|
54
|
+
case arg
|
55
|
+
when Integer
|
56
|
+
@input << rand(9999)
|
57
|
+
else
|
58
|
+
@input << arg
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Action method with new arguments.
|
64
|
+
begin
|
65
|
+
@output = @clone.send(@method, *@input)
|
66
|
+
# When fail.
|
67
|
+
rescue StandardError => message
|
68
|
+
@status = MESSAGE
|
69
|
+
@message = message
|
70
|
+
# When pass.
|
71
|
+
else
|
72
|
+
@status = PASS
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def result()
|
78
|
+
# Build reflection.
|
79
|
+
reflection = {
|
80
|
+
TIME => @time,
|
81
|
+
STATUS => @status,
|
82
|
+
INPUT => normalize_input(@input),
|
83
|
+
OUTPUT => normalize_output(@output),
|
84
|
+
MESSAGE => @message
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Normalize inputs.
|
90
|
+
#
|
91
|
+
# @param args - The actual inputs.
|
92
|
+
# @return - A generic inputs representation.
|
93
|
+
##
|
94
|
+
def normalize_input(args)
|
95
|
+
inputs = []
|
96
|
+
args.each do |arg|
|
97
|
+
input = {
|
98
|
+
TYPE => arg.class.to_s,
|
99
|
+
VALUE => normalize_value(arg)
|
100
|
+
}
|
101
|
+
if (arg.class == Array)
|
102
|
+
input[COUNT] = arg.count
|
103
|
+
end
|
104
|
+
inputs << input
|
105
|
+
end
|
106
|
+
inputs
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Normalize output.
|
111
|
+
#
|
112
|
+
# @param input - The actual output.
|
113
|
+
# @return - A generic output representation.
|
114
|
+
##
|
115
|
+
def normalize_output(input)
|
116
|
+
|
117
|
+
output = {
|
118
|
+
TYPE => input.class.to_s,
|
119
|
+
VALUE => normalize_value(input)
|
120
|
+
}
|
121
|
+
|
122
|
+
if (input.class == Array || input.class == Hash)
|
123
|
+
output[COUNT] = input.count
|
124
|
+
elsif (input.class == TrueClass || input.class == FalseClass)
|
125
|
+
output[TYPE] = :Boolean
|
126
|
+
end
|
127
|
+
|
128
|
+
return output
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def normalize_value(value)
|
133
|
+
|
134
|
+
unless value.nil?
|
135
|
+
value = value.to_s.gsub(/\r?\n/, " ").to_s
|
136
|
+
if value.length >= 30
|
137
|
+
value = value[0, value.rindex(/\s/,30)].rstrip() + '...'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return value
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/ShadowStack.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
################################################################################
|
2
|
+
# SHADOW STACK
|
3
|
+
#
|
4
|
+
# Track the executions in a call stack.
|
5
|
+
################################################################################
|
6
|
+
|
7
|
+
class ShadowStack
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@bottom = nil
|
11
|
+
@top = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def peek()
|
15
|
+
@top
|
16
|
+
end
|
17
|
+
|
18
|
+
def base()
|
19
|
+
@bottom
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Push Execution.
|
24
|
+
#
|
25
|
+
# @param object - The object being executed.
|
26
|
+
# @param args - The arguments being executed.
|
27
|
+
#
|
28
|
+
# @return Execution - The new execution.
|
29
|
+
##
|
30
|
+
def push(execution)
|
31
|
+
|
32
|
+
# Reference previous execution.
|
33
|
+
if @bottom.nil?
|
34
|
+
@bottom = execution
|
35
|
+
else
|
36
|
+
execution.child = @top
|
37
|
+
@top.parent = execution
|
38
|
+
end
|
39
|
+
|
40
|
+
# Place new execution at the top of the stack.
|
41
|
+
@top = execution
|
42
|
+
|
43
|
+
end
|
44
|
+
|
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
|
+
end
|
data/lib/reflekt.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'erb'
|
3
3
|
require 'rowdb'
|
4
|
+
require 'Control'
|
5
|
+
require 'Execution'
|
6
|
+
require 'Reflection'
|
7
|
+
require 'ShadowStack'
|
4
8
|
|
5
9
|
################################################################################
|
6
10
|
# REFLEKT
|
7
11
|
#
|
8
|
-
#
|
12
|
+
# An Execution is created each time a method is called.
|
13
|
+
# Multiple Refections are created per Execution.
|
14
|
+
# These Reflections execute on a ShadowStack on cloned objects.
|
15
|
+
# Then flow is returned to the original method and normal execution continues.
|
16
|
+
#
|
17
|
+
# Usage:
|
9
18
|
#
|
10
19
|
# class ExampleClass
|
11
20
|
# prepend Reflekt
|
@@ -13,135 +22,162 @@ require 'rowdb'
|
|
13
22
|
|
14
23
|
module Reflekt
|
15
24
|
|
16
|
-
|
25
|
+
# The amount of reflections to create per method call.
|
26
|
+
@@reflekt_reflect_amount = 2
|
27
|
+
|
28
|
+
# Limit the amount of reflections that can be created per instance method.
|
29
|
+
# A method called thousands of times doesn't need that many reflections.
|
30
|
+
@@reflection_limit = 10
|
17
31
|
|
18
32
|
def initialize(*args)
|
19
33
|
|
20
|
-
@
|
21
|
-
@reflekt_clones = []
|
34
|
+
@reflection_counts = {}
|
22
35
|
|
23
|
-
#
|
36
|
+
# Get instance methods.
|
37
|
+
# TODO: Include parent methods like "Array.include?".
|
24
38
|
self.class.instance_methods(false).each do |method|
|
39
|
+
|
40
|
+
# Don't process skipped methods.
|
41
|
+
next if self.class.reflekt_skipped?(method)
|
42
|
+
|
43
|
+
@reflection_counts[method] = 0
|
44
|
+
|
45
|
+
# When method called in flow.
|
25
46
|
self.define_singleton_method(method) do |*args|
|
26
47
|
|
27
|
-
#
|
28
|
-
|
29
|
-
|
48
|
+
# Don't reflect when limit reached.
|
49
|
+
unless @reflection_counts[method] >= @@reflection_limit
|
50
|
+
|
51
|
+
# Get current execution.
|
52
|
+
execution = @@reflekt_stack.peek()
|
53
|
+
|
54
|
+
# When stack empty or past execution done reflecting.
|
55
|
+
if execution.nil? || execution.has_finished_reflecting?
|
56
|
+
|
57
|
+
# Create execution.
|
58
|
+
execution = Execution.new(self, @@reflekt_reflect_amount)
|
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
|
+
class_name = execution.caller_class.to_s
|
70
|
+
method_name = method.to_s
|
71
|
+
|
72
|
+
# Create control.
|
73
|
+
control = Control.new(execution, method, true)
|
74
|
+
execution.control = control
|
75
|
+
|
76
|
+
# Execute control.
|
77
|
+
control.reflect(*args)
|
78
|
+
|
79
|
+
# Save control.
|
80
|
+
@@reflekt_db.get("#{class_name}.#{method_name}.controls").push(control.result())
|
81
|
+
|
82
|
+
# Multiple reflections per execution.
|
83
|
+
execution.reflections.each_with_index do |value, index|
|
84
|
+
|
85
|
+
# Create reflection.
|
86
|
+
reflection = Reflection.new(execution, method, false)
|
87
|
+
execution.reflections[index] = reflection
|
88
|
+
|
89
|
+
# Execute reflection.
|
90
|
+
reflection.reflect(*args)
|
91
|
+
|
92
|
+
# Save reflection.
|
93
|
+
@@reflekt_db.get("#{class_name}.#{method_name}.reflections").push(reflection.result())
|
30
94
|
|
31
|
-
# Reflekt on method.
|
32
|
-
@reflekt_clones.each do |clone|
|
33
|
-
reflekt_action(clone, method, *args)
|
34
95
|
end
|
35
96
|
|
36
97
|
# Save results.
|
37
98
|
@@reflekt_db.write()
|
38
99
|
|
39
100
|
# Render results.
|
40
|
-
|
41
|
-
template = File.read("#{@@reflekt_path}/web/template.html.erb")
|
42
|
-
rendered = ERB.new(template).result(binding)
|
43
|
-
File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
|
44
|
-
f.write rendered
|
45
|
-
end
|
46
|
-
|
47
|
-
# Add libraries.
|
48
|
-
alpinejs = File.read("#{@@reflekt_path}/web/alpine.js")
|
49
|
-
File.open("#{@@reflekt_output_path}/alpine.js", 'w+') do |f|
|
50
|
-
f.write alpinejs
|
51
|
-
end
|
101
|
+
reflekt_render()
|
52
102
|
|
103
|
+
execution.is_reflecting = false
|
53
104
|
end
|
105
|
+
|
54
106
|
end
|
55
107
|
|
56
|
-
|
108
|
+
@reflection_counts[method] = @reflection_counts[method] + 1
|
109
|
+
|
110
|
+
# Continue execution / shadow execution.
|
57
111
|
super *args
|
112
|
+
|
58
113
|
end
|
59
114
|
|
60
115
|
end
|
61
116
|
|
62
|
-
# Continue
|
117
|
+
# Continue initialization.
|
63
118
|
super
|
64
119
|
|
65
|
-
# Create forks.
|
66
|
-
reflekt_fork()
|
67
|
-
|
68
120
|
end
|
69
121
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
122
|
+
##
|
123
|
+
# Render results.
|
124
|
+
##
|
125
|
+
def reflekt_render()
|
75
126
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
127
|
+
# Get JSON.
|
128
|
+
@@reflekt_json = File.read("#{@@reflekt_output_path}/db.json")
|
79
129
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# Create new arguments.
|
86
|
-
new_args = []
|
87
|
-
args.each do |arg|
|
88
|
-
case arg
|
89
|
-
when Integer
|
90
|
-
new_args << rand(9999)
|
91
|
-
else
|
92
|
-
new_args << arg
|
93
|
-
end
|
130
|
+
# Save HTML.
|
131
|
+
template = File.read("#{@@reflekt_path}/web/template.html.erb")
|
132
|
+
rendered = ERB.new(template).result(binding)
|
133
|
+
File.open("#{@@reflekt_output_path}/index.html", 'w+') do |f|
|
134
|
+
f.write rendered
|
94
135
|
end
|
95
136
|
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
# Build reflection.
|
101
|
-
reflection = {
|
102
|
-
"time" => Time.now.to_i,
|
103
|
-
}
|
104
|
-
# When error.
|
105
|
-
rescue StandardError => error
|
106
|
-
reflection["status"] = "error"
|
107
|
-
reflection["error"] = error
|
108
|
-
# When success.
|
109
|
-
else
|
110
|
-
reflection["status"] = "success"
|
137
|
+
# Add JS.
|
138
|
+
javascript = File.read("#{@@reflekt_path}/web/script.js")
|
139
|
+
File.open("#{@@reflekt_output_path}/script.js", 'w+') do |f|
|
140
|
+
f.write javascript
|
111
141
|
end
|
112
142
|
|
113
|
-
#
|
114
|
-
|
115
|
-
|
143
|
+
# Add CSS.
|
144
|
+
stylesheet = File.read("#{@@reflekt_path}/web/style.css")
|
145
|
+
File.open("#{@@reflekt_output_path}/style.css", 'w+') do |f|
|
146
|
+
f.write stylesheet
|
147
|
+
end
|
116
148
|
|
117
149
|
end
|
118
150
|
|
119
151
|
private
|
120
152
|
|
121
|
-
# Prepend Klass to the instance's singleton class.
|
122
153
|
def self.prepended(base)
|
123
|
-
|
154
|
+
# Prepend class methods to the instance's singleton class.
|
155
|
+
base.singleton_class.prepend(SingletonClassMethods)
|
124
156
|
|
125
|
-
@@reflekt_setup ||=
|
157
|
+
@@reflekt_setup ||= reflekt_setup_class
|
126
158
|
end
|
127
159
|
|
128
|
-
# Setup
|
129
|
-
def self.
|
160
|
+
# Setup class.
|
161
|
+
def self.reflekt_setup_class()
|
130
162
|
|
131
|
-
# Receive configuration
|
163
|
+
# Receive configuration.
|
132
164
|
$ENV ||= {}
|
133
165
|
$ENV[:reflekt] ||= $ENV[:reflekt] = {}
|
134
166
|
|
167
|
+
# Set configuration.
|
135
168
|
@@reflekt_path = File.dirname(File.realpath(__FILE__))
|
136
169
|
|
137
|
-
# Create
|
170
|
+
# Create reflection tree.
|
171
|
+
@@reflekt_stack = ShadowStack.new()
|
172
|
+
|
173
|
+
# Build reflections directory.
|
138
174
|
if $ENV[:reflekt][:output_path]
|
139
175
|
@@reflekt_output_path = File.join($ENV[:reflekt][:output_path], 'reflections')
|
140
|
-
#
|
176
|
+
# Build reflections directory in current execution path.
|
141
177
|
else
|
142
178
|
@@reflekt_output_path = File.join(Dir.pwd, 'reflections')
|
143
179
|
end
|
144
|
-
|
180
|
+
# Create reflections directory.
|
145
181
|
unless Dir.exist? @@reflekt_output_path
|
146
182
|
Dir.mkdir(@@reflekt_output_path)
|
147
183
|
end
|
@@ -153,24 +189,28 @@ module Reflekt
|
|
153
189
|
return true
|
154
190
|
end
|
155
191
|
|
156
|
-
module
|
192
|
+
module SingletonClassMethods
|
157
193
|
|
158
|
-
@@
|
194
|
+
@@reflekt_skipped_methods = Set.new
|
159
195
|
|
160
196
|
##
|
161
197
|
# Skip a method.
|
162
198
|
#
|
163
|
-
# method - A symbol representing the method name.
|
199
|
+
# @param method - A symbol representing the method name.
|
164
200
|
##
|
165
201
|
def reflekt_skip(method)
|
166
|
-
@@
|
202
|
+
@@reflekt_skipped_methods.add(method)
|
167
203
|
end
|
168
204
|
|
169
|
-
def
|
170
|
-
return true if @@
|
205
|
+
def reflekt_skipped?(method)
|
206
|
+
return true if @@reflekt_skipped_methods.include?(method)
|
171
207
|
false
|
172
208
|
end
|
173
209
|
|
210
|
+
def reflekt_limit(amount)
|
211
|
+
@@reflection_limit = amount
|
212
|
+
end
|
213
|
+
|
174
214
|
end
|
175
215
|
|
176
216
|
end
|
File without changes
|
data/lib/web/style.css
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
/* Layout. */
|
2
|
+
body {
|
3
|
+
padding: 0.5rem;
|
4
|
+
background: #C9D2E6;
|
5
|
+
font-family: 'Roboto', sans-serif;
|
6
|
+
}
|
7
|
+
|
8
|
+
.container {
|
9
|
+
margin: 0 auto;
|
10
|
+
max-width: 1000px;
|
11
|
+
padding: 2rem;
|
12
|
+
background: white;
|
13
|
+
}
|
14
|
+
|
15
|
+
/* Header. */
|
16
|
+
#logo {
|
17
|
+
display: block;
|
18
|
+
margin: 0 auto;
|
19
|
+
max-width: 100px;
|
20
|
+
}
|
21
|
+
|
22
|
+
/* Structure */
|
23
|
+
ul.classes,
|
24
|
+
ul.methods,
|
25
|
+
ul.controls,
|
26
|
+
ul.reflections {
|
27
|
+
padding-left: 0;
|
28
|
+
}
|
29
|
+
|
30
|
+
li.class-container,
|
31
|
+
li.method-container {
|
32
|
+
list-style: none;
|
33
|
+
padding: 1rem;
|
34
|
+
border: 5px solid #dadcdc;
|
35
|
+
}
|
36
|
+
|
37
|
+
/* State. */
|
38
|
+
.status-row {
|
39
|
+
width: 100%;
|
40
|
+
display: flex;
|
41
|
+
align-items: center;
|
42
|
+
flex-direction: row;
|
43
|
+
color: white;
|
44
|
+
background: #A9B6D2;
|
45
|
+
}
|
46
|
+
.status-row.pass {
|
47
|
+
background: #008C32;
|
48
|
+
}
|
49
|
+
.status-row.fail {
|
50
|
+
background: #D04700;
|
51
|
+
}
|
52
|
+
|
53
|
+
/* Buttons. */
|
54
|
+
.method .buttons {
|
55
|
+
margin-left: auto;
|
56
|
+
margin-right: 1rem;
|
57
|
+
}
|
58
|
+
.buttons button {
|
59
|
+
padding: 1rem 2rem;
|
60
|
+
}
|
61
|
+
|
62
|
+
/* Stats. */
|
63
|
+
.class .stats {
|
64
|
+
margin-left: auto;
|
65
|
+
}
|
66
|
+
.stat {
|
67
|
+
color: #efefef;
|
68
|
+
font-size: 3.5rem;
|
69
|
+
font-family: 'Merriweather', serif;
|
70
|
+
}
|
71
|
+
|
72
|
+
/* Class. */
|
73
|
+
.class {
|
74
|
+
padding: 2rem;
|
75
|
+
margin-bottom: 1rem;
|
76
|
+
}
|
77
|
+
.class:hover {
|
78
|
+
cursor: pointer;
|
79
|
+
}
|
80
|
+
.class h2 {
|
81
|
+
margin: 0;
|
82
|
+
}
|
83
|
+
|
84
|
+
/* Method. */
|
85
|
+
.method {
|
86
|
+
padding: 2rem;
|
87
|
+
margin-left: 1rem;
|
88
|
+
margin-bottom: 1rem;
|
89
|
+
}
|
90
|
+
.method:hover {
|
91
|
+
cursor: pointer;
|
92
|
+
}
|
93
|
+
.method .stat {
|
94
|
+
font-size: 2.5rem;
|
95
|
+
}
|
96
|
+
|
97
|
+
/* Reflection. */
|
98
|
+
.control,
|
99
|
+
.reflection {
|
100
|
+
list-style: none;
|
101
|
+
margin-left: 2rem;
|
102
|
+
display: flex;
|
103
|
+
flex-direction: row;
|
104
|
+
align-items: center;
|
105
|
+
background: #EFEFEF;
|
106
|
+
padding: 0.5rem 1.5rem;
|
107
|
+
margin-bottom: 0.3rem;
|
108
|
+
}
|
109
|
+
.control .time,
|
110
|
+
.reflection .time {
|
111
|
+
color: #777777;
|
112
|
+
margin-right: 2rem;
|
113
|
+
}
|
114
|
+
|
115
|
+
.info {
|
116
|
+
display: flex;
|
117
|
+
flex-direction: row;
|
118
|
+
align-items: center;
|
119
|
+
padding: 0.5rem 1rem;
|
120
|
+
border: 1px solid #aaa;
|
121
|
+
border-radius: 5px;
|
122
|
+
}
|
123
|
+
.info:not(:last-child) {
|
124
|
+
margin-right: 0.5rem;
|
125
|
+
}
|
126
|
+
.info h4 {
|
127
|
+
margin: 0;
|
128
|
+
color: #777777;
|
129
|
+
font-size: 1.2rem;
|
130
|
+
font-weight: normal;
|
131
|
+
}
|
132
|
+
|
133
|
+
.info-items {
|
134
|
+
display: flex;
|
135
|
+
flex-direction: row;
|
136
|
+
}
|
137
|
+
.info-item {
|
138
|
+
padding-left: 1rem;
|
139
|
+
padding-right: 1rem;
|
140
|
+
border-right: 1px solid #ccc;
|
141
|
+
}
|
142
|
+
.info-item:last-of-type {
|
143
|
+
padding-right: 0;
|
144
|
+
border-right: 0;
|
145
|
+
}
|
146
|
+
.info-item strong {
|
147
|
+
padding-bottom: 0.1rem;
|
148
|
+
}
|
149
|
+
.info-item pre {
|
150
|
+
margin: 0;
|
151
|
+
}
|
data/lib/web/template.html.erb
CHANGED
@@ -1,29 +1,49 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
|
+
|
3
4
|
<head>
|
4
5
|
<meta charset="utf-8">
|
5
6
|
<title>Reflekt</title>
|
6
|
-
<meta name="description" content="">
|
7
|
-
<meta name="author" content="">
|
7
|
+
<meta name="description" content="Reflective testing results.">
|
8
|
+
<meta name="author" content="Maedi Prichard">
|
8
9
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
9
|
-
<link rel="stylesheet" href="">
|
10
|
+
<link rel="stylesheet" href="style.css">
|
10
11
|
<link rel="shortcut icon" href="">
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" rel="stylesheet">
|
11
14
|
</head>
|
15
|
+
|
12
16
|
<body>
|
13
17
|
|
14
18
|
<script>
|
15
19
|
|
16
|
-
|
20
|
+
// Reflection keys.
|
21
|
+
const TIME = "t";
|
22
|
+
const INPUT = "i";
|
23
|
+
const OUTPUT = "o";
|
24
|
+
const TYPE = "T";
|
25
|
+
const COUNT = "C";
|
26
|
+
const VALUE = "V";
|
27
|
+
const STATUS = "s";
|
28
|
+
const MESSAGE = "m";
|
29
|
+
// Reflection values.
|
30
|
+
const PASS = "p";
|
31
|
+
const FAIL = "f";
|
17
32
|
|
18
|
-
|
33
|
+
function getData() {
|
34
|
+
|
35
|
+
var data = JSON.parse(<%= @@reflekt_json %>);
|
19
36
|
var results = {};
|
20
37
|
|
21
|
-
|
22
|
-
|
38
|
+
console.log("DATA:");
|
39
|
+
console.log(data);
|
40
|
+
|
41
|
+
if ('reflekt' in data) {
|
42
|
+
delete(data.reflekt);
|
23
43
|
}
|
24
44
|
|
25
45
|
// Classes.
|
26
|
-
for ([class_id, class_value] of Object.entries(
|
46
|
+
for ([class_id, class_value] of Object.entries(data)) {
|
27
47
|
|
28
48
|
// Class pass rate.
|
29
49
|
results[class_id] = {
|
@@ -38,53 +58,200 @@
|
|
38
58
|
// Methods.
|
39
59
|
for ([method_id, method] of Object.entries(class_value)) {
|
40
60
|
|
41
|
-
//
|
42
|
-
var pass_count = method.reduce(function(obj, v) {
|
43
|
-
obj[v
|
61
|
+
// Reflection pass rate.
|
62
|
+
var pass_count = method.reflections.reduce(function(obj, v) {
|
63
|
+
obj[v[STATUS]] = (obj[v[STATUS]] || 0) + 1;
|
44
64
|
return obj;
|
45
65
|
}, {});
|
46
66
|
|
67
|
+
var pass_rate = (pass_count[PASS] / method.reflections.length) * 100;
|
47
68
|
results[class_id]['methods'][method_id] = {
|
48
69
|
'stats': {
|
49
|
-
'pass_rate':
|
50
|
-
'test_count': method.length,
|
51
|
-
'pass_count': pass_count[
|
70
|
+
'pass_rate': pass_rate,
|
71
|
+
'test_count': method.reflections.length,
|
72
|
+
'pass_count': pass_count[PASS]
|
52
73
|
}
|
53
74
|
};
|
75
|
+
if (pass_rate == 100) {
|
76
|
+
results[class_id]['methods'][method_id]['status'] = 'pass';
|
77
|
+
}
|
78
|
+
else if (pass_rate < 100) {
|
79
|
+
results[class_id]['methods'][method_id]['status'] = 'fail';
|
80
|
+
}
|
54
81
|
|
55
82
|
// Class pass rate.
|
56
|
-
results[class_id]['stats']['test_count'] += method.length;
|
57
|
-
results[class_id]['stats']['pass_count'] += pass_count[
|
83
|
+
results[class_id]['stats']['test_count'] += method.reflections.length;
|
84
|
+
results[class_id]['stats']['pass_count'] += pass_count[PASS];
|
58
85
|
|
59
86
|
}
|
60
87
|
|
61
88
|
// Class pass rate.
|
62
89
|
var class_stats = results[class_id]['stats'];
|
63
|
-
|
90
|
+
var pass_rate = (class_stats['pass_count'] / class_stats['test_count']) * 100;
|
91
|
+
class_stats['pass_rate'] = pass_rate;
|
92
|
+
if (pass_rate == 100) {
|
93
|
+
results[class_id]['status'] = 'pass';
|
94
|
+
}
|
95
|
+
else if (pass_rate < 100) {
|
96
|
+
results[class_id]['status'] = 'fail';
|
97
|
+
}
|
64
98
|
}
|
65
99
|
|
66
|
-
return {
|
100
|
+
return {
|
101
|
+
data: data,
|
102
|
+
results: results
|
103
|
+
};
|
67
104
|
}
|
68
105
|
|
69
106
|
</script>
|
70
107
|
|
71
|
-
<div x-data="
|
108
|
+
<div class="container" x-data="getData()">
|
72
109
|
|
73
|
-
<
|
110
|
+
<div id="header">
|
111
|
+
<svg id="logo" enable-background="new 0 0 500 500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
112
|
+
<path d="m307.5 80.5h-115l-57.5 205h230z" fill="#0047d0"/>
|
113
|
+
<path d="m178 76.5-53.1-44-117.9 139 116 112z" fill="#d04800"/>
|
114
|
+
<path d="m190.4 467.5h115l57.5-168h-229z" fill="#0047d0" opacity=".7"/>
|
115
|
+
<path d="m177 467.5-81-85-92-197 115 113z" fill="#d04800" opacity=".7"/>
|
116
|
+
<g fill="#008c33"><path d="m322 76.5 53.1-44 118 139-116 112z"/>
|
117
|
+
<path d="m320 467.5 84-85 92-197-117 113z" opacity=".7"/>
|
118
|
+
</g>
|
119
|
+
</svg>
|
120
|
+
</div>
|
74
121
|
|
75
|
-
|
76
|
-
|
77
|
-
<div x-text="class_id"></div>
|
78
|
-
<div x-text="klass.stats.pass_rate"></div>
|
122
|
+
<ul class="classes">
|
123
|
+
<template x-for="[class_id, klass] in Object.entries(results)" :key="class_id">
|
79
124
|
|
80
|
-
|
125
|
+
<li class="class-container">
|
126
|
+
|
127
|
+
<div class="status-row class" x-bind:class="`${klass.status}`" @click="klass['show'] = !klass['show']" :aria-expanded="klass['show'] ? 'true' : 'false'" :class="{ 'active': klass['show'] }">
|
128
|
+
<h2 x-text="`${class_id}()`"></h2>
|
129
|
+
<div class="stats">
|
130
|
+
<div class="stat" x-text="`${klass.stats.pass_rate.toFixed(2)}%`"></div>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
|
134
|
+
<ul class="methods" x-show="klass['show']">
|
81
135
|
<template x-for="[method_id, method] in Object.entries(klass['methods'])" :key="method_id">
|
82
|
-
<
|
83
|
-
|
84
|
-
<div x-
|
85
|
-
|
136
|
+
<li class="method-container">
|
137
|
+
|
138
|
+
<div class="status-row method" x-bind:class="`${method.status}`" :class="{ 'active': method['show_reflections'] }" :class="{ 'active': method['show_controls'] }">
|
139
|
+
<h3 x-text="`${method_id}()`"></h3>
|
140
|
+
<div class="buttons">
|
141
|
+
<button @click="method['show_controls'] = !method['show_controls']">Controls</button>
|
142
|
+
<button @click="method['show_reflections'] = !method['show_reflections']">Reflections</button>
|
143
|
+
</div>
|
144
|
+
<div class="stats">
|
145
|
+
<div class="stat" x-text="`${method.stats.pass_rate.toFixed(2)}%`"></div>
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
|
149
|
+
<ul class="reflections" x-show="method['show_reflections']">
|
150
|
+
<template x-for="[reflection_id, reflection] in Object.entries(data[class_id][method_id].reflections)">
|
151
|
+
|
152
|
+
<li class="reflection">
|
153
|
+
|
154
|
+
<div class="time" x-text="`${new Date(reflection.t * 1000).getFullYear()}/${new Date(reflection.t * 1000).getMonth() + 1}/${new Date(reflection.t * 1000).getDate()} ${new Date(reflection.t * 1000).getHours()}:${new Date(reflection.t * 1000).getMinutes()}:${new Date(reflection.t * 1000).getSeconds()}`"></div>
|
155
|
+
|
156
|
+
<template x-for="[input_id, input] in Object.entries(reflection.i)">
|
157
|
+
|
158
|
+
<div class="info">
|
159
|
+
<h4>Input</h4>
|
160
|
+
<div class="info-items">
|
161
|
+
<div class="info-item">
|
162
|
+
<strong>Type:</strong><div class="input" x-text="input.T"></div>
|
163
|
+
</div>
|
164
|
+
<template x-if="input.V != undefined">
|
165
|
+
<div class="info-item">
|
166
|
+
<strong>Value:</strong><pre><div class="output" x-text="input.V"></div></pre>
|
167
|
+
</div>
|
168
|
+
</template>
|
169
|
+
<template x-if="input.C != undefined">
|
170
|
+
<div class="info-item">
|
171
|
+
<strong>Count:</strong><div class="input" x-text="input.C"></div>
|
172
|
+
</div>
|
173
|
+
</template>
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
|
177
|
+
</template>
|
178
|
+
|
179
|
+
<div class="info">
|
180
|
+
<h4>Output</h4>
|
181
|
+
<div class="info-items">
|
182
|
+
<div class="info-item">
|
183
|
+
<strong>Type:</strong><div class="output" x-text="reflection.o.T"></div>
|
184
|
+
</div>
|
185
|
+
<template x-if="reflection.o.C != undefined">
|
186
|
+
<div class="info-item">
|
187
|
+
<strong>Count:</strong><div class="output" x-text="reflection.o.C"></div>
|
188
|
+
</div>
|
189
|
+
</template>
|
190
|
+
<div class="info-item">
|
191
|
+
<strong>Value:</strong><pre><div class="output" x-text="reflection.o.V"></div></pre>
|
192
|
+
</div>
|
193
|
+
</div>
|
194
|
+
</div>
|
195
|
+
</li>
|
196
|
+
|
197
|
+
</template>
|
198
|
+
</ul>
|
199
|
+
|
200
|
+
<ul class="controls" x-show="method['show_controls']">
|
201
|
+
<template x-for="[control_id, control] in Object.entries(data[class_id][method_id].controls)">
|
202
|
+
|
203
|
+
<li class="control">
|
204
|
+
|
205
|
+
<div class="time" x-text="`${new Date(control.t * 1000).getFullYear()}/${new Date(control.t * 1000).getMonth() + 1}/${new Date(control.t * 1000).getDate()} ${new Date(control.t * 1000).getHours()}:${new Date(control.t * 1000).getMinutes()}:${new Date(control.t * 1000).getSeconds()}`"></div>
|
206
|
+
|
207
|
+
<template x-for="[input_id, input] in Object.entries(control.i)">
|
208
|
+
|
209
|
+
<div class="info">
|
210
|
+
<h4>Input</h4>
|
211
|
+
<div class="info-items">
|
212
|
+
<div class="info-item">
|
213
|
+
<strong>Type:</strong><div class="input" x-text="input.T"></div>
|
214
|
+
</div>
|
215
|
+
<template x-if="input.V != undefined">
|
216
|
+
<div class="info-item">
|
217
|
+
<strong>Value:</strong><pre><div class="output" x-text="input.V"></div></pre>
|
218
|
+
</div>
|
219
|
+
</template>
|
220
|
+
<template x-if="input.C != undefined">
|
221
|
+
<div class="info-item">
|
222
|
+
<strong>Count:</strong><div class="input" x-text="input.C"></div>
|
223
|
+
</div>
|
224
|
+
</template>
|
225
|
+
</div>
|
226
|
+
</div>
|
227
|
+
|
228
|
+
</template>
|
229
|
+
|
230
|
+
<div class="info">
|
231
|
+
<h4>Output</h4>
|
232
|
+
<div class="info-items">
|
233
|
+
<div class="info-item">
|
234
|
+
<strong>Type:</strong><div class="output" x-text="control.o.T"></div>
|
235
|
+
</div>
|
236
|
+
<template x-if="control.o.C != undefined">
|
237
|
+
<div class="info-item">
|
238
|
+
<strong>Count:</strong><div class="output" x-text="control.o.C"></div>
|
239
|
+
</div>
|
240
|
+
</template>
|
241
|
+
<div class="info-item">
|
242
|
+
<strong>Value:</strong><pre><div class="output" x-text="control.o.V"></div></pre>
|
243
|
+
</div>
|
244
|
+
</div>
|
245
|
+
</div>
|
246
|
+
</li>
|
247
|
+
|
248
|
+
</template>
|
249
|
+
</ul>
|
250
|
+
|
251
|
+
</li>
|
86
252
|
</template>
|
87
|
-
</
|
253
|
+
</ul>
|
254
|
+
|
88
255
|
</li>
|
89
256
|
</template>
|
90
257
|
|
@@ -92,7 +259,8 @@
|
|
92
259
|
|
93
260
|
</div>
|
94
261
|
|
95
|
-
<script src="
|
262
|
+
<script src="script.js"></script>
|
96
263
|
|
97
264
|
</body>
|
265
|
+
|
98
266
|
</html>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reflekt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maedi Prichard
|
@@ -30,10 +30,15 @@ executables: []
|
|
30
30
|
extensions: []
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
|
+
- lib/Control.rb
|
34
|
+
- lib/Execution.rb
|
35
|
+
- lib/Reflection.rb
|
36
|
+
- lib/ShadowStack.rb
|
33
37
|
- lib/reflekt.rb
|
34
|
-
- lib/web/
|
38
|
+
- lib/web/script.js
|
39
|
+
- lib/web/style.css
|
35
40
|
- lib/web/template.html.erb
|
36
|
-
homepage: https://github.com/
|
41
|
+
homepage: https://github.com/refIekt/reflekt
|
37
42
|
licenses:
|
38
43
|
- MPL-2.0
|
39
44
|
metadata: {}
|