text:
import 'mocha';
import * as sinon from 'sinon';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as setupGcloud from '@google-github-actions/setup-cloud-sdk';
import { expect } from 'chai';
import { run, kvToString } from '../../src/main';

// These are mock data for github actions inputs, where camel case is expected.
const fakeInputs: { [key: string]: string } = {
  image: 'gcr.io/cloudrun/hello',
  service: 'test',
  metadata: '',
  credentials: '',
  project_id: 'my-test-project',
  env_vars: '',
  labels: '',
  source: '',
  suffix: '',
  tag: '',
  timeout: '',
  revision_traffic: '',
  tag_traffic: '',
};

function getInputMock(name: string): string {
  return fakeInputs[name];
}

describe('#deploy-cloudrun', function () {
  describe('#run', function () {
    beforeEach(async function () {
      // Stub somewhat annoying logs
      const doNothing = () => {
        /** do nothing */
      };
      sinon.stub(core, 'debug').callsFake(doNothing);
      sinon.stub(core, 'endGroup').callsFake(doNothing);
      sinon.stub(core, 'info').callsFake(doNothing);
      sinon.stub(core, 'setOutput').callsFake(doNothing);
      sinon.stub(core, 'startGroup').callsFake(doNothing);
      sinon.stub(core, 'warning').callsFake(doNothing);

      this.stubs = {
        getInput: sinon.stub(core, 'getInput').callsFake(getInputMock),
        getBooleanInput: sinon.stub(core, 'getBooleanInput').returns(false),
        exportVariable: sinon.stub(core, 'exportVariable'),
        setFailed: sinon.stub(core, 'setFailed'),
        installGcloudSDK: sinon.stub(setupGcloud, 'installGcloudSDK'),
        authenticateGcloudSDK: sinon.stub(setupGcloud, 'authenticateGcloudSDK'),
        isAuthenticated: sinon.stub(setupGcloud, 'isAuthenticated').resolves(true),
        isInstalled: sinon.stub(setupGcloud, 'isInstalled').returns(false),
        parseServiceAccountKey: sinon.stub(setupGcloud, 'parseServiceAccountKey'),
        isProjectIdSet: sinon.stub(setupGcloud, 'isProjectIdSet').resolves(true),
        installComponent: sinon.stub(setupGcloud, 'installComponent'),
        getExecOutput: sinon.stub(exec, 'getExecOutput'),
      };
    });

    afterEach(function () {
      sinon.restore();
      Object.keys(this.stubs).forEach((k) => this.stubs[k].restore());
      delete process.env.GITHUB_SHA;
    });

    it('sets the project ID if provided', async function () {
      this.stubs.getInput.withArgs('project_id').returns('my-test-project');
      await run();

      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--project', 'my-test-project']);
    });

    it('sets the project ID if GCLOUD_PROJECT is provided', async function () {
      this.stubs.getInput.withArgs('project_id').returns('');
      this.stubs.getInput.withArgs('credentials').returns('');
      process.env.GCLOUD_PROJECT = 'my-test-project';
      await run();

      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--project', 'my-test-project']);
    });

    it('installs the gcloud SDK if it is not already installed', async function () {
      this.stubs.isInstalled.returns(false);
      await run();
      expect(this.stubs.installGcloudSDK.callCount).to.eq(1);
    });

    it('uses the cached gcloud SDK if it was already installed', async function () {
      this.stubs.isInstalled.returns(true);
      await run();
      expect(this.stubs.installGcloudSDK.callCount).to.eq(0);
    });

    it('authenticates if key is provided', async function () {
      this.stubs.getInput.withArgs('credentials').returns('key');
      await run();
      expect(this.stubs.authenticateGcloudSDK.withArgs('key').callCount).to.eq(1);
    });

    it('uses project id from credentials if project_id is not provided', async function () {
      this.stubs.getInput.withArgs('credentials').returns('key');
      this.stubs.getInput.withArgs('project_id').returns('');
      await run();
      expect(this.stubs.parseServiceAccountKey.withArgs('key').callCount).to.eq(1);
    });

    it('fails if credentials and project_id are not provided', async function () {
      this.stubs.getInput.withArgs('credentials').returns('');
      this.stubs.getInput.withArgs('project_id').returns('');
      process.env.GCLOUD_PROJECT = '';
      await run();
      expect(this.stubs.setFailed.callCount).to.be.at.least(1);
    });

    it('sets labels', async function () {
      this.stubs.getInput.withArgs('labels').returns('foo=bar,zip=zap');

      process.env.GITHUB_SHA = 'abcdef123456';

      const expectedLabels = {
        'managed-by': 'github-actions',
        'commit-sha': 'abcdef123456',
        'foo': 'bar',
        'zip': 'zap',
      };

      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--update-labels', kvToString(expectedLabels)]);
    });

    it('overwrites default labels', async function () {
      this.stubs.getInput.withArgs('labels').returns('commit-sha=custom-value');

      process.env.GITHUB_SHA = 'abcdef123456';

      const expectedLabels = {
        'managed-by': 'github-actions',
        'commit-sha': 'custom-value',
      };

      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--update-labels', kvToString(expectedLabels)]);
    });

    it('sets source if given', async function () {
      this.stubs.getInput.withArgs('source').returns('example-app');
      this.stubs.getInput.withArgs('image').returns('');
      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--source', 'example-app']);
    });

    it('sets metadata if given', async function () {
      this.stubs.getInput.withArgs('metadata').returns('yaml');
      this.stubs.getInput.withArgs('image').returns('');
      this.stubs.getInput.withArgs('service').returns('');
      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['services', 'replace', 'yaml']);
    });

    it('sets timeout if given', async function () {
      this.stubs.getInput.withArgs('timeout').returns('55m12s');
      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--timeout', '55m12s']);
    });

    it('sets tag if given', async function () {
      this.stubs.getInput.withArgs('tag').returns('test');
      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--tag', 'test']);
    });

    it('sets tag traffic if given', async function () {
      this.stubs.getInput.withArgs('tag').returns('test');
      this.stubs.getInput.withArgs('name').returns('service-name');
      await run();
      const call = this.stubs.getExecOutput.getCall(0);
      expect(call).to.be;
      const args = call.args[1];
      expect(args).to.include.members(['--tag', 'test']);
    });

    it('fails if tag traffic and revision traffic are provided', async function () {
      this.stubs.getInput.withArgs('revision_traffic').returns('TEST=100');
      this.stubs.getInput.withArgs('tag_traffic').returns('TEST=100');
      await run();
      expect(this.stubs.setFailed.callCount).to.eq(1);
    });

    it('fails if name is not provided with tag traffic', async function () {
      this.stubs.getInput.withArgs('tag_traffic').returns('TEST=100');
      this.stubs.getInput.withArgs('name').returns('service-name');
      await run();
      expect(this.stubs.setFailed.callCount).to.eq(1);
    });

    it('fails if name is not provided with revision traffic', async function () {
      this.stubs.getInput.withArgs('revision_traffic').returns('TEST=100');
      this.stubs.getInput.withArgs('name').returns('service-name');
      await run();
      expect(this.stubs.setFailed.callCount).to.eq(1);
    });

    it('uses default components without gcloud_component flag', async function () {
      await run();
      expect(this.stubs.installComponent.callCount).to.eq(0);
    });

    it('throws error with invalid gcloud component flag', async function () {
      this.stubs.getInput.withArgs('gcloud_component').returns('wrong_value');
      await run();
      const msg = `google-github-actions/deploy-cloudrun failed with: invalid input received for gcloud_component: wrong_value`;
      expect(this.stubs.setFailed.withArgs(`${msg}`).callCount).to.be.at.least(1);
    });

    it('installs alpha component with alpha flag', async function () {
      this.stubs.getInput.withArgs('gcloud_component').returns('alpha');
      await run();
      expect(this.stubs.installComponent.withArgs('alpha').callCount).to.eq(1);
    });

    it('installs beta component with beta flag', async function () {
      this.stubs.getInput.withArgs('gcloud_component').returns('beta');
      await run();
      expect(this.stubs.installComponent.withArgs('beta').callCount).to.eq(1);
    });
  });

  describe('#kvToString', () => {
    const cases = [
      {
        name: `empty`,
        input: {},
        exp: ``,
      },
      {
        name: `single item`,
        input: { FOO: 'bar' },
        exp: `FOO=bar`,
      },
      {
        name: `multiple items`,
        input: { FOO: 'bar', ZIP: 'zap' },
        exp: `FOO=bar,ZIP=zap`,
      },
    ];

    cases.forEach((tc) => {
      it(tc.name, () => {
        const result = kvToString(tc.input as Record<string, string>);
        expect(result).to.eql(tc.exp);
      });
    });
  });
});


