Merge pull request #2942 from murgatroid99/node_parent_call

Add parent call propagation API to Node library
diff --git a/src/cpp/server/server.cc b/src/cpp/server/server.cc
index 90f3854..a70b555 100644
--- a/src/cpp/server/server.cc
+++ b/src/cpp/server/server.cc
@@ -240,8 +240,7 @@
               method->name());
       return false;
     }
-    SyncRequest request(method, tag);
-    sync_methods_->emplace_back(request);
+    sync_methods_->emplace_back(method, tag);
   }
   return true;
 }
@@ -286,7 +285,10 @@
   if (!has_generic_service_) {
     unknown_method_.reset(new RpcServiceMethod(
         "unknown", RpcMethod::BIDI_STREAMING, new UnknownMethodHandler));
-    sync_methods_->emplace_back(unknown_method_.get(), nullptr);
+    // Use of emplace_back with just constructor arguments is not accepted here
+    // by gcc-4.4 because it can't match the anonymous nullptr with a proper
+    // constructor implicitly. Construct the object and use push_back.
+    sync_methods_->push_back(SyncRequest(unknown_method_.get(), nullptr));
   }
   // Start processing rpcs.
   if (!sync_methods_->empty()) {
diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec
index 2dc10d2..f1f8f7c 100644
--- a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec
+++ b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec
@@ -15,7 +15,7 @@
     <copyright>Copyright 2015, Google Inc.</copyright>
     <tags>gRPC RPC Protocol HTTP/2 Auth OAuth2</tags>
 	<dependencies>
-	  <dependency id="Google.Apis.Auth" version="1.9.2" />
+	  <dependency id="Google.Apis.Auth" version="1.9.3" />
 	  <dependency id="Grpc.Core" version="$version$" />
     </dependencies>
   </metadata>
diff --git a/src/node/examples/perf_test.js b/src/node/examples/perf_test.js
index 214b938..ba8fbf8 100644
--- a/src/node/examples/perf_test.js
+++ b/src/node/examples/perf_test.js
@@ -40,7 +40,7 @@
 
 function runTest(iterations, callback) {
   var testServer = interop_server.getServer(0, false);
-  testServer.server.listen();
+  testServer.server.start();
   var client = new testProto.TestService('localhost:' + testServer.port,
                                          grpc.Credentials.createInsecure());
 
diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js
index 1ce4dbe..ec968b8 100644
--- a/src/node/examples/qps_test.js
+++ b/src/node/examples/qps_test.js
@@ -60,7 +60,7 @@
  */
 function runTest(concurrent_calls, seconds, callback) {
   var testServer = interop_server.getServer(0, false);
-  testServer.server.listen();
+  testServer.server.start();
   var client = new testProto.TestService('localhost:' + testServer.port,
                                          grpc.Credentials.createInsecure());
 
diff --git a/src/node/examples/route_guide_server.js b/src/node/examples/route_guide_server.js
index bb8e79b..465b32f 100644
--- a/src/node/examples/route_guide_server.js
+++ b/src/node/examples/route_guide_server.js
@@ -248,7 +248,7 @@
       throw err;
     }
     feature_list = JSON.parse(data);
-    routeServer.listen();
+    routeServer.start();
   });
 }
 
diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js
index dfcfe30..12e5479 100644
--- a/src/node/examples/stock_server.js
+++ b/src/node/examples/stock_server.js
@@ -81,7 +81,7 @@
 
 if (require.main === module) {
   stockServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
-  stockServer.listen();
+  stockServer.start();
 }
 
 module.exports = stockServer;
diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js
index ece22cc..1242a0f 100644
--- a/src/node/interop/interop_server.js
+++ b/src/node/interop/interop_server.js
@@ -194,7 +194,7 @@
   });
   var server_obj = getServer(argv.port, argv.use_tls === 'true');
   console.log('Server attaching to port ' + argv.port);
-  server_obj.server.listen();
+  server_obj.server.start();
 }
 
 /**
diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js
index a9df439..20c9a07 100644
--- a/src/node/test/server_test.js
+++ b/src/node/test/server_test.js
@@ -83,7 +83,7 @@
       server = new grpc.Server();
     });
   });
-  describe('listen', function() {
+  describe('start', function() {
     var server;
     before(function() {
       server = new grpc.Server();
@@ -92,7 +92,7 @@
     after(function() {
       server.shutdown();
     });
-    it('should listen without error', function() {
+    it('should start without error', function() {
       assert.doesNotThrow(function() {
         server.start();
       });
diff --git a/src/ruby/.rspec b/src/ruby/.rspec
index cd7c5fb..2320752 100755
--- a/src/ruby/.rspec
+++ b/src/ruby/.rspec
@@ -1,4 +1,5 @@
 -I.
+-Ipb
 --require spec_helper
 --format documentation
 --color
diff --git a/src/ruby/.rubocop.yml b/src/ruby/.rubocop.yml
index 47e382a..1b255f3 100644
--- a/src/ruby/.rubocop.yml
+++ b/src/ruby/.rubocop.yml
@@ -8,3 +8,4 @@
     - 'bin/interop/test/**/*'
     - 'bin/math.rb'
     - 'bin/math_services.rb'
