piggly 1.2.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 (59) hide show
  1. data/README.markdown +84 -0
  2. data/Rakefile +19 -0
  3. data/bin/piggly +245 -0
  4. data/lib/piggly/compiler/cache.rb +151 -0
  5. data/lib/piggly/compiler/pretty.rb +67 -0
  6. data/lib/piggly/compiler/queue.rb +46 -0
  7. data/lib/piggly/compiler/tags.rb +244 -0
  8. data/lib/piggly/compiler/trace.rb +91 -0
  9. data/lib/piggly/compiler.rb +5 -0
  10. data/lib/piggly/config.rb +43 -0
  11. data/lib/piggly/filecache.rb +40 -0
  12. data/lib/piggly/installer.rb +95 -0
  13. data/lib/piggly/parser/grammar.tt +747 -0
  14. data/lib/piggly/parser/nodes.rb +319 -0
  15. data/lib/piggly/parser/parser.rb +11783 -0
  16. data/lib/piggly/parser/traversal.rb +48 -0
  17. data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
  18. data/lib/piggly/parser.rb +67 -0
  19. data/lib/piggly/profile.rb +87 -0
  20. data/lib/piggly/reporter/html.rb +207 -0
  21. data/lib/piggly/reporter/piggly.css +187 -0
  22. data/lib/piggly/reporter/sortable.js +493 -0
  23. data/lib/piggly/reporter.rb +21 -0
  24. data/lib/piggly/task.rb +64 -0
  25. data/lib/piggly/util.rb +28 -0
  26. data/lib/piggly/version.rb +15 -0
  27. data/lib/piggly.rb +18 -0
  28. data/spec/compiler/cache_spec.rb +9 -0
  29. data/spec/compiler/pretty_spec.rb +9 -0
  30. data/spec/compiler/queue_spec.rb +3 -0
  31. data/spec/compiler/rewrite_spec.rb +3 -0
  32. data/spec/compiler/tags_spec.rb +285 -0
  33. data/spec/compiler/trace_spec.rb +173 -0
  34. data/spec/config_spec.rb +58 -0
  35. data/spec/filecache_spec.rb +70 -0
  36. data/spec/fixtures/snippets.sql +158 -0
  37. data/spec/grammar/expression_spec.rb +302 -0
  38. data/spec/grammar/statements/assignment_spec.rb +70 -0
  39. data/spec/grammar/statements/exception_spec.rb +52 -0
  40. data/spec/grammar/statements/if_spec.rb +178 -0
  41. data/spec/grammar/statements/loop_spec.rb +41 -0
  42. data/spec/grammar/statements/sql_spec.rb +71 -0
  43. data/spec/grammar/tokens/comment_spec.rb +58 -0
  44. data/spec/grammar/tokens/datatype_spec.rb +52 -0
  45. data/spec/grammar/tokens/identifier_spec.rb +58 -0
  46. data/spec/grammar/tokens/keyword_spec.rb +44 -0
  47. data/spec/grammar/tokens/label_spec.rb +40 -0
  48. data/spec/grammar/tokens/literal_spec.rb +30 -0
  49. data/spec/grammar/tokens/lval_spec.rb +50 -0
  50. data/spec/grammar/tokens/number_spec.rb +34 -0
  51. data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
  52. data/spec/grammar/tokens/string_spec.rb +54 -0
  53. data/spec/grammar/tokens/whitespace_spec.rb +40 -0
  54. data/spec/parser_spec.rb +8 -0
  55. data/spec/profile_spec.rb +5 -0
  56. data/spec/reporter/html_spec.rb +0 -0
  57. data/spec/spec_helper.rb +61 -0
  58. data/spec/spec_suite.rb +5 -0
  59. metadata +121 -0
