Merge "TBR playground/client: add JSON stream test coverage"
diff --git a/client/browser/api/index.js b/client/browser/api/index.js
index c13a196..5c082f1 100644
--- a/client/browser/api/index.js
+++ b/client/browser/api/index.js
@@ -12,8 +12,7 @@
var extend = require('xtend');
var prr = require('prr');
var config = require('../config');
-var JSONStream = require('./json-stream');
-var split = require('split');
+var jsonStream = require('./json-stream');
var defaults = {
// Timeout for HTTP requests, 5 secs in milliseconds.
timeout: 5 * 60 * 1000,
@@ -204,7 +203,7 @@
// immediately.
// TODO(jasoncampbell): stop pending xhr
// SEE: https://github.com/vanadium/issues/issues/39
-API.prototype.run = function(data, callback) {
+API.prototype.run = function(data) {
var api = this;
var uuid = data.uuid;
var uri = api.url({
@@ -212,10 +211,12 @@
debug: true
});
+ // NOTE: The UI prevents the channel from being fired while the run is in
+ // progress so this should never happen.
if (api.isPending(uuid)) {
var message = format('%s is already running');
var err = new Error(message);
- return callback(err);
+ throw err;
}
api.pending(uuid);
@@ -231,23 +232,20 @@
// TODO(jasoncampbell): Consolidate http libraries.
// TODO(jasoncampbell): Verify XHR timeout logic and handle appropriately.
var req = hyperquest.post(uri, options);
+ var stream = jsonStream();
- req.once('error', callback);
+ req.on('error', function(err) {
+ stream.emit('error', err);
+ });
- var stream = JSONStream(); // jshint ignore:line
-
- stream.on('end', function() {
+ req.on('end', function() {
api.done(uuid);
});
- req
- .pipe(split())
- .pipe(stream);
+ req.pipe(stream);
- callback(null, stream);
-
- var string = JSON.stringify(data);
-
- req.write(string);
+ req.write(JSON.stringify(data));
req.end();
+
+ return stream;
};
diff --git a/client/browser/api/json-stream.js b/client/browser/api/json-stream.js
index 6436c67..4834fa2 100644
--- a/client/browser/api/json-stream.js
+++ b/client/browser/api/json-stream.js
@@ -2,27 +2,21 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-var through2 = require('through2');
+// Using jxson/split#ignore-trailing until it is merged upstream.
+//
+// SEE: https://github.com/dominictarr/split/pull/15
+var split = require('split');
-module.exports = create;
+module.exports = JSONStream;
-function create() {
- return through2.obj(write);
+function JSONStream() {
+ return split(parse, null, { trailing: false });
}
-function write(buffer, enc, callback) {
+function parse(buffer) {
if (buffer.length === 0) {
- return callback();
+ return undefined;
+ } else {
+ return JSON.parse(buffer);
}
-
- var json;
- var err;
-
- try {
- json = JSON.parse(buffer);
- } catch (err) {
- err.data = buffer;
- }
-
- callback(err, json);
}
diff --git a/client/browser/components/bundle/index.js b/client/browser/components/bundle/index.js
index d31995f..30de7cd 100644
--- a/client/browser/components/bundle/index.js
+++ b/client/browser/components/bundle/index.js
@@ -115,27 +115,30 @@
debug('running');
state.running.set(true);
+ // Temporary message to provide feedback and show that the run is happening.
+ state.results.logs.push(log({
+ File: 'web client',
+ Message: 'Run request initiated.',
+ Stream: 'stdout'
+ }));
+
var data = {
uuid: state.uuid(),
files: toArray(state.files())
};
- api.run(data, function onrun(err, stream) {
- if (err) {
- // TODO(jasoncampbell): handle error appropriately.
- throw err;
- }
+ var stream = api.run(data);
- stream.on('error', function onerror(err) {
- throw err;
- });
+ stream.on('error', function(err) {
+ // TODO(jasoncampbell): handle errors appropriately.
+ throw err;
+ });
- stream.on('data', function ondata(data) {
- state.results.logs.push(log(data));
- });
+ stream.on('data', function ondata(data) {
+ state.results.logs.push(log(data));
+ });
- stream.on('end', function() {
- state.running.set(false);
- });
+ stream.on('end', function() {
+ state.running.set(false);
});
}
diff --git a/client/browser/components/log/normalize.js b/client/browser/components/log/normalize.js
index 11ccd37..c31521a 100644
--- a/client/browser/components/log/normalize.js
+++ b/client/browser/components/log/normalize.js
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var now = require('date-now');
+
module.exports = normalize;
// Takes data from API messages and converts them to appropriate objects
@@ -9,7 +11,7 @@
function normalize(data) {
// convert `data.Timestamp` nanosecond value to a float in milliseconds.
var oneMillion = 1e6;
- var timestamp = data.Timestamp / oneMillion;
+ var timestamp = data.Timestamp ? (data.Timestamp / oneMillion) : now();
return {
message: data.Message,
diff --git a/client/browser/exception-logger.js b/client/browser/exception-logger.js
index bbd0139..7bafe88 100644
--- a/client/browser/exception-logger.js
+++ b/client/browser/exception-logger.js
@@ -5,7 +5,9 @@
var window = require('global/window');
var UA = window.navigator ? window.navigator.userAgent : '';
-module.exports = init;
+module.exports = {
+ init: init
+};
function init() {
window.addEventListener('error', onerror);
diff --git a/client/package.json b/client/package.json
index fdea47f..8a9c96f 100644
--- a/client/package.json
+++ b/client/package.json
@@ -9,6 +9,7 @@
"main": "browser/index.js",
"dependencies": {
"brace": "^0.4.0",
+ "date-now": "^1.0.1",
"debug": "^2.1.3",
"domready": "^1.0.7",
"format": "^0.2.1",
@@ -20,7 +21,7 @@
"routes": "^2.0.0",
"run-parallel": "^1.1.0",
"single-page": "^1.0.0",
- "split": "^0.3.3",
+ "split": "jxson/split#ignore-trailing",
"superagent": "^1.1.0",
"through2": "^0.6.3",
"xtend": "^4.0.0"
diff --git a/client/test/index.js b/client/test/index.js
index 6cf839a..5dd9c9c 100644
--- a/client/test/index.js
+++ b/client/test/index.js
@@ -5,3 +5,5 @@
require('./test-api-url');
require('./test-config');
require('./test-component-log');
+require('./test-component-results');
+require('./test-api-json-stream');
diff --git a/client/test/test-api-json-stream.js b/client/test/test-api-json-stream.js
new file mode 100644
index 0000000..c0e8106
--- /dev/null
+++ b/client/test/test-api-json-stream.js
@@ -0,0 +1,138 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var test = require('tape');
+var jsonStream = require('../browser/api/json-stream');
+var Buffer = require('buffer').Buffer;
+
+test('jsonStream() - single entity per write', function(t) {
+ t.plan(4);
+
+ var stream = jsonStream();
+
+ stream
+ .on('data', function(data) {
+ t.same(data, { foo: 'bar' });
+ });
+
+ iterate(4, function() {
+ stream.write(new Buffer('{ "foo": "bar" }\n'));
+ });
+
+ stream.end();
+});
+
+test('jsonStream() - empty strings', function(t) {
+ t.plan(1);
+
+ var stream = jsonStream();
+
+ stream
+ .on('data', function(data) {
+ t.same(data, { foo: 'bar' });
+ });
+
+ stream.write(new Buffer('{ "foo": "bar" }\n'));
+ stream.write(new Buffer(''));
+
+ stream.end();
+});
+
+test('jsonStream() - single entry, multiple writes', function(t) {
+ t.plan(1);
+
+ var stream = jsonStream();
+
+ stream
+ .on('data', function(data) {
+ t.same(data, { foo: 'bar', baz: 'qux' });
+ });
+
+ stream.write(new Buffer('{ "foo":'));
+ stream.write(new Buffer('"ba'));
+ stream.write(new Buffer('r", "ba'));
+ stream.write(new Buffer('z":'));
+ stream.write(new Buffer('"qux" }\n'));
+ stream.end();
+});
+
+test('jsonStream() - chunk with several entries, then pieces', function(t) {
+ t.plan(11);
+
+ var stream = jsonStream();
+ var chunk = '';
+
+ stream
+ .on('data', function(data) {
+ t.same(data, { a: 'b' });
+ });
+
+ iterate(10, function() {
+ chunk += '{ "a": "b" }\n';
+ });
+
+ chunk += '{ "a":';
+
+ stream.write(new Buffer(chunk));
+ stream.write(new Buffer(' "b" }\n'));
+ stream.end();
+});
+
+test('jsonStream() - end with a partial entry', function(t) {
+ t.plan(2);
+
+ var stream = jsonStream();
+
+ stream
+ .on('end', t.end)
+ .on('error', function(err) {
+ t.fail('should not error');
+ })
+ .on('data', function(data) {
+ t.same(data, { a: 'b' });
+ });
+
+ stream.write('{ "a": "b" }\n');
+ stream.write('{ "a": "b" }\n');
+ stream.write('{ "a": ');
+ stream.end();
+});
+
+test('jsonStream() - blank line entries', function(t) {
+ t.plan(1);
+
+ var stream = jsonStream();
+
+ stream
+ .on('end', t.end)
+ .on('error', function(err) {
+ t.fail('should not error');
+ })
+ .on('data', function(data) {
+ t.same(data, { a: 'b' });
+ });
+
+ stream.write('{ "a": "b" }\n');
+ stream.write('');
+ stream.end();
+});
+
+test('jsonStream() - bad json entry', function(t) {
+ var stream = jsonStream();
+ var chunk = '{ bad: "json"';
+
+ stream.on('error', function(err) {
+ t.ok(err instanceof Error, 'should error');
+ t.ok(err instanceof SyntaxError, 'should be a SyntaxError');
+ t.end();
+ });
+
+ stream.write(new Buffer(chunk + '\n'));
+});
+
+function iterate(times, iterator) {
+ for (var i = 0; i < times; i++) {
+ iterator(i);
+ }
+}