text:
/**
Material design: [Surface reaction](https://www.google.com/design/spec/animation/responsive-interaction.html#responsive-interaction-surface-reaction)

`paper-ripple` provides a visual effect that other paper elements can
use to simulate a rippling effect emanating from the point of contact.  The
effect can be visualized as a concentric circle with motion.

Example:

      <paper-ripple></paper-ripple>

Note, it's important that the parent container of the ripple be relative position, otherwise
the ripple will emanate outside of the desired container.

`paper-ripple` listens to "mousedown" and "mouseup" events so it would display ripple
effect when touches on it.  You can also defeat the default behavior and
manually route the down and up actions to the ripple element.  Note that it is
important if you call `downAction()` you will have to make sure to call
`upAction()` so that `paper-ripple` would end the animation loop.

Example:

    <paper-ripple id="ripple" style="pointer-events: none;"></paper-ripple>
    ...
    downAction: function(e) {
      this.$.ripple.downAction(e.detail);
    },
    upAction: function(e) {
      this.$.ripple.upAction();
    }

Styling ripple effect:

  Use CSS color property to style the ripple:

    paper-ripple {
      color: #4285f4;
    }

  Note that CSS color property is inherited so it is not required to set it on
  the `paper-ripple` element directly.

By default, the ripple is centered on the point of contact.  Apply the `recenters`
attribute to have the ripple grow toward the center of its container.

    <paper-ripple recenters></paper-ripple>

You can also  center the ripple inside its container from the start.

    <paper-ripple center></paper-ripple>

Apply `circle` class to make the rippling effect within a circle.

    <paper-ripple class="circle"></paper-ripple>

@group Paper Elements
@element paper-ripple
@hero hero.svg
@demo demo/index.html
*/
/*
  FIXME(polymer-modulizer): the above comments were extracted
  from HTML and may be out of place here. Review them and
  then delete this comment!
*/
import '@polymer/polymer/polymer-legacy.js';

import { IronA11yKeysBehavior } from '@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js';
import { dom } from '@polymer/polymer/lib/legacy/polymer.dom.js';
import { Polymer } from '@polymer/polymer/lib/legacy/polymer-fn.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';

var Utility = {
  distance: function(x1, y1, x2, y2) {
    var xDelta = (x1 - x2);
    var yDelta = (y1 - y2);

    return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
  },

  now: window.performance && window.performance.now ?
      window.performance.now.bind(window.performance) :
      Date.now
};

/**
 * @param {HTMLElement} element
 * @constructor
 */
function ElementMetrics(element) {
  this.element = element;
  this.width = this.boundingRect.width;
  this.height = this.boundingRect.height;

  this.size = Math.max(this.width, this.height);
}

ElementMetrics.prototype = {
  get boundingRect() {
    return this.element.getBoundingClientRect();
  },

  furthestCornerDistanceFrom: function(x, y) {
    var topLeft = Utility.distance(x, y, 0, 0);
    var topRight = Utility.distance(x, y, this.width, 0);
    var bottomLeft = Utility.distance(x, y, 0, this.height);
    var bottomRight = Utility.distance(x, y, this.width, this.height);

    return Math.max(topLeft, topRight, bottomLeft, bottomRight);
  }
};

/**
 * @param {HTMLElement} element
 * @constructor
 */
function Ripple(element) {
  this.element = element;
  this.color = window.getComputedStyle(element).color;

  this.wave = document.createElement('div');
  this.waveContainer = document.createElement('div');
  this.wave.style.backgroundColor = this.color;
  this.wave.classList.add('wave');
  this.waveContainer.classList.add('wave-container');
  dom(this.waveContainer).appendChild(this.wave);

  this.resetInteractionState();
}

Ripple.MAX_RADIUS = 300;

