view src/app/components/jvm-info/jvm-gc/jvm-gc.controller.spec.js @ 263:87a683af97e8

Use jvm-gc /delta Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025439.html
author Andrew Azores <aazores@redhat.com>
date Tue, 24 Oct 2017 15:51:36 -0400
parents 9f3ee8157ac6
children
line wrap: on
line source

/**
 * Copyright 2012-2017 Red Hat, Inc.
 *
 * Thermostat is distributed under the GNU General Public License,
 * version 2 or any later version (with a special exception described
 * below, commonly known as the "Classpath Exception").
 *
 * A copy of GNU General Public License (GPL) is included in this
 * distribution, in the file COPYING.
 *
 * Linking Thermostat code with other modules is making a combined work
 * based on Thermostat.  Thus, the terms and conditions of the GPL
 * cover the whole combination.
 *
 * As a special exception, the copyright holders of Thermostat give you
 * permission to link this code with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on Thermostat code.  If you modify Thermostat, you may
 * extend this exception to your version of the software, but you are
 * not obligated to do so.  If you do not wish to do so, delete this
 * exception statement from your version.
 */

import controllerModule from './jvm-gc.controller.js';
import filtersModule from 'shared/filters/filters.module.js';
import { default as servicesModule, init as initServices } from 'shared/services/services.module.js';

