couchdb-ruby-server 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.hound.yml +4 -0
- data/.travis.yml +13 -0
- data/Gemfile +5 -0
- data/HISTORY.md +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/TODO.md +4 -0
- data/bin/couchdb-ruby-server +18 -0
- data/couchdb-ruby-server.gemspec +28 -0
- data/couchdb-ruby-server.sublime-project +27 -0
- data/lib/couchdb-ruby-server/server.rb +330 -0
- data/lib/couchdb-ruby-server/version.rb +5 -0
- data/lib/couchdb-ruby-server.rb +8 -0
- data/spec/couchdb-ruby-server_spec.rb +952 -0
- data/spec/spec_helper.rb +10 -0
- metadata +133 -0
@@ -0,0 +1,952 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
2
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
3
|
+
# the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
10
|
+
# License for the specific language governing permissions and limitations under
|
11
|
+
# the License.
|
12
|
+
|
13
|
+
# to run (requires ruby and rspec):
|
14
|
+
# rspec test/view_server/query_server_spec.rb
|
15
|
+
#
|
16
|
+
# environment options:
|
17
|
+
# QS_TRACE=true
|
18
|
+
# shows full output from the query server
|
19
|
+
# QS_LANG=lang
|
20
|
+
# run tests on the query server (for now, one of: js, erlang)
|
21
|
+
#
|
22
|
+
require 'json'
|
23
|
+
|
24
|
+
COUCH_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(COUCH_ROOT)
|
25
|
+
LANGUAGE = ENV["QS_LANG"] || "ruby"
|
26
|
+
|
27
|
+
puts "Running query server specs for #{LANGUAGE} query server"
|
28
|
+
|
29
|
+
require 'spec_helper'
|
30
|
+
|
31
|
+
class OSProcessRunner
|
32
|
+
def self.run
|
33
|
+
trace = ENV["QS_TRACE"] || false
|
34
|
+
puts "launching #{run_command}" if trace
|
35
|
+
if block_given?
|
36
|
+
IO.popen(run_command, "r+") do |io|
|
37
|
+
qs = QueryServerRunner.new(io, trace)
|
38
|
+
yield qs
|
39
|
+
end
|
40
|
+
else
|
41
|
+
io = IO.popen(run_command, "r+")
|
42
|
+
QueryServerRunner.new(io, trace)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
def initialize io, trace = false
|
46
|
+
@qsio = io
|
47
|
+
@trace = trace
|
48
|
+
end
|
49
|
+
def close
|
50
|
+
@qsio.close
|
51
|
+
end
|
52
|
+
def reset!
|
53
|
+
run(["reset"])
|
54
|
+
end
|
55
|
+
def add_fun(fun)
|
56
|
+
run(["add_fun", fun])
|
57
|
+
end
|
58
|
+
def teach_ddoc(ddoc)
|
59
|
+
run(["ddoc", "new", ddoc_id(ddoc), ddoc])
|
60
|
+
end
|
61
|
+
def ddoc_run(ddoc, fun_path, args)
|
62
|
+
run(["ddoc", ddoc_id(ddoc), fun_path, args])
|
63
|
+
end
|
64
|
+
def ddoc_id(ddoc)
|
65
|
+
d_id = ddoc["_id"]
|
66
|
+
raise 'ddoc must have _id' unless d_id
|
67
|
+
d_id
|
68
|
+
end
|
69
|
+
def get_chunks
|
70
|
+
resp = jsgets
|
71
|
+
raise "not a chunk" unless resp.first == "chunks"
|
72
|
+
return resp[1]
|
73
|
+
end
|
74
|
+
def run json
|
75
|
+
rrun json
|
76
|
+
jsgets
|
77
|
+
end
|
78
|
+
def rrun json
|
79
|
+
line = json.to_json
|
80
|
+
puts "run: #{line}" if @trace
|
81
|
+
@qsio.puts line
|
82
|
+
end
|
83
|
+
def rgets
|
84
|
+
resp = @qsio.gets
|
85
|
+
puts "got: #{resp}" if @trace
|
86
|
+
resp
|
87
|
+
end
|
88
|
+
def jsgets
|
89
|
+
resp = rgets
|
90
|
+
# err = @qserr.gets
|
91
|
+
# puts "err: #{err}" if err
|
92
|
+
if resp
|
93
|
+
begin
|
94
|
+
rj = JSON.parse("[#{resp.chomp}]")[0]
|
95
|
+
rescue JSON::ParserError
|
96
|
+
puts "JSON ERROR (dump under trace mode)"
|
97
|
+
# puts resp.chomp
|
98
|
+
while resp = rgets
|
99
|
+
# puts resp.chomp
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if rj.respond_to?(:[]) && rj.is_a?(Array)
|
103
|
+
if rj[0] == "log"
|
104
|
+
log = rj[1]
|
105
|
+
puts "log: #{log}" if @trace
|
106
|
+
rj = jsgets
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rj
|
110
|
+
else
|
111
|
+
raise "no response"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class QueryServerRunner < OSProcessRunner
|
117
|
+
|
118
|
+
COMMANDS = {
|
119
|
+
"js" => "#{COUCH_ROOT}/bin/couchjs #{COUCH_ROOT}/share/server/main.js",
|
120
|
+
"erlang" => "#{COUCH_ROOT}/test/view_server/run_native_process.es",
|
121
|
+
"ruby" => "bin/couchdb-ruby-server"
|
122
|
+
}
|
123
|
+
|
124
|
+
def self.run_command
|
125
|
+
COMMANDS[LANGUAGE]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ExternalRunner < OSProcessRunner
|
130
|
+
def self.run_command
|
131
|
+
"#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# we could organize this into a design document per language.
|
136
|
+
# that would make testing future languages really easy.
|
137
|
+
|
138
|
+
functions = {
|
139
|
+
"emit-twice" => {
|
140
|
+
"js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}},
|
141
|
+
"erlang" => <<-ERLANG,
|
142
|
+
fun({Doc}) ->
|
143
|
+
A = couch_util:get_value(<<"a">>, Doc, null),
|
144
|
+
Emit(<<"foo">>, A),
|
145
|
+
Emit(<<"bar">>, A)
|
146
|
+
end.
|
147
|
+
ERLANG
|
148
|
+
"ruby" => <<-RUBY
|
149
|
+
lambda {|doc| emit("foo",doc['a']); emit("bar",doc['a'])}
|
150
|
+
RUBY
|
151
|
+
},
|
152
|
+
"emit-once" => {
|
153
|
+
"js" => <<-JS,
|
154
|
+
function(doc){
|
155
|
+
emit("baz",doc.a)
|
156
|
+
}
|
157
|
+
JS
|
158
|
+
"erlang" => <<-ERLANG,
|
159
|
+
fun({Doc}) ->
|
160
|
+
A = couch_util:get_value(<<"a">>, Doc, null),
|
161
|
+
Emit(<<"baz">>, A)
|
162
|
+
end.
|
163
|
+
ERLANG
|
164
|
+
"ruby" => <<-RUBY
|
165
|
+
lambda {|doc| emit("baz", doc['a'])}
|
166
|
+
RUBY
|
167
|
+
},
|
168
|
+
"reduce-values-length" => {
|
169
|
+
"js" => %{function(keys, values, rereduce) { return values.length; }},
|
170
|
+
"erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.},
|
171
|
+
"ruby" => %{lambda {|keys, values, rereduce| values.size }}
|
172
|
+
},
|
173
|
+
"reduce-values-sum" => {
|
174
|
+
"js" => %{function(keys, values, rereduce) { return sum(values); }},
|
175
|
+
"erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.},
|
176
|
+
"ruby" => %{lambda {|keys, values, rereduce| values.reduce(0) {|sum, v| sum += v}}}
|
177
|
+
},
|
178
|
+
"validate-forbidden" => {
|
179
|
+
"js" => <<-JS,
|
180
|
+
function(newDoc, oldDoc, userCtx) {
|
181
|
+
if(newDoc.bad)
|
182
|
+
throw({forbidden:"bad doc"}); "foo bar";
|
183
|
+
}
|
184
|
+
JS
|
185
|
+
"erlang" => <<-ERLANG,
|
186
|
+
fun({NewDoc}, _OldDoc, _UserCtx) ->
|
187
|
+
case couch_util:get_value(<<"bad">>, NewDoc) of
|
188
|
+
undefined -> 1;
|
189
|
+
_ -> {[{forbidden, <<"bad doc">>}]}
|
190
|
+
end
|
191
|
+
end.
|
192
|
+
ERLANG
|
193
|
+
"ruby" => <<-RUBY
|
194
|
+
lambda do |newDoc, oldDoc, userCtx|
|
195
|
+
raise Error.new(:forbidden), 'bad doc' if newDoc['bad']
|
196
|
+
"foo bar"
|
197
|
+
end
|
198
|
+
RUBY
|
199
|
+
},
|
200
|
+
"show-simple" => {
|
201
|
+
"js" => <<-JS,
|
202
|
+
function(doc, req) {
|
203
|
+
log("ok");
|
204
|
+
return [doc.title, doc.body].join(' - ');
|
205
|
+
}
|
206
|
+
JS
|
207
|
+
"erlang" => <<-ERLANG,
|
208
|
+
fun({Doc}, Req) ->
|
209
|
+
Title = couch_util:get_value(<<"title">>, Doc),
|
210
|
+
Body = couch_util:get_value(<<"body">>, Doc),
|
211
|
+
Resp = <<Title/binary, " - ", Body/binary>>,
|
212
|
+
{[{<<"body">>, Resp}]}
|
213
|
+
end.
|
214
|
+
ERLANG
|
215
|
+
"ruby" => <<-RUBY
|
216
|
+
lambda { |doc, req|
|
217
|
+
log("ok");
|
218
|
+
[doc['title'], doc['body']].join(' - ');
|
219
|
+
}
|
220
|
+
RUBY
|
221
|
+
},
|
222
|
+
"show-headers" => {
|
223
|
+
"js" => <<-JS,
|
224
|
+
function(doc, req) {
|
225
|
+
var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}};
|
226
|
+
resp.body = [doc.title, doc.body].join(' - ');
|
227
|
+
return resp;
|
228
|
+
}
|
229
|
+
JS
|
230
|
+
"erlang" => <<-ERLANG,
|
231
|
+
fun({Doc}, Req) ->
|
232
|
+
Title = couch_util:get_value(<<"title">>, Doc),
|
233
|
+
Body = couch_util:get_value(<<"body">>, Doc),
|
234
|
+
Resp = <<Title/binary, " - ", Body/binary>>,
|
235
|
+
{[
|
236
|
+
{<<"code">>, 200},
|
237
|
+
{<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}},
|
238
|
+
{<<"body">>, Resp}
|
239
|
+
]}
|
240
|
+
end.
|
241
|
+
ERLANG
|
242
|
+
"ruby" => <<-RUBY
|
243
|
+
lambda { |doc, req|
|
244
|
+
resp = {:code=>200, :headers=>{"X-Plankton"=>"Rusty"}};
|
245
|
+
resp[:body] = [doc['title'], doc['body']].join(' - ');
|
246
|
+
resp
|
247
|
+
}
|
248
|
+
RUBY
|
249
|
+
},
|
250
|
+
"show-sends" => {
|
251
|
+
"js" => <<-JS,
|
252
|
+
function(head, req) {
|
253
|
+
start({headers:{"Content-Type" : "text/plain"}});
|
254
|
+
send("first chunk");
|
255
|
+
send('second "chunk"');
|
256
|
+
return "tail";
|
257
|
+
};
|
258
|
+
JS
|
259
|
+
"erlang" => <<-ERLANG,
|
260
|
+
fun(Head, Req) ->
|
261
|
+
Resp = {[
|
262
|
+
{<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}}
|
263
|
+
]},
|
264
|
+
Start(Resp),
|
265
|
+
Send(<<"first chunk">>),
|
266
|
+
Send(<<"second \\\"chunk\\\"">>),
|
267
|
+
<<"tail">>
|
268
|
+
end.
|
269
|
+
ERLANG
|
270
|
+
"ruby" => <<-RUBY
|
271
|
+
lambda { |head, req|
|
272
|
+
start({:headers => {"Content-Type" => "text/plain"}});
|
273
|
+
send("first chunk");
|
274
|
+
send('second "chunk"');
|
275
|
+
"tail"
|
276
|
+
}
|
277
|
+
RUBY
|
278
|
+
},
|
279
|
+
"show-while-get-rows" => {
|
280
|
+
"js" => <<-JS,
|
281
|
+
function(head, req) {
|
282
|
+
send("first chunk");
|
283
|
+
send(req.q);
|
284
|
+
var row;
|
285
|
+
log("about to getRow " + typeof(getRow));
|
286
|
+
while(row = getRow()) {
|
287
|
+
send(row.key);
|
288
|
+
};
|
289
|
+
return "tail";
|
290
|
+
};
|
291
|
+
JS
|
292
|
+
"erlang" => <<-ERLANG,
|
293
|
+
fun(Head, {Req}) ->
|
294
|
+
Send(<<"first chunk">>),
|
295
|
+
Send(couch_util:get_value(<<"q">>, Req)),
|
296
|
+
Fun = fun({Row}, _) ->
|
297
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
298
|
+
{ok, nil}
|
299
|
+
end,
|
300
|
+
{ok, _} = FoldRows(Fun, nil),
|
301
|
+
<<"tail">>
|
302
|
+
end.
|
303
|
+
ERLANG
|
304
|
+
"ruby" => <<-RUBY
|
305
|
+
lambda { |head, req|
|
306
|
+
send("first chunk");
|
307
|
+
send(req['q']);
|
308
|
+
while(row = get_row)
|
309
|
+
send(row['key']);
|
310
|
+
end
|
311
|
+
"tail";
|
312
|
+
}
|
313
|
+
RUBY
|
314
|
+
},
|
315
|
+
"show-while-get-rows-multi-send" => {
|
316
|
+
"js" => <<-JS,
|
317
|
+
function(head, req) {
|
318
|
+
send("bacon");
|
319
|
+
var row;
|
320
|
+
log("about to getRow " + typeof(getRow));
|
321
|
+
while(row = getRow()) {
|
322
|
+
send(row.key);
|
323
|
+
send("eggs");
|
324
|
+
};
|
325
|
+
return "tail";
|
326
|
+
};
|
327
|
+
JS
|
328
|
+
"erlang" => <<-ERLANG,
|
329
|
+
fun(Head, Req) ->
|
330
|
+
Send(<<"bacon">>),
|
331
|
+
Fun = fun({Row}, _) ->
|
332
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
333
|
+
Send(<<"eggs">>),
|
334
|
+
{ok, nil}
|
335
|
+
end,
|
336
|
+
FoldRows(Fun, nil),
|
337
|
+
<<"tail">>
|
338
|
+
end.
|
339
|
+
ERLANG
|
340
|
+
"ruby" => <<-RUBY
|
341
|
+
lambda do |head, req|
|
342
|
+
send("bacon");
|
343
|
+
while(row = get_row)
|
344
|
+
send(row['key']);
|
345
|
+
send("eggs");
|
346
|
+
end
|
347
|
+
"tail";
|
348
|
+
end
|
349
|
+
RUBY
|
350
|
+
},
|
351
|
+
"list-simple" => {
|
352
|
+
"js" => <<-JS,
|
353
|
+
function(head, req) {
|
354
|
+
send("first chunk");
|
355
|
+
send(req.q);
|
356
|
+
var row;
|
357
|
+
while(row = getRow()) {
|
358
|
+
send(row.key);
|
359
|
+
};
|
360
|
+
return "early";
|
361
|
+
};
|
362
|
+
JS
|
363
|
+
"erlang" => <<-ERLANG,
|
364
|
+
fun(Head, {Req}) ->
|
365
|
+
Send(<<"first chunk">>),
|
366
|
+
Send(couch_util:get_value(<<"q">>, Req)),
|
367
|
+
Fun = fun({Row}, _) ->
|
368
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
369
|
+
{ok, nil}
|
370
|
+
end,
|
371
|
+
FoldRows(Fun, nil),
|
372
|
+
<<"early">>
|
373
|
+
end.
|
374
|
+
ERLANG
|
375
|
+
"ruby" => <<-RUBY
|
376
|
+
lambda do |head, req|
|
377
|
+
send("first chunk");
|
378
|
+
send(req['q']);
|
379
|
+
while(row = get_row)
|
380
|
+
send(row['key']);
|
381
|
+
end
|
382
|
+
"early";
|
383
|
+
end
|
384
|
+
RUBY
|
385
|
+
},
|
386
|
+
"list-chunky" => {
|
387
|
+
"js" => <<-JS,
|
388
|
+
function(head, req) {
|
389
|
+
send("first chunk");
|
390
|
+
send(req.q);
|
391
|
+
var row, i=0;
|
392
|
+
while(row = getRow()) {
|
393
|
+
send(row.key);
|
394
|
+
i += 1;
|
395
|
+
if (i > 2) {
|
396
|
+
return('early tail');
|
397
|
+
}
|
398
|
+
};
|
399
|
+
};
|
400
|
+
JS
|
401
|
+
"erlang" => <<-ERLANG,
|
402
|
+
fun(Head, {Req}) ->
|
403
|
+
Send(<<"first chunk">>),
|
404
|
+
Send(couch_util:get_value(<<"q">>, Req)),
|
405
|
+
Fun = fun
|
406
|
+
({Row}, Count) when Count < 2 ->
|
407
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
408
|
+
{ok, Count+1};
|
409
|
+
({Row}, Count) when Count == 2 ->
|
410
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
411
|
+
{stop, <<"early tail">>}
|
412
|
+
end,
|
413
|
+
{ok, Tail} = FoldRows(Fun, 0),
|
414
|
+
Tail
|
415
|
+
end.
|
416
|
+
ERLANG
|
417
|
+
"ruby" => <<-RUBY
|
418
|
+
lambda do |head, req|
|
419
|
+
send("first chunk");
|
420
|
+
send(req['q']);
|
421
|
+
i=0
|
422
|
+
while(row = get_row)
|
423
|
+
send(row['key']);
|
424
|
+
i += 1;
|
425
|
+
return 'early tail' if i > 2
|
426
|
+
end
|
427
|
+
end
|
428
|
+
RUBY
|
429
|
+
},
|
430
|
+
"list-old-style" => {
|
431
|
+
"js" => <<-JS,
|
432
|
+
function(head, req, foo, bar) {
|
433
|
+
return "stuff";
|
434
|
+
}
|
435
|
+
JS
|
436
|
+
"erlang" => <<-ERLANG,
|
437
|
+
fun(Head, Req, Foo, Bar) ->
|
438
|
+
<<"stuff">>
|
439
|
+
end.
|
440
|
+
ERLANG
|
441
|
+
"ruby" => <<-RUBY
|
442
|
+
lambda do |head, req, foo, bar|
|
443
|
+
send "stuff"
|
444
|
+
end
|
445
|
+
RUBY
|
446
|
+
},
|
447
|
+
"list-capped" => {
|
448
|
+
"js" => <<-JS,
|
449
|
+
function(head, req) {
|
450
|
+
send("bacon")
|
451
|
+
var row, i = 0;
|
452
|
+
while(row = getRow()) {
|
453
|
+
send(row.key);
|
454
|
+
i += 1;
|
455
|
+
if (i > 2) {
|
456
|
+
return('early');
|
457
|
+
}
|
458
|
+
};
|
459
|
+
}
|
460
|
+
JS
|
461
|
+
"erlang" => <<-ERLANG,
|
462
|
+
fun(Head, Req) ->
|
463
|
+
Send(<<"bacon">>),
|
464
|
+
Fun = fun
|
465
|
+
({Row}, Count) when Count < 2 ->
|
466
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
467
|
+
{ok, Count+1};
|
468
|
+
({Row}, Count) when Count == 2 ->
|
469
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
470
|
+
{stop, <<"early">>}
|
471
|
+
end,
|
472
|
+
{ok, Tail} = FoldRows(Fun, 0),
|
473
|
+
Tail
|
474
|
+
end.
|
475
|
+
ERLANG
|
476
|
+
"ruby" => <<-RUBY
|
477
|
+
lambda do |head, req|
|
478
|
+
send("bacon")
|
479
|
+
i = 0;
|
480
|
+
while(row = get_row)
|
481
|
+
send(row['key']);
|
482
|
+
i += 1;
|
483
|
+
return 'early' if i > 2
|
484
|
+
end
|
485
|
+
end
|
486
|
+
RUBY
|
487
|
+
},
|
488
|
+
"list-raw" => {
|
489
|
+
"js" => <<-JS,
|
490
|
+
function(head, req) {
|
491
|
+
// log(this.toSource());
|
492
|
+
// log(typeof send);
|
493
|
+
send("first chunk");
|
494
|
+
send(req.q);
|
495
|
+
var row;
|
496
|
+
while(row = getRow()) {
|
497
|
+
send(row.key);
|
498
|
+
};
|
499
|
+
return "tail";
|
500
|
+
};
|
501
|
+
JS
|
502
|
+
"erlang" => <<-ERLANG,
|
503
|
+
fun(Head, {Req}) ->
|
504
|
+
Send(<<"first chunk">>),
|
505
|
+
Send(couch_util:get_value(<<"q">>, Req)),
|
506
|
+
Fun = fun({Row}, _) ->
|
507
|
+
Send(couch_util:get_value(<<"key">>, Row)),
|
508
|
+
{ok, nil}
|
509
|
+
end,
|
510
|
+
FoldRows(Fun, nil),
|
511
|
+
<<"tail">>
|
512
|
+
end.
|
513
|
+
ERLANG
|
514
|
+
"ruby" => <<-RUBY
|
515
|
+
lambda do |head, req|
|
516
|
+
# log(this.toSource());
|
517
|
+
# log(typeof send);
|
518
|
+
send("first chunk");
|
519
|
+
send(req['q']);
|
520
|
+
while(row = get_row)
|
521
|
+
send(row['key']);
|
522
|
+
end
|
523
|
+
"tail";
|
524
|
+
end
|
525
|
+
RUBY
|
526
|
+
},
|
527
|
+
"filter-basic" => {
|
528
|
+
"js" => <<-JS,
|
529
|
+
function(doc, req) {
|
530
|
+
if (doc.good) {
|
531
|
+
return true;
|
532
|
+
}
|
533
|
+
}
|
534
|
+
JS
|
535
|
+
"erlang" => <<-ERLANG,
|
536
|
+
fun({Doc}, Req) ->
|
537
|
+
couch_util:get_value(<<"good">>, Doc)
|
538
|
+
end.
|
539
|
+
ERLANG
|
540
|
+
"ruby" => <<-RUBY
|
541
|
+
lambda { |doc, req|
|
542
|
+
doc['good']
|
543
|
+
}
|
544
|
+
RUBY
|
545
|
+
},
|
546
|
+
"update-basic" => {
|
547
|
+
"js" => <<-JS,
|
548
|
+
function(doc, req) {
|
549
|
+
doc.world = "hello";
|
550
|
+
var resp = [doc, "hello doc"];
|
551
|
+
return resp;
|
552
|
+
}
|
553
|
+
JS
|
554
|
+
"erlang" => <<-ERLANG,
|
555
|
+
fun({Doc}, Req) ->
|
556
|
+
Doc2 = [{<<"world">>, <<"hello">>}|Doc],
|
557
|
+
[{Doc2}, {[{<<"body">>, <<"hello doc">>}]}]
|
558
|
+
end.
|
559
|
+
ERLANG
|
560
|
+
"ruby" => <<-RUBY
|
561
|
+
lambda { |doc, req|
|
562
|
+
doc['world'] = "hello";
|
563
|
+
resp = [doc, "hello doc"];
|
564
|
+
return resp;
|
565
|
+
}
|
566
|
+
RUBY
|
567
|
+
},
|
568
|
+
"error" => {
|
569
|
+
"js" => <<-JS,
|
570
|
+
function() {
|
571
|
+
throw(["error","error_key","testing"]);
|
572
|
+
}
|
573
|
+
JS
|
574
|
+
"erlang" => <<-ERLANG,
|
575
|
+
fun(A, B) ->
|
576
|
+
throw([<<"error">>,<<"error_key">>,<<"testing">>])
|
577
|
+
end.
|
578
|
+
ERLANG
|
579
|
+
"ruby" => <<-RUBY
|
580
|
+
lambda { |*args|
|
581
|
+
raise Error.new(:error_key), "testing"
|
582
|
+
}
|
583
|
+
RUBY
|
584
|
+
},
|
585
|
+
"fatal" => {
|
586
|
+
"js" => <<-JS,
|
587
|
+
function() {
|
588
|
+
throw(["fatal","error_key","testing"]);
|
589
|
+
}
|
590
|
+
JS
|
591
|
+
"erlang" => <<-ERLANG,
|
592
|
+
fun(A, B) ->
|
593
|
+
throw([<<"fatal">>,<<"error_key">>,<<"testing">>])
|
594
|
+
end.
|
595
|
+
ERLANG
|
596
|
+
"ruby" => <<-RUBY
|
597
|
+
lambda { |*args|
|
598
|
+
raise Fatal.new(:error_key), "testing"
|
599
|
+
}
|
600
|
+
RUBY
|
601
|
+
}
|
602
|
+
}
|
603
|
+
|
604
|
+
def make_ddoc(fun_path, fun_str)
|
605
|
+
doc = {"_id"=>"foo"}
|
606
|
+
d = doc
|
607
|
+
while p = fun_path.shift
|
608
|
+
l = p
|
609
|
+
if !fun_path.empty?
|
610
|
+
d[p] = {}
|
611
|
+
d = d[p]
|
612
|
+
end
|
613
|
+
end
|
614
|
+
d[l] = fun_str
|
615
|
+
doc
|
616
|
+
end
|
617
|
+
|
618
|
+
describe "query server normal case" do
|
619
|
+
before(:all) do
|
620
|
+
`cd #{COUCH_ROOT} && make` unless LANGUAGE == 'ruby'
|
621
|
+
@qs = QueryServerRunner.run
|
622
|
+
end
|
623
|
+
after(:all) do
|
624
|
+
@qs.close
|
625
|
+
end
|
626
|
+
it "should reset" do
|
627
|
+
@qs.run(["reset"]).must_equal true
|
628
|
+
end
|
629
|
+
it "should not erase ddocs on reset" do
|
630
|
+
@fun = functions["show-simple"][LANGUAGE]
|
631
|
+
@ddoc = make_ddoc(["shows","simple"], @fun)
|
632
|
+
@qs.teach_ddoc(@ddoc)
|
633
|
+
@qs.run(["reset"]).must_equal true
|
634
|
+
@qs.ddoc_run(@ddoc,
|
635
|
+
["shows","simple"],
|
636
|
+
[{:title => "Best ever", :body => "Doc body"}, {}]).
|
637
|
+
must_equal ["resp", {"body" => "Best ever - Doc body"}]
|
638
|
+
end
|
639
|
+
|
640
|
+
it "should run map funs" do
|
641
|
+
@qs.reset!
|
642
|
+
@qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).must_equal true
|
643
|
+
@qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).must_equal true
|
644
|
+
rows = @qs.run(["map_doc", {:a => "b"}])
|
645
|
+
rows[0][0].must_equal ["foo", "b"]
|
646
|
+
rows[0][1].must_equal ["bar", "b"]
|
647
|
+
rows[1][0].must_equal ["baz", "b"]
|
648
|
+
end
|
649
|
+
describe "reduce" do
|
650
|
+
before(:all) do
|
651
|
+
@fun = functions["reduce-values-length"][LANGUAGE]
|
652
|
+
@qs.reset!
|
653
|
+
end
|
654
|
+
it "should reduce" do
|
655
|
+
kvs = (0...10).collect{|i|[i,i*2]}
|
656
|
+
@qs.run(["reduce", [@fun], kvs]).must_equal [true, [10]]
|
657
|
+
end
|
658
|
+
end
|
659
|
+
describe "rereduce" do
|
660
|
+
before(:all) do
|
661
|
+
@fun = functions["reduce-values-sum"][LANGUAGE]
|
662
|
+
@qs.reset!
|
663
|
+
end
|
664
|
+
it "should rereduce" do
|
665
|
+
vs = (0...10).collect{|i|i}
|
666
|
+
@qs.run(["rereduce", [@fun], vs]).must_equal [true, [45]]
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
describe "design docs" do
|
671
|
+
before(:all) do
|
672
|
+
@ddoc = {
|
673
|
+
"_id" => "foo"
|
674
|
+
}
|
675
|
+
@qs.reset!
|
676
|
+
end
|
677
|
+
it "should learn design docs" do
|
678
|
+
@qs.teach_ddoc(@ddoc).must_equal true
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
# it "should validate"
|
683
|
+
describe "validation" do
|
684
|
+
before(:all) do
|
685
|
+
@fun = functions["validate-forbidden"][LANGUAGE]
|
686
|
+
@ddoc = make_ddoc(["validate_doc_update"], @fun)
|
687
|
+
@qs.teach_ddoc(@ddoc)
|
688
|
+
end
|
689
|
+
it "should allow good updates" do
|
690
|
+
@qs.ddoc_run(@ddoc,
|
691
|
+
["validate_doc_update"],
|
692
|
+
[{"good" => true}, {}, {}]).must_equal 1
|
693
|
+
end
|
694
|
+
it "should reject invalid updates" do
|
695
|
+
@qs.ddoc_run(@ddoc,
|
696
|
+
["validate_doc_update"],
|
697
|
+
[{"bad" => true}, {}, {}]).must_equal({"forbidden"=>"bad doc"})
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
describe "show" do
|
702
|
+
before(:all) do
|
703
|
+
@fun = functions["show-simple"][LANGUAGE]
|
704
|
+
@ddoc = make_ddoc(["shows","simple"], @fun)
|
705
|
+
@qs.teach_ddoc(@ddoc)
|
706
|
+
end
|
707
|
+
it "should show" do
|
708
|
+
@qs.ddoc_run(@ddoc,
|
709
|
+
["shows","simple"],
|
710
|
+
[{:title => "Best ever", :body => "Doc body"}, {}]).
|
711
|
+
must_equal ["resp", {"body" => "Best ever - Doc body"}]
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
describe "show with headers" do
|
716
|
+
before(:all) do
|
717
|
+
# TODO we can make real ddocs up there.
|
718
|
+
@fun = functions["show-headers"][LANGUAGE]
|
719
|
+
@ddoc = make_ddoc(["shows","headers"], @fun)
|
720
|
+
@qs.teach_ddoc(@ddoc)
|
721
|
+
end
|
722
|
+
it "should show headers" do
|
723
|
+
@qs.ddoc_run(
|
724
|
+
@ddoc,
|
725
|
+
["shows","headers"],
|
726
|
+
[{:title => "Best ever", :body => "Doc body"}, {}]
|
727
|
+
).
|
728
|
+
must_equal ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
describe "recoverable error" do
|
733
|
+
before(:all) do
|
734
|
+
@fun = functions["error"][LANGUAGE]
|
735
|
+
@ddoc = make_ddoc(["shows","error"], @fun)
|
736
|
+
@qs.teach_ddoc(@ddoc)
|
737
|
+
end
|
738
|
+
it "should not exit" do
|
739
|
+
@qs.ddoc_run(@ddoc, ["shows","error"],
|
740
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
741
|
+
must_equal ["error", "error_key", "testing"]
|
742
|
+
# still running
|
743
|
+
@qs.run(["reset"]).must_equal true
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
describe "changes filter" do
|
748
|
+
before(:all) do
|
749
|
+
@fun = functions["filter-basic"][LANGUAGE]
|
750
|
+
@ddoc = make_ddoc(["filters","basic"], @fun)
|
751
|
+
@qs.teach_ddoc(@ddoc)
|
752
|
+
end
|
753
|
+
it "should only return true for good docs" do
|
754
|
+
@qs.ddoc_run(@ddoc,
|
755
|
+
["filters","basic"],
|
756
|
+
[[{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]
|
757
|
+
).
|
758
|
+
must_equal [true, [true, false, true]]
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
762
|
+
describe "update" do
|
763
|
+
before(:all) do
|
764
|
+
# in another patch we can remove this duplication
|
765
|
+
# by setting up the design doc for each language ahead of time.
|
766
|
+
@fun = functions["update-basic"][LANGUAGE]
|
767
|
+
@ddoc = make_ddoc(["updates","basic"], @fun)
|
768
|
+
@qs.teach_ddoc(@ddoc)
|
769
|
+
end
|
770
|
+
it "should return a doc and a resp body" do
|
771
|
+
up, doc, resp = @qs.ddoc_run(@ddoc,
|
772
|
+
["updates","basic"],
|
773
|
+
[{"foo" => "gnarly"}, {"method" => "POST"}]
|
774
|
+
)
|
775
|
+
up.must_equal "up"
|
776
|
+
doc.must_equal({"foo" => "gnarly", "world" => "hello"})
|
777
|
+
resp["body"].must_equal "hello doc"
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
# end
|
782
|
+
# LIST TESTS
|
783
|
+
# __END__
|
784
|
+
|
785
|
+
describe "ddoc list" do
|
786
|
+
before(:all) do
|
787
|
+
@ddoc = {
|
788
|
+
"_id" => "foo",
|
789
|
+
"lists" => {
|
790
|
+
"simple" => functions["list-simple"][LANGUAGE],
|
791
|
+
"headers" => functions["show-sends"][LANGUAGE],
|
792
|
+
"rows" => functions["show-while-get-rows"][LANGUAGE],
|
793
|
+
"buffer-chunks" => functions["show-while-get-rows-multi-send"][LANGUAGE],
|
794
|
+
"chunky" => functions["list-chunky"][LANGUAGE]
|
795
|
+
}
|
796
|
+
}
|
797
|
+
@qs.teach_ddoc(@ddoc)
|
798
|
+
end
|
799
|
+
|
800
|
+
describe "example list" do
|
801
|
+
it "should run normal" do
|
802
|
+
@qs.ddoc_run(@ddoc,
|
803
|
+
["lists","simple"],
|
804
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]
|
805
|
+
).must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
|
806
|
+
@qs.run(["list_row", {"key"=>"baz"}]).must_equal ["chunks", ["baz"]]
|
807
|
+
@qs.run(["list_row", {"key"=>"bam"}]).must_equal ["chunks", ["bam"]]
|
808
|
+
@qs.run(["list_row", {"key"=>"foom"}]).must_equal ["chunks", ["foom"]]
|
809
|
+
@qs.run(["list_row", {"key"=>"fooz"}]).must_equal ["chunks", ["fooz"]]
|
810
|
+
@qs.run(["list_row", {"key"=>"foox"}]).must_equal ["chunks", ["foox"]]
|
811
|
+
@qs.run(["list_end"]).must_equal ["end" , ["early"]]
|
812
|
+
end
|
813
|
+
end
|
814
|
+
|
815
|
+
describe "headers" do
|
816
|
+
it "should do headers proper" do
|
817
|
+
@qs.ddoc_run(@ddoc, ["lists","headers"],
|
818
|
+
[{"total_rows"=>1000}, {"q" => "ok"}]
|
819
|
+
).must_equal ["start", ["first chunk", 'second "chunk"'],
|
820
|
+
{"headers"=>{"Content-Type"=>"text/plain"}}]
|
821
|
+
@qs.rrun(["list_end"])
|
822
|
+
@qs.jsgets.must_equal ["end", ["tail"]]
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
describe "with rows" do
|
827
|
+
it "should list em" do
|
828
|
+
@qs.ddoc_run(@ddoc, ["lists","rows"],
|
829
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
830
|
+
must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
|
831
|
+
@qs.rrun(["list_row", {"key"=>"baz"}])
|
832
|
+
@qs.get_chunks.must_equal ["baz"]
|
833
|
+
@qs.rrun(["list_row", {"key"=>"bam"}])
|
834
|
+
@qs.get_chunks.must_equal ["bam"]
|
835
|
+
@qs.rrun(["list_end"])
|
836
|
+
@qs.jsgets.must_equal ["end", ["tail"]]
|
837
|
+
end
|
838
|
+
it "should work with zero rows" do
|
839
|
+
@qs.ddoc_run(@ddoc, ["lists","rows"],
|
840
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
841
|
+
must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
|
842
|
+
@qs.rrun(["list_end"])
|
843
|
+
@qs.jsgets.must_equal ["end", ["tail"]]
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
describe "should buffer multiple chunks sent for a single row." do
|
848
|
+
it "should should buffer em" do
|
849
|
+
@qs.ddoc_run(@ddoc, ["lists","buffer-chunks"],
|
850
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
851
|
+
must_equal ["start", ["bacon"], {"headers"=>{}}]
|
852
|
+
@qs.rrun(["list_row", {"key"=>"baz"}])
|
853
|
+
@qs.get_chunks.must_equal ["baz", "eggs"]
|
854
|
+
@qs.rrun(["list_row", {"key"=>"bam"}])
|
855
|
+
@qs.get_chunks.must_equal ["bam", "eggs"]
|
856
|
+
@qs.rrun(["list_end"])
|
857
|
+
@qs.jsgets.must_equal ["end", ["tail"]]
|
858
|
+
end
|
859
|
+
end
|
860
|
+
it "should end after 2" do
|
861
|
+
@qs.ddoc_run(@ddoc, ["lists","chunky"],
|
862
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
863
|
+
must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
|
864
|
+
|
865
|
+
@qs.run(["list_row", {"key"=>"baz"}]).
|
866
|
+
must_equal ["chunks", ["baz"]]
|
867
|
+
|
868
|
+
@qs.run(["list_row", {"key"=>"bam"}]).
|
869
|
+
must_equal ["chunks", ["bam"]]
|
870
|
+
|
871
|
+
@qs.run(["list_row", {"key"=>"foom"}]).
|
872
|
+
must_equal ["end", ["foom", "early tail"]]
|
873
|
+
# here's where js has to discard quit properly
|
874
|
+
@qs.run(["reset"]).
|
875
|
+
must_equal true
|
876
|
+
end
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
|
881
|
+
|
882
|
+
def should_have_exited qs
|
883
|
+
begin
|
884
|
+
qs.run(["reset"])
|
885
|
+
"raise before this (except Erlang)".must_equal true
|
886
|
+
rescue RuntimeError => e
|
887
|
+
e.message.must_equal "no response"
|
888
|
+
rescue Errno::EPIPE
|
889
|
+
true.must_equal true
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
describe "query server that exits" do
|
894
|
+
before(:each) do
|
895
|
+
@qs = QueryServerRunner.run
|
896
|
+
@ddoc = {
|
897
|
+
"_id" => "foo",
|
898
|
+
"lists" => {
|
899
|
+
"capped" => functions["list-capped"][LANGUAGE],
|
900
|
+
"raw" => functions["list-raw"][LANGUAGE]
|
901
|
+
},
|
902
|
+
"shows" => {
|
903
|
+
"fatal" => functions["fatal"][LANGUAGE]
|
904
|
+
}
|
905
|
+
}
|
906
|
+
@qs.teach_ddoc(@ddoc)
|
907
|
+
end
|
908
|
+
after(:each) do
|
909
|
+
@qs.close
|
910
|
+
end
|
911
|
+
|
912
|
+
describe "only goes to 2 list" do
|
913
|
+
it "should exit if erlang sends too many rows" do
|
914
|
+
@qs.ddoc_run(@ddoc, ["lists","capped"],
|
915
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
916
|
+
must_equal ["start", ["bacon"], {"headers"=>{}}]
|
917
|
+
@qs.run(["list_row", {"key"=>"baz"}]).must_equal ["chunks", ["baz"]]
|
918
|
+
@qs.run(["list_row", {"key"=>"foom"}]).must_equal ["chunks", ["foom"]]
|
919
|
+
@qs.run(["list_row", {"key"=>"fooz"}]).must_equal ["end", ["fooz", "early"]]
|
920
|
+
e = @qs.run(["list_row", {"key"=>"foox"}])
|
921
|
+
e[0].must_equal "error"
|
922
|
+
e[1].must_equal "unknown_command"
|
923
|
+
should_have_exited @qs
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
describe "raw list" do
|
928
|
+
it "should exit if it gets a non-row in the middle" do
|
929
|
+
@qs.ddoc_run(@ddoc, ["lists","raw"],
|
930
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
931
|
+
must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
|
932
|
+
e = @qs.run(["reset"])
|
933
|
+
e[0].must_equal "error"
|
934
|
+
e[1].must_equal "list_error"
|
935
|
+
should_have_exited @qs
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
describe "fatal error" do
|
940
|
+
it "should exit" do
|
941
|
+
@qs.ddoc_run(@ddoc, ["shows","fatal"],
|
942
|
+
[{"foo"=>"bar"}, {"q" => "ok"}]).
|
943
|
+
must_equal ["error", "error_key", "testing"]
|
944
|
+
should_have_exited @qs
|
945
|
+
end
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
describe "thank you for using the tests" do
|
950
|
+
it "for more info run with QS_TRACE=true or see query_server_spec.rb file header" do
|
951
|
+
end
|
952
|
+
end
|