Ripple.prototype = {
  get recenters() {
    return this.element.recenters;
  },

  get center() {
    return this.element.center;
  },

  get mouseDownElapsed() {
    var elapsed;

    if (!this.mouseDownStart) {
      return 0;
    }

    elapsed = Utility.now() - this.mouseDownStart;

    if (this.mouseUpStart) {
      elapsed -= this.mouseUpElapsed;
    }

    return elapsed;
  },

  get mouseUpElapsed() {
    return this.mouseUpStart ? Utility.now() - this.mouseUpStart : 0;
  },

  get mouseDownElapsedSeconds() {
    return this.mouseDownElapsed / 1000;
  },

  get mouseUpElapsedSeconds() {
    return this.mouseUpElapsed / 1000;
  },

  get mouseInteractionSeconds() {
    return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
  },

  get initialOpacity() {
    return this.element.initialOpacity;
  },

  get opacityDecayVelocity() {
    return this.element.opacityDecayVelocity;
  },

  get radius() {
    var width2 = this.containerMetrics.width * this.containerMetrics.width;
    var height2 = this.containerMetrics.height * this.containerMetrics.height;
    var waveRadius =
        Math.min(Math.sqrt(width2 + height2), Ripple.MAX_RADIUS) * 1.1 + 5;

    var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
    var timeNow = this.mouseInteractionSeconds / duration;
    var size = waveRadius * (1 - Math.pow(80, -timeNow));

    return Math.abs(size);
  },

  get opacity() {
    if (!this.mouseUpStart) {
      return this.initialOpacity;
    }

    return Math.max(
        0,
        this.initialOpacity -
            this.mouseUpElapsedSeconds * this.opacityDecayVelocity);
  },

  get outerOpacity() {
    // Linear increase in background opacity, capped at the opacity
    // of the wavefront (waveOpacity).
    var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
    var waveOpacity = this.opacity;

    return Math.max(0, Math.min(outerOpacity, waveOpacity));
  },

  get isOpacityFullyDecayed() {
    return this.opacity < 0.01 &&
        this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
  },

  get isRestingAtMaxRadius() {
    return this.opacity >= this.initialOpacity &&
        this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
  },

  get isAnimationComplete() {
    return this.mouseUpStart ? this.isOpacityFullyDecayed :
                               this.isRestingAtMaxRadius;
  },

  get translationFraction() {
    return Math.min(
        1, this.radius / this.containerMetrics.size * 2 / Math.sqrt(2));
  },

  get xNow() {
    if (this.xEnd) {
      return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
    }

    return this.xStart;
  },

  get yNow() {
    if (this.yEnd) {
      return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
    }

    return this.yStart;
  },

  get isMouseDown() {
    return this.mouseDownStart && !this.mouseUpStart;
  },

  resetInteractionState: function() {
    this.maxRadius = 0;
    this.mouseDownStart = 0;
    this.mouseUpStart = 0;

    this.xStart = 0;
    this.yStart = 0;
    this.xEnd = 0;
    this.yEnd = 0;
    this.slideDistance = 0;

    this.containerMetrics = new ElementMetrics(this.element);
  },

  draw: function() {
    var scale;
    var dx;
    var dy;

    this.wave.style.opacity = this.opacity;

    scale = this.radius / (this.containerMetrics.size / 2);
    dx = this.xNow - (this.containerMetrics.width / 2);
    dy = this.yNow - (this.containerMetrics.height / 2);

    // 2d transform for safari because of border-radius and overflow:hidden
    // clipping bug. https://bugs.webkit.org/show_bug.cgi?id=98538
    this.waveContainer.style.webkitTransform =
        'translate(' + dx + 'px, ' + dy + 'px)';
    this.waveContainer.style.transform =
        'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
    this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
    this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
  },

  /** @param {Event=} event */
  downAction: function(event) {
    var xCenter = this.containerMetrics.width / 2;
    var yCenter = this.containerMetrics.height / 2;

    this.resetInteractionState();
    this.mouseDownStart = Utility.now();

    if (this.center) {
      this.xStart = xCenter;
      this.yStart = yCenter;
      this.slideDistance =
          Utility.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
    } else {
      this.xStart = event ?
          event.detail.x - this.containerMetrics.boundingRect.left :
          this.containerMetrics.width / 2;
      this.yStart = event ?
          event.detail.y - this.containerMetrics.boundingRect.top :
          this.containerMetrics.height / 2;
    }

    if (this.recenters) {
      this.xEnd = xCenter;
      this.yEnd = yCenter;
      this.slideDistance =
          Utility.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
    }

    this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
        this.xStart, this.yStart);

    this.waveContainer.style.top =
        (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
    this.waveContainer.style.left =
        (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';

    this.waveContainer.style.width = this.containerMetrics.size + 'px';
    this.waveContainer.style.height = this.containerMetrics.size + 'px';
  },

  /** @param {Event=} event */
  upAction: function(event) {
    if (!this.isMouseDown) {
      return;
    }

    this.mouseUpStart = Utility.now();
  },

  remove: function() {
    dom(this.waveContainer.parentNode).removeChild(this.waveContainer);
  }
};

Polymer({
  _template: html`

`,

  is: 'paper-ripple',
  behaviors: [IronA11yKeysBehavior],

  properties: {
    /**
     * The initial opacity set on the wave.
     *
     * @attribute initialOpacity
     * @type number
     * @default 0.25
     */
    initialOpacity: {type: Number, value: 0.25},

    /**
     * How fast (opacity per second) the wave fades out.
     *
     * @attribute opacityDecayVelocity
     * @type number
     * @default 0.8
     */
    opacityDecayVelocity: {type: Number, value: 0.8},

    /**
     * If true, ripples will exhibit a gravitational pull towards
     * the center of their container as they fade away.
     *
     * @attribute recenters
     * @type boolean
     * @default false
     */
    recenters: {type: Boolean, value: false},

    /**
     * If true, ripples will center inside its container
     *
     * @attribute recenters
     * @type boolean
     * @default false
     */
    center: {type: Boolean, value: false},

    /**
     * A list of the visual ripples.
     *
     * @attribute ripples
     * @type Array
     * @default []
     */
    ripples: {
      type: Array,
      value: function() {
        return [];
      }
    },

    /**
     * True when there are visible ripples animating within the
     * element.
     */
    animating:
        {type: Boolean, readOnly: true, reflectToAttribute: true, value: false},

    /**
     * If true, the ripple will remain in the "down" state until `holdDown`
     * is set to false again.
     */
    holdDown: {type: Boolean, value: false, observer: '_holdDownChanged'},

    /**
     * If true, the ripple will not generate a ripple effect
     * via pointer interaction.
     * Calling ripple's imperative api like `simulatedRipple` will
     * still generate the ripple effect.
     */
    noink: {type: Boolean, value: false},

    _animating: {type: Boolean},

    _boundAnimate: {
      type: Function,
      value: function() {
        return this.animate.bind(this);
      }
    }
  },

  get target() {
    return this.keyEventTarget;
  },

  /**
   * @type {!Object}
   */
  keyBindings: {
    'enter:keydown': '_onEnterKeydown',
    'space:keydown': '_onSpaceKeydown',
    'space:keyup': '_onSpaceKeyup'
  },

  attached: function() {
    // Set up a11yKeysBehavior to listen to key events on the target,
    // so that space and enter activate the ripple even if the target doesn't
    // handle key events. The key handlers deal with `noink` themselves.
    if (this.parentNode.nodeType == 11) {  // DOCUMENT_FRAGMENT_NODE
      this.keyEventTarget = dom(this).getOwnerRoot().host;
    } else {
      this.keyEventTarget = this.parentNode;
    }
    var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget);
    this.listen(keyEventTarget, 'up', 'uiUpAction');
    this.listen(keyEventTarget, 'down', 'uiDownAction');
  },

  detached: function() {
    this.unlisten(this.keyEventTarget, 'up', 'uiUpAction');
    this.unlisten(this.keyEventTarget, 'down', 'uiDownAction');
    this.keyEventTarget = null;
  },

  get shouldKeepAnimating() {
    for (var index = 0; index < this.ripples.length; ++index) {
      if (!this.ripples[index].isAnimationComplete) {
        return true;
      }
    }

    return false;
  },

  simulatedRipple: function() {
    this.downAction(null);

    // Please see polymer/polymer#1305
    this.async(function() {
      this.upAction();
    }, 1);
  },

  /**
   * Provokes a ripple down effect via a UI event,
   * respecting the `noink` property.
   * @param {Event=} event
   */
  uiDownAction: function(event) {
    if (!this.noink) {
      this.downAction(event);
    }
  },

  /**
   * Provokes a ripple down effect via a UI event,
   * *not* respecting the `noink` property.
   * @param {Event=} event
   */
  downAction: function(event) {
    if (this.holdDown && this.ripples.length > 0) {
      return;
    }

    var ripple = this.addRipple();

    ripple.downAction(event);

    if (!this._animating) {
      this._animating = true;
      this.animate();
    }
  },

  /**
   * Provokes a ripple up effect via a UI event,
   * respecting the `noink` property.
   * @param {Event=} event
   */
  uiUpAction: function(event) {
    if (!this.noink) {
      this.upAction(event);
    }
  },

  /**
   * Provokes a ripple up effect via a UI event,
   * *not* respecting the `noink` property.
   * @param {Event=} event
   */
  upAction: function(event) {
    if (this.holdDown) {
      return;
    }

    this.ripples.forEach(function(ripple) {
      ripple.upAction(event);
    });

    this._animating = true;
    this.animate();
  },

  onAnimationComplete: function() {
    this._animating = false;
    this.$.background.style.backgroundColor = null;
    this.fire('transitionend');
  },

  addRipple: function() {
    var ripple = new Ripple(this);

    dom(this.$.waves).appendChild(ripple.waveContainer);
    this.$.background.style.backgroundColor = ripple.color;
    this.ripples.push(ripple);

    this._setAnimating(true);

    return ripple;
  },

  removeRipple: function(ripple) {
    var rippleIndex = this.ripples.indexOf(ripple);

    if (rippleIndex < 0) {
      return;
    }

    this.ripples.splice(rippleIndex, 1);

    ripple.remove();

    if (!this.ripples.length) {
      this._setAnimating(false);
    }
  },

  /**
   * This conflicts with Element#antimate().
   * https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
   * @suppress {checkTypes}
   */
  animate: function() {
    if (!this._animating) {
      return;
    }
    var index;
    var ripple;

    for (index = 0; index < this.ripples.length; ++index) {
      ripple = this.ripples[index];

      ripple.draw();

      this.$.background.style.opacity = ripple.outerOpacity;

      if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
        this.removeRipple(ripple);
      }
    }

    if (!this.shouldKeepAnimating && this.ripples.length === 0) {
      this.onAnimationComplete();
    } else {
      window.requestAnimationFrame(this._boundAnimate);
    }
  },

  _onEnterKeydown: function() {
    this.uiDownAction();
    this.async(this.uiUpAction, 1);
  },

  _onSpaceKeydown: function() {
    this.uiDownAction();
  },

  _onSpaceKeyup: function() {
    this.uiUpAction();
  },

  // note: holdDown does not respect noink since it can be a focus based
  // effect.
  _holdDownChanged: function(newVal, oldVal) {
    if (oldVal === undefined) {
      return;
    }
    if (newVal) {
      this.downAction();
    } else {
      this.upAction();
    }
  }

  /**
  Fired when the animation finishes.
  This is useful if you want to wait until
  the ripple animation finishes to perform some action.

  @event transitionend
  @param {{node: Object}} detail Contains the animated node.
  */
});

