Added tests for MeanTelemetryProvider

This commit is contained in:
Henry 2017-11-22 09:08:02 -08:00 committed by Victor Woeltjen
parent 0a9ea48355
commit 8a66731271
5 changed files with 632 additions and 33 deletions

View File

@ -1,33 +1,55 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./src/MeanTelemetryProvider'], function (MeanTelemetryProvider) {
var DEFAULT_SAMPLES = 10;
function plugin() {
return function install(openmct) {
openmct.types.addType('telemetry-mean', {
name: 'Telemetry Mean',
name: 'Telemetry Filter',
description: 'Provides telemetry values that represent the mean of the last N values of a telemetry stream',
creatable: true,
cssClass: 'icon-telemetry',
initialize: function (domainObject) {
domainObject.samples = DEFAULT_SAMPLES;
domainObject.telemetry = {};
domainObject.telemetry.values =
domainObject.telemetry.values =
openmct.time.getAllTimeSystems().map(function (timeSystem, index) {
return {
key: timeSystem.key,
name: timeSystem.name,
hints: {
domain: index + 1
}
}
}
};
});
domainObject.telemetry.values.push({
key: "value",
name: "Value",
hints: {
range: 1
}
});
key: "value",
name: "Value",
hints: {
range: 1
}
});
},
form: [
{
@ -44,7 +66,7 @@ define(['./src/MeanTelemetryProvider'], function (MeanTelemetryProvider) {
"required": true,
"cssClass": "l-input-sm"
}
]
]
});
openmct.telemetry.addProvider(new MeanTelemetryProvider(openmct));
};

View File

