javascript/core: allow raised exceptions in glob stream

SEE: vanadium/issues#587
Change-Id: I273d8979ef85484575f59ef90f81fb1f1be7808e
diff --git a/src/proxy/stream-handler.js b/src/proxy/stream-handler.js
index 9566da0..c76c6ee 100644
--- a/src/proxy/stream-handler.js
+++ b/src/proxy/stream-handler.js
@@ -76,6 +76,10 @@
     emitStreamError(handler._stream,
                     new vError.InternalError(
                       handler._ctx, 'Failed to decode result: ', e));
+  }).catch(function(e) {
+    process.nextTick(function() {
+      throw e;
+    });
   });
 };
 
diff --git a/src/rpc/client.js b/src/rpc/client.js
index aca847f..d9ebfb9 100644
--- a/src/rpc/client.js
+++ b/src/rpc/client.js
@@ -251,9 +251,13 @@
   var rpc = this;
   return vom.decode(data, false, this._typeDecoder).then(function(data) {
     rpc._def.stream._queueRead(data);
-  }).catch(function(e) {
+  }, function(e) {
     rpc.handleError(
       new verror.InternalError(rpc._ctx, 'Failed to decode result: ', e));
+  }).catch(function(e) {
+    process.nextTick(function() {
+      throw e;
+    });
   });
 };
 
diff --git a/test/integration/test-js-client-server.js b/test/integration/test-js-client-server.js
index 7c58e0b..d909e9c 100644
--- a/test/integration/test-js-client-server.js
+++ b/test/integration/test-js-client-server.js
@@ -984,3 +984,52 @@
   // 5. End the stream.
   stream.end();
 }
+
+test('Test server stream exception handling', function(assert) {
+  var exception;
+
+  if (typeof window === 'undefined') {
+    process.on('uncaughtException', function setexception(err) {
+      exception = err;
+      process.removeListener('uncaughtException', setexception);
+    });
+  } else {
+    window.onerror = function(message, url, line, column, err) {
+      exception = err;
+      // Put it back to the default
+      window.onerror = null;
+    };
+  }
+
+  var service = {
+    get: function get(context, serverCall, $stream, cb) {
+      $stream.on('end', cb.bind(null, null, {}));
+      $stream.on('error', cb);
+      $stream.on('data', function ondata(buffer) {
+        throw new Error('Woah!');
+      });
+    }
+  };
+
+  setup(service, function ready(err, context, remote, end) {
+    assert.error(err, 'should not error on setup');
+
+    var stream = remote.get(context, function(err) {
+      assert.error(err);
+      assert.ok(exception, 'stream exceptions should be raised');
+      end(assert);
+    }).stream;
+
+    stream.write('foo');
+    stream.end();
+  });
+
+  function setup(service, cb) {
+    var dispatcher = leafDispatcher(service);
+    var name = 'test/server-stream-exception';
+
+    serve(name, dispatcher, function(err, res) {
+      cb(err, res.runtime.getContext(), res.service, res.end);
+    });
+  }
+});
diff --git a/test/integration/test-namespace.js b/test/integration/test-namespace.js
index ee56a42..5712e4e 100644
--- a/test/integration/test-namespace.js
+++ b/test/integration/test-namespace.js
@@ -50,6 +50,60 @@
   }
 });
 
+test('Test glob().stream - exception handling', function(assert) {
+  vanadium.init(function onruntime(err, runtime) {
+    if (err) {
+      return end(err);
+    }
+
+    var namespace = runtime.namespace();
+    var context = runtime.getContext();
+    var promise = namespace.glob(context, '*');
+    var stream = promise.stream;
+    var exception;
+
+    promise.catch(function(err) {
+      assert.error(err, 'should not catch expceptions in stream');
+    });
+
+    if (typeof window === 'undefined') {
+      process.on('uncaughtException', function setexception(err) {
+        exception = err;
+        process.removeListener('uncaughtException', setexception);
+      });
+    } else {
+      window.onerror = function(message, url, line, column, err) {
+        exception = err;
+        // Put it back to the default
+        window.onerror = null;
+      };
+    }
+
+    // NOTE: Using assert.throws(fn) here is not possible since the fix is to
+    // use process.nextTick(...) wich will have the raised exception bypass the
+    // try/catch stack around where assert.throws(...) calls the passed in fn.
+    stream.once('data', function onentry(entry) {
+      throw new Error('Woah!');
+    });
+
+    stream.on('end', end);
+
+    function end(err) {
+      if (err) {
+        assert.error(err, 'should not error');
+      }
+
+      assert.ok(exception, 'glob-stream exceptions should be raised');
+
+      if (runtime) {
+        runtime.close(assert.end);
+      } else {
+        assert.end();
+      }
+    }
+  });
+});
+
 test('Test globbing nested levels - glob(' + PREFIX + 'cottage/*/*/*)',
   function(assert) {
     var runtime;