redpajama_set_name: RedPajamaGithub


text:
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package com.android.webview.chromium;

import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.WebViewRenderProcess;
import android.webkit.WebViewRenderProcessClient;

import org.chromium.android_webview.AwRenderProcess;
import org.chromium.base.annotations.VerifiesOnQ;

import java.util.concurrent.Executor;

/**
 * Utility class to use new APIs that were added in Q (API level 29). These need to exist in a
 * separate class so that Android framework can successfully verify glue layer classes without
 * encountering the new APIs. Note that GlueApiHelper is only for APIs that cannot go to ApiHelper
 * in base/, for reasons such as using system APIs or instantiating an adapter class that is
 * specific to glue layer.
 */
@VerifiesOnQ
@TargetApi(Build.VERSION_CODES.Q)
public final class GlueApiHelperForQ {
    private GlueApiHelperForQ() {}

    /** @see {@link WebView#getWebViewRenderProcess()} */
    public static WebViewRenderProcess getWebViewRenderProcess(AwRenderProcess awRenderProcess) {
        return WebViewRenderProcessAdapter.getInstanceFor(awRenderProcess);
    }

    /**
     * @see {@link WebView#setWebViewRenderProcessClient(Executor,
     * WebViewRenderProcessClient)}
     */
    public static void setWebViewRenderProcessClient(SharedWebViewChromium sharedWebViewChromium,
            Executor executor, WebViewRenderProcessClient client) {
        sharedWebViewChromium.setWebViewRendererClientAdapter(
                new WebViewRenderProcessClientAdapter(executor, client));
    }

    /** @see {@link WebView#getWebViewRenderProcessClient() */
    public static WebViewRenderProcessClient getWebViewRenderProcessClient(
            SharedWebViewRendererClientAdapter adapter) {
        return ((WebViewRenderProcessClientAdapter) adapter).getWebViewRenderProcessClient();
    }
}


text:
package com.google.cloud.solutions.bqremoteencryptionfn;

import java.util.List;

/** Interface describing a general contract for any tokenization algorithm. */
public interface TransformFn {

