piggly 1.2.0

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