+    - 'pb/grpc/health/v1alpha/*'
diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile
index 02af9a8..cc7832b 100755
--- a/src/ruby/Rakefile
+++ b/src/ruby/Rakefile
@@ -20,7 +20,8 @@
   { id: :bidi, title: 'bidi tests', dir: %w(spec/generic),
     tag: 'bidi' },
   { id: :server, title: 'rpc server thread tests', dir: %w(spec/generic),
-    tag: 'server' }
+    tag: 'server' },
+  { id: :pb, title: 'protobuf service tests', dir: %w(spec/pb) }
 ]
 namespace :suite do
   SPEC_SUITES.each do |suite|
@@ -50,7 +51,8 @@
 task 'suite:idiomatic' => 'suite:wrapper'
 task 'suite:bidi' => 'suite:wrapper'
 task 'suite:server' => 'suite:wrapper'
+task 'suite:pb' => 'suite:server'
 
 desc 'Compiles the gRPC extension then runs all the tests'
-task all: ['suite:idiomatic', 'suite:bidi', 'suite:server']
+task all: ['suite:idiomatic', 'suite:bidi', 'suite:pb', 'suite:server']
 task default: :all
diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb
index da4caa8..78ae217 100755
--- a/src/ruby/bin/interop/interop_client.rb
+++ b/src/ruby/bin/interop/interop_client.rb
@@ -110,6 +110,14 @@
       end
     end
 
+    if opts.test_case == 'oauth2_auth_token'
+      auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
+      kw = auth_creds.updater_proc.call({})  # gives as an auth token
+
+      # use a metadata update proc that just adds the auth token.
+      stub_opts[:update_metadata] = proc { |md| md.merge(kw) }
+    end
+
     if opts.test_case == 'jwt_token_creds'  # don't use a scope
       auth_creds = Google::Auth.get_application_default
       stub_opts[:update_metadata] = auth_creds.updater_proc
@@ -228,6 +236,33 @@
     p 'OK: compute_engine_creds'
   end
 
+  def oauth2_auth_token
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true)
+    json_key = File.read(ENV[AUTH_ENV])
+    wanted_email = MultiJson.load(json_key)['client_email']
+    assert_equal(wanted_email, resp.username,
+                 "#{__callee__}: incorrect username")
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           "#{__callee__}: incorrect oauth_scope")
+    p "OK: #{__callee__}"
+  end
+
+  def per_rpc_creds
+    auth_creds = Google::Auth.get_application_default(@args.oauth_scope)
+    kw = auth_creds.updater_proc.call({})
+    resp = perform_large_unary(fill_username: true,
+                               fill_oauth_scope: true,
+                               **kw)
+    json_key = File.read(ENV[AUTH_ENV])
+    wanted_email = MultiJson.load(json_key)['client_email']
+    assert_equal(wanted_email, resp.username,
+                 "#{__callee__}: incorrect username")
+    assert(@args.oauth_scope.include?(resp.oauth_scope),
+           "#{__callee__}: incorrect oauth_scope")
+    p "OK: #{__callee__}"
+  end
+
   def client_streaming
     msg_sizes = [27_182, 8, 1828, 45_904]
     wanted_aggregate_size = 74_922
@@ -265,6 +300,29 @@
     p 'OK: ping_pong'
   end
 
+  def timeout_on_sleeping_server
+    msg_sizes = [[27_182, 31_415]]
+    ppp = PingPongPlayer.new(msg_sizes)
+    resps = @stub.full_duplex_call(ppp.each_item, timeout: 0.001)
+    resps.each { |r| ppp.queue.push(r) }
+    fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
+  rescue GRPC::BadStatus => e
+    assert_equal(e.code, GRPC::Core::StatusCodes::DEADLINE_EXCEEDED)
+    p "OK: #{__callee__}"
+  end
+
+  def empty_stream
+    ppp = PingPongPlayer.new([])
+    resps = @stub.full_duplex_call(ppp.each_item)
+    count = 0
+    resps.each do
+      |r| ppp.queue.push(r)
+      count += 1
+    end
+    assert_equal(0, count, 'too many responses, expect 0')
+    p 'OK: empty_stream'
+  end
+
   def cancel_after_begin
     msg_sizes = [27_182, 8, 1828, 45_904]
     reqs = msg_sizes.map do |x|
@@ -283,7 +341,7 @@
     ppp = PingPongPlayer.new(msg_sizes)
     op = @stub.full_duplex_call(ppp.each_item, return_op: true)
     ppp.canceller_op = op  # causes ppp to cancel after the 1st message
-    op.execute.each { |r| ppp.queue.push(r) }
+    assert_raises(GRPC::Cancelled) { op.execute.each { |r| ppp.queue.push(r) } }
     op.wait
     assert(op.cancelled, 'call operation was not CANCELLED')
     p 'OK: cancel_after_first_response'
@@ -300,7 +358,7 @@
 
   private
 