describe('JvmGcController', () => {

  let interval, dateFilterStub, dateFormatSpy, svc, promise, snapshotPromise, ctrl, translate, sanitizeService;
  beforeEach(() => {
    angular.mock.module(filtersModule);
    angular.mock.module(servicesModule);
    initServices();
    angular.mock.module(controllerModule);
    angular.mock.inject(($controller) => {
      'ngInject';

      dateFilterStub = sinon.stub().returns('mockDate');
      dateFormatSpy = {
        time: {
          medium: sinon.spy()
        }
      };

      interval = sinon.stub().returns('interval-sentinel');
      interval.cancel = sinon.spy();

      promise = { then: sinon.spy() };
      snapshotPromise = { then: sinon.stub() };
      svc = {
        getJvmGcData: sinon.stub().returns(promise),
        getSnapshot: sinon.stub().returns(snapshotPromise)
      };

      sanitizeService = { sanitize: sinon.spy() };

      translate = sinon.stub().returns({
        then: sinon.stub().yields({
          'jvmGc.chart.UNITS': 'microseconds',
          'jvmGc.chart.X_AXIS_LABEL': 'timestamp',
          'jvmGc.chart.Y_AXIS_LABEL': 'elapsed'
        })
      });

      ctrl = $controller('JvmGcController', {
        $stateParams: { jvmId: 'foo-jvmId' },
        $interval: interval,
        dateFilter: dateFilterStub,
        DATE_FORMAT: dateFormatSpy,
        jvmGcService: svc,
        sanitizeService: sanitizeService,
        $translate: translate,
        metricToNumberFilter: m => parseInt(m.$numberLong)
      });
      ctrl.$onInit();
    });
  });

  it('should exist', () => {
    should.exist(ctrl);
  });

  it('should update on init', () => {
    svc.getJvmGcData.should.be.calledWith('foo-jvmId');
  });

  it('should call to service on update', () => {
    svc.getJvmGcData.should.be.calledOnce();
    promise.then.should.be.calledOnce();
    ctrl._update();
    svc.getJvmGcData.should.be.calledTwice();
    promise.then.should.be.calledTwice();

    promise.then.secondCall.should.be.calledWith(sinon.match.func);
    ctrl.collectors.should.deepEqual([]);
    let successHandler = promise.then.args[1][0];
    successHandler([
      {
        collectorName: 'fooCollector',
        timeStamp: { $numberLong: '100' },
        wallTimeInMicros: { $numberLong: '5050' },
        wallTimeDelta: { $numberLong: '50' }
      }
    ]);
    ctrl.collectors.should.deepEqual(['fooCollector']);
  });

  it('should reset interval on refreshRate change', () => {
    ctrl.should.have.ownProperty('_refresh');
    ctrl.refreshRate = '1';
    interval.should.be.calledWith(sinon.match.func, sinon.match(1));
    ctrl.should.have.ownProperty('_refresh');
    ctrl._refresh.should.equal('interval-sentinel');
    ctrl.refreshRate.should.equal('1');
  });

  it('should trim data on dataAgeLimit change', () => {
    sinon.spy(ctrl, '_trimData');
    ctrl._trimData.should.not.be.called();
    ctrl.dataAgeLimit = 10000;
    ctrl._trimData.should.be.calledOnce();
    ctrl._trimData.restore();
    ctrl.dataAgeLimit.should.equal('10000');
  });

  it('should trim old data', () => {
    let oldSample = {
      micros: 100,
      timestamp: 1
    };

    let futureSample = {
      micros: 200,
      timestamp: Date.now() + 600000
    };

    ctrl._collectorData.set('fooCollector', [oldSample, futureSample]);
    ctrl.dataAgeLimit = '30000';

    let expected = new Map();
    expected.set('fooCollector', [futureSample]);

    ctrl._collectorData.should.deepEqual(expected);
  });

  it('should set interval on start', () => {
    interval.should.be.calledOnce();
    interval.should.be.calledWith(sinon.match.func, '1000');
    interval.cancel.should.not.be.called();
  });

  it('should disable when set refreshRate is called with a non-positive value', () => {
    interval.cancel.should.not.be.called();
    ctrl._update.should.not.be.called();

    ctrl.refreshRate = '1';

    interval.cancel.should.be.calledOnce();
    ctrl.should.have.ownProperty('_refresh');

    ctrl.refreshRate = '-1';

    interval.cancel.should.be.calledTwice();
    ctrl.should.not.have.ownProperty('_refresh');
  });

  it('should call controller#_update() on refresh', () => {
    ctrl.refreshRate = 1;
    let func = interval.args[0][0];
    let callCount = svc.getJvmGcData.callCount;
    func();
    svc.getJvmGcData.callCount.should.equal(callCount + 1);
  });

  describe('_makeConfig', () => {
    let cfg;
    beforeEach(() => {
      ctrl._makeConfig('fooCollector');
      cfg = ctrl.chartConfigs.fooCollector;
    });

    it('should cache', () => {
      ctrl.collectors.should.deepEqual(['fooCollector']);
      ctrl._makeConfig('fooCollector');
      ctrl.collectors.should.deepEqual(['fooCollector']);
    });

    it('should sort', () => {
      ctrl._makeConfig('gooCollector');
      ctrl._makeConfig('booCollector');
      ctrl.collectors.should.deepEqual(['booCollector', 'fooCollector', 'gooCollector']);
    });

    it('should set chartId', () => {
      cfg.chartId.should.equal('chart-fooCollector');
    });

    it('should use dateFilter with DATE_FORMAT.time.medium to format x ticks', () => {
      let fn = cfg.axis.x.tick.format;
      fn.should.be.a.Function();
      fn('fooTimestamp').should.equal('mockDate');
      dateFilterStub.should.be.calledWith('fooTimestamp', dateFormatSpy.time.medium);
    });

    it('should format y ticks directly', () => {
      let fn = cfg.axis.y.tick.format;
      fn.should.be.a.Function();
      fn(100).should.equal(100);
    });

    it('should set tooltip', () => {
      let fmt = cfg.tooltip.format;
      fmt.should.have.ownProperty('title');
      fmt.title.should.be.a.Function();
      fmt.should.have.ownProperty('value');
      fmt.value.should.be.a.Function();

      fmt.title(100).should.equal(100);
      fmt.value(200).should.equal(200);
    });

    it('should stop on mouseover', () => {
      ctrl._start();
      let fn = cfg.onmouseover;
      fn.should.be.a.Function();
      interval.cancel.should.be.calledOnce();
      fn();
      interval.cancel.should.be.calledTwice();
    });

    it('should start on mouseout', () => {
      ctrl._stop();
      let fn = cfg.onmouseout;
      fn.should.be.a.Function();
      interval.should.be.calledOnce();
      fn();
      interval.should.be.calledTwice();
    });
  });

  describe('_constructChartData', () => {
    it('should leave chartData empty if no collectorData present', () => {
      ctrl._collectorData = new Map();
      ctrl._constructChartData;
      ctrl.chartData.should.deepEqual({});
    });

    it('should construct chartData according to collectorData', () => {
      let map = new Map();
      map.set('fooCollector', [{ timestamp: 100, micros: 50 }, { timestamp: 101, micros: 60 }]);
      ctrl._collectorData = map;
      ctrl._constructChartData();

      ctrl.chartData.should.deepEqual({
        fooCollector: {
          xData: ['timestamps', 100, 101],
          yData: ['fooCollector', 50, 60]
        }
      });
    });
  });

  describe('_processData', () => {
    it('should process multiple service results', () => {
      ctrl.collectors.should.deepEqual([]);
      ctrl.chartConfigs.should.deepEqual({});
      ctrl._collectorData.has('fooCollector').should.be.false();
      let timestampA = Date.now();
      let timestampB = timestampA - 10;
      ctrl._processData([
        {
          collectorName: 'fooCollector',
          timeStamp: { $numberLong: timestampA.toString() },
          wallTimeDelta: { $numberLong: '50' },
          wallTimeInMicros: { $numberLong: '5050' }
        },
        {
          collectorName: 'fooCollector',
          timeStamp: { $numberLong: timestampB.toString() },
          wallTimeDelta: { $numberLong: '25' },
          wallTimeInMicros: { $numberLong: '2525' }
        }
      ]);
      ctrl._collectorData.has('fooCollector').should.be.true();
      let result = ctrl._collectorData.get('fooCollector');
      result.should.be.an.Array();
      result.should.deepEqual([
        { timestamp: timestampA, micros: 50 },
        { timestamp: timestampB, micros: 25 }
      ]);
    });

    it('should append new data', () => {
      let timestampA = Date.now();
      let timestampB = timestampA + 5000;
      ctrl._processData([
        {
          collectorName: 'fooCollector',
          timeStamp: { $numberLong: timestampA.toString() },
          wallTimeDelta: { $numberLong: '50' },
          wallTimeInMicros: { $numberLong: '5050' }
        }
      ]);
      ctrl._processData([
        {
          collectorName: 'fooCollector',
          timeStamp: { $numberLong: timestampB.toString() },
          wallTimeDelta: { $numberLong: '100' },
          wallTimeInMicros: { $numberLong: '10100' }
        }
      ]);
      ctrl._collectorData.has('fooCollector').should.be.true();
      let result = ctrl._collectorData.get('fooCollector');
      result.should.be.an.Array();
      result.should.deepEqual([
        { timestamp: timestampA, micros: 50 },
        { timestamp: timestampB, micros: 100 }
      ]);
    });
  });

  describe('ondestroy handler', () => {
    it('should cancel refresh', () => {
      ctrl.$onDestroy();
      interval.cancel.should.be.calledWith('interval-sentinel');
    });

    it('should do nothing if refresh is undefined', () => {
      delete ctrl._refresh;
      ctrl.$onDestroy();
      interval.cancel.should.not.be.called();
    });
  });

  describe('sanitize()', () => {
    it('should delegate to sanitizeService', () => {
      sanitizeService.sanitize.should.not.be.called();
      ctrl.sanitize('foo');
      sanitizeService.sanitize.should.be.calledOnce();
      sanitizeService.sanitize.should.be.calledWith('foo');
    });
  });

  describe('multichartFn', () => {
    it('should return a promise', () => {
      let res = ctrl.multichartFn();
      res.should.be.a.Promise();
    });

    it('should resolve jvm-gc stat', done => {
      svc.getSnapshot.should.not.be.called();
      snapshotPromise.then.should.not.be.called();
      ctrl.multichartFn('foo-collector').then(v => {
        v.should.equal(400);
        done();
      });
      svc.getSnapshot.should.be.calledOnce();
      svc.getSnapshot.should.be.calledWith('foo-jvmId', 'foo-collector');
      snapshotPromise.then.should.be.calledOnce();
      snapshotPromise.then.yield({ $numberLong: '400' });
    });
  });

});