PageRenderTime 45ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/apps/couch/test/view_server/query_server_spec.rb

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