-  def perform_large_unary(fill_username: false, fill_oauth_scope: false)
+  def perform_large_unary(fill_username: false, fill_oauth_scope: false, **kw)
     req_size, wanted_response_size = 271_828, 314_159
     payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
     req = SimpleRequest.new(response_type: :COMPRESSABLE,
@@ -308,7 +366,7 @@
                             payload: payload)
     req.fill_username = fill_username
     req.fill_oauth_scope = fill_oauth_scope
-    resp = @stub.unary_call(req)
+    resp = @stub.unary_call(req, **kw)
     assert_equal(:COMPRESSABLE, resp.payload.type,
                  'large_unary: payload had the wrong type')
     assert_equal(wanted_response_size, resp.payload.body.length,
diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c
index 70f0795..b09d4e2 100644
--- a/src/ruby/ext/grpc/rb_call.c
+++ b/src/ruby/ext/grpc/rb_call.c
@@ -629,13 +629,9 @@
     rb_raise(grpc_rb_eOutOfTime, "grpc_call_start_batch timed out");
     return Qnil;
   }
-  if (!ev.success) {
-    grpc_run_batch_stack_cleanup(&st);
-    rb_raise(grpc_rb_eCallError, "start_batch completion failed");
-    return Qnil;
-  }
 
-  /* Build and return the BatchResult struct result */
+  /* Build and return the BatchResult struct result,
+     if there is an error, it's reflected in the status */
   result = grpc_run_batch_stack_build_result(&st);
   grpc_run_batch_stack_cleanup(&st);
   return result;
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index eb74845..22fafe1 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -24,7 +24,7 @@
   %w(math noproto).each do |b|
     s.executables += ["#{b}_client.rb", "#{b}_server.rb"]
   end
-  s.require_paths = %w( bin lib )
+  s.require_paths = %w( bin lib pb )
   s.platform      = Gem::Platform::RUBY
 
   s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb
index 215c006..17da401 100644
--- a/src/ruby/lib/grpc/generic/active_call.rb
+++ b/src/ruby/lib/grpc/generic/active_call.rb
@@ -74,8 +74,7 @@
     #
     # @param call [Call] a call on which to start and invocation
     # @param q [CompletionQueue] the completion queue
-    # @param deadline [Fixnum,TimeSpec] the deadline
-    def self.client_invoke(call, q, _deadline, **kw)
+    def self.client_invoke(call, q, **kw)
       fail(TypeError, '!Core::Call') unless call.is_a? Core::Call
       unless q.is_a? Core::CompletionQueue
         fail(TypeError, '!Core::CompletionQueue')
@@ -418,7 +417,7 @@
     # @return [Enumerator, nil] a response Enumerator
     def bidi_streamer(requests, **kw, &blk)
       start_call(**kw) unless @started
-      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, @deadline)
+      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal)
       bd.run_on_client(requests, @op_notifier, &blk)
     end
 
@@ -434,7 +433,7 @@
     #
     # @param gen_each_reply [Proc] generates the BiDi stream replies
     def run_server_bidi(gen_each_reply)
-      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, @deadline)
+      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal)
       bd.run_on_server(gen_each_reply)
     end
 
@@ -456,7 +455,7 @@
     # Starts the call if not already started
     def start_call(**kw)
       return if @started
-      @metadata_tag = ActiveCall.client_invoke(@call, @cq, @deadline, **kw)
+      @metadata_tag = ActiveCall.client_invoke(@call, @cq, **kw)
       @started = true
     end
 
diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb
index 3b0c713..9dbbb74 100644
--- a/src/ruby/lib/grpc/generic/bidi_call.rb
+++ b/src/ruby/lib/grpc/generic/bidi_call.rb
@@ -56,15 +56,13 @@
     #          the call
     # @param marshal [Function] f(obj)->string that marshal requests
     # @param unmarshal [Function] f(string)->obj that unmarshals responses
-    # @param deadline [Fixnum] the deadline for the call to complete
-    def initialize(call, q, marshal, unmarshal, deadline)
+    def initialize(call, q, marshal, unmarshal)
       fail(ArgumentError, 'not a call') unless call.is_a? Core::Call
       unless q.is_a? Core::CompletionQueue
         fail(ArgumentError, 'not a CompletionQueue')
       end
       @call = call
       @cq = q
-      @deadline = deadline
       @marshal = marshal
       @op_notifier = nil  # signals completion on clients
       @readq = Queue.new
@@ -99,7 +97,7 @@
     # @param gen_each_reply [Proc] generates the BiDi stream replies.
     def run_on_server(gen_each_reply)
       replys = gen_each_reply.call(each_queued_msg)
-      @loop_th = start_read_loop
+      @loop_th = start_read_loop(is_client: false)
       write_loop(replys, is_client: false)
     end
 
@@ -127,7 +125,7 @@
         count += 1
         req = @readq.pop
         GRPC.logger.debug("each_queued_msg: req = #{req}")
-        throw req if req.is_a? StandardError
+        fail req if req.is_a? StandardError
         break if req.equal?(END_OF_READS)
         yield req
       end
@@ -147,12 +145,9 @@
       GRPC.logger.debug("bidi-write-loop: #{count} writes done")
       if is_client
         GRPC.logger.debug("bidi-write-loop: client sent #{count}, waiting")
