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 23COUCH_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(COUCH_ROOT) 24LANGUAGE = ENV["QS_LANG"] || "js" 25 26puts "Running query server specs for #{LANGUAGE} query server" 27 28require 'rspec' 29require 'json' 30 31class 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 114end 115 116class 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 } 122 123 def self.run_command 124 COMMANDS[LANGUAGE] 125 end 126end 127 128class ExternalRunner < OSProcessRunner 129 def self.run_command 130 "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js" 131 end 132end 133 134# we could organize this into a design document per language. 135# that would make testing future languages really easy. 136 137functions = { 138 "emit-twice" => { 139 "js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}}, 140 "erlang" => <<-ERLANG 141 fun({Doc}) -> 142 A = couch_util:get_value(<<"a">>, Doc, null), 143 Emit(<<"foo">>, A), 144 Emit(<<"bar">>, A) 145 end. 146 ERLANG 147 }, 148 "emit-once" => { 149 "js" => <<-JS, 150 function(doc){ 151 emit("baz",doc.a) 152 } 153 JS 154 "erlang" => <<-ERLANG 155 fun({Doc}) -> 156 A = couch_util:get_value(<<"a">>, Doc, null), 157 Emit(<<"baz">>, A) 158 end. 159 ERLANG 160 }, 161 "reduce-values-length" => { 162 "js" => %{function(keys, values, rereduce) { return values.length; }}, 163 "erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.} 164 }, 165 "reduce-values-sum" => { 166 "js" => %{function(keys, values, rereduce) { return sum(values); }}, 167 "erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.} 168 }, 169 "validate-forbidden" => { 170 "js" => <<-JS, 171 function(newDoc, oldDoc, userCtx) { 172 if(newDoc.bad) 173 throw({forbidden:"bad doc"}); "foo bar"; 174 } 175 JS 176 "erlang" => <<-ERLANG 177 fun({NewDoc}, _OldDoc, _UserCtx) -> 178 case couch_util:get_value(<<"bad">>, NewDoc) of 179 undefined -> 1; 180 _ -> {[{forbidden, <<"bad doc">>}]} 181 end 182 end. 183 ERLANG 184 }, 185 "show-simple" => { 186 "js" => <<-JS, 187 function(doc, req) { 188 log("ok"); 189 return [doc.title, doc.body].join(' - '); 190 } 191 JS 192 "erlang" => <<-ERLANG 193 fun({Doc}, Req) -> 194 Title = couch_util:get_value(<<"title">>, Doc), 195 Body = couch_util:get_value(<<"body">>, Doc), 196 Resp = <<Title/binary, " - ", Body/binary>>, 197 {[{<<"body">>, Resp}]} 198 end. 199 ERLANG 200 }, 201 "show-headers" => { 202 "js" => <<-JS, 203 function(doc, req) { 204 var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}}; 205 resp.body = [doc.title, doc.body].join(' - '); 206 return resp; 207 } 208 JS 209 "erlang" => <<-ERLANG 210 fun({Doc}, Req) -> 211 Title = couch_util:get_value(<<"title">>, Doc), 212 Body = couch_util:get_value(<<"body">>, Doc), 213 Resp = <<Title/binary, " - ", Body/binary>>, 214 {[ 215 {<<"code">>, 200}, 216 {<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}}, 217 {<<"body">>, Resp} 218 ]} 219 end. 220 ERLANG 221 }, 222 "show-sends" => { 223 "js" => <<-JS, 224 function(head, req) { 225 start({headers:{"Content-Type" : "text/plain"}}); 226 send("first chunk"); 227 send('second "chunk"'); 228 return "tail"; 229 }; 230 JS 231 "erlang" => <<-ERLANG 232 fun(Head, Req) -> 233 Resp = {[ 234 {<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}} 235 ]}, 236 Start(Resp), 237 Send(<<"first chunk">>), 238 Send(<<"second \\\"chunk\\\"">>), 239 <<"tail">> 240 end. 241 ERLANG 242 }, 243 "show-while-get-rows" => { 244 "js" => <<-JS, 245 function(head, req) { 246 send("first chunk"); 247 send(req.q); 248 var row; 249 log("about to getRow " + typeof(getRow)); 250 while(row = getRow()) { 251 send(row.key); 252 }; 253 return "tail"; 254 }; 255 JS 256 "erlang" => <<-ERLANG, 257 fun(Head, {Req}) -> 258 Send(<<"first chunk">>), 259 Send(couch_util:get_value(<<"q">>, Req)), 260 Fun = fun({Row}, _) -> 261 Send(couch_util:get_value(<<"key">>, Row)), 262 {ok, nil} 263 end, 264 {ok, _} = FoldRows(Fun, nil), 265 <<"tail">> 266 end. 267 ERLANG 268 }, 269 "show-while-get-rows-multi-send" => { 270 "js" => <<-JS, 271 function(head, req) { 272 send("bacon"); 273 var row; 274 log("about to getRow " + typeof(getRow)); 275 while(row = getRow()) { 276 send(row.key); 277 send("eggs"); 278 }; 279 return "tail"; 280 }; 281 JS 282 "erlang" => <<-ERLANG, 283 fun(Head, Req) -> 284 Send(<<"bacon">>), 285 Fun = fun({Row}, _) -> 286 Send(couch_util:get_value(<<"key">>, Row)), 287 Send(<<"eggs">>), 288 {ok, nil} 289 end, 290 FoldRows(Fun, nil), 291 <<"tail">> 292 end. 293 ERLANG 294 }, 295 "list-simple" => { 296 "js" => <<-JS, 297 function(head, req) { 298 send("first chunk"); 299 send(req.q); 300 var row; 301 while(row = getRow()) { 302 send(row.key); 303 }; 304 return "early"; 305 }; 306 JS 307 "erlang" => <<-ERLANG, 308 fun(Head, {Req}) -> 309 Send(<<"first chunk">>), 310 Send(couch_util:get_value(<<"q">>, Req)), 311 Fun = fun({Row}, _) -> 312 Send(couch_util:get_value(<<"key">>, Row)), 313 {ok, nil} 314 end, 315 FoldRows(Fun, nil), 316 <<"early">> 317 end. 318 ERLANG 319 }, 320 "list-chunky" => { 321 "js" => <<-JS, 322 function(head, req) { 323 send("first chunk"); 324 send(req.q); 325 var row, i=0; 326 while(row = getRow()) { 327 send(row.key); 328 i += 1; 329 if (i > 2) { 330 return('early tail'); 331 } 332 }; 333 }; 334 JS 335 "erlang" => <<-ERLANG, 336 fun(Head, {Req}) -> 337 Send(<<"first chunk">>), 338 Send(couch_util:get_value(<<"q">>, Req)), 339 Fun = fun 340 ({Row}, Count) when Count < 2 -> 341 Send(couch_util:get_value(<<"key">>, Row)), 342 {ok, Count+1}; 343 ({Row}, Count) when Count == 2 -> 344 Send(couch_util:get_value(<<"key">>, Row)), 345 {stop, <<"early tail">>} 346 end, 347 {ok, Tail} = FoldRows(Fun, 0), 348 Tail 349 end. 350 ERLANG 351 }, 352 "list-old-style" => { 353 "js" => <<-JS, 354 function(head, req, foo, bar) { 355 return "stuff"; 356 } 357 JS 358 "erlang" => <<-ERLANG, 359 fun(Head, Req, Foo, Bar) -> 360 <<"stuff">> 361 end. 362 ERLANG 363 }, 364 "list-capped" => { 365 "js" => <<-JS, 366 function(head, req) { 367 send("bacon") 368 var row, i = 0; 369 while(row = getRow()) { 370 send(row.key); 371 i += 1; 372 if (i > 2) { 373 return('early'); 374 } 375 }; 376 } 377 JS 378 "erlang" => <<-ERLANG, 379 fun(Head, Req) -> 380 Send(<<"bacon">>), 381 Fun = fun 382 ({Row}, Count) when Count < 2 -> 383 Send(couch_util:get_value(<<"key">>, Row)), 384 {ok, Count+1}; 385 ({Row}, Count) when Count == 2 -> 386 Send(couch_util:get_value(<<"key">>, Row)), 387 {stop, <<"early">>} 388 end, 389 {ok, Tail} = FoldRows(Fun, 0), 390 Tail 391 end. 392 ERLANG 393 }, 394 "list-raw" => { 395 "js" => <<-JS, 396 function(head, req) { 397 // log(this.toSource()); 398 // log(typeof send); 399 send("first chunk"); 400 send(req.q); 401 var row; 402 while(row = getRow()) { 403 send(row.key); 404 }; 405 return "tail"; 406 }; 407 JS 408 "erlang" => <<-ERLANG, 409 fun(Head, {Req}) -> 410 Send(<<"first chunk">>), 411 Send(couch_util:get_value(<<"q">>, Req)), 412 Fun = fun({Row}, _) -> 413 Send(couch_util:get_value(<<"key">>, Row)), 414 {ok, nil} 415 end, 416 FoldRows(Fun, nil), 417 <<"tail">> 418 end. 419 ERLANG 420 }, 421 "filter-basic" => { 422 "js" => <<-JS, 423 function(doc, req) { 424 if (doc.good) { 425 return true; 426 } 427 } 428 JS 429 "erlang" => <<-ERLANG, 430 fun({Doc}, Req) -> 431 couch_util:get_value(<<"good">>, Doc) 432 end. 433 ERLANG 434 }, 435 "update-basic" => { 436 "js" => <<-JS, 437 function(doc, req) { 438 doc.world = "hello"; 439 var resp = [doc, "hello doc"]; 440 return resp; 441 } 442 JS 443 "erlang" => <<-ERLANG, 444 fun({Doc}, Req) -> 445 Doc2 = [{<<"world">>, <<"hello">>}|Doc], 446 [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}] 447 end. 448 ERLANG 449 }, 450 "rewrite-basic" => { 451 "js" => <<-JS, 452 function(req) { 453 return "new/location"; 454 } 455 JS 456 "erlang" => <<-ERLANG, 457 fun(Req) -> 458 {[{"path", "new/location"}]} 459 end. 460 ERLANG 461 }, 462 "rewrite-no-rule" => { 463 "js" => <<-JS, 464 function(req) { 465 return; 466 } 467 JS 468 "erlang" => <<-ERLANG, 469 fun(Req) -> 470 undefined 471 end. 472 ERLANG 473 }, 474 "error" => { 475 "js" => <<-JS, 476 function() { 477 throw(["error","error_key","testing"]); 478 } 479 JS 480 "erlang" => <<-ERLANG 481 fun(A, B) -> 482 throw([<<"error">>,<<"error_key">>,<<"testing">>]) 483 end. 484 ERLANG 485 }, 486 "fatal" => { 487 "js" => <<-JS, 488 function() { 489 throw(["fatal","error_key","testing"]); 490 } 491 JS 492 "erlang" => <<-ERLANG 493 fun(A, B) -> 494 throw([<<"fatal">>,<<"error_key">>,<<"testing">>]) 495 end. 496 ERLANG 497 } 498} 499 500def make_ddoc(fun_path, fun_str) 501 doc = {"_id"=>"foo"} 502 d = doc 503 while p = fun_path.shift 504 l = p 505 if !fun_path.empty? 506 d[p] = {} 507 d = d[p] 508 end 509 end 510 d[l] = fun_str 511 doc 512end 513 514describe "query server normal case" do 515 before(:all) do 516 `cd #{COUCH_ROOT} && make` 517 @qs = QueryServerRunner.run 518 end 519 after(:all) do 520 @qs.close 521 end 522 it "should reset" do 523 @qs.run(["reset"]).should == true 524 end 525 it "should not erase ddocs on reset" do 526 @fun = functions["show-simple"][LANGUAGE] 527 @ddoc = make_ddoc(["shows","simple"], @fun) 528 @qs.teach_ddoc(@ddoc) 529 @qs.run(["reset"]).should == true 530 @qs.ddoc_run(@ddoc, 531 ["shows","simple"], 532 [{:title => "Best ever", :body => "Doc body"}, {}]).should == 533 ["resp", {"body" => "Best ever - Doc body"}] 534 end 535 536 it "should run map funs" do 537 @qs.reset! 538 @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).should == true 539 @qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).should == true 540 rows = @qs.run(["map_doc", {:a => "b"}]) 541 rows[0][0].should == ["foo", "b"] 542 rows[0][1].should == ["bar", "b"] 543 rows[1][0].should == ["baz", "b"] 544 end 545 describe "reduce" do 546 before(:all) do 547 @fun = functions["reduce-values-length"][LANGUAGE] 548 @qs.reset! 549 end 550 it "should reduce" do 551 kvs = (0...10).collect{|i|[i,i*2]} 552 @qs.run(["reduce", [@fun], kvs]).should == [true, [10]] 553 end 554 end 555 describe "rereduce" do 556 before(:all) do 557 @fun = functions["reduce-values-sum"][LANGUAGE] 558 @qs.reset! 559 end 560 it "should rereduce" do 561 vs = (0...10).collect{|i|i} 562 @qs.run(["rereduce", [@fun], vs]).should == [true, [45]] 563 end 564 end 565 566 describe "design docs" do 567 before(:all) do 568 @ddoc = { 569 "_id" => "foo" 570 } 571 @qs.reset! 572 end 573 it "should learn design docs" do 574 @qs.teach_ddoc(@ddoc).should == true 575 end 576 end 577 578 # it "should validate" 579 describe "validation" do 580 before(:all) do 581 @fun = functions["validate-forbidden"][LANGUAGE] 582 @ddoc = make_ddoc(["validate_doc_update"], @fun) 583 @qs.teach_ddoc(@ddoc) 584 end 585 it "should allow good updates" do 586 @qs.ddoc_run(@ddoc, 587 ["validate_doc_update"], 588 [{"good" => true}, {}, {}]).should == 1 589 end 590 it "should reject invalid updates" do 591 @qs.ddoc_run(@ddoc, 592 ["validate_doc_update"], 593 [{"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"} 594 end 595 end 596 597 describe "show" do 598 before(:all) do 599 @fun = functions["show-simple"][LANGUAGE] 600 @ddoc = make_ddoc(["shows","simple"], @fun) 601 @qs.teach_ddoc(@ddoc) 602 end 603 it "should show" do 604 @qs.ddoc_run(@ddoc, 605 ["shows","simple"], 606 [{:title => "Best ever", :body => "Doc body"}, {}]).should == 607 ["resp", {"body" => "Best ever - Doc body"}] 608 end 609 end 610 611 describe "show with headers" do 612 before(:all) do 613 # TODO we can make real ddocs up there. 614 @fun = functions["show-headers"][LANGUAGE] 615 @ddoc = make_ddoc(["shows","headers"], @fun) 616 @qs.teach_ddoc(@ddoc) 617 end 618 it "should show headers" do 619 @qs.ddoc_run( 620 @ddoc, 621 ["shows","headers"], 622 [{:title => "Best ever", :body => "Doc body"}, {}] 623 ). 624 should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}] 625 end 626 end 627 628 describe "recoverable error" do 629 before(:all) do 630 @fun = functions["error"][LANGUAGE] 631 @ddoc = make_ddoc(["shows","error"], @fun) 632 @qs.teach_ddoc(@ddoc) 633 end 634 it "should not exit" do 635 @qs.ddoc_run(@ddoc, ["shows","error"], 636 [{"foo"=>"bar"}, {"q" => "ok"}]). 637 should == ["error", "error_key", "testing"] 638 # still running 639 @qs.run(["reset"]).should == true 640 end 641 end 642 643 describe "changes filter" do 644 before(:all) do 645 @fun = functions["filter-basic"][LANGUAGE] 646 @ddoc = make_ddoc(["filters","basic"], @fun) 647 @qs.teach_ddoc(@ddoc) 648 end 649 it "should only return true for good docs" do 650 @qs.ddoc_run(@ddoc, 651 ["filters","basic"], 652 [[{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}] 653 ). 654 should == [true, [true, false, true]] 655 end 656 end 657 658 describe "update" do 659 before(:all) do 660 # in another patch we can remove this duplication 661 # by setting up the design doc for each language ahead of time. 662 @fun = functions["update-basic"][LANGUAGE] 663 @ddoc = make_ddoc(["updates","basic"], @fun) 664 @qs.teach_ddoc(@ddoc) 665 end 666 it "should return a doc and a resp body" do 667 up, doc, resp = @qs.ddoc_run(@ddoc, 668 ["updates","basic"], 669 [{"foo" => "gnarly"}, {"method" => "POST"}] 670 ) 671 up.should == "up" 672 doc.should == {"foo" => "gnarly", "world" => "hello"} 673 resp["body"].should == "hello doc" 674 end 675 end 676 677# end 678# LIST TESTS 679# __END__ 680 681 describe "ddoc list" do 682 before(:all) do 683 @ddoc = { 684 "_id" => "foo", 685 "lists" => { 686 "simple" => functions["list-simple"][LANGUAGE], 687 "headers" => functions["show-sends"][LANGUAGE], 688 "rows" => functions["show-while-get-rows"][LANGUAGE], 689 "buffer-chunks" => functions["show-while-get-rows-multi-send"][LANGUAGE], 690 "chunky" => functions["list-chunky"][LANGUAGE] 691 } 692 } 693 @qs.teach_ddoc(@ddoc) 694 end 695 696 describe "example list" do 697 it "should run normal" do 698 @qs.ddoc_run(@ddoc, 699 ["lists","simple"], 700 [{"foo"=>"bar"}, {"q" => "ok"}] 701 ).should == ["start", ["first chunk", "ok"], {"headers"=>{}}] 702 @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] 703 @qs.run(["list_row", {"key"=>"bam"}]).should == ["chunks", ["bam"]] 704 @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] 705 @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]] 706 @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]] 707 @qs.run(["list_end"]).should == ["end" , ["early"]] 708 end 709 end 710 711 describe "headers" do 712 it "should do headers proper" do 713 @qs.ddoc_run(@ddoc, ["lists","headers"], 714 [{"total_rows"=>1000}, {"q" => "ok"}] 715 ).should == ["start", ["first chunk", 'second "chunk"'], 716 {"headers"=>{"Content-Type"=>"text/plain"}}] 717 @qs.rrun(["list_end"]) 718 @qs.jsgets.should == ["end", ["tail"]] 719 end 720 end 721 722 describe "with rows" do 723 it "should list em" do 724 @qs.ddoc_run(@ddoc, ["lists","rows"], 725 [{"foo"=>"bar"}, {"q" => "ok"}]). 726 should == ["start", ["first chunk", "ok"], {"headers"=>{}}] 727 @qs.rrun(["list_row", {"key"=>"baz"}]) 728 @qs.get_chunks.should == ["baz"] 729 @qs.rrun(["list_row", {"key"=>"bam"}]) 730 @qs.get_chunks.should == ["bam"] 731 @qs.rrun(["list_end"]) 732 @qs.jsgets.should == ["end", ["tail"]] 733 end 734 it "should work with zero rows" do 735 @qs.ddoc_run(@ddoc, ["lists","rows"], 736 [{"foo"=>"bar"}, {"q" => "ok"}]). 737 should == ["start", ["first chunk", "ok"], {"headers"=>{}}] 738 @qs.rrun(["list_end"]) 739 @qs.jsgets.should == ["end", ["tail"]] 740 end 741 end 742 743 describe "should buffer multiple chunks sent for a single row." do 744 it "should should buffer em" do 745 @qs.ddoc_run(@ddoc, ["lists","buffer-chunks"], 746 [{"foo"=>"bar"}, {"q" => "ok"}]). 747 should == ["start", ["bacon"], {"headers"=>{}}] 748 @qs.rrun(["list_row", {"key"=>"baz"}]) 749 @qs.get_chunks.should == ["baz", "eggs"] 750 @qs.rrun(["list_row", {"key"=>"bam"}]) 751 @qs.get_chunks.should == ["bam", "eggs"] 752 @qs.rrun(["list_end"]) 753 @qs.jsgets.should == ["end", ["tail"]] 754 end 755 end 756 it "should end after 2" do 757 @qs.ddoc_run(@ddoc, ["lists","chunky"], 758 [{"foo"=>"bar"}, {"q" => "ok"}]). 759 should == ["start", ["first chunk", "ok"], {"headers"=>{}}] 760 761 @qs.run(["list_row", {"key"=>"baz"}]). 762 should == ["chunks", ["baz"]] 763 764 @qs.run(["list_row", {"key"=>"bam"}]). 765 should == ["chunks", ["bam"]] 766 767 @qs.run(["list_row", {"key"=>"foom"}]). 768 should == ["end", ["foom", "early tail"]] 769 # here's where js has to discard quit properly 770 @qs.run(["reset"]). 771 should == true 772 end 773 end 774 775 describe "ddoc rewrites" do 776 describe "simple rewrite" do 777 before(:all) do 778 @ddoc = { 779 "_id" => "foo", 780 "rewrites" => functions["rewrite-basic"][LANGUAGE] 781 } 782 @qs.teach_ddoc(@ddoc) 783 end 784 it "should run normal" do 785 ok, resp = @qs.ddoc_run(@ddoc, 786 ["rewrites"], 787 [{"path" => "foo/bar"}, {"method" => "POST"}] 788 ) 789 ok.should == "ok" 790 resp["path"].should == "new/location" 791 end 792 end 793 794 describe "no rule" do 795 before(:all) do 796 @ddoc = { 797 "_id" => "foo", 798 "rewrites" => functions["rewrite-no-rule"][LANGUAGE] 799 } 800 @qs.teach_ddoc(@ddoc) 801 end 802 it "should run normal" do 803 resp = @qs.ddoc_run(@ddoc, 804 ["rewrites"], 805 [{"path" => "foo/bar"}, {"method" => "POST"}] 806 ) 807 resp.should == ['no_dispatch_rule'] 808 end 809 end 810 end 811end 812 813 814 815def should_have_exited qs 816 begin 817 qs.run(["reset"]) 818 "raise before this (except Erlang)".should == true 819 rescue RuntimeError => e 820 e.message.should == "no response" 821 rescue Errno::EPIPE 822 true.should == true 823 end 824end 825 826describe "query server that exits" do 827 before(:each) do 828 @qs = QueryServerRunner.run 829 @ddoc = { 830 "_id" => "foo", 831 "lists" => { 832 "capped" => functions["list-capped"][LANGUAGE], 833 "raw" => functions["list-raw"][LANGUAGE] 834 }, 835 "shows" => { 836 "fatal" => functions["fatal"][LANGUAGE] 837 } 838 } 839 @qs.teach_ddoc(@ddoc) 840 end 841 after(:each) do 842 @qs.close 843 end 844 845 describe "only goes to 2 list" do 846 it "should exit if erlang sends too many rows" do 847 @qs.ddoc_run(@ddoc, ["lists","capped"], 848 [{"foo"=>"bar"}, {"q" => "ok"}]). 849 should == ["start", ["bacon"], {"headers"=>{}}] 850 @qs.run(["list_row", {"key"=>"baz"}]).should == ["chunks", ["baz"]] 851 @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]] 852 @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]] 853 e = @qs.run(["list_row", {"key"=>"foox"}]) 854 e[0].should == "error" 855 e[1].should == "unknown_command" 856 should_have_exited @qs 857 end 858 end 859 860 describe "raw list" do 861 it "should exit if it gets a non-row in the middle" do 862 @qs.ddoc_run(@ddoc, ["lists","raw"], 863 [{"foo"=>"bar"}, {"q" => "ok"}]). 864 should == ["start", ["first chunk", "ok"], {"headers"=>{}}] 865 e = @qs.run(["reset"]) 866 e[0].should == "error" 867 e[1].should == "list_error" 868 should_have_exited @qs 869 end 870 end 871 872 describe "fatal error" do 873 it "should exit" do 874 @qs.ddoc_run(@ddoc, ["shows","fatal"], 875 [{"foo"=>"bar"}, {"q" => "ok"}]). 876 should == ["error", "error_key", "testing"] 877 should_have_exited @qs 878 end 879 end 880end 881 882describe "thank you for using the tests" do 883 it "for more info run with QS_TRACE=true or see query_server_spec.rb file header" do 884 end 885end 886