piggly 1.2.1 → 2.0.0

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.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +163 -0
  3. data/Rakefile +29 -15
  4. data/bin/piggly +4 -244
  5. data/lib/piggly.rb +19 -17
  6. data/lib/piggly/command.rb +9 -0
  7. data/lib/piggly/command/base.rb +148 -0
  8. data/lib/piggly/command/report.rb +162 -0
  9. data/lib/piggly/command/test.rb +157 -0
  10. data/lib/piggly/command/trace.rb +90 -0
  11. data/lib/piggly/command/untrace.rb +78 -0
  12. data/lib/piggly/compiler.rb +7 -5
  13. data/lib/piggly/compiler/cache_dir.rb +119 -0
  14. data/lib/piggly/compiler/coverage_report.rb +63 -0
  15. data/lib/piggly/compiler/trace_compiler.rb +105 -0
  16. data/lib/piggly/config.rb +47 -22
  17. data/lib/piggly/dumper.rb +9 -0
  18. data/lib/piggly/dumper/index.rb +121 -0
  19. data/lib/piggly/dumper/qualified_name.rb +36 -0
  20. data/lib/piggly/dumper/qualified_type.rb +81 -0
  21. data/lib/piggly/dumper/reified_procedure.rb +142 -0
  22. data/lib/piggly/dumper/skeleton_procedure.rb +102 -0
  23. data/lib/piggly/installer.rb +84 -42
  24. data/lib/piggly/parser.rb +43 -49
  25. data/lib/piggly/parser/grammar.tt +289 -313
  26. data/lib/piggly/parser/nodes.rb +270 -211
  27. data/lib/piggly/parser/traversal.rb +35 -33
  28. data/lib/piggly/parser/treetop_ruby19_patch.rb +1 -1
  29. data/lib/piggly/profile.rb +81 -60
  30. data/lib/piggly/reporter.rb +5 -18
  31. data/lib/piggly/reporter/base.rb +103 -0
  32. data/lib/piggly/reporter/html_dsl.rb +63 -0
  33. data/lib/piggly/reporter/index.rb +108 -0
  34. data/lib/piggly/reporter/procedure.rb +104 -0
  35. data/lib/piggly/reporter/resources/highlight.js +21 -0
  36. data/lib/piggly/reporter/{piggly.css → resources/piggly.css} +52 -12
  37. data/lib/piggly/reporter/{sortable.js → resources/sortable.js} +0 -0
  38. data/lib/piggly/tags.rb +280 -0
  39. data/lib/piggly/task.rb +191 -40
  40. data/lib/piggly/util.rb +8 -27
  41. data/lib/piggly/util/blankslate.rb +114 -0
  42. data/lib/piggly/util/cacheable.rb +19 -0
  43. data/lib/piggly/util/enumerable.rb +44 -0
  44. data/lib/piggly/util/file.rb +17 -0
  45. data/lib/piggly/util/process_queue.rb +96 -0
  46. data/lib/piggly/util/thunk.rb +39 -0
  47. data/lib/piggly/version.rb +8 -8
  48. data/spec/examples/compiler/cacheable_spec.rb +190 -0
  49. data/spec/examples/compiler/report_spec.rb +25 -0
  50. data/spec/{compiler → examples/compiler}/trace_spec.rb +7 -57
  51. data/spec/examples/config_spec.rb +61 -0
  52. data/spec/examples/dumper/index_spec.rb +197 -0
  53. data/spec/examples/dumper/procedure_spec.rb +116 -0
  54. data/spec/{grammar → examples/grammar}/expression_spec.rb +60 -60
  55. data/spec/{grammar → examples/grammar}/statements/assignment_spec.rb +15 -15
  56. data/spec/examples/grammar/statements/declaration_spec.rb +21 -0
  57. data/spec/{grammar → examples/grammar}/statements/exception_spec.rb +10 -10
  58. data/spec/{grammar → examples/grammar}/statements/if_spec.rb +47 -34
  59. data/spec/{grammar → examples/grammar}/statements/loop_spec.rb +5 -5
  60. data/spec/{grammar → examples/grammar}/statements/sql_spec.rb +11 -11
  61. data/spec/{grammar → examples/grammar}/tokens/comment_spec.rb +11 -11
  62. data/spec/{grammar → examples/grammar}/tokens/datatype_spec.rb +14 -8
  63. data/spec/{grammar → examples/grammar}/tokens/identifier_spec.rb +26 -10
  64. data/spec/{grammar → examples/grammar}/tokens/keyword_spec.rb +5 -5
  65. data/spec/{grammar → examples/grammar}/tokens/label_spec.rb +7 -7
  66. data/spec/{grammar → examples/grammar}/tokens/literal_spec.rb +1 -1
  67. data/spec/examples/grammar/tokens/lval_spec.rb +50 -0
  68. data/spec/{grammar → examples/grammar}/tokens/number_spec.rb +1 -1
  69. data/spec/{grammar → examples/grammar}/tokens/sqlkeywords_spec.rb +1 -1
  70. data/spec/{grammar → examples/grammar}/tokens/string_spec.rb +9 -9
  71. data/spec/{grammar → examples/grammar}/tokens/whitespace_spec.rb +1 -1
  72. data/spec/examples/installer_spec.rb +59 -0
  73. data/spec/examples/parser/nodes_spec.rb +73 -0
  74. data/spec/examples/parser/traversal_spec.rb +14 -0
  75. data/spec/examples/parser_spec.rb +115 -0
  76. data/spec/examples/profile_spec.rb +153 -0
  77. data/spec/{reporter/html_spec.rb → examples/reporter/html/dsl_spec.rb} +0 -0
  78. data/spec/examples/reporter/html/index_spec.rb +0 -0
  79. data/spec/examples/reporter/html_spec.rb +1 -0
  80. data/spec/examples/reporter_spec.rb +0 -0
  81. data/spec/{compiler → examples}/tags_spec.rb +10 -10
  82. data/spec/examples/task_spec.rb +0 -0
  83. data/spec/examples/util/cacheable_spec.rb +41 -0
  84. data/spec/examples/util/enumerable_spec.rb +64 -0
  85. data/spec/examples/util/file_spec.rb +40 -0
  86. data/spec/examples/util/process_queue_spec.rb +16 -0
  87. data/spec/examples/util/thunk_spec.rb +58 -0
  88. data/spec/examples/version_spec.rb +0 -0
  89. data/spec/issues/007_spec.rb +25 -0
  90. data/spec/issues/008_spec.rb +73 -0
  91. data/spec/issues/018_spec.rb +25 -0
  92. data/spec/spec_helper.rb +253 -9
  93. metadata +136 -93
  94. data/README.markdown +0 -116
  95. data/lib/piggly/compiler/cache.rb +0 -151
  96. data/lib/piggly/compiler/pretty.rb +0 -67
  97. data/lib/piggly/compiler/queue.rb +0 -46
  98. data/lib/piggly/compiler/tags.rb +0 -244
  99. data/lib/piggly/compiler/trace.rb +0 -91
  100. data/lib/piggly/filecache.rb +0 -40
  101. data/lib/piggly/parser/parser.rb +0 -11794
  102. data/lib/piggly/reporter/html.rb +0 -207
  103. data/spec/compiler/cache_spec.rb +0 -9
  104. data/spec/compiler/pretty_spec.rb +0 -9
  105. data/spec/compiler/queue_spec.rb +0 -3
  106. data/spec/compiler/rewrite_spec.rb +0 -3
  107. data/spec/config_spec.rb +0 -58
  108. data/spec/filecache_spec.rb +0 -70
  109. data/spec/fixtures/snippets.sql +0 -158
  110. data/spec/grammar/tokens/lval_spec.rb +0 -50
  111. data/spec/parser_spec.rb +0 -8
  112. data/spec/profile_spec.rb +0 -5
