piggly 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +84 -0
- data/Rakefile +19 -0
- data/bin/piggly +245 -0
- data/lib/piggly/compiler/cache.rb +151 -0
- data/lib/piggly/compiler/pretty.rb +67 -0
- data/lib/piggly/compiler/queue.rb +46 -0
- data/lib/piggly/compiler/tags.rb +244 -0
- data/lib/piggly/compiler/trace.rb +91 -0
- data/lib/piggly/compiler.rb +5 -0
- data/lib/piggly/config.rb +43 -0
- data/lib/piggly/filecache.rb +40 -0
- data/lib/piggly/installer.rb +95 -0
- data/lib/piggly/parser/grammar.tt +747 -0
- data/lib/piggly/parser/nodes.rb +319 -0
- data/lib/piggly/parser/parser.rb +11783 -0
- data/lib/piggly/parser/traversal.rb +48 -0
- data/lib/piggly/parser/treetop_ruby19_patch.rb +17 -0
- data/lib/piggly/parser.rb +67 -0
- data/lib/piggly/profile.rb +87 -0
- data/lib/piggly/reporter/html.rb +207 -0
- data/lib/piggly/reporter/piggly.css +187 -0
- data/lib/piggly/reporter/sortable.js +493 -0
- data/lib/piggly/reporter.rb +21 -0
- data/lib/piggly/task.rb +64 -0
- data/lib/piggly/util.rb +28 -0
- data/lib/piggly/version.rb +15 -0
- data/lib/piggly.rb +18 -0
- data/spec/compiler/cache_spec.rb +9 -0
- data/spec/compiler/pretty_spec.rb +9 -0
- data/spec/compiler/queue_spec.rb +3 -0
- data/spec/compiler/rewrite_spec.rb +3 -0
- data/spec/compiler/tags_spec.rb +285 -0
- data/spec/compiler/trace_spec.rb +173 -0
- data/spec/config_spec.rb +58 -0
- data/spec/filecache_spec.rb +70 -0
- data/spec/fixtures/snippets.sql +158 -0
- data/spec/grammar/expression_spec.rb +302 -0
- data/spec/grammar/statements/assignment_spec.rb +70 -0
- data/spec/grammar/statements/exception_spec.rb +52 -0
- data/spec/grammar/statements/if_spec.rb +178 -0
- data/spec/grammar/statements/loop_spec.rb +41 -0
- data/spec/grammar/statements/sql_spec.rb +71 -0
- data/spec/grammar/tokens/comment_spec.rb +58 -0
- data/spec/grammar/tokens/datatype_spec.rb +52 -0
- data/spec/grammar/tokens/identifier_spec.rb +58 -0
- data/spec/grammar/tokens/keyword_spec.rb +44 -0
- data/spec/grammar/tokens/label_spec.rb +40 -0
- data/spec/grammar/tokens/literal_spec.rb +30 -0
- data/spec/grammar/tokens/lval_spec.rb +50 -0
- data/spec/grammar/tokens/number_spec.rb +34 -0
- data/spec/grammar/tokens/sqlkeywords_spec.rb +45 -0
- data/spec/grammar/tokens/string_spec.rb +54 -0
- data/spec/grammar/tokens/whitespace_spec.rb +40 -0
- data/spec/parser_spec.rb +8 -0
- data/spec/profile_spec.rb +5 -0
- data/spec/reporter/html_spec.rb +0 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/spec_suite.rb +5 -0
- 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
|
data/spec/config_spec.rb
ADDED
@@ -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
|