@ -19,7 +19,8 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jshint latedef: nofunc */
/*global console */
define([
'../../../api/objects/object-utils',
'./TelemetryAverager'
@ -47,18 +48,20 @@ define([
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
var samples = domainObject.samples;
this.objectAPI.get(objectId).then(function (linkedDomainObject) {
if (!unsubscribeCalled) {
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
}
}.bind(this));
this.objectAPI.get(objectId)
.then(function (linkedDomainObject) {
if (!unsubscribeCalled) {
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
}
}.bind(this))
.catch(logError);
return function unsubscribe(){
return function unsubscribe() {
unsubscribeCalled = true;
if (wrappedUnsubscribe !== undefined) {
wrappedUnsubscribe();
}
}
};
};
MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, samples, callback) {
@ -67,11 +70,11 @@ define([
return this.telemetryAPI.subscribe(domainObject, function (telemetryDatum) {
var avgData = telemetryAverager.createAverageDatum(telemetryDatum);
if (telemetryAverager.sampleCount() === samples){
if (telemetryAverager.sampleCount() === samples) {
callback(avgData);
}
}.bind(this));
}
};
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
var objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
@ -89,18 +92,18 @@ define([
var averageData = [];
var telemetryAverager = new TelemetryAverager(this.telemetryAPI, this.timeAPI, domainObject, samples);
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData){
telemetryData.forEach(function (datum){
var avgData = telemetryAverager.createAverageDatum(datum);
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData) {
telemetryData.forEach(function (datum) {
var avgDatum = telemetryAverager.createAverageDatum(datum);
if (telemetryAverager.sampleCount() === samples){
averageData.push(avgData);
if (telemetryAverager.sampleCount() === samples) {
averageData.push(avgDatum);
}
}.bind(this));
return averageData;
}.bind(this));
}
};
/**
* @private
@ -110,5 +113,13 @@ define([
return this.objectAPI.get(objectId);
};
function logError(error) {
if (error.stack) {
console.error(error.stack);
} else {
console.error(error);
}
}
return MeanTelemetryProvider;
});

View File

@ -0,0 +1,460 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*jshint latedef: nofunc */
define([
"./MeanTelemetryProvider",
"./MockTelemetryApi"
], function (
MeanTelemetryProvider,
MockTelemetryApi
) {
var RANGE_KEY = 'value';
describe("The Mean Telemetry Provider", function () {
var mockApi;
var meanTelemetryProvider;
var outstandingPromises = 0;
var mockDomainObject;
var associatedObject;
beforeEach(function () {
createMockApi();
setTimeSystemTo('utc');
createMockObjects();
meanTelemetryProvider = new MeanTelemetryProvider(mockApi);
});
it("supports telemetry-mean objects only", function () {
var mockTelemetryMeanObject = mockObjectWithType('telemetry-mean');
var mockOtherObject = mockObjectWithType('other');
expect(meanTelemetryProvider.canProvideTelemetry(mockTelemetryMeanObject)).toBe(true);
expect(meanTelemetryProvider.canProvideTelemetry(mockOtherObject)).toBe(false);
});
describe("the subscribe function", function () {
var subscriptionCallback;
beforeEach(function () {
subscriptionCallback = jasmine.createSpy('subscriptionCallback');
});
it("subscribes to telemetry for the associated object", function () {
meanTelemetryProvider.subscribe(mockDomainObject);
expectObjectWasSubscribedTo(associatedObject);
});
it("returns a function that unsubscribes from the associated object", function () {
var unsubscribe = meanTelemetryProvider.subscribe(mockDomainObject);
waitsFor(allPromisesToBeResolved);
runs(unsubscribe);
expectUnsubscribeFrom(associatedObject);
});
it("returns an average only when the sample size is reached", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313}
];
setSampleSize(5);
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
feedInputTelemetry(inputTelemetry);
expectNoAverageForTelemetry(inputTelemetry);
});
it("correctly averages a sample of five values", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231}
];
var expectedAverages = [{
'utc': 5, 'value': 222.44888
}];
setSampleSize(5);
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
});
it("correctly averages a sample of ten values", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231},
{'utc': 6, 'defaultRange': 2323.12},
{'utc': 7, 'defaultRange': 532.12},
{'utc': 8, 'defaultRange': 453.543},
{'utc': 9, 'defaultRange': 89.2111},
{'utc': 10, 'defaultRange': 0.543}
];
var expectedAverages = [{
'utc': 10, 'value': 451.07815
}];
setSampleSize(10);
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
});
it("only averages values within its sample window", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231},
{'utc': 6, 'defaultRange': 2323.12},
{'utc': 7, 'defaultRange': 532.12},
{'utc': 8, 'defaultRange': 453.543},
{'utc': 9, 'defaultRange': 89.2111},
{'utc': 10, 'defaultRange': 0.543}
];
var expectedAverages = [
{'utc': 5, 'value': 222.44888},
{'utc': 6, 'value': 662.4482599999999},
{'utc': 7, 'value': 704.6078},
{'utc': 8, 'value': 773.02748},
{'utc': 9, 'value': 679.8234399999999},
{'utc': 10, 'value': 679.70742}
];
setSampleSize(5);
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(expectedAverages, inputTelemetry);
});
describe("given telemetry input with range values", function () {
var inputTelemetry;
beforeEach(function () {
inputTelemetry = [{
'utc': 1,
'rangeKey': 5678,
'otherKey': 9999
}];
setSampleSize(1);
});
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
var averageTelemetryForRangeKey = [{
'utc': 1,
'value': 5678
}];
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
mockApi.telemetry.setDefaultRangeTo('rangeKey');
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
});
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
var averageTelemetryForOtherKey = [{
'utc': 1,
'value': 9999
}];
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
mockApi.telemetry.setDefaultRangeTo('otherKey');
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
});
});
describe("given telemetry input with range values", function () {
var inputTelemetry;
beforeEach(function () {
inputTelemetry = [{
'utc': 1,
'rangeKey': 5678,
'otherKey': 9999
}];
setSampleSize(1);
});
it("uses the 'rangeKey' input range, when it is the default, to calculate the average", function () {
var averageTelemetryForRangeKey = [{
'utc': 1,
'value': 5678
}];
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
mockApi.telemetry.setDefaultRangeTo('rangeKey');
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(averageTelemetryForRangeKey, inputTelemetry);
});
it("uses the 'otherKey' input range, when it is the default, to calculate the average", function () {
var averageTelemetryForOtherKey = [{
'utc': 1,
'value': 9999
}];
meanTelemetryProvider.subscribe(mockDomainObject, subscriptionCallback);
mockApi.telemetry.setDefaultRangeTo('otherKey');
feedInputTelemetry(inputTelemetry);
expectAveragesForTelemetry(averageTelemetryForOtherKey, inputTelemetry);
});
});
function feedInputTelemetry(inputTelemetry) {
waitsFor(allPromisesToBeResolved);
runs(function () {
inputTelemetry.forEach(mockApi.telemetry.mockReceiveTelemetry);
});
}
function expectNoAverageForTelemetry(inputTelemetry) {
waitsFor(allPromisesToBeResolved);
runs(function () {
expect(subscriptionCallback).not.toHaveBeenCalled();
});
}
function expectAveragesForTelemetry(expectedAverages) {
waitsFor(allPromisesToBeResolved);
runs(function () {
expectedAverages.forEach(function (averageDatum) {
expect(subscriptionCallback).toHaveBeenCalledWith(averageDatum);
});
});
}
function expectObjectWasSubscribedTo(object) {
waitsFor(allPromisesToBeResolved);
runs(function () {
expect(mockApi.telemetry.subscribe).toHaveBeenCalledWith(object, jasmine.any(Function));
});
}
function expectUnsubscribeFrom() {
waitsFor(allPromisesToBeResolved);
runs(function () {
expect(mockApi.telemetry.unsubscribe).toHaveBeenCalled();
});
}
});
describe("the request function", function () {
it("requests telemetry for the associated object", function () {
meanTelemetryProvider.request(mockDomainObject);
expectTelemetryToBeRequestedFor(associatedObject);
});
it("returns an average only when the sample size is reached", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313}
];
var promiseForAverage;
setSampleSize(5);
whenTelemetryRequestedReturn(inputTelemetry);
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
expectEmptyResponse(promiseForAverage);
});
it("correctly averages a sample of five values", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231}
];
var promiseForAverage;
setSampleSize(5);
whenTelemetryRequestedReturn(inputTelemetry);
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
expectAverageToBe(222.44888, promiseForAverage);
});
it("correctly averages a sample of ten values", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231},
{'utc': 6, 'defaultRange': 2323.12},
{'utc': 7, 'defaultRange': 532.12},
{'utc': 8, 'defaultRange': 453.543},
{'utc': 9, 'defaultRange': 89.2111},
{'utc': 10, 'defaultRange': 0.543}
];
var promiseForAverage;
setSampleSize(10);
whenTelemetryRequestedReturn(inputTelemetry);
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
expectAverageToBe(451.07815, promiseForAverage);
});
it("only averages values within its sample window", function () {
var inputTelemetry = [
{'utc': 1, 'defaultRange': 123.1231},
{'utc': 2, 'defaultRange': 321.3223},
{'utc': 3, 'defaultRange': 111.4446},
{'utc': 4, 'defaultRange': 555.2313},
{'utc': 5, 'defaultRange': 1.1231},
{'utc': 6, 'defaultRange': 2323.12},
{'utc': 7, 'defaultRange': 532.12},
{'utc': 8, 'defaultRange': 453.543},
{'utc': 9, 'defaultRange': 89.2111},
{'utc': 10, 'defaultRange': 0.543}
];
var promiseForAverage;
setSampleSize(5);
whenTelemetryRequestedReturn(inputTelemetry);
promiseForAverage = meanTelemetryProvider.request(mockDomainObject);
expectAverageToBe(679.70742, promiseForAverage);
});
function expectAverageToBe(expectedValue, promiseForAverage) {
var averageData;
promiseForAverage.then(function (data) {
averageData = data;
});
waitsFor(function () {
return averageData !== undefined;
}, 'data to return from request', 1);
runs(function () {
var averageDatum = averageData[averageData.length - 1];
expect(averageDatum[RANGE_KEY]).toBe(expectedValue);
});
}
function expectEmptyResponse(promiseForAverage) {
var averageData;
promiseForAverage.then(function (data) {
averageData = data;
});
waitsFor(function () {
return averageData !== undefined;
}, 'data to return from request', 1);
runs(function () {
expect(averageData.length).toBe(0);
});
}
function whenTelemetryRequestedReturn(telemetry) {
mockApi.telemetry.request.andReturn(resolvePromiseWith(telemetry));
}
function expectTelemetryToBeRequestedFor(object) {
waitsFor(allPromisesToBeResolved);
runs(function () {
expect(mockApi.telemetry.request).toHaveBeenCalledWith(object, undefined);
});
}
});
function createMockObjects() {
mockDomainObject = {
telemetryPoint: 'someTelemetryPoint'
};
associatedObject = {};
mockApi.objects.get.andReturn(resolvePromiseWith(associatedObject));
}
function setSampleSize(sampleSize) {
mockDomainObject.samples = sampleSize;
}
function createMockApi() {
mockApi = {
telemetry: new MockTelemetryApi(),
objects: createMockObjectApi(),
time: createMockTimeApi()
};
}
function createMockObjectApi() {
return jasmine.createSpyObj('ObjectAPI', [
'get'
]);
}
function mockObjectWithType(type) {
return {
type: type
};
}
function resolvePromiseWith(value) {
outstandingPromises++;
return Promise.resolve(value).then(function () {
outstandingPromises--;
return value;
});
}
function allPromisesToBeResolved() {
return outstandingPromises === 0;
}
function createMockTimeApi() {
return jasmine.createSpyObj("timeApi", ['timeSystem']);
}
function setTimeSystemTo(timeSystemKey) {
mockApi.time.timeSystem.andReturn({
key: timeSystemKey
});
}
});
});