  /**
   * Returns Deidentified/Encrypted list of strings for the provided list of messages using the
   * specific tokenization technique. The order of output should be the same as the order of input.
   *
   * @param rows UTF-8 encoded message strings
   * @return the deidentified list of messages in the same order as input.
   * @throws Exception when any exception occours in tokenization.
   */
  List<String> deidentify(List<List<Object>> rows) throws Exception;

  /**
   * Returns Reidentified/Decrypted list of strings for the provided list of encrypted messages
   * using the specific tokenization technique. The order of output should be the same as the order
   * of input.
   *
   * @param rows UTF-8 encoded encrypted message strings
   * @return the ReIdentified/Decrypted list of messages in the same order as input.
   * @throws Exception when any exception occurs or the input messages are not in the same
   *     encryption format.
   */
  List<String> reidentify(List<List<Object>> rows) throws Exception;

  /**
   * Returns the name of the encryption algorithm implemented. Needs to be unique for all loaded
   * function classes.
   */
  String getName();
}


text:
class Tbb < Formula
  desc "Rich and complete approach to parallelism in C++"
  homepage "https://www.threadingbuildingblocks.org/"
  url "https://www.threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb2017_20160916oss_src.tgz"
  version "4.4-20160916"
  sha256 "600c67f1c5a730e411b3bf2e792cfe5808f40c4f0354a9b613827fff0ef27fc4"

  bottle do
    cellar :any
    sha256 "73c1cd36c36438422b4ef200d8928c2d22145bc17ec6b2548039d80ab6107dfe" => :sierra
    sha256 "6cb9dd98e549c62298aeadfca71acdaf487b03fcce9f51ad3955714b5017a79a" => :el_capitan
    sha256 "ede1550b833c4e2f9aad3d3d33edb07bbfea2dac58d0ef8237b5386c12cca11a" => :yosemite
  end

  option :cxx11

  # requires malloc features first introduced in Lion
  # https://github.com/Homebrew/homebrew/issues/32274
  depends_on :macos => :lion
  depends_on :python if MacOS.version <= :snow_leopard
  depends_on "swig" => :build

  def install
    # Intel sets varying O levels on each compile command.
    ENV.no_optimization

    compiler = ENV.compiler == :clang ? "clang" : "gcc"
    args = %W[tbb_build_prefix=BUILDPREFIX compiler=#{compiler}]

    if build.cxx11?
      ENV.cxx11
      args << "cpp0x=1" << "stdlib=libc++"
    end

    system "make", *args
    lib.install Dir["build/BUILDPREFIX_release/*.dylib"]
    include.install "include/tbb"

    cd "python" do
      ENV["TBBROOT"] = prefix
      system "python", *Language::Python.setup_install_args(prefix)
    end
  end

  test do
    (testpath/"test.cpp").write <<-EOS.undent
      #include <tbb/task_scheduler_init.h>
      #include <iostream>

      int main()
      {
        std::cout << tbb::task_scheduler_init::default_num_threads();
        return 0;
      }
    EOS
    system ENV.cxx, "test.cpp", "-ltbb", "-o", "test"
    system "./test"
  end
end


text:
translating---geekpi

Dockerizing Compiled Software ┈ Tianon's Ramblings ✿
======
I recently went through a stint of closing a huge number of issues in the [docker-library/php][1] repository, and one of the oldest (and longest) discussions was related to installing depedencies for a compiling extensions, and I wrote a [semi-long comment][2] explaining how I do this in a general way for any software I wish to Dockerize.

I'm going to copy most of that comment here and perhaps expand a little bit more in order to have a better/cleaner place to link to!

The first step I take is to write the naive version of the `Dockerfile`: download the source, run `./configure && make` etc, clean up. I then try building my naive creation, and in doing so hope for an error message. (yes, really!)

The error message will usually take the form of something like `error: could not find "xyz.h"` or `error: libxyz development headers not found`.

If I'm building in Debian, I'll hit `https://packages.debian.org/file:xyz.h` (replacing "xyz.h" with the name of the header file from the error message), or even just Google something like "xyz.h debian", to figure out the name of the package I require.

If I'm building in Alpine, I'll use <https://pkgs.alpinelinux.org/contents> to perform a similar search.

The same works to some extent for "libxyz development headers", but in my experience Google works better for those since different distributions and projects will call these development packages by different names, so sometimes it's a little harder to figure out exactly which one is the "right" one to install.

Once I've got a package name, I add that package name to my `Dockerfile`, rinse, and repeat. Eventually, this usually leads to a successful build. Occationally I find that some library either isn't in Debian or Alpine, or isn't new enough, and I've also got to build it from source, but those instances are rare in my own experience - YMMV.

I'll also often check the source for the Debian (via <https://sources.debian.org>) or Alpine (via <https://git.alpinelinux.org/cgit/aports/tree>) package of the software I'm looking to compile, especially paying attention to `Build-Depends` (ala [`php7.0=7.0.26-1`'s `debian/control` file][3]) and/or `makedepends` (ala [`php7`'s `APKBUILD` file][4]) for package name clues.

Personally, I find this sort of detective work interesting and rewarding, but I realize I'm probably a bit of a unique creature. Another good technique I use occationally is to determine whether anyone else has already Dockerized the thing I'm trying to, so I can simply learn directly from their `Dockerfile` which packages I'll need to install.

For the specific case of PHP extensions, there's almost always someone who's already figured out what's necessary for this or that module, and all I have to do is some light detective work to find them.

Anyways, that's my method! Hope it's helpful, and happy hunting!

--------------------------------------------------------------------------------

via: https://tianon.github.io/post/2017/12/26/dockerize-compiled-software.html

作者:[Tianon Gravi][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

[a]:https://tianon.github.io
[1]:https://github.com/docker-library/php
[2]:https://github.com/docker-library/php/issues/75#issuecomment-353673374
[3]:https://sources.debian.org/src/php7.0/7.0.26-1/debian/control/
[4]:https://git.alpinelinux.org/cgit/aports/tree/community/php7/APKBUILD?id=d0ca197f031f96d4664cafaa618aeccf94640a1e


text:
require 'rails_helper'

describe 'articles/index', type: :view do
  before do
    controller.singleton_class.class_eval do
      protected

      def articles
        [Article.create!(name:        'Name',
                         description: 'Description'),
         Article.create!(name:        'Name',
                         description: 'Description')]
      end
      helper_method :articles
    end
  end

  it 'renders a list of articles' do
    render
    assert_select 'tr>td', text: 'Name',        count: 2
    assert_select 'tr>td', text: 'Description', count: 2
  end
end


text:
Regex Parser Operator
=====================

## Operator Objective
**RegexParser** is designed to parse records based on a regex pattern and construct a concrete java class also known as ["POJO"](https://en.wikipedia.org/wiki/Plain_Old_Java_Object) out of it. User needs to provide the regex pattern and schema definition to describe the data pattern. Based on regex pattern, the operator will split the data and then schema definition will be used to map the incoming record to POJO. User can also provide date format if any, in the schema. The supported constraints are listed in [constraints table](#constraints).

The regex pattern has to match the tuple in its entirety. Valid records will be emitted as POJOs while invalid ones are emitted on the error port with an error message if the corresponding ports are connected.

**Note**: field names of POJO must match field names in schema and in the same order as it appears in the incoming data.

## Overview
The operator is **idempotent**, **fault-tolerant** and **partitionable**.

## Operator Information
1. Operator location: **_malhar-contrib_**
2. Available since: **_3.7.0_**
3. Operator state: **_Evolving_**
4. Java Package: [com.datatorrent.contrib.parser.RegexParser](https://github.com/apache/apex-malhar/blob/master/contrib/src/main/java/com/datatorrent/contrib/parser/RegexParser.java)

## Properties of RegexParser
User needs to set the `schema` JSON string which describes data as well as specify the format on date fields if any.
**Note**: In the examples below {ApplicationName} and {OperatorName} are placeholders for the respective names of the application and the operator.

e.g.

```xml
    <property>
        <name>dt.application.{ApplicationName}.operator.{OperatorName}.prop.schema</name>
        <value>{
                 "fields": [
                   {
                     "name": "date",
                     "type": "Date",
                     "constraints": {
                       "format": "yyyy:MM:dd:hh:mm:ss"
                     }
                   },
                   {
                     "name": "id",
                     "type": "Integer"
                   },
                   {
                     "name": "signInId",
                     "type": "String"
                   },
                   {
                     "name": "ipAddress",
                     "type": "String"
                   },
                   {
                     "name": "serviceId",
                     "type": "Double"
                   },
                   {
                     "name": "accountId",
                     "type": "Long"
                   },
                   {
                     "name": "platform",
                     "type": "Boolean"
                   }
                 ]
               }
        </value>
    </property>
```

Note that `Boolean` type in the above example accepts case insensitive values for either true or false.

User needs to set the `splitRegexPattern` property whose value is the regular expression that describes the pattern of the incoming data.
Below is the example for setting `splitRegexPattern` from `properties.xml` of the application.

```xml
    <property>
        <name>dt.application.{ApplicationName}.operator.{OperatorName}.prop.splitRegexPattern</name>
        <value>.+\[SEQ=\w+\]\s*(\d+:[\d\d:]+)\s(\d+)\s* sign-in_id=(\S+) .*ip_address=(\S+).* service_id=(\S+).*account_id=(\S+).*platform=(\S+)</value>
    </property>
```

| **Property** | **Description** | **Type** | **Mandatory** | **Default Value** |
| -------- | ----------- | ---- | ------------------ | ------------- |
| *schema* | [Schema](https://github.com/apache/apex-malhar/blob/master/contrib/src/main/java/com/datatorrent/contrib/parser/DelimitedSchema.java)  describing data (see example above) | String | YES | N/A |
| *splitRegexPattern* | regex expression that describes the pattern of incoming data | String | YES | N/A |

## Platform Attributes that influence operator behavior

| **Attribute** | **Description** | **Type** | **Mandatory** |
| -------- | ----------- | ---- | ------------------ |
| *TUPLE_CLASS* | TUPLE_CLASS attribute on output port which tells operator the class of POJO which need to be emitted | Class| Yes |

The operator takes care of converting the byte array received on the input port to a string by decoding using the JVM's default `Charset`. Then, splits the string using the `splitRegexPattern` and populates an object using the `schema`. Apex platform converts this object to the object of `TUPLE_CLASS` attribute value while emitting.

Below is the example for setting `TUPLE_CLASS` attribute on output port from `properties.xml` file of the application.

```xml
    <property>
        <name>dt.application.{ApplicationName}.operator.{OperatorName}.port.out.attr.TUPLE_CLASS</name>
        <value>com.datatorrent.tutorial.regexparser.ServerLog</value>
    </property>
```

Below is the example for setting `TUPLE_CLASS` attribute on output port from `Application.java` file of the application.

```java
RegexParser regexParser = dag.addOperator("regexParser", RegexParser.class);
dag.setOutputPortAttribute(regexParser.out, Context.PortContext.TUPLE_CLASS, ServerLog.class);
```

where the value (ServerLog) set above is the expected output POJO class from the operator and example is as below.

```java
    public class ServerLog
    {
      private Date date;
      private int id;
      private String signInId;
      private String ipAddress;
      private double serviceId;
      private long accountId;
      private boolean platform;
      public int getId()
      {
        return id;
      }
      public void setId(int id)
      {
        this.id = id;
      }
      public Date getDate()
      {
        return date;
      }
      public void setDate(Date date)
      {
        this.date = date;
      }
      public String getSignInId()
      {
        return signInId;
      }
      public void setSignInId(String signInId)
      {
        this.signInId = signInId;
      }
      public String getIpAddress()
      {
        return ipAddress;
      }
      public void setIpAddress(String ipAddress)
      {
        this.ipAddress = ipAddress;
      }
      public double getServiceId()
      {
        return serviceId;
      }
      public void setServiceId(double serviceId)
      {
        this.serviceId = serviceId;
      }
      public long getAccountId()
      {
        return accountId;
      }
      public void setAccountId(long accountId)
      {
        this.accountId = accountId;
      }
      public boolean getPlatform()
      {
        return platform;
      }
      public void setPlatform(boolean platform)
      {
        this.platform = platform;
      }
    }
```

Let us look at how the data gets populated into the POJO using the example `schema`, `splitRegexPattern` and `TUPLE_CLASS` definitions given above.

Consider sample event log as below that matches with the `splitRegexPattern`.

```
2015-10-01T03:14:49.000-07:00 lvn-d1-dev DevServer[9876]: INFO: [EVENT][SEQ=248717] 2015:10:01:03:14:49 101 sign-in_id=11111@psop.com ip_address=1.1.1.1  service_id=IP1234-NPB12345_00 result=RESULT_SUCCES console_id=0000000138e91b4e58236bf32besdafasdfasdfasdfsadf  account_id=11111  platform=pik
```

The below images depict the expression match on the data. The parentheses corresponding to [capturing groups](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#cg) are highlighted in green and each such group corresponds to one field of the POJO. There will be 7 such groups in the current example.

![Regular Expression pattern match](images/regexparser/regexcapturedgroups.png)

The matched data in the event log is highlighted with 7 different colors below.

![Matched Log Data](images/regexparser/logcapturedgroups.png)

The matched fields above will be populated onto an object based on the `schema` definition defined above. Object population will be based on one to one mapping from matched data to `schema` definition fields in the match order. Once the object is populated, it will be converted to the `TUPLE_CLASS` type while emitting on the output port `out` by the Apex platform.

## Supported DataTypes in Schema
  * Integer
  * Long
  * Double
  * Character
  * String
  * Boolean
  * Date
  * Float

## Schema Constraints

Only Date constraints are supported by the operator as of now.

|**DataType**| **Constraints** | **Description** |
| -------- | -------- | -------- |
| *Date* | format | A simple date format as specified in the [SimpleDateFormat](http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html?is-external=true) class |

## Ports

| **Port** | **Description** | **Type** | **Mandatory** |
| -------- | ----------- | ---- | ------------------ |
| *in* | Tuples that needs to be parsed are received on this port | byte[] | Yes |
| *out* | Valid tuples that are emitted as POJO | Object (POJO) | No |
| *err* | Invalid tuples are emitted with error message | KeyValPair <String, String\> | No |

## Partitioning
Regex Parser can be statically or dynamically partitioned.
### Static Partitioning
This can be achieved in the below 2 ways.

Specifying the partitioner and number of partitions in the populateDAG() method.
```java
    RegexParser regexParser = dag.addOperator("regexParser", RegexParser.class);
    StatelessPartitioner<RegexParser> partitioner = new StatelessPartitioner<RegexParser>(2);
    dag.setAttribute(regexParser, Context.OperatorContext.PARTITIONER, partitioner);
```
Specifying the partitioner in properties file.
```xml
    <property>
        <name>dt.application.{ApplicationName}.operator.{OperatorName}.attr.PARTITIONER</name>
        <value>com.datatorrent.common.partitioner.StatelessPartitioner:2</value>
    </property>
```

Above lines will partition RegexParser statically 2 times. Above value can be changed accordingly to change the number of static partitions.

### Dynamic Partitioning
RegexParser can be dynamically partitioned using the out-of-the-box partitioner:

#### Throughput based
Following code can be added to the `populateDAG` method of application to dynamically partition RegexParser:
```java
    RegexParser regexParser = dag.addOperator("regexParser", RegexParser.class);
    StatelessThroughputBasedPartitioner<RegexParser> partitioner = new StatelessThroughputBasedPartitioner<>();
    partitioner.setCooldownMillis(conf.getLong(COOL_DOWN_MILLIS, 10000));
    partitioner.setMaximumEvents(conf.getLong(MAX_THROUGHPUT, 30000));
    partitioner.setMinimumEvents(conf.getLong(MIN_THROUGHPUT, 10000));
    dag.setAttribute(regexParser, OperatorContext.STATS_LISTENERS, Arrays.asList(new StatsListener[]{partitioner}));
    dag.setAttribute(regexParser, OperatorContext.PARTITIONER, partitioner);
```

Above code will dynamically partition RegexParser when the throughput changes.
If the overall throughput of regexParser goes beyond 30000 or less than 10000, the platform will repartition RegexParser
to balance throughput of a single partition to be between 10000 and 30000.
CooldownMillis of 10000 will be used as the threshold time for which the throughput change is observed.

## Example

Coming Soon


text: You do not have any items in your wishlist


text:
package org.apache.shiro.crypto.hash;

import java.util.Random;
import java.util.Set;

/**
 * Service Provider Interface for password hashing algorithms.
 *
 *
Apache Shiro will load algorithm implementations based on the method {@link #getImplementedAlgorithms()}.
 * Loaded providers are expected to return a suitable hash implementation.

 *
 *
Modern kdf-based hash implementations can extend the {@link AbstractCryptHash} class.

 *
 * @since 2.0
 */
public interface HashSpi {

    /**
     * A list of algorithms recognized by this implementation.
     *
     *
Example values are {@code argon2id} and {@code argon2i} for the Argon2 service provider and
     * {@code 2y} and {@code 2a} for the BCrypt service provider.

     *
     * @return a set of recognized algorithms.
     */
    Set<String> getImplementedAlgorithms();

    /**
     * Creates a Hash instance from the given format string recognized by this provider.
     *
     *
There is no global format which this provider must accept. Each provider can define their own
     * format, but they are usually based on the {@code crypt(3)} formats used in {@code /etc/shadow} files.

     *
     *
Implementations should overwrite this javadoc to add examples of the accepted formats.

     *
     * @param format the format string to be parsed by this implementation.
     * @return a class extending Hash.
     */
    Hash fromString(String format);

    /**
     * A factory class for the hash of the type {@code <T>}.
     *
     *
Implementations are highly encouraged to use the given random parameter as
     * source of random bytes (e.g. for seeds).

     *
     * @param random a source of {@link Random}, usually {@code SecureRandom}.
     * @return a factory class for creating instances of {@code <T>}.
     */
    HashFactory newHashFactory(Random random);

    interface HashFactory {

        /**
         * Generates a hash from the given hash request.
         *
         *
If the hash requests' optional parameters are not set, the {@link HashFactory} implementation
         * should use default parameters where applicable.

         *
If the hash requests' salt is missing or empty, the implementation should create a salt
         * with a default size.

         * @param hashRequest the request to build a Hash from.
         * @return a generated Hash according to the specs.
         * @throws IllegalArgumentException if any of the parameters is outside of valid boundaries (algorithm-specific)
         * or if the given algorithm is not applicable for this {@link HashFactory}.
         */
        Hash generate(HashRequest hashRequest);
    }
}


text:
#include "local.h"

/*!
 * \addtogroup modules
 *
 * @{
 */

/*!
 * @param g_class \class{GObject} class.
 * Initialize interface.
 */
static void cream_module_base_init (gpointer g_class)
{
     static gboolean is_initialized = FALSE;

     if (!is_initialized)
     {
          GParamSpec *pspec;

          pspec = g_param_spec_string ("name", "Name", "Module's name", "(none)", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
          g_object_interface_install_property (g_class, pspec);

          is_initialized = TRUE;
     }
}

/*! @return The interface's \class{GType} */
GType cream_module_get_type (void)
{
     static GType iface_type = 0;

     if (iface_type == 0)
     {
          static const GTypeInfo info =
          {
               sizeof (CreamModuleIface),
               cream_module_base_init,
               NULL
          };

          iface_type = g_type_register_static (G_TYPE_INTERFACE, "CreamModule", &info, 0);
     }

     return iface_type;
}

/*! Initialize modules. */
void modules_init (void)
{
     GObject *mod = NULL;

#ifdef HAVE_MOD_DUMMY
     mod = g_object_new (CREAM_MODULE_TYPE_DUMMY, "name", "dummy", NULL);
#endif
#ifdef HAVE_MOD_WEBKIT
     mod = g_object_new (CREAM_MODULE_TYPE_WEBKIT, "name", "webkit", NULL);
#endif

     (void) mod;
}

/*! @} */

/*!
 * \defgroup mod-members Members
 * \ingroup modules
 * @{
 */

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @return A new widget to be used in #WebView.
 */
GtkWidget *cream_module_webview_new (CreamModule *self)
{
     CreamModuleIface *iface;
     g_return_val_if_fail (CREAM_IS_MODULE (self), NULL);
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_val_if_fail (iface->webview_new != NULL, NULL);
     return iface->webview_new (self);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 * @param uri URI to load, see #UriScheme.
 */
void cream_module_load_uri (CreamModule *self, GtkWidget *webview, UriScheme *uri)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->load_uri != NULL);
     iface->load_uri (self, webview, uri);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 */
void cream_module_reload (CreamModule *self, GtkWidget *webview)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->reload != NULL);
     iface->reload (self, webview);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 */
gboolean cream_module_can_go_back (CreamModule *self, GtkWidget *webview)
{
     CreamModuleIface *iface;
     g_return_val_if_fail (CREAM_IS_MODULE (self), FALSE);
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_val_if_fail (iface->can_go_back != NULL, FALSE);
     return iface->can_go_back (self, webview);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 */
void cream_module_backward (CreamModule *self, GtkWidget *webview)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->backward != NULL);
     iface->backward (self, webview);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 */
gboolean cream_module_can_go_forward (CreamModule *self, GtkWidget *webview)
{
     CreamModuleIface *iface;
     g_return_val_if_fail (CREAM_IS_MODULE (self), FALSE);
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_val_if_fail (iface->can_go_forward != NULL, FALSE);
     return iface->can_go_forward (self, webview);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 */
void cream_module_forward (CreamModule *self, GtkWidget *webview)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->forward != NULL);
     iface->forward (self, webview);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param webview A webview.
 * @param text Text to look for.
 * @param forward Search forward or backward.
 * @return \c TRUE on success, \c FALSE otherwise.
 *
 * Looks for a specified string inside webview.
 */
gboolean cream_module_search (CreamModule *self, GtkWidget *webview, const gchar *text, gboolean forward)
{
     CreamModuleIface *iface;
     g_return_val_if_fail (CREAM_IS_MODULE (self), FALSE);
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_val_if_fail (iface->search != NULL, FALSE);
     return iface->search (self, webview, text, forward);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param uri Proxy URL.
 *
 * Set uri as a proxy in the module.
 */
void cream_module_proxy (CreamModule *self, const gchar *uri)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->proxy != NULL);
     iface->proxy (self, uri);
}

/*!
 * \public \memberof CreamModule
 * @param self The module to use.
 * @param ua Useragent.
 *
 * Set the useragent.
 */
void cream_module_useragent (CreamModule *self, const gchar *ua)
{
     CreamModuleIface *iface;
     g_return_if_fail (CREAM_IS_MODULE (self));
     iface = CREAM_MODULE_GET_INTERFACE (self);
     g_return_if_fail (iface->useragent != NULL);
     iface->useragent (self, ua);
}

/*! @} */


text:
import unittest

"""
NOTES
-----
In order to run the tests, you have to enter in your login credentials
for either MySQL or PostgreSQL.
Then, you can run either one of the following commands from the root directory
$ sudo python setup.py test
$ nosetests

You can enter in manual test data below in the TestInfo class

"""

# add your credentials to credentials.py in order to run tests
import credentials as creds

# You can edit variable info to test for using this class
class TestInfo:
  def __init__(self):
    self.uname = ""
    self.dbname = ""
    self.pw = ""
    self.query_test = "SELECT * FROM moretest;"
    self.table_test = "cats"

# Main test class
class TestDB(unittest.TestCase):

  def setUp(self):
    login = TestInfo()

    postgres = True #make True for psql and False for mysql

    if postgres:
      # print "\n\n****************POSTGRES*****************"
      from viewmydb import psqlDB
      self.db = psqlDB.Psql()
      login.uname = creds.psqluname
      login.dbname = creds.psqldbname
      login.pw = creds.psqlpass
    else:
      # print "\n\n******************MYSQL******************"
      from viewmydb import mysqlDB
      self.db = mysqlDB.MYsql()
      login.uname = creds.mysqluname
      login.dbname = creds.mysqldbname
      login.pw = creds.mysqlpass

    self.login = login
    self.conn = self.db.connectdb(login.dbname, login.uname, login.pw)
    self.tablenames = self.db.gettables(self.conn)

  def tearDown(self):
    if self.conn != -1:
      self.conn.close()

# ------------------------------------------------
# tests
# ------------------------------------------------
  def test_connect_db(self):
    self.assertNotEqual(self.conn, -1, "Could not connect to DB")

  def test_tablenames(self):
    self.assertNotEqual(self.tablenames, -1, "Could not get table names")

  def test_columnnames(self):
    self.assertNotEqual(self.db.getcolnames(self.conn, self.tablenames[0]), -1, "Could not get column names")

  def test_tablerows(self):
    self.assertNotEqual(self.db.allrows(self.conn, self.tablenames[0]), -1, "Could not get table rows")

  def test_runquery(self):
    data = self.db.runquery(self.conn, self.login.query_test, True)
    self.assertTrue(data['success'], "Could not run query")

  def test_dbinfo(self):
    self.assertNotEqual(self.db.getdbinfo(self.conn, self.login.dbname), -1, "Could not get db info")

  def test_dbtableinfo(self):
    self.assertNotEqual(self.db.getdb_tableinfo(self.conn), -1, "Could not get db table info")

  def test_tableinfo(self):
    self.assertNotEqual(self.db.gettableinfo(self.conn, self.login.table_test), -1, "Could not get table info")

if __name__ == '__main__':
  unittest.main()


text:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	version="3.1">
	<servlet>
		<servlet-name>Jersey2Config</servlet-name>
		<servlet-class>io.swagger.jersey.config.JerseyJaxrsConfig</servlet-class>
		<init-param>
			<param-name>api.version</param-name>
			<param-value>1.0.0</param-value>
		</init-param>
		<init-param>
			<param-name>swagger.api.basepath</param-name>
			<param-value>/user-management/</param-value>
		</init-param>
		<load-on-startup>2</load-on-startup>
	</servlet>
</web-app>