piggly 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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