View File

@ -0,0 +1,106 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global jasmine, spyOn */
define([], function () {
function MockTelemetryApi() {
this.createSpy('subscribe');
this.createSpy('getMetadata');
this.metadata = this.createMockMetadata();
this.setDefaultRangeTo('defaultRange');
this.unsubscribe = jasmine.createSpy('unsubscribe');
this.mockReceiveTelemetry = this.mockReceiveTelemetry.bind(this);
}
MockTelemetryApi.prototype.subscribe = function () {
return this.unsubscribe;
};
MockTelemetryApi.prototype.getMetadata = function (object) {
return this.metadata;
};
MockTelemetryApi.prototype.request = jasmine.createSpy('request');
MockTelemetryApi.prototype.getValueFormatter = function (valueMetadata) {
var mockValueFormatter = jasmine.createSpyObj("valueFormatter", [
"parse"
]);
mockValueFormatter.parse.andCallFake(function (value) {
return value[valueMetadata.key];
});
return mockValueFormatter;
};
MockTelemetryApi.prototype.mockReceiveTelemetry = function (newTelemetryDatum) {
var subscriptionCallback = this.subscribe.mostRecentCall.args[1];
subscriptionCallback(newTelemetryDatum);
};
/**
* @private
*/
MockTelemetryApi.prototype.onRequestReturn = function (telemetryData) {
this.requestTelemetry = telemetryData;
};
/**
* @private
*/
MockTelemetryApi.prototype.setDefaultRangeTo = function (rangeKey) {
var mockMetadataValue = {
key: rangeKey
};
this.metadata.valuesForHints.andReturn([mockMetadataValue]);
};
/**
* @private
*/
MockTelemetryApi.prototype.createMockMetadata = function () {
var mockMetadata = jasmine.createSpyObj("metadata", [
'value',
'valuesForHints'
]);
mockMetadata.value.andCallFake(function (key) {
return {
key: key
};
});
return mockMetadata;
};
/**
* @private
*/
MockTelemetryApi.prototype.createSpy = function (functionName) {
this[functionName] = this[functionName].bind(this);
spyOn(this, functionName);
this[functionName].andCallThrough();
};
return MockTelemetryApi;
});

View File

@ -30,10 +30,10 @@ define([], function () {
this.samples = samples;
this.averagingWindow = [];
this.rangeKey = undefined
this.rangeKey = undefined;
this.rangeFormatter = undefined;
this.setRangeKeyAndFormatter();
// Defined dynamically based on current time system
this.domainKey = undefined;
this.domainFormatter = undefined;
@ -44,16 +44,16 @@ define([], function () {
var timeValue = this.domainFormatter.parse(telemetryDatum);
var rangeValue = this.rangeFormatter.parse(telemetryDatum);
this.averagingWindow.push(rangeValue);
if (this.averagingWindow.length > this.samples) {
this.averagingWindow.shift();
}
var averageValue = this.calculateMean();
var meanDatum = {};
meanDatum[this.domainKey] = timeValue;
meanDatum['value'] = averageValue
meanDatum.value = averageValue;
return meanDatum;
};
@ -92,7 +92,7 @@ define([], function () {
this.rangeKey = rangeValues[0].key;
this.rangeFormatter = this.getFormatter(this.rangeKey);
}
};
/**
* @private