@@ -1,67 +0,0 @@
1
- require File.join(File.dirname(__FILE__), *%w(.. reporter))
2
-
3
- module Piggly
4
-
5
- #
6
- # Produces HTML output to report coverage of tagged nodes in the tree
7
- #
8
- class PrettyCompiler
9
- include Piggly::HtmlTag
10
-
11
- def self.compile(path, profile)
12
- new(profile).send(:compile, path)
13
- end
14
-
15
- def initialize(profile)
16
- @profile = profile
17
- end
18
-
19
- private
20
-
21
- def compile(path)
22
- lines = File.read(path).count("\n") + 1
23
-
24
- # recompile (should be cache hit) to identify tagged nodes
25
- data = TraceCompiler.cache(path)
26
- html = traverse(data['tree'])
27
-
28
- return 'html' => html,
29
- 'lines' => 1..lines,
30
- 'tags' => data['tags']
31
- end
32
-
33
- def traverse(node, string='')
34
- if node.terminal?
35
- # terminals (leaves) are never tagged
36
- if node.style
37
- string << '<span class="' << node.style << '">' << e(node.text_value) << '</span>'
38
- else
39
- string << e(node.text_value)
40
- end
41
- else
42
- # non-terminals never write their text_value
43
- node.elements.each do |child|
44
- if child.tagged?
45
-
46
- # retreive the profiled tag
47
- tag = @profile.by_id[child.tag_id]
48
-
49
- if tag.complete?
50
- string << '<span class="' << tag.style << '" id="T' << tag.id << '">'
51
- else
52
- string << '<span class="' << tag.style << '" id="T' << tag.id << '" title="' << tag.description << '">'
53
- end
54
-
55
- traverse(child, string)
56
- string << '</span>'
57
- else
58
- traverse(child, string)
59
- end
60
- end
61
- end
62
-
63
- string
64
- end
65
-
66
- end
67
- end
@@ -1,46 +0,0 @@
1
- module Piggly
2
-
3
- #
4
- # Executes blocks in parallel subprocesses
5
- #
6
- class Queue
7
-
8
- def self.children=(value)
9
- @children = value
10
- end
11
-
12
- # add a compile job to the queue
13
- def self.queue(&block)
14
- (@queue ||= []) << block
15
- end
16
-
17
- def self.child
18
- queue { yield }
19
- end
20
-
21
- # start scheduler thread
22
- def self.start
23
- @active = 0
24
- @children ||= 1
25
- @queue ||= []
26
-
27
- while block = @queue.shift
28
- if @active >= @children
29
- pid = Process.wait
30
- @active -= 1
31
- end
32
-
33
- # enable enterprise ruby feature
34
- GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
35
-
36
- # use exit! to avoid auto-running any test suites
37
- pid = Process.fork{ block.call; exit! 0 }
38
-
39
- @active += 1
40
- end
41
-
42
- Process.waitall
43
- end
44
-
45
- end
46
- end
@@ -1,244 +0,0 @@
1
- module Piggly
2
-
3
- #
4
- # Coverage is tracked by attaching these compiler-generated tags to various nodes in a stored
5
- # procedure's parse tree. These tags each have a unique string identifier which is printed by
6
- # various parts of the recompiled stored procedure, and the output is then recognized by
7
- # Profile.notice_processor, which calls #ping on the tag corresponding to the printed string.
8
- #
9
- # After test execution is complete, each AST is walked and Tag values attached to NodeClass
10
- # values are used to produce the coverage report
11
- #
12
- class Tag
13
- PATTERN = /[0-9a-f]{16}/
14
-
15
- attr_accessor :id
16
-
17
- def initialize(prefix = nil, id = nil)
18
- @id = Digest::MD5.hexdigest(prefix.to_s + (id || object_id).to_s).slice(0, 16)
19
- end
20
-
21
- alias to_s id
22
-
23
- # Defined here in case ActiveSupport hasn't defined it on Object
24
- def tap
25
- yield self
26
- self
27
- end
28
- end
29
-
30
- class EvaluationTag < Tag
31
- def initialize(*args)
32
- clear
33
- super
34
- end
35
-
36
- def type
37
- :block
38
- end
39
-
40
- def ping(value)
41
- @ran = true
42
- end
43
-
44
- def style
45
- "c#{@ran ? '1' : '0'}"
46
- end
47
-
48
- def to_f
49
- @ran ? 100.0 : 0.0
50
- end
51
-
52
- def complete?
53
- @ran
54
- end
55
-
56
- def description
57
- @ran ? 'full coverage' : 'never evaluated'
58
- end
59
-
60
- # Resets code coverage
61
- def clear
62
- @ran = false
63
- end
64
- end
65
-
66
- #
67
- # Sequence of statements
68
- #
69
- class BlockTag < EvaluationTag
70
- end
71
-
72
- #
73
- # Procedure calls, raise exception, exits, returns
74
- #
75
- class UnconditionalBranchTag < EvaluationTag
76
- # aggregate this coverage data with conditional branches
77
- def type
78
- :branch
79
- end
80
- end
81
-
82
- class LoopConditionTag < Tag
83
- STATES = { # never terminates normally (so @pass must be false)
84
- 0b0000 => 'condition was never evaluated',
85
- 0b0001 => 'condition never evaluates false (terminates early). loop always iterates more than once',
86
- 0b0010 => 'condition never evaluates false (terminates early). loop always iterates only once',
87
- 0b0011 => 'condition never evaluates false (terminates early)',
88
- # terminates normally (one of @pass, @once, @twice must be true)
89
- 0b1001 => 'loop always iterates more than once',
90
- 0b1010 => 'loop always iterates only once',
91
- 0b1011 => 'loop never passes through',
92
- 0b1100 => 'loop always passes through',
93
- 0b1101 => 'loop never iterates only once',
94
- 0b1110 => 'loop never iterates more than once',
95
- 0b1111 => 'full coverage' }
96
-
97
- attr_reader :pass, :once, :twice, :ends, :count
98
-
99
- def initialize(*args)
100
- clear
101
- super
102
- end
103
-
104
- def type
105
- :loop
106
- end
107
-
108
- def ping(value)
109
- case value
110
- when 't'
111
- # loop iterated
112
- @count += 1
113
- else
114
- # loop terminated
115
- case @count
116
- when 0; @pass = true
117
- when 1; @once = true
118
- else; @twice = true
119
- end
120
- @count = 0
121
-
122
- # this isn't accurate. there needs to be a signal at the end
123
- # of the loop body to indicate it was reached. otherwise its
124
- # possible each iteration restarts early with 'continue'
125
- @ends = true
126
- end
127
- end
128
-
129
- def style
130
- "l#{[@pass, @once, @twice, @ends].map{|b| b ? 1 : 0}}"
131
- end
132
-
133
- def to_f
134
- # value space:
135
- # (1,2,X) - loop iterated at least twice and terminated normally
136
- # (1,X) - loop iterated only once and terminated normally
137
- # (0,X) - loop never iterated and terminated normally (pass-thru)
138
- # () - loop condition was never executed
139
- #
140
- # these combinations are ignored, because adding tests for them will probably not reveal bugs
141
- # (1,2) - loop iterated at least twice but terminated early
142
- # (1) - loop iterated only once but terminated early
143
- 100 * ([@pass, @once, @twice, @ends].count{|x| x } / 4.0)
144
- end
145
-
146
- def complete?
147
- @pass and @once and @twice and @ends
148
- end
149
-
150
- def description
151
- # weird hack so ForCollectionTag uses its separate constant
152
- self.class::STATES.fetch(n = state, "unknown tag state: #{n}")
153
- end
154
-
155
- # Returns state represented as a 4-bit integer
156
- def state
157
- [@ends,@pass,@once,@twice].reverse.inject([0,0]){|(k,n), bit| [k + 1, n | (bit ? 1 : 0) << k] }.last
158
- end
159
-
160
- def clear
161
- @pass = false
162
- @once = false
163
- @twice = false
164
- @ends = false
165
- @count = 0
166
- end
167
- end
168
-
169
- class ForCollectionTag < LoopConditionTag
170
- STATES = LoopConditionTag::STATES.merge \
171
- 0b0001 => 'loop always iterates more than once and always terminates early.',
172
- 0b0010 => 'loop always iterates only once and always terminates early.',
173
- 0b0011 => 'loop always terminates early',
174
- 0b0100 => 'loop always passes through'
175
-
176
- def ping(value)
177
- case value
178
- when 't'
179
- # start of iteration
180
- @count += 1
181
- when '@'
182
- # end of iteration
183
- @ends = true
184
- when 'f'
185
- # loop exit
186
- case @count
187
- when 0; @pass = true
188
- when 1; @once = true
189
- else; @twice = true
190
- end
191
- @count = 0
192
- end
193
- end
194
- end
195
-
196
- class BranchConditionTag < Tag
197
- attr_reader :true, :false
198
-
199
- def initialize(*args)
200
- clear
201
- super
202
- end
203
-
204
- def type
205
- :branch
206
- end
207
-
208
- def ping(value)
209
- case value
210
- when 't'; @true = true
211
- when 'f'; @false = true
212
- end
213
- end
214
-
215
- def style
216
- "b#{@true ? 1 : 0}#{@false ? 1 : 0 }"
217
- end
218
-
219
- def to_f
220
- (@true and @false) ? 100.0 : (@true or @false) ? 50.0 : 0.0
221
- end
222
-
223
- def complete?
224
- @true and @false
225
- end
226
-
227
- def description
228
- if @true and @false
229
- 'full coverage'
230
- elsif @true
231
- 'never evaluates false'
232
- elsif @false
233
- 'never evaluates true'
234
- else
235
- 'never evaluated'
236
- end
237
- end
238
-
239
- def clear
240
- @true, @false = false
241
- end
242
- end
243
-
244
- end
@@ -1,91 +0,0 @@
1
- module Piggly
2
-
3
- #
4
- # Walks the parse tree, attaching Tag values and rewriting source code to ping them.
5
- #
6
- class TraceCompiler
7
- include FileCache
8
- include CompilerCache
9
-
10
- attr_accessor :nodes
11
-
12
- def self.compile(tree, args)
13
- new(args.fetch(:path)).send(:compile, tree)
14
- end
15
-
16
- def self.compiler_path
17
- __FILE__
18
- end
19
-
20
- def initialize(path)
21
- # create unique prefix for each file, prepended to each node's tag
22
- @prefix = File.expand_path(path)
23
- @tags = []
24
- end
25
-
26
- #
27
- # Destructively modifies +tree+ (by attaching tags) and returns the tree
28
- # along with the modified source code, and the list of tags. The tag list
29
- # is passed along to Profile to compute coverage information. The tree is
30
- # passed to PrettyCompiler
31
- #
32
- def compile(tree)
33
- puts "Compiling #{@prefix}"
34
- return 'code.sql' => traverse(tree),
35
- 'tree' => tree,
36
- 'tags' => @tags,
37
- 'prefix' => @prefix
38
- end
39
-
40
- def traverse(node)
41
- if node.terminal? or node.expression?
42
- node.source_text
43
- else
44
- if node.respond_to?(:condStub) and node.respond_to?(:cond)
45
- # preserve opening parenthesis and whitespace before injecting code. this way
46
- # IF(test) becomes IF(piggly_cond(TAG, test)) instead of IFpiggly_cond(TAG, (test))
47
- pre, cond = node.cond.expr.text_value.match(/\A(\(?[\t\n\r ]*)(.+)\z/m).captures
48
- node.cond.source_text = ""
49
-
50
- @tags << node.cond.tag(@prefix)
51
-
52
- node.condStub.source_text = "#{pre}piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, #{cond})"
53
- node.condStub.source_text << traverse(node.cond.tail) # preserve trailing whitespace
54
- end
55
-
56
- if node.respond_to?(:bodyStub)
57
- if node.respond_to?(:exitStub) and node.respond_to?(:cond)
58
- @tags << node.body.tag(@prefix)
59
- @tags << node.cond.tag(@prefix)
60
-
61
- # a hack to simulate a loop conditional statement in ForLoop. signal condition was true
62
- # when body is executed. when exit stub is reached, signal condition was false
63
- node.bodyStub.source_text = "perform piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, true);#{node.indent(:bodySpace)}"
64
- node.bodyStub.source_text << "perform piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"
65
-
66
- if node.respond_to?(:doneStub)
67
- # signal the end of an iteration was reached
68
- node.doneStub.source_text = "#{node.indent(:bodySpace)}perform piggly_signal($PIGGLY$#{node.cond.tag_id}$PIGGLY$, $PIGGLY$@$PIGGLY$);"
69
- node.doneStub.source_text << node.body.indent
70
- end
71
-
72
- # signal the loop terminated
73
- node.exitStub.source_text = "\n#{node.indent}perform piggly_cond($PIGGLY$#{node.cond.tag_id}$PIGGLY$, false);"
74
- elsif node.respond_to?(:body)
75
- # no condition:
76
- # BEGIN ... END;
77
- # LOOP ... END;
78
- # ... ELSE ... END;
79
- # CONTINUE label;
80
- # EXIT label;
81
- @tags << node.body.tag(@prefix)
82
- node.bodyStub.source_text = "perform piggly_branch($PIGGLY$#{node.body.tag_id}$PIGGLY$);#{node.indent(:bodySpace)}"
83
- end
84
- end
85
-
86
- # traverse children (in which we just injected code)
87
- node.elements.map{|e| traverse(e) }.join
88
- end
89
- end
90
- end
91
- end