-        batch_result = @call.run_batch(@cq, write_tag, INFINITE_FUTURE,
-                                       SEND_CLOSE_FROM_CLIENT => nil,
-                                       RECV_STATUS_ON_CLIENT => nil)
-        @call.status = batch_result.status
-        batch_result.check_status
-        GRPC.logger.debug("bidi-write-loop: done status #{@call.status}")
+        @call.run_batch(@cq, write_tag, INFINITE_FUTURE,
+                        SEND_CLOSE_FROM_CLIENT => nil)
+        GRPC.logger.debug('bidi-write-loop: done')
         notify_done
       end
       GRPC.logger.debug('bidi-write-loop: finished')
@@ -164,7 +159,7 @@
     end
 
     # starts the read loop
-    def start_read_loop
+    def start_read_loop(is_client: true)
       Thread.new do
         GRPC.logger.debug('bidi-read-loop: starting')
         begin
@@ -177,9 +172,19 @@
             # TODO: ensure metadata is read if available, currently it's not
             batch_result = @call.run_batch(@cq, read_tag, INFINITE_FUTURE,
                                            RECV_MESSAGE => nil)
+
             # handle the next message
             if batch_result.message.nil?
               GRPC.logger.debug("bidi-read-loop: null batch #{batch_result}")
+
+              if is_client
+                batch_result = @call.run_batch(@cq, read_tag, INFINITE_FUTURE,
+                                               RECV_STATUS_ON_CLIENT => nil)
+                @call.status = batch_result.status
+                batch_result.check_status
+                GRPC.logger.debug("bidi-read-loop: done status #{@call.status}")
+              end
+
               @readq.push(END_OF_READS)
               GRPC.logger.debug('bidi-read-loop: done reading!')
               break
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
index cce7185..24ec179 100644
--- a/src/ruby/lib/grpc/generic/client_stub.rb
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -161,15 +161,21 @@
     # @param marshal [Function] f(obj)->string that marshals requests
     # @param unmarshal [Function] f(string)->obj that unmarshals responses
     # @param timeout [Numeric] (optional) the max completion time in seconds
+    # @param deadline [Time] (optional) the time the request should complete
     # @param parent [Core::Call] a prior call whose reserved metadata
     #   will be propagated by this one.
     # @param return_op [true|false] return an Operation if true
     # @return [Object] the response received from the server