@@ -0,0 +1,285 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe Tag do
6
+ end
7
+
8
+ describe EvaluationTag do
9
+ end
10
+
11
+ describe BlockTag do
12
+ end
13
+
14
+ describe UnconditionalBranchTag do
15
+ end
16
+
17
+ describe LoopConditionTag do
18
+ end
19
+
20
+ describe ForCollectionTag do
21
+ before do
22
+ @tag = ForCollectionTag.new('for-loop')
23
+ end
24
+
25
+ it "detects state 00 (0b0000)" do
26
+ # - terminates normally
27
+ # - pass through
28
+ # - iterate only once
29
+ # - iterate more than once
30
+ @tag.state.should == 0b0000
31
+ end
32
+
33
+ it "detects state 01 (0b0001)" do
34
+ # - terminates normally
35
+ # - pass through
36
+ # - iterate only once
37
+ # + iterate more than once
38
+
39
+ # two iterations
40
+ @tag.ping('t')
41
+ @tag.ping('t')
42
+ @tag.ping('f')
43
+
44
+ @tag.state.should == 0b0001
45
+ end
46
+
47
+ it "detects state 02 (0b0010)" do
48
+ # - terminates normally
49
+ # - pass through
50
+ # + iterate only once
51
+ # - iterate more than once
52
+
53
+ # one iteration
54
+ @tag.ping('t')
55
+ @tag.ping('f')
56
+
57
+ @tag.state.should == 0b0010
58
+ end
59
+
60
+ it "detects state 03 (0b0011)" do
61
+ # - terminates normally
62
+ # - pass through
63
+ # + iterate only once
64
+ # + iterate more than once
65
+
66
+ # one iteration
67
+ @tag.ping('t')
68
+ @tag.ping('f')
69
+
70
+ # two iterations
71
+ @tag.ping('t')
72
+ @tag.ping('t')
73
+ @tag.ping('f')
74
+
75
+ @tag.state.should == 0b0011
76
+ end
77
+
78
+ it "detects state 04 (0b0100)" do
79
+ # - terminates normally
80
+ # + pass through
81
+ # - iterate only once
82
+ # - iterate more than once
83
+
84
+ # zero iterations
85
+ @tag.ping('f')
86
+
87
+ @tag.state.should == 0b0100
88
+ end
89
+
90
+ it "detects state 05 (0b0101)" do
91
+ # - terminates normally
92
+ # + pass through
93
+ # - iterate only once
94
+ # + iterate more than once
95
+
96
+ # zero iterations
97
+ @tag.ping('f')
98
+
99
+ # two iterations
100
+ @tag.ping('t')
101
+ @tag.ping('t')
102
+ @tag.ping('f')
103
+
104
+ @tag.state.should == 0b0101
105
+ end
106
+
107
+ it "detects state 06 (0b0110)" do
108
+ # - terminates normally
109
+ # + pass through
110
+ # + iterate only once
111
+ # - iterate more than once
112
+
113
+ # zero iterations
114
+ @tag.ping('f')
115
+
116
+ # one iteration
117
+ @tag.ping('t')
118
+ @tag.ping('f')
119
+
120
+ @tag.state.should == 0b0110
121
+ end
122
+
123
+ it "detects state 07 (0b0111)" do
124
+ # - terminates normally
125
+ # + pass through
126
+ # + iterate only once
127
+ # + iterate more than once
128
+
129
+ # zero iterations
130
+ @tag.ping('f')
131
+
132
+ # one iteration
133
+ @tag.ping('t')
134
+ @tag.ping('f')
135
+
136
+ # two iterations
137
+ @tag.ping('t')
138
+ @tag.ping('t')
139
+ @tag.ping('f')
140
+
141
+ @tag.state.should == 0b0111
142
+ end
143
+
144
+ it "detects state 08 (0b1000)" do
145
+ # + terminates normally
146
+ # - pass through
147
+ # - iterate only once
148
+ # - iterate more than once
149
+
150
+ # TODO invalid
151
+ @tag.ping('@')
152
+
153
+ @tag.state.should == 0b1000
154
+ end
155
+
156
+ it "detects state 09 (0b1001)" do
157
+ # + terminates normally
158
+ # - pass through
159
+ # - iterate only once
160
+ # + iterate more than once
161
+
162
+ # iterate twice
163
+ @tag.ping('t')
164
+ @tag.ping('@')
165
+ @tag.ping('t')
166
+ @tag.ping('@')
167
+ @tag.ping('f')
168
+
169
+ @tag.state.should == 0b1001
170
+ end
171
+
172
+ it "detects state 10 (0b1010)" do
173
+ # + terminates normally
174
+ # - pass through
175
+ # + iterate only once
176
+ # - iterate more than once
177
+
178
+ # iterate once
179
+ @tag.ping('t')
180
+ @tag.ping('@')
181
+ @tag.ping('f')
182
+
183
+ @tag.state.should == 0b1010
184
+ end
185
+
186
+ it "detects state 11 (0b1011)" do
187
+ # + terminates normally
188
+ # - pass through
189
+ # + iterate only once
190
+ # + iterate more than once
191
+
192
+ # iterate once
193
+ @tag.ping('t')
194
+ @tag.ping('@')
195
+ @tag.ping('f')
196
+
197
+ # iterate twice
198
+ @tag.ping('t')
199
+ @tag.ping('@')
200
+ @tag.ping('t')
201
+ @tag.ping('@')
202
+ @tag.ping('f')
203
+
204
+ @tag.state.should == 0b1011
205
+ end
206
+
207
+ it "detects state 12 (0b1100)" do
208
+ # + terminates normally
209
+ # + pass through
210
+ # - iterate only once
211
+ # - iterate more than once
212
+
213
+ # TODO invalid
214
+ @tag.ping('@')
215
+ @tag.ping('f')
216
+
217
+ @tag.state.should == 0b1100
218
+ end
219
+
220
+ it "detects state 13 (0b1101)" do
221
+ # + terminates normally
222
+ # + pass through
223
+ # - iterate only once
224
+ # + iterate more than once
225
+
226
+ # iterate twice
227
+ @tag.ping('t')
228
+ @tag.ping('@')
229
+ @tag.ping('t')
230
+ @tag.ping('@')
231
+ @tag.ping('f')
232
+
233
+ # pass through
234
+ @tag.ping('f')
235
+
236
+ @tag.state.should == 0b1101
237
+ end
238
+
239
+ it "detects state 14 (0b1110)" do
240
+ # + terminates normally
241
+ # + pass through
242
+ # + iterate only once
243
+ # - iterate more than once
244
+
245
+ # pass through
246
+ @tag.ping('f')
247
+
248
+ # iterate once
249
+ @tag.ping('t')
250
+ @tag.ping('@')
251
+ @tag.ping('f')
252
+
253
+ @tag.state.should == 0b1110
254
+ end
255
+
256
+ it "detects state 15 (0b1111)" do
257
+ # + terminates normally
258
+ # + pass through
259
+ # + iterate only once
260
+ # + iterate more than once
261
+
262
+ # pass through
263
+ @tag.ping('f')
264
+
265
+ # iterate once
266
+ @tag.ping('t')
267
+ @tag.ping('@')
268
+ @tag.ping('f')
269
+
270
+ # iterate twice
271
+ @tag.ping('t')
272
+ @tag.ping('@')
273
+ @tag.ping('t')
274
+ @tag.ping('@')
275
+ @tag.ping('f')
276
+
277
+ @tag.state.should == 0b1111
278
+ end
279
+
280
+ end
281
+
282
+ describe BranchConditionTag do
283
+ end
284
+
285
+ end
@@ -0,0 +1,173 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+ require 'ostruct'
3
+
4
+ module Piggly
5
+
6
+ # MockSyntaxNode
7
+ class N < OpenStruct
8
+ def self.terminal(value)
9
+ new :value => value,
10
+ :terminal? => true
11
+ end
12
+
13
+ def self.space
14
+ terminal(' ')
15
+ end
16
+
17
+ def self.node(*children)
18
+ new :elements => children
19
+ end
20
+
21
+ class Branch < N
22
+ def elements
23
+ children[0..-3] + [stubNode] + children[-2..-1]
24
+ end
25
+ end
26
+
27
+ def self.branch(*children)
28
+ raise ArgumentError unless children.size > 2
29
+ Branch.new(:body => children[-2],
30
+ :stubNode => terminal(''),
31
+ :children => children)
32
+ end
33
+
34
+ def initialize(hash)
35
+ super({:terminal? => false}.update(hash))
36
+ end
37
+
38
+ def trace?
39
+ respond_to?(:stubNode)
40
+ end
41
+
42
+ def interval(start=0)
43
+ @start ||= start
44
+ @stop ||= if terminal?
45
+ @start + value.size
46
+ else
47
+ # recursively compute intervals
48
+ elements.inject(@start) do |prev, e|
49
+ e.interval(prev).end
50
+ end
51
+ end
52
+ @start...@stop
53
+ end
54
+ end
55
+
56
+ =begin
57
+ describe TraceCompiler, "with terminal root node" do
58
+ before do
59
+ @tree = N.terminal('code')
60
+ @compiler = TraceCompiler.new('file.sql')
61
+ end
62
+
63
+ it "compiles to original terminal" do
64
+ code, _ = @compiler.compile(@tree)
65
+ code.should eql('code')
66
+ end
67
+
68
+ it "compiles to single block sequence" do
69
+ code, data = @compiler.compile(@tree)
70
+ blocks = data[:blocks].flatten
71
+
72
+ blocks.size.should eql(1)
73
+ blocks.first.should eql([:root, 0..code.size - 1])
74
+ end
75
+ end
76
+
77
+ describe TraceCompiler, "with regular root node" do
78
+ before do
79
+ @tree = N.node N.terminal('statement'),
80
+ N.terminal('statement'),
81
+ N.node(N.terminal('statement'),
82
+ N.terminal('statement')),
83
+ N.terminal('statement'),
84
+ N.terminal('statement')
85
+ @compiler = TraceCompiler.new('file.sql')
86
+ end
87
+
88
+ it "compiles" do
89
+ code, _ = @compiler.compile(@tree)
90
+ code.should eql('statement' * 6)
91
+ end
92
+
93
+ it "flattens" do
94
+ code, data = @compiler.compile(@tree)
95
+ blocks = data[:blocks].flatten
96
+ blocks.size.should eql(1)
97
+
98
+ # compiled code has no added instrumentation
99
+ blocks.first.should eql([:root, 0..code.size - 1])
100
+ end
101
+ end
102
+
103
+ describe TraceCompiler, "root node contains branches" do
104
+ before do
105
+ @tree = N.node N.node(N.terminal('statement-1;'), N.space),
106
+ N.branch(N.node(N.terminal('if-1'), N.space),
107
+ N.node(N.terminal('condition'), N.space),
108
+ N.node(N.terminal('then'), N.space),
109
+ N.node(N.terminal('consequence'), N.space),
110
+ N.node(N.terminal('end if;'), N.space)),
111
+ N.node(N.terminal('statement-2;'), N.space),
112
+ N.branch(N.node(N.terminal('if-2'), N.space),
113
+ N.node(N.terminal('condition'), N.space),
114
+ N.node(N.terminal('then'), N.space),
115
+ N.node(N.terminal('consequence'), N.space),
116
+ N.terminal('end if;'))
117
+ @tree.interval # force computation
118
+ @compiler = TraceCompiler.new('file.sql')
119
+ end
120
+
121
+ it "flattens" do
122
+ code, data = @compiler.compile(@tree)
123
+ blocks = data[:blocks].flatten
124
+
125
+ first = blocks.select{|id, interval| id == 1 }.map{|e| e.last }
126
+ first.size.should eql(1)
127
+ 'consequence'.size.should eql(first[0].end - first[0].begin)
128
+
129
+ second = blocks.select{|id, interval| id == 2 }.map{|e| e.last }
130
+ second.size.should eql(1)
131
+ 'consequence'.size.should eql(second[0].end - second[0].begin)
132
+
133
+ roots = blocks.select{|id, interval| id == :root }.map{|e| e.last }
134
+ roots.size.should eql(3) # root <consequence> root <consequence> root
135
+
136
+ # compiled code size = source code size + instrumentation code size
137
+ roots.first.begin.should eql(0)
138
+ roots.last.end.should eql(code.size - ("raise notice 'PIGGLY-file.sql-n';\n".size * 2) - 1)
139
+ end
140
+
141
+ it "compiles" do
142
+ code, _ = @compiler.compile(@tree)
143
+ code.should eql(%w[statement-1;
144
+ if-1 condition then
145
+ raise notice 'PIGGLY-file.sql-1';\n
146
+ consequence
147
+ end if;
148
+ statement-2;
149
+ if-2 condition then
150
+ raise notice 'PIGGLY-file.sql-2';\n
151
+ consequence
152
+ end if;].join(' ').gsub('\n ', "\n"))
153
+ end
154
+
155
+ it "prepends Config.trace_prefix to raise statements" do
156
+ Config.trace_prefix = 'PREFIX'
157
+
158
+ code, _ = @compiler.compile(@tree)
159
+ code.should eql(%w[statement-1;
160
+ if-1 condition then
161
+ raise notice 'PREFIX-file.sql-1';\n
162
+ consequence
163
+ end if;
164
+ statement-2;
165
+ if-2 condition then
166
+ raise notice 'PREFIX-file.sql-2';\n
167
+ consequence
168
+ end if;].join(' ').gsub('\n ', "\n"))
169
+ end
170
+ end
171
+ =end
172
+
173
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe Config do
6
+ it "has class accessors and mutators" do
7
+ Config.should respond_to(:cache_root)
8
+ Config.should respond_to(:cache_root=)
9
+ Config.should respond_to(:report_root)
10
+ Config.should respond_to(:report_root=)
11
+ Config.should respond_to(:trace_prefix)
12
+ Config.should respond_to(:trace_prefix=)
13
+ end
14
+
15
+ it "has default values" do
16
+ Config.cache_root = nil
17
+ Config.cache_root.should_not be_nil
18
+ Config.cache_root.should =~ /cache$/
19
+
20
+ Config.report_root = nil
21
+ Config.report_root.should_not be_nil
22
+ Config.report_root.should =~ /reports$/
23
+
24
+ Config.trace_prefix = nil
25
+ Config.trace_prefix.should_not be_nil
26
+ Config.trace_prefix.should == 'PIGGLY'
27
+ end
28
+
29
+ describe "path" do
30
+ it "doesn't reparent absolute paths" do
31
+ Config.path('/tmp', '/usr/bin/ps').should == '/usr/bin/ps'
32
+ Config.path('A:/data/tmp', 'C:/USER/tmp').should == 'C:/USER/tmp'
33
+ Config.path('/tmp/data', '../data.txt').should == '../data.txt'
34
+ end
35
+
36
+ it "reparents relative paths" do
37
+ Config.path('/tmp', 'note.txt').should == '/tmp/note.txt'
38
+ end
39
+
40
+ it "doesn't require path parameter" do
41
+ Config.path('/tmp').should == '/tmp'
42
+ end
43
+ end
44
+
45
+ describe "mkpath" do
46
+ it "creates root if doesn't exist" do
47
+ FileUtils.stub!(:makedirs).and_return(true)
48
+ FileUtils.should_receive(:makedirs).with('x/y').once
49
+ Config.mkpath('x/y', 'z')
50
+ end
51
+
52
+ it "throws an error on path components that exist as files" do
53
+ lambda { Config.mkpath('/etc/passwd/file') }.should raise_error(Errno::EEXIST)
54
+ end
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ module Piggly
4
+
5
+ describe File, "cache invalidation" do
6
+ before do
7
+ mtime = Hash['a' => 1, 'b' => 2, 'c' => 3]
8
+ File.stub!(:mtime).and_return{|f| mtime.fetch(f) }
9
+ File.stub!(:exists?).and_return{|f| mtime.include?(f) }
10
+ end
11
+
12
+ it "invalidates non-existant cache file" do
13
+ File.stale?('d', 'a').should == true
14
+ File.stale?('d', 'a', 'b').should == true
15
+ end
16
+
17
+ it "performs validation using file mtimes" do
18
+ File.stale?('c', 'b').should_not be_true
19
+ File.stale?('c', 'a').should_not be_true
20
+ File.stale?('c', 'b', 'a').should_not be_true
21
+ File.stale?('c', 'a', 'b').should_not be_true
22
+
23
+ File.stale?('b', 'a').should_not be_true
24
+ File.stale?('b', 'c').should be_true
25
+ File.stale?('b', 'a', 'c').should be_true
26
+ File.stale?('b', 'c', 'a').should be_true
27
+
28
+ File.stale?('a', 'b').should be_true
29
+ File.stale?('a', 'c').should be_true
30
+ File.stale?('a', 'b', 'c').should be_true
31
+ File.stale?('a', 'c', 'b').should be_true
32
+ end
33
+
34
+ it "assumes sources exist" do
35
+ lambda{ File.stale?('a', 'd') }.should raise_error(StandardError)
36
+ lambda{ File.stale?('c', 'a', 'x') }.should raise_error(StandardError)
37
+ end
38
+ end
39
+
40
+ class ExampleClass; include FileCache; end
41
+ class ExampleCacheClass; include FileCache; end
42
+ class PigglyExampleClassHTML; include FileCache; end
43
+ class PigglyExampleHTMLClass; include FileCache; end
44
+ class HTMLPiggly; include FileCache; end
45
+ class ExampleRedefined
46
+ include FileCache
47
+ def self.cache_path(file)
48
+ 'redefined'
49
+ end
50
+ end
51
+
52
+ describe FileCache do
53
+ it "installs class methods" do
54
+ ExampleClass.should respond_to(:cache_path)
55
+ end
56
+
57
+ it "uses class name as cache subdirectory" do
58
+ Config.cache_root = '/'
59
+ FileUtils.should_receive(:makedirs).at_least(:once)
60
+
61
+ ExampleClass.cache_path('a.ext').should =~ %r(/Example/a.ext$)
62
+ ExampleCacheClass.cache_path('a.ext').should =~ %r(/ExampleCache/a.ext$)
63
+ PigglyExampleClassHTML.cache_path('a.ext').should =~ %r(/PigglyExampleClassHTML/a.ext$)
64
+ PigglyExampleHTMLClass.cache_path('a.ext').should =~ %r(/PigglyExampleHTML/a.ext$)
65
+ HTMLPiggly.cache_path('a.ext').should =~ %r(/HTML/a.ext$)
66
+ ExampleRedefined.cache_path('a.ext').should == 'redefined'
67
+ end
68
+ end
69
+
70
+ end