<commit_before>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

define(['utils', 'settings', 'config', 'map', 'file',
        'plugins/sync/js/login',
        'plugins/sync/js/download',
        'plugins/sync/js/pcapi',
        './database'],
       function(utils, settings, config, map, file, login, download, pcapi, db){// jshint ignore:line

    var layersDir, root, layers = [];

    var createLayersListForDownload = function(){
        var $layersList = $(".layers-list");
        var list = [];

        pcapi.setUserId(login.getUser().id);
        utils.showPageLoadingMsg('Checking for Layers ');
        //fetch the metadata from mbtiles and add them to the listview
        pcapi.getItems('tiles', function(success, data){
            list.push('
On device
');
            if(layers.length>0){
                for(var i=0; i<layers.length; i++){
                    list.push('
'+layers[i]+'
');
                }
            }
            list.push('
On cloud
');
            if(success){
                $.each(data.metadata, $.proxy(function(i, item){
                    var fileName = item.substring(item.lastIndexOf('/') + 1, item.length);
                    list.push('
'+fileName+'
');
                }, this));
            }
            else{
                utils.inform('No layers to sync');
            }
            $layersList.html(list.join(""));
            $layersList.listview("refresh");
            $.mobile.hidePageLoadingMsg();
        });
    };

    var createLayersListForMap = function(){
        var $layersList = $(".layers-list");
        var list = [];
        if(utils.isMobileDevice()){
            list.push('
On device
');
            if(layers.length>0){
                for(var i=0; i<layers.length; i++){
                    list.push('
'+layers[i]+'
');
                }
            }
            $layersList.html(list.join(""));
            $layersList.listview("refresh");
        }
        else{
            pcapi.setUserId(login.getUser().id);
            pcapi.getItems('tiles', function(success, data){
                list.push('
OnLine
');
                if(success){
                    $.each(data.metadata, $.proxy(function(i, item){
                        var fileName = item.substring(item.lastIndexOf('/') + 1, item.length);
                        list.push('
'+fileName+'
');
                    }, this));
                    $layersList.html(list.join(""));
                    $layersList.listview("refresh");
                }
                else{
                    utils.inform('No layers to sync');
                }
            });
        }
    };

    var MapWithLocalMBTiles = OpenLayers.Class(OpenLayers.Layer.TMS, {
        initialize: function(options) {
            this.serviceVersion = options.serviceVersion;
            this.layername = options.layerName;
            this.type = options.type;
            this.dbname = options.dbname;
            this.isBaseLayer = options.isBaseLayer;
            this.opacity = options.opacity;
            this.visibility = true;

            // this boolean determines which overriden method is called getURLasync
            // or getURL. Using getURLasync was causing the application to freeze,
            // often getting a ANR
            //this.async = typeof(webdb) !== 'undefined';
            this.async = true;
            if(this.async === true){
                db.open(this.dbname);
            }

            OpenLayers.Layer.TMS.prototype.initialize.apply(
                this,
                [options.name, options.url, {}]
            );
        },
        getURLasync: function(bounds, callback, scope) {
            var url = OpenLayers.Layer.TMS.prototype.getURL.apply(this, [bounds]);
            var data = url.match(/\/(\d+)/g).join("").split("/");
            db.getTiles(callback, scope, data[2], data[3], data[1], url);
        },
        getURL: function(bounds) {
            return OpenLayers.Layer.TMS.prototype.getURL.apply(this, [bounds]);
        },
    });

    if(utils.isMobileDevice()){
        // check settings first for defined pcapi root url
        root = settings.get("pcapi-url");
        if(root === undefined){
            root = config.pcapiurl;
        }
        // create directory structure for layers
        file.createDir('tiles', function(dir){
            layersDir = dir;
            var directoryReader = layersDir.createReader();
            directoryReader.readEntries(function(entries){
                for(var i=0;i<entries.length;i++){
                    layers.push(entries[i].name);
                }
            });
        });
    }
    else{
        root = 'http://' + location.hostname;
        if(location.port){
            root += ':' + location.port;
        }
    }
    pcapi.init({"url": root, "version": config.pcapiversion});
    //TODO getit externally
    pcapi.setProvider('dropbox');

    $(document).on('pageshow', '#map-page', createLayersListForMap);
    $(document).on('pageshow', '#saved-layers-page', createLayersListForDownload);

    $(document).off('vclick', '.download-layer');
    $(document).on(
        'vclick',
        '.download-layer',
        function(){
            var layer = $(this).text();
            var $popup = $('#saved-layers-download-popup');
            var text;
            if($.inArray(layer, layers)){
                text = "The layer "+layer+" already exists. Do you still want to download it?";
            }
            else{
                text = "You are going to download layer "+layer;
            }
            $popup.empty();
            /*jshint multistr: true */
            $popup.append(
                '
\

'+text+'\

\
                Cancel\
                Continue\

').trigger('create');
            $popup.popup('open');

            $(document).off('vmousedown', '.download-layer-confirm');
            $(document).on(
                'vmousedown',
                '.download-layer-confirm',
                function(event){
                    event.preventDefault();
                    $popup.popup('close');

                    utils.showPageLoadingMsg('Download Layer '+layer);
                    var options = {"fileName": layer, "remoteDir": "tiles", "localDir": layersDir};
                    //TODO rename the file while downloading it
                    download.downloadItem(options, function(){
                        $.mobile.hidePageLoadingMsg();
                        $.mobile.changePage('map.html');
                        createLayersListForMap();
                    });
                }
            );
        }
    );

    $(document).off('vclick', '.show-layer');
    $(document).on('vclick', '.show-layer', function(event){
        $.mobile.changePage('map.html');
        var layerName = $(this).text(), projections;
        if(utils.isMobileDevice()){
            if(!map.checkIfLayerExists(layerName)){
                var tileLayer = new MapWithLocalMBTiles({
                    name: layerName,
                    dbname: file.getFilePathWithoutCDV(layersDir)+'/'+layerName.split(".")[0],
                    url: utils.getMapServerUrl(),
                    layerName: layerName,
                    type: 'png',
                    isBaseLayer: false,
                    opacity: 0.5,
                    serviceVersion: ''
                });
                map.addMapLayer(tileLayer);
                projections = map.getProjections();
                //TODO get the bbox from the database
                map.zoomToExtent(new OpenLayers.Bounds(-4.2709,52.3857,-3.5184,52.8094).transform(projections[1], projections[0]));
            }
        }
        else{
            //TODO get rid of it or get the url externally
            map.addRemoteMBTilesLayer({
                "name": layerName,
                "url": "<url>",
                "db": layerName
            });
            projections = map.getProjections();
            map.zoomToExtent(new OpenLayers.Bounds(-4.2709,52.3857,-3.5184,52.8094).transform(projections[1], projections[0]));
        }
    });

    $('head').prepend('<link rel="stylesheet" href="plugins/map-mbtiles/css/style.css" type="text/css" />');
});<commit_msg>edina/fieldtrip-cobweb#2: if the downloaded item is mbtiles file rename the extension to db<commit_after>/*
Copyright (c) 2014, EDINA.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
   list of conditions and the following disclaimer in the documentation and/or
   other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
   display the following acknowledgement: This product includes software
   developed by the EDINA.
4. Neither the name of the EDINA nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY EDINA ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL EDINA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

"use strict";

define(['utils', 'settings', 'config', 'map', 'file',
        'plugins/sync/js/login',
        'plugins/sync/js/download',
        'plugins/sync/js/pcapi',
        './database'],
       function(utils, settings, config, map, file, login, download, pcapi, db){// jshint ignore:line

    var layersDir, root, layers = [];

    var createLayersListForDownload = function(){
        var $layersList = $(".layers-list");
        var list = [];

        pcapi.setUserId(login.getUser().id);
        utils.showPageLoadingMsg('Checking for Layers ');
        //fetch the metadata from mbtiles and add them to the listview
        pcapi.getItems('tiles', function(success, data){
            list.push('
On device
');
            if(layers.length>0){
                for(var i=0; i<layers.length; i++){
                    list.push('
'+layers[i]+'
');
                }
            }
            list.push('
On cloud
');
            if(success){
                $.each(data.metadata, $.proxy(function(i, item){
                    var fileName = item.substring(item.lastIndexOf('/') + 1, item.length);
                    list.push('
'+fileName+'
');
                }, this));
            }
            else{
                utils.inform('No layers to sync');
            }
            $layersList.html(list.join(""));
            $layersList.listview("refresh");
            $.mobile.hidePageLoadingMsg();
        });
    };

    var createLayersListForMap = function(){
        var $layersList = $(".layers-list");
        var list = [];
        if(utils.isMobileDevice()){
            list.push('
On device
');
            if(layers.length>0){
                for(var i=0; i<layers.length; i++){
                    list.push('
'+layers[i]+'
');
                }
            }
            $layersList.html(list.join(""));
            $layersList.listview("refresh");
        }
        else{
            pcapi.setUserId(login.getUser().id);
            pcapi.getItems('tiles', function(success, data){
                list.push('
OnLine
');
                if(success){
                    $.each(data.metadata, $.proxy(function(i, item){
                        var fileName = item.substring(item.lastIndexOf('/') + 1, item.length);
                        list.push('
'+fileName+'
');
                    }, this));
                    $layersList.html(list.join(""));
                    $layersList.listview("refresh");
                }
                else{
                    utils.inform('No layers to sync');
                }
            });
        }
    };

    var MapWithLocalMBTiles = OpenLayers.Class(OpenLayers.Layer.TMS, {
        initialize: function(options) {
            this.serviceVersion = options.serviceVersion;
            this.layername = options.layerName;
            this.type = options.type;
            this.dbname = options.dbname;
            this.isBaseLayer = options.isBaseLayer;
            this.opacity = options.opacity;
            this.visibility = true;

            // this boolean determines which overriden method is called getURLasync
            // or getURL. Using getURLasync was causing the application to freeze,
            // often getting a ANR
            //this.async = typeof(webdb) !== 'undefined';
            this.async = true;
            if(this.async === true){
                db.open(this.dbname);
            }

            OpenLayers.Layer.TMS.prototype.initialize.apply(
                this,
                [options.name, options.url, {}]
            );
        },
        getURLasync: function(bounds, callback, scope) {
            var url = OpenLayers.Layer.TMS.prototype.getURL.apply(this, [bounds]);
            var data = url.match(/\/(\d+)/g).join("").split("/");
            db.getTiles(callback, scope, data[2], data[3], data[1], url);
        },
        getURL: function(bounds) {
            return OpenLayers.Layer.TMS.prototype.getURL.apply(this, [bounds]);
        },
    });

    if(utils.isMobileDevice()){
        // check settings first for defined pcapi root url
        root = settings.get("pcapi-url");
        if(root === undefined){
            root = config.pcapiurl;
        }
        // create directory structure for layers
        file.createDir('tiles', function(dir){
            layersDir = dir;
            var directoryReader = layersDir.createReader();
            directoryReader.readEntries(function(entries){
                for(var i=0;i<entries.length;i++){
                    layers.push(entries[i].name);
                }
            });
        });
    }
    else{
        root = 'http://' + location.hostname;
        if(location.port){
            root += ':' + location.port;
        }
    }
    pcapi.init({"url": root, "version": config.pcapiversion});
    //TODO getit externally
    pcapi.setProvider('dropbox');

    $(document).on('pageshow', '#map-page', createLayersListForMap);
    $(document).on('pageshow', '#saved-layers-page', createLayersListForDownload);

    $(document).off('vclick', '.download-layer');
    $(document).on(
        'vclick',
        '.download-layer',
        function(){
            var layer = $(this).text();
            var $popup = $('#saved-layers-download-popup');
            var text;
            if($.inArray(layer, layers)){
                text = "The layer "+layer+" already exists. Do you still want to download it?";
            }
            else{
                text = "You are going to download layer "+layer;
            }
            $popup.empty();
            /*jshint multistr: true */
            $popup.append(
                '
\

'+text+'\

\
                Cancel\
                Continue\

').trigger('create');
            $popup.popup('open');

            $(document).off('vmousedown', '.download-layer-confirm');
            $(document).on(
                'vmousedown',
                '.download-layer-confirm',
                function(event){
                    event.preventDefault();
                    $popup.popup('close');

                    utils.showPageLoadingMsg('Download Layer '+layer);
                    var options;
                    if(layer.indexOf("mbtiles")){
                        var localFileName = layer.substring(layer.lastIndexOf('/')+1, layer.lastIndexOf('.'))+".db";
                        options = {"fileName": layer, "remoteDir": "tiles", "localDir": layersDir, "localFileName": localFileName};
                    }
                    else{
                        options = {"fileName": layer, "remoteDir": "tiles", "localDir": layersDir};
                    }
                    //TODO rename the file while downloading it
                    download.downloadItem(options, function(){
                        $.mobile.hidePageLoadingMsg();
                        $.mobile.changePage('map.html');
                        createLayersListForMap();
                    });
                }
            );
        }
    );

    $(document).off('vclick', '.show-layer');
    $(document).on('vclick', '.show-layer', function(event){
        $.mobile.changePage('map.html');
        var layerName = $(this).text(), projections;
        if(utils.isMobileDevice()){
            if(!map.checkIfLayerExists(layerName)){
                var tileLayer = new MapWithLocalMBTiles({
                    name: layerName,
                    dbname: file.getFilePathWithoutCDV(layersDir)+'/'+layerName.split(".")[0],
                    url: utils.getMapServerUrl(),
                    layerName: layerName,
                    type: 'png',
                    isBaseLayer: false,
                    opacity: 0.5,
                    serviceVersion: ''
                });
                map.addMapLayer(tileLayer);
                projections = map.getProjections();
                //TODO get the bbox from the database
                map.zoomToExtent(new OpenLayers.Bounds(-4.2709,52.3857,-3.5184,52.8094).transform(projections[1], projections[0]));
            }
        }
        else{
            //TODO get rid of it or get the url externally
            map.addRemoteMBTilesLayer({
                "name": layerName,
                "url": "<url>",
                "db": layerName
            });
            projections = map.getProjections();
            map.zoomToExtent(new OpenLayers.Bounds(-4.2709,52.3857,-3.5184,52.8094).transform(projections[1], projections[0]));
        }
    });

    $('head').prepend('<link rel="stylesheet" href="plugins/map-mbtiles/css/style.css" type="text/css" />');
});<|endoftext|>


<commit_before>import {jsdom} from 'jsdom';
import _test from './test-tools/test';
import _createElement from 'virtual-dom/create-element';
import h from 'virtual-dom/h';
import {curryRight2} from '1-liners';

import view from '../module/view';

const test = _test('The view');
const doc = (
  (typeof window !== 'undefined' && window.document) ||
  jsdom().defaultView.document
);
const createElement = curryRight2(_createElement)({document: doc});

const goodMock = createElement(
  h('bare-select', [
    h('label', {for: 'switch'}),
    h('input', {type: 'checkbox', id: 'switch'}),
    h('ul', [
      h('li', [
        h('input', {type: 'radio', name: 'radio-group',
          value: 'a',
        }),
        h('input', {type: 'radio', name: 'radio-group',
          value: 'b',
          checked: '',
        }),
        h('input', {type: 'radio', name: 'radio-group',
          value: 'c',
        }),
      ]),
    ]),
  ])
);

test('The API is in good shape.', (is) => {
  is.equal(
    typeof view,
    'function',
    'is a constructor function'
  );

  is.end();
});
<commit_msg>Specify the view API<commit_after>import {jsdom} from 'jsdom';
import _test from './test-tools/test';
import _createElement from 'virtual-dom/create-element';
import h from 'virtual-dom/h';
import {curryRight2} from '1-liners';

import _view from '../module/view';

const test = _test('The view');
const createElement = curryRight2(_createElement)({document: (
  (typeof window !== 'undefined' && window.document) ||
  jsdom().defaultView.document
)});

const goodMock = createElement(
  h('bare-select', [
    h('label', {for: 'switch'}),
    h('input', {type: 'checkbox', id: 'switch'}),
    h('ul', [
      h('li', [
        h('input', {type: 'radio', name: 'radio-group',
          value: 'a',
        }),
        h('input', {type: 'radio', name: 'radio-group',
          value: 'b',
          checked: '',
        }),
        h('input', {type: 'radio', name: 'radio-group',
          value: 'c',
        }),
      ]),
    ]),
  ])
);

test('The API is in good shape.', (is) => {
  is.equal(
    typeof _view,
    'function',
    'is a constructor function'
  );

  const view = _view(goodMock);

  is.ok(
    Object.isFrozen(view),
    'returning a frozen object'
  );

  is.deepEqual(
    Object.keys(view.selection).map((key) => ({
      property: key,
      type: typeof view.selection[key],
    })),
    [
      {property: 'emit', type: 'function'},
    ],
    'with an input channel `selection`'
  );

  is.deepEqual(
    Object.keys(view.captionContent).map((key) => ({
      property: key,
      type: typeof view.captionContent[key],
    })),
    [
      {property: 'emit', type: 'function'},
    ],
    'with an input channel `captionContent`'
  );

  is.deepEqual(
    Object.keys(view.options).map((key) => ({
      property: key,
      type: typeof view.options[key],
    })),
    [
      {property: 'on', type: 'function'},
      {property: 'when', type: 'function'},
    ],
    'with a cacheable output channel `options`'
  );

  is.deepEqual(
    Object.keys(view.captionElement).map((key) => ({
      property: key,
      type: typeof view.captionElement[key],
    })),
    [
      {property: 'on', type: 'function'},
    ],
    'with an output channel `captionElement`'
  );

  is.end();
});
<|endoftext|>


<commit_before>/**
 * Dependencies.
 */

var events = require('events');
var util = require('util');

/**
 * Emitted for each fired event. May be useful to display a progress bar.
 *
 * @event done
 * @param {Number} fired  Number of fired events.
 * @param {Number} total  Total number of required events to fire.
 * @param {Object} [data] Optional data about the fired event.
 */

var EVENT_DONE = 'done';

/**
 * Emitted when all the required events have fired.
 *
 * @event alldone
 * @param {Number} total  Number of required events to fire.
 */

var EVENT_ALLDONE = 'alldone';

/**
 * Emitted if the given timeout expires before all the required events have
 * fired.
 *
 * @event timeout
 * @param {Number} fired  Number of fired events.
 * @param {Number} total  Total number of required events to fire.
 */

var EVENT_TIMEOUT = 'timeout';

/**
 * Emitted in case of error. For example, when expecting 2 events and calling
 * `done()` more than 2 times.
 *
 * @event error
 * @param {Error} error
 */

var EVENT_ERROR = 'error';

function isPositiveInteger(x) {
  return (typeof x === 'number') && (x % 1 === 0) && (x > 0);
}

/**
 * EventCollector class.
 *
 * @class EventCollector
 * @constructor
 * @param {Number} total  Number of events that must fire.
 */

var EventCollector = function(total, timeout) {
  events.EventEmitter.call(this);

  if (! isPositiveInteger(total)) {
    throw new Error('`total` must be a positive integer');
  }

  if (timeout && ! isPositiveInteger(timeout)) {
    throw new Error('`timeout` must be a positive integer');
  }

  this.destroyed = false;
  this.total = total;
	this.fired = 0;
  if (timeout) {
    this.timer = setTimeout(function() {
      this.onTimeout();
    }.bind(this), timeout);
  }
};

util.inherits(EventCollector, events.EventEmitter);

/**
 * Tell the EventCollector that a required event has been emitted.
 *
 * @method done
 * @param {Object} [data]  Optional data about the fired event.
 * @chainable
 */

EventCollector.prototype.done = function(data) {
  if (this.destroyed) { return; }

  this.fired++;

  if (this.fired > this.total) {
    this.emit(EVENT_ERROR, '`done` called more times than required');
  }

  this.emit(EVENT_DONE, this.fired, this.total, data);

  if (this.fired === this.total) {
    clearTimeout(this.timer);
    this.emit(EVENT_ALLDONE, this.total);
  }

  return this;
};

/**
 * Destroy the EventCollector. No more events will be emitted.
 *
 * @method destroy
 */

EventCollector.prototype.destroy = function() {
  this.destroyed = true;

  clearTimeout(this.timer);
};

/**
 * Timeout expired.
 *
 * @private
 * @method onTimeout
 */

EventCollector.prototype.onTimeout = function() {
  if (this.destroyed) { return; }

  this.emit(EVENT_TIMEOUT, this.fired, this.total);
};

/**
 * Export a function returning an instance of EventCollector.
 *
 *     var eventcollector = require('eventcollector')(2, 5000);
 */

module.exports = function(total, timeout) {
  return new EventCollector(total, timeout);
};
<commit_msg>Fix 'error' event (emit an Error rather than a String, upps)<commit_after>/**
 * Dependencies.
 */

var events = require('events');
var util = require('util');

/**
 * Emitted for each fired event. May be useful to display a progress bar.
 *
 * @event done
 * @param {Number} fired  Number of fired events.
 * @param {Number} total  Total number of required events to fire.
 * @param {Object} [data] Optional data about the fired event.
 */

var EVENT_DONE = 'done';

/**
 * Emitted when all the required events have fired.
 *
 * @event alldone
 * @param {Number} total  Number of required events to fire.
 */

var EVENT_ALLDONE = 'alldone';

/**
 * Emitted if the given timeout expires before all the required events have
 * fired.
 *
 * @event timeout
 * @param {Number} fired  Number of fired events.
 * @param {Number} total  Total number of required events to fire.
 */

var EVENT_TIMEOUT = 'timeout';

/**
 * Emitted in case of error. For example, when expecting 2 events and calling
 * `done()` more than 2 times.
 *
 * @event error
 * @param {Error} error
 */

var EVENT_ERROR = 'error';

function isPositiveInteger(x) {
  return (typeof x === 'number') && (x % 1 === 0) && (x > 0);
}

/**
 * EventCollector class.
 *
 * @class EventCollector
 * @constructor
 * @param {Number} total  Number of events that must fire.
 */

var EventCollector = function(total, timeout) {
  events.EventEmitter.call(this);

  if (! isPositiveInteger(total)) {
    throw new Error('`total` must be a positive integer');
  }

  if (timeout && ! isPositiveInteger(timeout)) {
    throw new Error('`timeout` must be a positive integer');
  }

  this.destroyed = false;
  this.total = total;
	this.fired = 0;
  if (timeout) {
    this.timer = setTimeout(function() {
      this.onTimeout();
    }.bind(this), timeout);
  }
};

util.inherits(EventCollector, events.EventEmitter);

/**
 * Tell the EventCollector that a required event has been emitted.
 *
 * @method done
 * @param {Object} [data]  Optional data about the fired event.
 * @chainable
 */

EventCollector.prototype.done = function(data) {
  if (this.destroyed) { return; }

  this.fired++;

  if (this.fired > this.total) {
    this.emit(EVENT_ERROR, new Error('`done` called more times than required'));
  }

  this.emit(EVENT_DONE, this.fired, this.total, data);

  if (this.fired === this.total) {
    clearTimeout(this.timer);
    this.emit(EVENT_ALLDONE, this.total);
  }

  return this;
};

/**
 * Destroy the EventCollector. No more events will be emitted.
 *
 * @method destroy
 */

EventCollector.prototype.destroy = function() {
  this.destroyed = true;

  clearTimeout(this.timer);
};

/**
 * Timeout expired.
 *
 * @private
 * @method onTimeout
 */

EventCollector.prototype.onTimeout = function() {
  if (this.destroyed) { return; }

  this.emit(EVENT_TIMEOUT, this.fired, this.total);
};

/**
 * Export a function returning an instance of EventCollector.
 *
 *     var eventcollector = require('eventcollector')(2, 5000);
 */

module.exports = function(total, timeout) {
  return new EventCollector(total, timeout);
};
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
'use strict';

var assert = require('chai').assert
var wd = require('weex-wd')
var path = require('path');
var util = require("../util.js");
var assert = require('chai').assert

describe('border test', function () {
  this.timeout(util.getTimeoutMills());
  var driver = util.createDriver(wd);

  before(function () {
    return util.init(driver)
      .get(util.getPage('/css/border.js'))
      .waitForElementById('test-text',util.getGETActionWaitTimeMills() + 2000,1000)
  });

  after(function () {
    return util.quit(driver);
  })

  it('#1 border screenshot diff', () => {
    return driver
    .takeScreenshot()
    .then(imgData => {
      var newImg = new Buffer(imgData, 'base64');
      var screenshotFolder = path.resolve(__dirname, '../../screenshot');
      var oldImgPath = path.join(screenshotFolder, process.env.platform === 'android' ? 'border-android.png' : 'border-ios.png');
      var diffImgPath = path.join(screenshotFolder, process.env.platform === 'android' ? 'border-android-diff.png' : 'border-ios-diff.png');
      return util.diffImage(oldImgPath, newImg, 100, diffImgPath);
    })
    .then(result => {
      console.log(result)
      assert.isOk(result)
    })
  })
});

<commit_msg>* [test] add sleep to border case<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
'use strict';

var assert = require('chai').assert
var wd = require('weex-wd')
var path = require('path');
var util = require("../util.js");
var assert = require('chai').assert

describe('border test', function () {
  this.timeout(util.getTimeoutMills());
  var driver = util.createDriver(wd);

  before(function () {
    return util.init(driver)
      .get(util.getPage('/css/border.js'))
      .waitForElementById('test-text',util.getGETActionWaitTimeMills() + 2000,1000)
  });

  after(function () {
    return util.quit(driver);
  })

  it('#1 border screenshot diff', () => {
    return driver
    .sleep(2000)
    .takeScreenshot()
    .then(imgData => {
      var newImg = new Buffer(imgData, 'base64');
      var screenshotFolder = path.resolve(__dirname, '../../screenshot');
      var oldImgPath = path.join(screenshotFolder, process.env.platform === 'android' ? 'border-android.png' : 'border-ios.png');
      var diffImgPath = path.join(screenshotFolder, process.env.platform === 'android' ? 'border-android-diff.png' : 'border-ios-diff.png');
      return util.diffImage(oldImgPath, newImg, 100, diffImgPath);
    })
    .then(result => {
      console.log(result)
      assert.isOk(result)
    })
  })
});

<|endoftext|>


<commit_before>function MapModel() {

    this.rooms = {};
    this.exits = {};

    this.changeListeners = [];
}

MapModel.prototype.addChangeListener = function(listener) {

    this.changeListeners.insert(listener);
};

MapModel.prototype.removeChangeListener = function(listener) {

    this.changeListeners.removeAll(listener);
};

MapModel.prototype.notifyChangeListeners = function() {

    this.changeListeners.forEach(function(listener) {
        listener();
    });
};

MapModel.prototype.load = function() {

    var self = this;

    controller.sendApiCall("rooms-list", function(data) {
        for (var i = 0; i < data.length; i++) {
            var room = JSON.parse(data[i]);
            room.x = room.position ? room.position[0] : 0;
            room.y = room.position ? room.position[1] : 0;
            room.z = room.position ? room.position[2] : 0;
            self.rooms[room.id] = room;
        }

        controller.sendApiCall("exits-list", function(data) {
            for (var i = 0; i < data.length; i++) {
                var exit = JSON.parse(data[i]);
                self.exits[exit.id] = exit;
            }

            for (var id in self.rooms) {
                self.resolvePointers(self.rooms[id], ["exits", "visibleRooms"]);
            }
            for (id in self.exits) {
                self.resolvePointers(self.exits[id], ["destination", "oppositeExit"]);
            }

            self.notifyChangeListeners();
        });
    });
};

MapModel.prototype.setRoomProperty = function(roomId, propertyName, value) {

    var self = this;
    var room = this.rooms[roomId];

    if (!room.position) {
        room.position = [ 0, 0, 0 ];
    }

    if (propertyName === "x" || propertyName === "y" || propertyName === "z") {
        value = parseInt(value, 10);

        if (propertyName === "x") {
            room.x = value;
        } else if (propertyName === "y") {
            room.y = value;
        } else {
            room.z = value;
        }

        propertyName = "position";
        value = "[" + room.x + "," + room.y + "," + room.z + "]";
    }

    controller.sendApiCall("property-set #" + roomId + " " + propertyName +
                           " " + value, function() {
        room[propertyName] = value;
        self.notifyChangeListeners();
    });
}

MapModel.prototype.setExit = function(exit) {

    this.exits[exit.id] = exit;
    this.notifyChangeListeners();
}

MapModel.prototype.resolvePointer = function(pointer) {

    if (pointer.startsWith("room:")) {
        return this.rooms[parseInt(pointer.substr(5), 10)];
    } else if (pointer.startsWith("exit:")) {
        return this.exits[parseInt(pointer.substr(5), 10)];
    } else {
        return null;
    }
};

MapModel.prototype.resolvePointers = function(object, propertyNames) {

    var self = this;

    propertyNames.forEach(function(propertyName) {
        if (object.contains(propertyName)) {
            var value = object[propertyName];
            if (value instanceof Array) {
                for (var i = 0, length = value.length; i < length; i++) {
                    value[i] = self.resolvePointer(value[i]);
                }
            } else {
                object[propertyName] = self.resolvePointer(value);
            }
        }
    });
};

scriptLoaded("map.model.js");
<commit_msg>Don't choke on rooms without exits.<commit_after>function MapModel() {

    this.rooms = {};
    this.exits = {};

    this.changeListeners = [];
}

MapModel.prototype.addChangeListener = function(listener) {

    this.changeListeners.insert(listener);
};

MapModel.prototype.removeChangeListener = function(listener) {

    this.changeListeners.removeAll(listener);
};

MapModel.prototype.notifyChangeListeners = function() {

    this.changeListeners.forEach(function(listener) {
        listener();
    });
};

MapModel.prototype.load = function() {

    var self = this;

    controller.sendApiCall("rooms-list", function(data) {
        for (var i = 0; i < data.length; i++) {
            var room = JSON.parse(data[i]);
            room.x = room.position ? room.position[0] : 0;
            room.y = room.position ? room.position[1] : 0;
            room.z = room.position ? room.position[2] : 0;
            room.exits = room.exits || [];
            self.rooms[room.id] = room;
        }

        controller.sendApiCall("exits-list", function(data) {
            for (var i = 0; i < data.length; i++) {
                var exit = JSON.parse(data[i]);
                self.exits[exit.id] = exit;
            }

            for (var id in self.rooms) {
                self.resolvePointers(self.rooms[id], ["exits", "visibleRooms"]);
            }
            for (id in self.exits) {
                self.resolvePointers(self.exits[id], ["destination", "oppositeExit"]);
            }

            self.notifyChangeListeners();
        });
    });
};

MapModel.prototype.setRoomProperty = function(roomId, propertyName, value) {

    var self = this;
    var room = this.rooms[roomId];

    if (!room.position) {
        room.position = [ 0, 0, 0 ];
    }

    if (propertyName === "x" || propertyName === "y" || propertyName === "z") {
        value = parseInt(value, 10);

        if (propertyName === "x") {
            room.x = value;
        } else if (propertyName === "y") {
            room.y = value;
        } else {
            room.z = value;
        }

        propertyName = "position";
        value = "[" + room.x + "," + room.y + "," + room.z + "]";
    }

    controller.sendApiCall("property-set #" + roomId + " " + propertyName +
                           " " + value, function() {
        room[propertyName] = value;
        self.notifyChangeListeners();
    });
}

MapModel.prototype.setExit = function(exit) {

    this.exits[exit.id] = exit;
    this.notifyChangeListeners();
}

MapModel.prototype.resolvePointer = function(pointer) {

    if (pointer.startsWith("room:")) {
        return this.rooms[parseInt(pointer.substr(5), 10)];
    } else if (pointer.startsWith("exit:")) {
        return this.exits[parseInt(pointer.substr(5), 10)];
    } else {
        return null;
    }
};

MapModel.prototype.resolvePointers = function(object, propertyNames) {

    var self = this;

    propertyNames.forEach(function(propertyName) {
        if (object.contains(propertyName)) {
            var value = object[propertyName];
            if (value instanceof Array) {
                for (var i = 0, length = value.length; i < length; i++) {
                    value[i] = self.resolvePointer(value[i]);
                }
            } else {
                object[propertyName] = self.resolvePointer(value);
            }
        }
    });
};

scriptLoaded("map.model.js");
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var TypedError = require('error/typed');
var WrappedError = require('error/wrapped');
var timers = require('timers');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var Reporter = require('../tcollector/reporter.js');
var TChannelJSON = require('../as/json.js');

var AdvertisementTimeoutError = WrappedError({
    type: 'hyperbahn-client.advertisement-timeout',
    message: 'Hyperbahn advertisement timed out after {time} ms!.\n' +
        '{origMessage}.\n'
});

var AlreadyDestroyed = TypedError({
    type: 'hyperbahn-client.already-destroyed',
    message: 'HyperbahnClient was already destroyed.\n' +
        'Cannot invoke {method}.',
    method: null
});

var States = {
    UNADVERTISED: 'UNADVERTISED',
    ADVERTISED: 'ADVERTISED'
};
var DEFAULT_TTL = 60 * 1000;
var DEFAULT_ERROR_RETRY_TIMES = [
    200, // One fast retry

    1000, // Fibonacci backoff
    1000,
    2000,
    3000,
    5000,
    8000,

    10000 // Max out at 10 seconds
];

module.exports = HyperbahnClient;

// # HyperbahnClient
// options.
//   * (required) tchannel: tchannel instance
//   * (required) serviceName: string service name
//   * (required) hostPortList: array of initial hyperbahn nodes
//   * callerName: the caller name
//   * logger: logtron instance
//   * reportTracing: Whether to report tracing
//   * hardFail: boolean; default false; whether or not to fail hard when we
//     can't advertise or on unexpected hyperbahn errors
//   * registrationTimeout: deprecated
//   * advertisementTimeout: integer. In hardFail mode we default to 5000. If
//     not in hardFail mode we don't time out advertisements.
function HyperbahnClient(options) {
    /*eslint max-statements: [2, 25] complexity: [2, 20] */
    if (!(this instanceof HyperbahnClient)) {
        return new HyperbahnClient(options);
    }

    var self = this;

    EventEmitter.call(this);

    assert(options && options.tchannel && !options.tchannel.topChannel,
        'Must pass in top level tchannel');
    assert(options.tchannel.tracer, 'Top channel must have trace enabled');
    assert(options.serviceName, 'must pass in serviceName');
    assert(Array.isArray(options.hostPortList),
        'Must pass in hostPortList');

    self.hostPortList = options.hostPortList;
    self.serviceName = options.serviceName;
    self.callerName = options.callerName || options.serviceName;
    self.tchannel = options.tchannel;
    self.reportTracing = 'reportTracing' in options ?
        options.reportTracing : true;
    self.hardFail = !!options.hardFail;
    self.advertiseInterval = options.advertiseInterval || DEFAULT_TTL;
    self.timeoutFuzz = self.advertiseInterval;
    self.errorRetryTimes = options.errorRetryTimes || DEFAULT_ERROR_RETRY_TIMES;

    self.logger = options.logger || self.tchannel.logger;
    self.statsd = options.statsd;

    self.reporter = Reporter({
        channel: self.getClientChannel({
            serviceName: 'tcollector',
            trace: false
        }),
        logger: self.logger,
        callerName: self.callerName
    });
    self.tchannel.tracer.reporter = function report(span) {
        if (self.reportTracing) {
            self.reporter.report(span);
        }
    };

    if (!self.reportTracing) {
        self.logger.warn('HyperbahnClient tcollector tracing is OFF', {
            service: self.serviceName
        });
    }

    self.hyperbahnChannel = self.tchannel.subChannels.hyperbahn ||
        self.tchannel.makeSubChannel({
            serviceName: 'hyperbahn',
            peers: self.hostPortList
        });
    self.tchannelJSON = TChannelJSON();

    self.lastError = null;
    self.latestAdvertisementResult = null;
    self.attemptCounter = 0;
    self.state = States.UNADVERTISED;
    self._destroyed = false;

    var advertisementTimeout = options.advertisementTimeout ||
        options.registrationTimeout;
    if (self.hardFail) {
        self.advertisementTimeoutTime = advertisementTimeout || 5000;
    } else {
        self.advertisementTimeoutTime = advertisementTimeout;
    }
}

util.inherits(HyperbahnClient, EventEmitter);

HyperbahnClient.prototype.setReportTracing = function setReportTracing(bool) {
    var self = this;

    self.reportTracing = bool;
};

// Gets the subchannel for hitting a particular service.
HyperbahnClient.prototype.getClientChannel =
function getClientChannel(options) {
    var self = this;

    assert(options && options.serviceName, 'must pass serviceName');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'getClientChannel'
        }));
        return null;
    }

    if (self.tchannel.subChannels[options.serviceName]) {
        return self.tchannel.subChannels[options.serviceName];
    }

    var channelOptions = {
        peers: self.hostPortList,
        serviceName: options.serviceName,
        requestDefaults: {
            serviceName: options.serviceName,
            headers: {
                cn: self.callerName
            }
        }
    };
    if ('trace' in options) {
        channelOptions.trace = options.trace;
    }
    if ('timeout' in options) {
        channelOptions.requestDefaults.timeout = options.timeout;
    }
    if ('retryLimit' in options) {
        channelOptions.requestDefaults.retryLimit = options.retryLimit;
    }

    return self.tchannel.makeSubChannel(channelOptions);
};

// ## advertisementTimeout
// Called after a certain amount of time to have a fatal error when reg fails.
// Will not be called if options.advertisementTimeout isn't passed in
HyperbahnClient.prototype.advertisementTimeout =
function advertisementTimeout() {
    var self = this;

    if (self.state === States.UNADVERTISED) {
        var lastError = self.lastError ||
            new Error('advertisement timeout!');
        var err = AdvertisementTimeoutError(lastError, {
            time: self.advertisementTimeoutTime
        });

        self.logger.fatal('HyperbahnClient: advertisement timed out', {
            timeout: self.advertisementTimeout,
            error: err
        });

        self.advertisementFailure(err);
    }
    // TODO else warn
};

HyperbahnClient.prototype.advertisementFailure =
function advertisementFailure(err) {
    var self = this;

    self.destroy();
    self.emit('error', err);

    if (self.statsd) {
        self.statsd.increment(
            'hyperbahn-client.' + self.serviceName + '.advertisement.failure'
        );
    }
};

HyperbahnClient.prototype.sendAdvertiseRequest =
function sendAdvertiseRequest(opts, cb) {
    var self = this;

    var req = self.hyperbahnChannel.request({
        serviceName: 'hyperbahn',
        timeout: (opts && opts.timeout) || 50,
        hasNoParent: true,
        trace: false,
        headers: {
            cn: self.callerName
        }
    });
    self.tchannelJSON.send(req, 'ad', null, {
        services: [{
            cost: 0,
            serviceName: self.serviceName
        }]
    }, cb);
};

// ## advertise
// Advertise with Hyperbahn. If called with a callback, the callback will not be
// called until there has been a successful advertisement. This function
// attempts a advertise and retries until there are no healthy servers left; it
// will then repeatedly choose random servers to try until it finds one that
// works.
// register is **deprecated**
HyperbahnClient.prototype.register =
HyperbahnClient.prototype.advertise =
function advertise(opts) {
    var self = this;
    // Attempt a advertisement. If it succeeds, setTimeout to re-advertise with
    // the same server after the TTL.

    assert(self.tchannel.hostPort,
        'must call tchannel.listen() before advertise()');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'advertise'
        }));
        return;
    }

    if (self.advertisementTimeoutTime) {
        // Start the timeout timer
        self.advertisementTimeoutTimer = timers.setTimeout(
            function advertisementTimeoutTimer() {
                self.advertisementTimeout();
            },
            self.advertisementTimeoutTime
        );
    } else {
        self.logger.info('HyperbahnClient advertisement timeout disabled', {
            service: self.serviceName
        });
    }

    self.attemptCounter++;
    self.sendAdvertiseRequest(opts, advertiseInternalCb);
    self.emit('advertise-attempt');

    function advertiseInternalCb(err, result) {
        /*eslint max-statements: [2, 40] */
        if (err) {
            self.logger[self.hardFail ? 'error' : 'warn'](
                'HyperbahnClient: advertisement failure, ' +
                'marking server as sick', {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            self.lastError = err;

            self.advertiseAgain(self.getErrorRetryTime());
            return;
        }

        if (result.ok === false) {
            err = result.body;
            var errInfo2 = {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            };

            self.logger[self.hardFail ? 'fatal' : 'warn'](
                'HyperbahnClient: unexpected failure (from Hyperbahn)',
                errInfo2
            );

            if (self.hardFail) {
                self.advertisementFailure(err);
            } else {
                self.advertiseAgain(self.getErrorRetryTime());
            }

            return;
        }

        if (self.statsd) {
            self.statsd.increment(
                'hyperbahn-client.' + self.serviceName +
                    '.advertisement.success'
            );
        }

        self.latestAdvertisementResult = result;
        self.attemptCounter = 0;
        self.state = States.ADVERTISED;
        timers.clearTimeout(self.advertisementTimeoutTimer);

        self.advertiseAgain(self.getHealthyRetryTime());

        // registered event is deprecated
        self.emit('registered');
        self.emit('advertised');
    }
};

HyperbahnClient.prototype.getErrorRetryTime = function getErrorRetryTime() {
    var self = this;

    return self.errorRetryTimes[self.attemptCounter - 1] ||
        self.errorRetryTimes[self.errorRetryTimes.length - 1];
};

HyperbahnClient.prototype.getHealthyRetryTime = function getHealthyRetryTime() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    var delay = Math.round(Math.floor(Math.random() * fuzz)) - (fuzz / 2);

    return self.advertiseInterval + delay;
};

HyperbahnClient.prototype.advertiseAgain =
function advertiseAgain(delay) {
    var self = this;

    if (self._destroyed) {
        return;
    }

    self._advertisementTimer = timers.setTimeout(
        function advertiseTimeout() {
            self.advertise();
        },
        delay
    );
};

// ## destroy
HyperbahnClient.prototype.destroy = function destroy() {
    this._destroyed = true;
    timers.clearTimeout(this._advertisementTimer);
    timers.clearTimeout(this.advertisementTimeoutTimer);
};
<commit_msg>do not retry hyperbahn client ad<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var TypedError = require('error/typed');
var WrappedError = require('error/wrapped');
var timers = require('timers');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var Reporter = require('../tcollector/reporter.js');
var TChannelJSON = require('../as/json.js');

var AdvertisementTimeoutError = WrappedError({
    type: 'hyperbahn-client.advertisement-timeout',
    message: 'Hyperbahn advertisement timed out after {time} ms!.\n' +
        '{origMessage}.\n'
});

var AlreadyDestroyed = TypedError({
    type: 'hyperbahn-client.already-destroyed',
    message: 'HyperbahnClient was already destroyed.\n' +
        'Cannot invoke {method}.',
    method: null
});

var States = {
    UNADVERTISED: 'UNADVERTISED',
    ADVERTISED: 'ADVERTISED'
};
var DEFAULT_TTL = 60 * 1000;
var DEFAULT_ERROR_RETRY_TIMES = [
    200, // One fast retry

    1000, // Fibonacci backoff
    1000,
    2000,
    3000,
    5000,
    8000,

    10000 // Max out at 10 seconds
];

module.exports = HyperbahnClient;

// # HyperbahnClient
// options.
//   * (required) tchannel: tchannel instance
//   * (required) serviceName: string service name
//   * (required) hostPortList: array of initial hyperbahn nodes
//   * callerName: the caller name
//   * logger: logtron instance
//   * reportTracing: Whether to report tracing
//   * hardFail: boolean; default false; whether or not to fail hard when we
//     can't advertise or on unexpected hyperbahn errors
//   * registrationTimeout: deprecated
//   * advertisementTimeout: integer. In hardFail mode we default to 5000. If
//     not in hardFail mode we don't time out advertisements.
function HyperbahnClient(options) {
    /*eslint max-statements: [2, 25] complexity: [2, 20] */
    if (!(this instanceof HyperbahnClient)) {
        return new HyperbahnClient(options);
    }

    var self = this;

    EventEmitter.call(this);

    assert(options && options.tchannel && !options.tchannel.topChannel,
        'Must pass in top level tchannel');
    assert(options.tchannel.tracer, 'Top channel must have trace enabled');
    assert(options.serviceName, 'must pass in serviceName');
    assert(Array.isArray(options.hostPortList),
        'Must pass in hostPortList');

    self.hostPortList = options.hostPortList;
    self.serviceName = options.serviceName;
    self.callerName = options.callerName || options.serviceName;
    self.tchannel = options.tchannel;
    self.reportTracing = 'reportTracing' in options ?
        options.reportTracing : true;
    self.hardFail = !!options.hardFail;
    self.advertiseInterval = options.advertiseInterval || DEFAULT_TTL;
    self.timeoutFuzz = self.advertiseInterval;
    self.errorRetryTimes = options.errorRetryTimes || DEFAULT_ERROR_RETRY_TIMES;

    self.logger = options.logger || self.tchannel.logger;
    self.statsd = options.statsd;

    self.reporter = Reporter({
        channel: self.getClientChannel({
            serviceName: 'tcollector',
            trace: false
        }),
        logger: self.logger,
        callerName: self.callerName
    });
    self.tchannel.tracer.reporter = function report(span) {
        if (self.reportTracing) {
            self.reporter.report(span);
        }
    };

    if (!self.reportTracing) {
        self.logger.warn('HyperbahnClient tcollector tracing is OFF', {
            service: self.serviceName
        });
    }

    self.hyperbahnChannel = self.tchannel.subChannels.hyperbahn ||
        self.tchannel.makeSubChannel({
            serviceName: 'hyperbahn',
            peers: self.hostPortList
        });
    self.tchannelJSON = TChannelJSON();

    self.lastError = null;
    self.latestAdvertisementResult = null;
    self.attemptCounter = 0;
    self.state = States.UNADVERTISED;
    self._destroyed = false;

    var advertisementTimeout = options.advertisementTimeout ||
        options.registrationTimeout;
    if (self.hardFail) {
        self.advertisementTimeoutTime = advertisementTimeout || 5000;
    } else {
        self.advertisementTimeoutTime = advertisementTimeout;
    }
}

util.inherits(HyperbahnClient, EventEmitter);

HyperbahnClient.prototype.setReportTracing = function setReportTracing(bool) {
    var self = this;

    self.reportTracing = bool;
};

// Gets the subchannel for hitting a particular service.
HyperbahnClient.prototype.getClientChannel =
function getClientChannel(options) {
    var self = this;

    assert(options && options.serviceName, 'must pass serviceName');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'getClientChannel'
        }));
        return null;
    }

    if (self.tchannel.subChannels[options.serviceName]) {
        return self.tchannel.subChannels[options.serviceName];
    }

    var channelOptions = {
        peers: self.hostPortList,
        serviceName: options.serviceName,
        requestDefaults: {
            serviceName: options.serviceName,
            headers: {
                cn: self.callerName
            }
        }
    };
    if ('trace' in options) {
        channelOptions.trace = options.trace;
    }
    if ('timeout' in options) {
        channelOptions.requestDefaults.timeout = options.timeout;
    }
    if ('retryLimit' in options) {
        channelOptions.requestDefaults.retryLimit = options.retryLimit;
    }

    return self.tchannel.makeSubChannel(channelOptions);
};

// ## advertisementTimeout
// Called after a certain amount of time to have a fatal error when reg fails.
// Will not be called if options.advertisementTimeout isn't passed in
HyperbahnClient.prototype.advertisementTimeout =
function advertisementTimeout() {
    var self = this;

    if (self.state === States.UNADVERTISED) {
        var lastError = self.lastError ||
            new Error('advertisement timeout!');
        var err = AdvertisementTimeoutError(lastError, {
            time: self.advertisementTimeoutTime
        });

        self.logger.fatal('HyperbahnClient: advertisement timed out', {
            timeout: self.advertisementTimeout,
            error: err
        });

        self.advertisementFailure(err);
    }
    // TODO else warn
};

HyperbahnClient.prototype.advertisementFailure =
function advertisementFailure(err) {
    var self = this;

    self.destroy();
    self.emit('error', err);

    if (self.statsd) {
        self.statsd.increment(
            'hyperbahn-client.' + self.serviceName + '.advertisement.failure'
        );
    }
};

HyperbahnClient.prototype.sendAdvertiseRequest =
function sendAdvertiseRequest(opts, cb) {
    var self = this;

    var req = self.hyperbahnChannel.request({
        serviceName: 'hyperbahn',
        timeout: (opts && opts.timeout) || 50,
        hasNoParent: true,
        trace: false,
        retryLimit: 1,
        headers: {
            cn: self.callerName
        }
    });
    self.tchannelJSON.send(req, 'ad', null, {
        services: [{
            cost: 0,
            serviceName: self.serviceName
        }]
    }, cb);
};

// ## advertise
// Advertise with Hyperbahn. If called with a callback, the callback will not be
// called until there has been a successful advertisement. This function
// attempts a advertise and retries until there are no healthy servers left; it
// will then repeatedly choose random servers to try until it finds one that
// works.
// register is **deprecated**
HyperbahnClient.prototype.register =
HyperbahnClient.prototype.advertise =
function advertise(opts) {
    var self = this;
    // Attempt a advertisement. If it succeeds, setTimeout to re-advertise with
    // the same server after the TTL.

    assert(self.tchannel.hostPort,
        'must call tchannel.listen() before advertise()');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'advertise'
        }));
        return;
    }

    if (self.advertisementTimeoutTime) {
        // Start the timeout timer
        self.advertisementTimeoutTimer = timers.setTimeout(
            function advertisementTimeoutTimer() {
                self.advertisementTimeout();
            },
            self.advertisementTimeoutTime
        );
    } else {
        self.logger.info('HyperbahnClient advertisement timeout disabled', {
            service: self.serviceName
        });
    }

    self.attemptCounter++;
    self.sendAdvertiseRequest(opts, advertiseInternalCb);
    self.emit('advertise-attempt');

    function advertiseInternalCb(err, result) {
        /*eslint max-statements: [2, 40] */
        if (err) {
            self.logger[self.hardFail ? 'error' : 'warn'](
                'HyperbahnClient: advertisement failure, ' +
                'marking server as sick', {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            self.lastError = err;

            self.advertiseAgain(self.getErrorRetryTime());
            return;
        }

        if (result.ok === false) {
            err = result.body;
            var errInfo2 = {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            };

            self.logger[self.hardFail ? 'fatal' : 'warn'](
                'HyperbahnClient: unexpected failure (from Hyperbahn)',
                errInfo2
            );

            if (self.hardFail) {
                self.advertisementFailure(err);
            } else {
                self.advertiseAgain(self.getErrorRetryTime());
            }

            return;
        }

        if (self.statsd) {
            self.statsd.increment(
                'hyperbahn-client.' + self.serviceName +
                    '.advertisement.success'
            );
        }

        self.latestAdvertisementResult = result;
        self.attemptCounter = 0;
        self.state = States.ADVERTISED;
        timers.clearTimeout(self.advertisementTimeoutTimer);

        self.advertiseAgain(self.getHealthyRetryTime());

        // registered event is deprecated
        self.emit('registered');
        self.emit('advertised');
    }
};

HyperbahnClient.prototype.getErrorRetryTime = function getErrorRetryTime() {
    var self = this;

    return self.errorRetryTimes[self.attemptCounter - 1] ||
        self.errorRetryTimes[self.errorRetryTimes.length - 1];
};

HyperbahnClient.prototype.getHealthyRetryTime = function getHealthyRetryTime() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    var delay = Math.round(Math.floor(Math.random() * fuzz)) - (fuzz / 2);

    return self.advertiseInterval + delay;
};

HyperbahnClient.prototype.advertiseAgain =
function advertiseAgain(delay) {
    var self = this;

    if (self._destroyed) {
        return;
    }

    self._advertisementTimer = timers.setTimeout(
        function advertiseTimeout() {
            self.advertise();
        },
        delay
    );
};

// ## destroy
HyperbahnClient.prototype.destroy = function destroy() {
    this._destroyed = true;
    timers.clearTimeout(this._advertisementTimer);
    timers.clearTimeout(this.advertisementTimeoutTimer);
};
<|endoftext|>


<commit_before>/* jshint -W085 */
// DEV: The above directive ignores `with` errors
// Load in dependencies
var expect = require('chai').expect;
var scriptUtils = require('./utils/script');

// Run our tests
describe('ecma-variable-scope', function () {
  describe('marking up an AST with a var inside referenced through a `with`', function () {
    scriptUtils.interpretFnAst(function () {
      var hello = 'world';
      var obj = {hello: 'moon'};
      with (obj) {
        console.log(hello);
      }
    });

    it('marks the initialization without a `with`', function () {
      // {Program} (ast) -> {var} (body[0]) -> hello (declarations[0].id)
      var identifier = this.ast.body[0].declarations[0].id;
      expect(identifier.scopeInfo).to.have.property('insideWith', false);
    });

    it('marks the inner `with` variable as with a `with`', function () {
      // {Program} (ast) -> {with content} (body[2].body.body) -> `hello` ([0].expression['arguments'][0])
      var identifier = this.ast.body[2].body.body[0].expression['arguments'][0];
      expect(identifier.scopeInfo).to.have.property('insideWith', true);
    });

    it.only('marks the `with` object as not used in a `with`', function () {
      // {Program} (ast) -> {with} (body[2]) -> obj (object)
      var identifier = this.ast.body[2].object;
      expect(identifier.scopeInfo).to.have.property('insideWith', false);
    });

    it('marks the outer variable with a `usedInAWith`', function () {
      var identifier = this.ast.body[0].declarations[0].id;
      expect(identifier.scopeInfo).to.have.property('usedInAWith', true);
    });

    it('marks the inner `with` variable with a `usedInAWith`', function () {
      var identifier = this.ast.body[2].body.body[0].expression['arguments'][0];
      expect(identifier.scopeInfo).to.have.property('usedInAWith', true);
    });

    it('marks the `with` object without a `usedInAWith`', function () {
      // {Program} (ast) -> {with} (body[2]) -> obj (object)
      var identifier = this.ast.body[2].object;
      expect(identifier.scopeInfo).to.have.property('usedInAWith', false);    });
  });
});
<commit_msg>Removed dev .only<commit_after>/* jshint -W085 */
// DEV: The above directive ignores `with` errors
// Load in dependencies
var expect = require('chai').expect;
var scriptUtils = require('./utils/script');

// Run our tests
describe('ecma-variable-scope', function () {
  describe('marking up an AST with a var inside referenced through a `with`', function () {
    scriptUtils.interpretFnAst(function () {
      var hello = 'world';
      var obj = {hello: 'moon'};
      with (obj) {
        console.log(hello);
      }
    });

    it('marks the initialization without a `with`', function () {
      // {Program} (ast) -> {var} (body[0]) -> hello (declarations[0].id)
      var identifier = this.ast.body[0].declarations[0].id;
      expect(identifier.scopeInfo).to.have.property('insideWith', false);
    });

    it('marks the inner `with` variable as with a `with`', function () {
      // {Program} (ast) -> {with content} (body[2].body.body) -> `hello` ([0].expression['arguments'][0])
      var identifier = this.ast.body[2].body.body[0].expression['arguments'][0];
      expect(identifier.scopeInfo).to.have.property('insideWith', true);
    });

    it('marks the `with` object as not used in a `with`', function () {
      // {Program} (ast) -> {with} (body[2]) -> obj (object)
      var identifier = this.ast.body[2].object;
      expect(identifier.scopeInfo).to.have.property('insideWith', false);
    });

    it('marks the outer variable with a `usedInAWith`', function () {
      var identifier = this.ast.body[0].declarations[0].id;
      expect(identifier.scopeInfo).to.have.property('usedInAWith', true);
    });

    it('marks the inner `with` variable with a `usedInAWith`', function () {
      var identifier = this.ast.body[2].body.body[0].expression['arguments'][0];
      expect(identifier.scopeInfo).to.have.property('usedInAWith', true);
    });

    it('marks the `with` object without a `usedInAWith`', function () {
      // {Program} (ast) -> {with} (body[2]) -> obj (object)
      var identifier = this.ast.body[2].object;
      expect(identifier.scopeInfo).to.have.property('usedInAWith', false);    });
  });
});
<|endoftext|>


<commit_before>/*
  Text plugin
*/
exports.translate = function(load) {
  if (this.builder && this.transpiler) {
    load.metadata.format = 'esm';
    return 'export default ' + JSON.stringify(load.source) + ';';
  }

  load.metadata.format = 'amd';
  return 'def' + 'ine(function() {\nreturn ' + JSON.stringify(load.source) + ';\n});';
}
<commit_msg>possible es detection?<commit_after>/*
  Text plugin
*/
exports.translate = function(load) {
  if (this.builder && this.transpiler) {
    load.metadata.format = 'esm';
    return 'exp' + 'ort default ' + JSON.stringify(load.source) + ';';
  }

  load.metadata.format = 'amd';
  return 'def' + 'ine(function() {\nreturn ' + JSON.stringify(load.source) + ';\n});';
}
<|endoftext|>


<commit_before>Bills = new Meteor.Collection('bills');

Template.billList.myBills = function(){
  return Bills.find({paid: false});
};
<commit_msg>Changed the query to get only bills name "Cox"<commit_after>Bills = new Meteor.Collection('bills');

Template.billList.myBills = function(){
  return Bills.find({name: 'Cox'});
};
<|endoftext|>


<commit_before>const Github = require('github')
const eslint = require('eslint')
const exec = require('child_process').execSync
const spawn = require('child_process').spawnSync

const cli = new eslint.CLIEngine()

const setStatuses = () => {
  const gh = new Github()
  const status = authenticateWithGithub(gh, process.env)

  setLintStatus(gh, status)
  setFlowStatus(gh, status)
  setJestStatus(gh, status)
}

const authenticateWithGithub = (gh, { TRAVIS_EVENT_TYPE, TRAVIS_REPO_SLUG, TRAVIS_JOB_ID, GITHUB_TOKEN }) => {
  const sha = getCommitSha(TRAVIS_EVENT_TYPE)
  const repoSlug = TRAVIS_REPO_SLUG
  const target_url = `https://travis-ci.org/${repoSlug}/jobs/${TRAVIS_JOB_ID}`
  const parsedSlug = repoSlug.split('/')
  const owner = parsedSlug[0]
  const repo = parsedSlug[1]

  gh.authenticate({
    token: GITHUB_TOKEN,
    type: 'oauth',
  }, err => {
    if (err) console.error('Error authenticating GitHub', err)
  })

  return {
    sha,
    target_url,
    owner,
    repo,
  }
}

const getCommitSha = eventType => {
  if (eventType === 'push') {
    return process.env.TRAVIS_COMMIT
  }
  else if (eventType === 'pull_request') {
    const travisCommitRange = process.env.TRAVIS_COMMIT_RANGE
    const parsed = travisCommitRange.split('...')
    return parsed.length === 1 ? travisCommitRange : parsed[1]
  }

  console.error('event type \'%s\' not supported', eventType)
  return null
}

const setLintStatus = (gh, status) => {
  const stdout = exec(`git diff --name-only ${process.env.TRAVIS_COMMIT_RANGE} -- '*.js'`, { encoding: 'utf8' })
  const files = stdout // paths of *.js files that changed in the commit/PR
      .split('\n')
      .slice(0, -1) // Remove the extra "" caused by the last newline

  const { errorCount, warningCount, results } = cli.executeOnFiles(cli.resolveFileGlobPatterns(files))

  const description = `errors: ${errorCount} warnings: ${warningCount}`
  const success = errorCount === 0
  setStatus(gh, status, 'ESLint Report', description, success)

  const format = cli.getFormatter()
  const log = format(results)
  console.log(log)
}

const setFlowStatus = (gh, status) => {
  const { stdout } = spawn('./node_modules/.bin/flow', ['check'], { encoding: 'utf8' })
  const lines = stdout.split('\n')
  const lastLine = lines[lines.length - 2]
  const errorCount = parseInt(lastLine.replace('Found ', ''))

  const description = `errors: ${errorCount}`
  const success = errorCount === 0
  setStatus(gh, status, 'Flow Report', description, success)

  console.log(stdout)
}

const setJestStatus = (gh, status) => {
  const { stderr } = spawn('./node_modules/.bin/jest', ['--coverage'], { encoding: 'utf8' })

  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/
  const [passedCount, testCount] = regex
    .exec(stderr)
    .slice(1, 3)
    .map(num => parseInt(num))

  const description = `${passedCount} passed, ${testCount} total`
  const success = passedCount === testCount
  setStatus(gh, status, 'Jest Tests', description, success)

  console.log(stderr)
}

const setStatus = (gh, status, context, description, success) => {
  gh.repos.createStatus(Object.assign({
    context,
    description,
    state: success ? 'success' : 'failure',
  }, status), err => {
    console.log(`${context}: DONE!`)

    if (err) {
      console.error(`${context}: Error creating status`, err)
    }
    else {
      console.log(`${context}: SUCCESS`)
    }
  })

  if (!success) {
    process.exitCode = 1
  }
}

setStatuses()

/**

exec('./node_modules/.bin/jest').then(res => {
  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/gm
  const str = res.stderr

  let m

  if ((m = regex.exec(str)) !== null) {
      m.forEach((match, groupIndex) => {
          console.log(`Found match, group ${groupIndex}: ${match}`)
      })
  }
})

exec('./node_modules/.bin/jest').then(res => {
  console.log('DONE', res)
})

exec('./node_modules/.bin/jest').then(res => {
  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/
  const str = res.stderr

  const arr = regex
    .exec(str)
    .slice(1, 3)
    .map(num => parseInt(num))

  console.log(arr)
})

**/
<commit_msg>test eslint break<commit_after>const Github = require('github')
const eslint = require('eslint')
const exec = require('child_process').execSync
const spawn = require('child_process').spawnSync

const cli = new eslint.CLIEngine()

const setStatuses = () => {
  const gh = new Github()
  const status = authenticateWithGithub(gh, process.env)

  setLintStatus(gh, status)
  setFlowStatus(gh, status)
  setJestStatus(gh, status);
}

const authenticateWithGithub = (gh, { TRAVIS_EVENT_TYPE, TRAVIS_REPO_SLUG, TRAVIS_JOB_ID, GITHUB_TOKEN }) => {
  const sha = getCommitSha(TRAVIS_EVENT_TYPE)
  const repoSlug = TRAVIS_REPO_SLUG
  const target_url = `https://travis-ci.org/${repoSlug}/jobs/${TRAVIS_JOB_ID}`
  const parsedSlug = repoSlug.split('/')
  const owner = parsedSlug[0]
  const repo = parsedSlug[1]

  gh.authenticate({
    token: GITHUB_TOKEN,
    type: 'oauth',
  }, err => {
    if (err) console.error('Error authenticating GitHub', err)
  })

  return {
    sha,
    target_url,
    owner,
    repo,
  }
}

const getCommitSha = eventType => {
  if (eventType === 'push') {
    return process.env.TRAVIS_COMMIT
  }
  else if (eventType === 'pull_request') {
    const travisCommitRange = process.env.TRAVIS_COMMIT_RANGE
    const parsed = travisCommitRange.split('...')
    return parsed.length === 1 ? travisCommitRange : parsed[1]
  }

  console.error('event type \'%s\' not supported', eventType)
  return null
}

const setLintStatus = (gh, status) => {
  const stdout = exec(`git diff --name-only ${process.env.TRAVIS_COMMIT_RANGE} -- '*.js'`, { encoding: 'utf8' })
  const files = stdout // paths of *.js files that changed in the commit/PR
      .split('\n')
      .slice(0, -1) // Remove the extra "" caused by the last newline

  const { errorCount, warningCount, results } = cli.executeOnFiles(cli.resolveFileGlobPatterns(files))

  const description = `errors: ${errorCount} warnings: ${warningCount}`
  const success = errorCount === 0
  setStatus(gh, status, 'ESLint Report', description, success)

  const format = cli.getFormatter()
  const log = format(results)
  console.log(log)
}

const setFlowStatus = (gh, status) => {
  const { stdout } = spawn('./node_modules/.bin/flow', ['check'], { encoding: 'utf8' })
  const lines = stdout.split('\n')
  const lastLine = lines[lines.length - 2]
  const errorCount = parseInt(lastLine.replace('Found ', ''))

  const description = `errors: ${errorCount}`
  const success = errorCount === 0
  setStatus(gh, status, 'Flow Report', description, success)

  console.log(stdout)
}

const setJestStatus = (gh, status) => {
  const { stderr } = spawn('./node_modules/.bin/jest', ['--coverage'], { encoding: 'utf8' })

  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/
  const [passedCount, testCount] = regex
    .exec(stderr)
    .slice(1, 3)
    .map(num => parseInt(num))

  const description = `${passedCount} passed, ${testCount} total`
  const success = passedCount === testCount
  setStatus(gh, status, 'Jest Tests', description, success)

  console.log(stderr)
}

const setStatus = (gh, status, context, description, success) => {
  gh.repos.createStatus(Object.assign({
    context,
    description,
    state: success ? 'success' : 'failure',
  }, status), err => {
    console.log(`${context}: DONE!`)

    if (err) {
      console.error(`${context}: Error creating status`, err)
    }
    else {
      console.log(`${context}: SUCCESS`)
    }
  })

  if (!success) {
    process.exitCode = 1
  }
}

setStatuses()

/**

exec('./node_modules/.bin/jest').then(res => {
  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/gm
  const str = res.stderr

  let m

  if ((m = regex.exec(str)) !== null) {
      m.forEach((match, groupIndex) => {
          console.log(`Found match, group ${groupIndex}: ${match}`)
      })
  }
})

exec('./node_modules/.bin/jest').then(res => {
  console.log('DONE', res)
})

exec('./node_modules/.bin/jest').then(res => {
  const regex = /Tests:\s+(\d+)\D+(\d+)\s+total/
  const str = res.stderr

  const arr = regex
    .exec(str)
    .slice(1, 3)
    .map(num => parseInt(num))

  console.log(arr)
})

**/
<|endoftext|>