blob: c5d24c7359e08efb723bf8180bd7081c6add8c18 [file] [log] [blame]
// 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('prova');
var addAttributes = require('../../../src/lib/add-attributes');
var store = require('../../../src/lib/store');
var proxyquire = require('proxyquireify')(require);
// Mock of the smart service implementation file. Uses a single dumbLearner.
var smartServiceImplementationMock = {
LEARNER_DUMB: 0,
LEARNER_MAP: {
0: dumbLearner
},
LEARNER_METHODS: {
0: {
update: dumbLearnerUpdate,
predict: dumbLearnerPredict
}
}
};
// The dumbLearner simply counts the calls to update and predict.
function dumbLearner(type, params) {
this.type = type;
this.params = params;
this.recordCount = 0;
this.predictCount = 0;
addAttributes(this, smartServiceImplementationMock.LEARNER_METHODS[type]);
}
function dumbLearnerUpdate(input) {
this.recordCount++;
}
function dumbLearnerPredict(input) {
this.predictCount++;
return [this.recordCount, this.predictCount];
}
var smartService = proxyquire(
'../../../src/services/smart/service', {
'./service-implementation': smartServiceImplementationMock
});
function makeLearner(id) {
return smartService.loadOrCreate(
id,
smartService.constants.LEARNER_DUMB,
{ learningRate: 0.1, regularize: false }
);
}
test('update and predict', function(t) {
var id1 = 'first';
var id2 = 'second';
// First, ensure that these learners aren't in the store.
store.removeValue(id1).then(function() {
return store.removeValue(id2);
}).then(function() {
// Then, create learners using makeLearner.
return makeLearner(id1);
}).then(function() {
return makeLearner(id2);
}).then(function() {
// The # of predictions increases as we call predict on learner 1.
return smartService.predict(id1, 'some input');
}).then(function(value) {
t.deepEqual(value, [0, 1], 'learner 1 => predict called 1 time');
return smartService.predict(id1, 'some input');
}).then(function(value) {
t.deepEqual(value, [0, 2], 'learner 1 => predict called 2 times');
// The # of predictions increases as we call predict on learner 2.
return smartService.predict(id2, 'some input');
}).then(function(value) {
t.deepEqual(value, [0, 1], 'learner 2 => predict called 1 time');
// The # of updates rises when calling update on learner 1.
return smartService.update(id1, 'some input');
}).then(function() {
return smartService.predict(id1, 'some input');
}).then(function(value) {
t.deepEqual(value, [1, 3], 'learner 1 => record x1, predict x3');
// The # of updates rises when calling update on learner 2.
return smartService.update(id2, 'some input');
}).then(function() {
return smartService.update(id2, 'some input');
}).then(function() {
return smartService.predict(id2, 'some input');
}).then(function(value) {
t.deepEqual(value, [2, 2], 'learner 2 => record x2, predict x2');
t.end();
}).catch(t.end);
});
test('save, load, and reset', function(t) {
var id1 = 'third';
var id2 = 'fourth';
// The store does not have these values initially.
store.getValue(id1).then(function(value) {
t.equal(value, null, 'learner 1 does not start in the store');
return store.getValue(id2);
}).then(function(value) {
t.equal(value, null, 'learner 2 does not start in the store');
// Make learner 1 through registration.
return makeLearner(id1);
}).then(function() {
return smartService.save(id1);
}).then(function() {
return store.getValue(id1);
}).then(function(value) {
t.notEqual(value, null, 'saving learner 1 puts it in the store');
// Copy this value into the store at learner 2's location.
return store.setValue(id2, value);
}).then(function(value) {
// Then load this copy, learner 2.
return smartService.loadOrCreate(id2);
}).then(function(learner2) {
// We can call predict and update on id2 (a loaded learner).
// Note: predict and update are required functions on any learner object.
t.doesNotThrow(
function() { learner2.update('some input'); },
'loaded learners can update'
);
t.doesNotThrow(
function() { learner2.predict('some other input'); },
'loaded learners can predict'
);
// Additionally, the dumb learner's counts match what they ought to be.
t.deepEqual(learner2.predict('random input'), [1, 2],
'learner 2 => record x1, predict x2');
return smartService.reset(id1); // Reset id1 but not id2.
}).then(function() {
return store.getValue(id1);
}).then(function(value) {
t.equal(value, null, 'learner 1 is not in the store after reset');
return store.getValue(id2);
}).then(function(value) {
t.notEqual(value, null, 'learner 2 remains in the store (not reset)');
return smartService.reset(id2); // Now reset id2 and its value becomes null.
}).then(function() {
return store.getValue(id2);
}).then(function(value) {
t.equal(value, null, 'learner 2 is not in the store after reset');
t.end();
}).catch(function(err) {
// Cleanup, just in case.
return Promise.all([
store.removeValue(id1),
store.removeValue(id2)
]).then(function() {
t.end(err);
}).catch(t.end);
});
});
test('rejects on bad loadOrCreate', function(t) {
var id = 'multiload';
smartService.loadOrCreate(id, null, {}).then(function success(learner) {
t.fail('Should not succeed with bad learner type');
}, function failure(err) {
t.ok(err, 'Bad learner type => loadOrCreate rejected');
}).then(function() {
return smartService.loadOrCreate(id, 0, {}); // first loadOrCreate
}).then(function() {
return smartService.loadOrCreate(id, 0, {}); // second loadOrCreate
}).then(function success(learner) {
t.end('Should not return a learner if already created/loaded');
}, function failure(err) {
t.ok(err, 'Already used learner id => loadOrCreate rejected');
t.end();
}).catch(t.end);
});
test('rejects on bad save', function(t) {
smartService.save('this id not here').then(function success() {
t.end('Should not save an unregistered learner');
}, function failure(err) {
t.ok(err, 'Unregistered learner => save rejected');
t.end();
}).catch(t.end);
});
test('rejects on bad reset', function(t) {
smartService.reset('this id not here').then(function success() {
t.end('Should not reset an unregistered learner');
}, function failure(err) {
t.ok(err, 'Unregistered learner => reset rejected');
t.end();
}).catch(t.end);
});