-    def request_response(method, req, marshal, unmarshal, timeout = nil,
+    def request_response(method, req, marshal, unmarshal,
+                         deadline: nil,
+                         timeout: nil,
                          return_op: false,
                          parent: parent,
                          **kw)
-      c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
+      c = new_active_call(method, marshal, unmarshal,
+                          deadline: deadline,
+                          timeout: timeout,
+                          parent: parent)
       kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
       md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.request_response(req, **md) unless return_op
@@ -222,16 +228,22 @@
     # @param requests [Object] an Enumerable of requests to send
     # @param marshal [Function] f(obj)->string that marshals requests
     # @param unmarshal [Function] f(string)->obj that unmarshals responses
-    # @param timeout [Numeric] the max completion time in seconds
+    # @param timeout [Numeric] (optional) the max completion time in seconds
+    # @param deadline [Time] (optional) the time the request should complete
     # @param return_op [true|false] return an Operation if true
     # @param parent [Core::Call] a prior call whose reserved metadata
     #   will be propagated by this one.
     # @return [Object|Operation] the response received from the server
-    def client_streamer(method, requests, marshal, unmarshal, timeout = nil,
+    def client_streamer(method, requests, marshal, unmarshal,
+                        deadline: nil,
+                        timeout: nil,
                         return_op: false,
                         parent: nil,
                         **kw)
-      c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
+      c = new_active_call(method, marshal, unmarshal,
+                          deadline: deadline,
+                          timeout: timeout,
+                          parent: parent)
       kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
       md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.client_streamer(requests, **md) unless return_op
@@ -292,18 +304,24 @@
     # @param req [Object] the request sent to the server
     # @param marshal [Function] f(obj)->string that marshals requests
     # @param unmarshal [Function] f(string)->obj that unmarshals responses
-    # @param timeout [Numeric] the max completion time in seconds
+    # @param timeout [Numeric] (optional) the max completion time in seconds
+    # @param deadline [Time] (optional) the time the request should complete
     # @param return_op [true|false]return an Operation if true
     # @param parent [Core::Call] a prior call whose reserved metadata
     #   will be propagated by this one.
     # @param blk [Block] when provided, is executed for each response
     # @return [Enumerator|Operation|nil] as discussed above
-    def server_streamer(method, req, marshal, unmarshal, timeout = nil,
+    def server_streamer(method, req, marshal, unmarshal,
+                        deadline: nil,
+                        timeout: nil,
                         return_op: false,
                         parent: nil,
                         **kw,
                         &blk)
-      c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
+      c = new_active_call(method, marshal, unmarshal,
+                          deadline: deadline,
+                          timeout: timeout,
+                          parent: parent)
       kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
       md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.server_streamer(req, **md, &blk) unless return_op
@@ -404,17 +422,23 @@
     # @param marshal [Function] f(obj)->string that marshals requests
     # @param unmarshal [Function] f(string)->obj that unmarshals responses
     # @param timeout [Numeric] (optional) the max completion time in seconds
+    # @param deadline [Time] (optional) the time the request should complete
     # @param parent [Core::Call] a prior call whose reserved metadata
     #   will be propagated by this one.
     # @param return_op [true|false] return an Operation if true
     # @param blk [Block] when provided, is executed for each response
     # @return [Enumerator|nil|Operation] as discussed above
-    def bidi_streamer(method, requests, marshal, unmarshal, timeout = nil,
+    def bidi_streamer(method, requests, marshal, unmarshal,
+                      deadline: nil,
+                      timeout: nil,
                       return_op: false,
                       parent: nil,
                       **kw,
                       &blk)
-      c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
+      c = new_active_call(method, marshal, unmarshal,
+                          deadline: deadline,
+                          timeout: timeout,
+                          parent: parent)
       kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
       md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.bidi_streamer(requests, **md, &blk) unless return_op
@@ -438,8 +462,13 @@
     # @param parent [Grpc::Call] a parent call, available when calls are
     #   made from server
     # @param timeout [TimeConst]
-    def new_active_call(method, marshal, unmarshal, timeout = nil, parent: nil)
-      deadline = from_relative_time(timeout.nil? ? @timeout : timeout)
+    def new_active_call(method, marshal, unmarshal,
+                        deadline: nil,
+                        timeout: nil,
+                        parent: nil)
+      if deadline.nil?
+        deadline = from_relative_time(timeout.nil? ? @timeout : timeout)
+      end
       call = @ch.create_call(@queue,
                              parent, # parent call
                              @propagate_mask, # propagation options
diff --git a/src/ruby/lib/grpc/generic/service.rb b/src/ruby/lib/grpc/generic/service.rb
index 3b9743e..80ff669 100644
--- a/src/ruby/lib/grpc/generic/service.rb
+++ b/src/ruby/lib/grpc/generic/service.rb
@@ -174,26 +174,24 @@
             unmarshal = desc.unmarshal_proc(:output)
             route = "/#{route_prefix}/#{name}"
             if desc.request_response?
-              define_method(mth_name) do |req, deadline = nil, **kw|
+              define_method(mth_name) do |req, **kw|
                 GRPC.logger.debug("calling #{@host}:#{route}")
-                request_response(route, req, marshal, unmarshal, deadline, **kw)
+                request_response(route, req, marshal, unmarshal, **kw)
               end
             elsif desc.client_streamer?
-              define_method(mth_name) do |reqs, deadline = nil, **kw|
+              define_method(mth_name) do |reqs, **kw|
                 GRPC.logger.debug("calling #{@host}:#{route}")
-                client_streamer(route, reqs, marshal, unmarshal, deadline, **kw)
+                client_streamer(route, reqs, marshal, unmarshal, **kw)
               end
             elsif desc.server_streamer?
-              define_method(mth_name) do |req, deadline = nil, **kw, &blk|
+              define_method(mth_name) do |req, **kw, &blk|
                 GRPC.logger.debug("calling #{@host}:#{route}")
-                server_streamer(route, req, marshal, unmarshal, deadline, **kw,
-                                &blk)
+                server_streamer(route, req, marshal, unmarshal, **kw, &blk)
               end
             else  # is a bidi_stream
-              define_method(mth_name) do |reqs, deadline = nil, **kw, &blk|
+              define_method(mth_name) do |reqs, **kw, &blk|
                 GRPC.logger.debug("calling #{@host}:#{route}")
-                bidi_streamer(route, reqs, marshal, unmarshal, deadline, **kw,
-                              &blk)
+                bidi_streamer(route, reqs, marshal, unmarshal, **kw, &blk)
               end
             end
           end
diff --git a/src/ruby/pb/README.md b/src/ruby/pb/README.md
new file mode 100644
index 0000000..0b067ed
--- /dev/null
+++ b/src/ruby/pb/README.md
@@ -0,0 +1,27 @@
+Protocol Buffers
+================
+
+This folder contains protocol buffers provided with gRPC ruby, and the generated
+code to them.
+
+PREREQUISITES
+-------------
+
+The code is is generated using the protoc (> 3.0.0.alpha.1) and the
+grpc_ruby_plugin.  These must be installed to regenerate the IDL defined
+classes, but that's not necessary just to use them.
+
+health_check/v1alpha
+--------------------
+
+This package defines the surface of a simple health check service that gRPC
+servers may choose to implement, and provides an implementation for it. To
+re-generate the surface.
+
+```bash
+$ # (from this directory)
+$ protoc -I . grpc/health/v1alpha/health.proto \
+    --grpc_out=. \
+    --ruby_out=. \
+    --plugin=protoc-gen-grpc=`which grpc_ruby_plugin`
+```
diff --git a/src/ruby/pb/grpc/health/checker.rb b/src/ruby/pb/grpc/health/checker.rb
new file mode 100644
index 0000000..8c692e7
--- /dev/null
+++ b/src/ruby/pb/grpc/health/checker.rb
@@ -0,0 +1,75 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require 'grpc'
+require 'grpc/health/v1alpha/health_services'
+require 'thread'
+
+module Grpc
+  # Health contains classes and modules that support providing a health check
+  # service.
+  module Health
+    # Checker is implementation of the schema-specified health checking service.
+    class Checker < V1alpha::Health::Service
+      StatusCodes = GRPC::Core::StatusCodes
+      HealthCheckResponse = V1alpha::HealthCheckResponse
+
+      # Initializes the statuses of participating services
+      def initialize
+        @statuses = {}
+        @status_mutex = Mutex.new  # guards access to @statuses
+      end
+
+      # Implements the rpc IDL API method
+      def check(req, _call)
+        status = nil
+        @status_mutex.synchronize do
+          status = @statuses["#{req.host}/#{req.service}"]
+        end
+        fail GRPC::BadStatus, StatusCodes::NOT_FOUND if status.nil?
+        HealthCheckResponse.new(status: status)
+      end
+
+      # Adds the health status for a given host and service.
+      def add_status(host, service, status)
+        @status_mutex.synchronize { @statuses["#{host}/#{service}"] = status }
+      end
+
+      # Clears the status for the given host or service.
+      def clear_status(host, service)
+        @status_mutex.synchronize { @statuses.delete("#{host}/#{service}") }
+      end
+
+      # Clears alls the statuses.
+      def clear_all
+        @status_mutex.synchronize { @statuses = {} }
+      end
+    end
+  end
+end
diff --git a/src/ruby/pb/grpc/health/v1alpha/health.proto b/src/ruby/pb/grpc/health/v1alpha/health.proto
new file mode 100644
index 0000000..d31df1e
--- /dev/null
+++ b/src/ruby/pb/grpc/health/v1alpha/health.proto
@@ -0,0 +1,50 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package grpc.health.v1alpha;
+
+message HealthCheckRequest {
+  string host = 1;
+  string service = 2;
+}
+
+message HealthCheckResponse {
+  enum ServingStatus {
+    UNKNOWN = 0;
+    SERVING = 1;
+    NOT_SERVING = 2;
+  }
+  ServingStatus status = 1;
+}
+
+service Health {
+  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
+}
\ No newline at end of file
diff --git a/src/ruby/pb/grpc/health/v1alpha/health.rb b/src/ruby/pb/grpc/health/v1alpha/health.rb
new file mode 100644
index 0000000..9c04298
--- /dev/null
+++ b/src/ruby/pb/grpc/health/v1alpha/health.rb
@@ -0,0 +1,29 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: grpc/health/v1alpha/health.proto
+
+require 'google/protobuf'
+
+Google::Protobuf::DescriptorPool.generated_pool.build do
+  add_message "grpc.health.v1alpha.HealthCheckRequest" do
+    optional :host, :string, 1
+    optional :service, :string, 2
+  end
+  add_message "grpc.health.v1alpha.HealthCheckResponse" do
+    optional :status, :enum, 1, "grpc.health.v1alpha.HealthCheckResponse.ServingStatus"
+  end
+  add_enum "grpc.health.v1alpha.HealthCheckResponse.ServingStatus" do
+    value :UNKNOWN, 0
+    value :SERVING, 1
+    value :NOT_SERVING, 2
+  end
+end
+
+module Grpc
+  module Health
+    module V1alpha
+      HealthCheckRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckRequest").msgclass
+      HealthCheckResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse").msgclass
+      HealthCheckResponse::ServingStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse.ServingStatus").enummodule
+    end
+  end
+end
diff --git a/src/ruby/pb/grpc/health/v1alpha/health_checker.rb b/src/ruby/pb/grpc/health/v1alpha/health_checker.rb
new file mode 100644
index 0000000..f04bf5e
--- /dev/null
+++ b/src/ruby/pb/grpc/health/v1alpha/health_checker.rb
@@ -0,0 +1,39 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require 'grpc/health/v1alpha/health_services'
+
+module Grpc
+  # Health contains classes and modules that support providing a health check
+  # service.
+  module Health
+    class Checker
+    end
+  end
+end
diff --git a/src/ruby/pb/grpc/health/v1alpha/health_services.rb b/src/ruby/pb/grpc/health/v1alpha/health_services.rb
new file mode 100644
index 0000000..d5cba2e
--- /dev/null
+++ b/src/ruby/pb/grpc/health/v1alpha/health_services.rb
@@ -0,0 +1,28 @@
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# Source: grpc/health/v1alpha/health.proto for package 'grpc.health.v1alpha'
+
+require 'grpc'
+require 'grpc/health/v1alpha/health'
+
+module Grpc
+  module Health
+    module V1alpha
+      module Health
+
+        # TODO: add proto service documentation here
+        class Service
+
+          include GRPC::GenericService
+
+          self.marshal_class_method = :encode
+          self.unmarshal_class_method = :decode
+          self.service_name = 'grpc.health.v1alpha.Health'
+
+          rpc :Check, HealthCheckRequest, HealthCheckResponse
+        end
+
+        Stub = Service.rpc_stub_class
+      end
+    end
+  end
+end
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index 0bf65ba..26208b7 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -57,7 +57,7 @@
   describe 'restricted view methods' do
     before(:each) do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       @client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                     @pass_through, deadline,
                                     metadata_tag: md_tag)
@@ -87,7 +87,7 @@
   describe '#remote_send' do
     it 'allows a client to send a payload to the server' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       @client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                     @pass_through, deadline,
                                     metadata_tag: md_tag)
@@ -111,7 +111,7 @@
 
     it 'marshals the payload using the marshal func' do
       call = make_test_call
-      ActiveCall.client_invoke(call, @client_queue, deadline)
+      ActiveCall.client_invoke(call, @client_queue)
       marshal = proc { |x| 'marshalled:' + x }
       client_call = ActiveCall.new(call, @client_queue, marshal,
                                    @pass_through, deadline)
@@ -134,8 +134,7 @@
   describe '#client_invoke' do
     it 'sends keywords as metadata to the server when the are present' do
       call = make_test_call
-      ActiveCall.client_invoke(call, @client_queue, deadline,
-                               k1: 'v1', k2: 'v2')
+      ActiveCall.client_invoke(call, @client_queue, k1: 'v1', k2: 'v2')
       recvd_rpc =  @server.request_call(@server_queue, @server_tag, deadline)
       recvd_call = recvd_rpc.call
       expect(recvd_call).to_not be_nil
@@ -148,7 +147,7 @@
   describe '#remote_read' do
     it 'reads the response sent by a server' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -161,7 +160,7 @@
 
     it 'saves no metadata when the server adds no metadata' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -176,7 +175,7 @@
 
     it 'saves metadata add by the server' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -192,7 +191,7 @@
 
     it 'get a nil msg before a status when an OK status is sent' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -209,7 +208,7 @@
 
     it 'unmarshals the response using the unmarshal func' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       unmarshal = proc { |x| 'unmarshalled:' + x }
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    unmarshal, deadline,
@@ -234,7 +233,7 @@
 
     it 'the returns an enumerator that can read n responses' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -252,7 +251,7 @@
 
     it 'the returns an enumerator that stops after an OK Status' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -275,7 +274,7 @@
   describe '#writes_done' do
     it 'finishes ok if the server sends a status response' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -291,7 +290,7 @@
 
     it 'finishes ok if the server sends an early status response' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
@@ -307,7 +306,7 @@
 
     it 'finishes ok if writes_done is true' do
       call = make_test_call
-      md_tag = ActiveCall.client_invoke(call, @client_queue, deadline)
+      md_tag = ActiveCall.client_invoke(call, @client_queue)
       client_call = ActiveCall.new(call, @client_queue, @pass_through,
                                    @pass_through, deadline,
                                    metadata_tag: md_tag)
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb
index 68d4b11..edcc962 100644
--- a/src/ruby/spec/generic/client_stub_spec.rb
+++ b/src/ruby/spec/generic/client_stub_spec.rb
@@ -408,6 +408,26 @@
 
       it_behaves_like 'bidi streaming'
     end
+
+    describe 'without enough time to run' do
+      before(:each) do
+        @sent_msgs = Array.new(3) { |i| 'msg_' + (i + 1).to_s }
+        @replys = Array.new(3) { |i| 'reply_' + (i + 1).to_s }
+        server_port = create_test_server
+        @host = "localhost:#{server_port}"
+      end
+
+      it 'should fail with DeadlineExceeded', bidi: true do
+        @server.start
+        stub = GRPC::ClientStub.new(@host, @cq)
+        blk = proc do
+          e = stub.bidi_streamer(@method, @sent_msgs, noop, noop,
+                                 timeout: 0.001)
+          e.collect { |r| r }
+        end
+        expect(&blk).to raise_error GRPC::BadStatus, /Deadline Exceeded/
+      end
+    end
   end
 
   def run_server_streamer(expected_input, replys, status, **kw)
diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb
index 0326f6e..1295fd7 100644
--- a/src/ruby/spec/generic/rpc_server_spec.rb
+++ b/src/ruby/spec/generic/rpc_server_spec.rb
@@ -396,8 +396,9 @@
         @srv.wait_till_running
         req = EchoMsg.new
         stub = SlowStub.new(@host, **client_opts)
-        deadline = service.delay + 1.0 # wait for long enough
-        expect(stub.an_rpc(req, deadline, k1: 'v1', k2: 'v2')).to be_a(EchoMsg)
+        timeout = service.delay + 1.0 # wait for long enough
+        resp = stub.an_rpc(req, timeout: timeout, k1: 'v1', k2: 'v2')
+        expect(resp).to be_a(EchoMsg)
         wanted_md = [{ 'k1' => 'v1', 'k2' => 'v2' }]
         check_md(wanted_md, service.received_md)
         @srv.stop
@@ -411,8 +412,8 @@
         @srv.wait_till_running
         req = EchoMsg.new
         stub = SlowStub.new(@host, **client_opts)
-        deadline = 0.1  # too short for SlowService to respond
-        blk = proc { stub.an_rpc(req, deadline, k1: 'v1', k2: 'v2') }
+        timeout = 0.1  # too short for SlowService to respond
+        blk = proc { stub.an_rpc(req, timeout: timeout, k1: 'v1', k2: 'v2') }
         expect(&blk).to raise_error GRPC::BadStatus
         wanted_md = []
         expect(service.received_md).to eq(wanted_md)
diff --git a/src/ruby/spec/pb/health/checker_spec.rb b/src/ruby/spec/pb/health/checker_spec.rb
new file mode 100644
index 0000000..0aeae44
--- /dev/null
+++ b/src/ruby/spec/pb/health/checker_spec.rb
@@ -0,0 +1,185 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require 'grpc'
+require 'grpc/health/v1alpha/health'
+require 'grpc/health/checker'
+
+describe Grpc::Health::Checker do
+  StatusCodes = GRPC::Core::StatusCodes
+  ServingStatus = Grpc::Health::V1alpha::HealthCheckResponse::ServingStatus
+  HCResp = Grpc::Health::V1alpha::HealthCheckResponse
+  HCReq = Grpc::Health::V1alpha::HealthCheckRequest
+  success_tests =
+    [
+      {
+        desc: 'neither host or service are specified',
+        host: '',
+        service: ''
+      }, {
+        desc: 'only the host is specified',
+        host: 'test-fake-host',
+        service: ''
+      }, {
+        desc: 'the host and service are specified',
+        host: 'test-fake-host',
+        service: 'fake-service-1'
+      }, {
+        desc: 'only the service is specified',
+        host: '',
+        service: 'fake-service-2'
+      }
+    ]
+
+  context 'initialization' do
+    it 'can be constructed with no args' do
+      expect(subject).to_not be(nil)
+    end
+  end
+
+  context 'method `add_status` and `check`' do
+    success_tests.each do |t|
+      it "should succeed when #{t[:desc]}" do
+        subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
+        got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
+                            nil)
+        want = HCResp.new(status: ServingStatus::NOT_SERVING)
+        expect(got).to eq(want)
+      end
+    end
+  end
+
+  context 'method `check`' do
+    success_tests.each do |t|
+      it "should fail with NOT_FOUND when #{t[:desc]}" do
+        blk = proc do
+          subject.check(HCReq.new(host: t[:host], service: t[:service]), nil)
+        end
+        expected_msg = /#{StatusCodes::NOT_FOUND}/
+        expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+      end
+    end
+  end
+
+  context 'method `clear_status`' do
+    success_tests.each do |t|
+      it "should fail after clearing status when #{t[:desc]}" do
+        subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
+        got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
+                            nil)
+        want = HCResp.new(status: ServingStatus::NOT_SERVING)
+        expect(got).to eq(want)
+
+        subject.clear_status(t[:host], t[:service])
+        blk = proc do
+          subject.check(HCReq.new(host: t[:host], service: t[:service]),
+                        nil)
+        end
+        expected_msg = /#{StatusCodes::NOT_FOUND}/
+        expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+      end
+    end
+  end
+
+  context 'method `clear_all`' do
+    it 'should return NOT_FOUND after being invoked' do
+      success_tests.each do |t|
+        subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
+        got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
+                            nil)
+        want = HCResp.new(status: ServingStatus::NOT_SERVING)
+        expect(got).to eq(want)
+      end
+
+      subject.clear_all
+
+      success_tests.each do |t|
+        blk = proc do
+          subject.check(HCReq.new(host: t[:host], service: t[:service]), nil)
+        end
+        expected_msg = /#{StatusCodes::NOT_FOUND}/
+        expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+      end
+    end
+  end
+
+  describe 'running on RpcServer' do
+    RpcServer = GRPC::RpcServer
+    StatusCodes = GRPC::Core::StatusCodes
+    CheckerStub = Grpc::Health::Checker.rpc_stub_class
+
+    before(:each) do
+      @server_queue = GRPC::Core::CompletionQueue.new
+      server_host = '0.0.0.0:0'
+      @server = GRPC::Core::Server.new(@server_queue, nil)
+      server_port = @server.add_http2_port(server_host)
+      @host = "localhost:#{server_port}"
+      @ch = GRPC::Core::Channel.new(@host, nil)
+      @client_opts = { channel_override: @ch }
+      server_opts = {
+        server_override: @server,
+        completion_queue_override: @server_queue,
+        poll_period: 1
+      }
+      @srv = RpcServer.new(**server_opts)
+    end
+
+    after(:each) do
+      @srv.stop
+    end
+
+    it 'should receive the correct status', server: true do
+      @srv.handle(subject)
+      subject.add_status('', '', ServingStatus::NOT_SERVING)
+      t = Thread.new { @srv.run }
+      @srv.wait_till_running
+
+      stub = CheckerStub.new(@host, **@client_opts)
+      got = stub.check(HCReq.new)
+      want = HCResp.new(status: ServingStatus::NOT_SERVING)
+      expect(got).to eq(want)
+      @srv.stop
+      t.join
+    end
+
+    it 'should fail on unknown services', server: true do
+      @srv.handle(subject)
+      t = Thread.new { @srv.run }
+      @srv.wait_till_running
+      blk = proc do
+        stub = CheckerStub.new(@host, **@client_opts)
+        stub.check(HCReq.new(host: 'unknown', service: 'unknown'))
+      end
+      expected_msg = /#{StatusCodes::NOT_FOUND}/
+      expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+      @srv.stop
+      t.join
+    end
+  end
+end