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.
- 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
|