<commit_before>import { Protocol, errors } from '../protocol';
import os from 'os';
import commands from './commands';
import * as helpers from './helpers';
import log from './logger';
import DeviceSettings from './device-settings';
import { desiredCapabilityConstraints } from './desired-caps';
import { validateCaps } from './capabilities';
import B from 'bluebird';
import _ from 'lodash';
import { util } from 'appium-support';
import { ImageElement, makeImageElementCache, getImgElFromArgs } from './image-element';
import { W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY } from '../protocol/protocol';


B.config({
  cancellation: true,
});

const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;

const EVENT_SESSION_INIT = 'newSessionRequested';
const EVENT_SESSION_START = 'newSessionStarted';
const EVENT_SESSION_QUIT_START = 'quitSessionRequested';
const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';

class BaseDriver extends Protocol {

  constructor (opts = {}, shouldValidateCaps = true) {
    super();

    // setup state
    this.sessionId = null;
    this.opts = opts;
    this.caps = null;
    this.helpers = helpers;

    // timeout initialization
    this.newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
    this.implicitWaitMs = 0;

    this._constraints = _.cloneDeep(desiredCapabilityConstraints);
    this.locatorStrategies = [];
    this.webLocatorStrategies = [];

    // use a custom tmp dir to avoid losing data and app when computer is
    // restarted
    this.opts.tmpDir = this.opts.tmpDir ||
                       process.env.APPIUM_TMP_DIR ||
                       os.tmpdir();

    // base-driver internals
    this.curCommand = B.resolve(); // see note in execute
    this.curCommandCancellable = B.resolve(); // see note in execute
    this.shutdownUnexpectedly = false;
    this.noCommandTimer = null;
    this.shouldValidateCaps = shouldValidateCaps;

    // settings should be instantiated by drivers which extend BaseDriver, but
    // we set it to an empty DeviceSettings instance here to make sure that the
    // default settings are applied even if an extending driver doesn't utilize
    // the settings functionality itself
    this.settings = new DeviceSettings({}, _.noop);

    this.resetOnUnexpectedShutdown();

    // keeping track of initial opts
    this.initialOpts = _.cloneDeep(this.opts);

    // allow subclasses to have internal drivers
    this.managedDrivers = [];

    // store event timings
    this._eventHistory = {
      commands: [] // commands get a special place
    };

    // cache the image elements
    this._imgElCache = makeImageElementCache();

    this.protocol = null;
  }

  /**
   * This property is used by AppiumDriver to store the data of the
   * specific driver sessions. This data can be later used to adjust
   * properties for driver instances running in parallel.
   * Override it in inherited driver classes if necessary.
   *
   * @return {object} Driver properties mapping
   */
  get driverData () {
    return {};
  }

  /**
   * This property controls the way {#executeCommand} method
   * handles new driver commands received from the client.
   * Override it for inherited classes only in special cases.
   *
   * @return {boolean} If the returned value is true (default) then all the commands
   *   received by the particular driver instance are going to be put into the queue,
   *   so each following command will not be executed until the previous command
   *   execution is completed. False value disables that queue, so each driver command
   *   is executed independently and does not wait for anything.
   */
  get isCommandsQueueEnabled () {
    return true;
  }

  /*
   * make eventHistory a property and return a cloned object so a consumer can't
   * inadvertently change data outside of logEvent
   */
  get eventHistory () {
    return _.cloneDeep(this._eventHistory);
  }

  /*
   * API method for driver developers to log timings for important events
   */
  logEvent (eventName) {
    if (eventName === 'commands') {
      throw new Error('Cannot log commands directly');
    }
    if (typeof eventName !== 'string') {
      throw new Error(`Invalid eventName ${eventName}`);
    }
    if (!this._eventHistory[eventName]) {
      this._eventHistory[eventName] = [];
    }
    let ts = Date.now();
    let logTime = (new Date(ts)).toTimeString();
    this._eventHistory[eventName].push(ts);
    log.debug(`Event '${eventName}' logged at ${ts} (${logTime})`);
  }

  /*
   * Overridden in appium driver, but here so that individual drivers can be
   * tested with clients that poll
   */
  async getStatus () { // eslint-disable-line require-await
    return {};
  }

  /*
   * Initialize a new onUnexpectedShutdown promise, cancelling existing one.
   */
  resetOnUnexpectedShutdown () {
    if (this.onUnexpectedShutdown && !this.onUnexpectedShutdown.isFulfilled()) {
      this.onUnexpectedShutdown.cancel();
    }
    this.onUnexpectedShutdown = new B((resolve, reject, onCancel) => {
      onCancel(() => reject(new B.CancellationError()));
      this.unexpectedShutdownDeferred = {resolve, reject};
    });
    // noop handler to avoid warning.
    this.onUnexpectedShutdown.catch(() => {});
  }

  // we only want subclasses to ever extend the contraints
  set desiredCapConstraints (constraints) {
    this._constraints = Object.assign(this._constraints, constraints);
    // 'presence' means different things in different versions of the validator,
    // when we say 'true' we mean that it should not be able to be empty
    for (const [, value] of _.toPairs(this._constraints)) {
      if (value && value.presence === true) {
        value.presence = {
          allowEmpty: false,
        };
      }
    }
  }

  get desiredCapConstraints () {
    return this._constraints;
  }

  // method required by MJSONWP in order to determine whether it should
  // respond with an invalid session response
  sessionExists (sessionId) {
    if (!sessionId) return false; // eslint-disable-line curly
    return sessionId === this.sessionId;
  }

  // method required by MJSONWP in order to determine if the command should
  // be proxied directly to the driver
  driverForSession (/*sessionId*/) {
    return this;
  }

  logExtraCaps (caps) {
    let extraCaps = _.difference(_.keys(caps),
                                 _.keys(this._constraints));
    if (extraCaps.length) {
      log.warn(`The following capabilities were provided, but are not ` +
               `recognized by appium: ${extraCaps.join(', ')}.`);
    }
  }

  validateDesiredCaps (caps) {
    if (!this.shouldValidateCaps) {
      return true;
    }

    try {
      validateCaps(caps, this._constraints);<commit_msg>Be more explicit about unrecognized caps (#317)<commit_after>import { Protocol, errors } from '../protocol';
import os from 'os';
import commands from './commands';
import * as helpers from './helpers';
import log from './logger';
import DeviceSettings from './device-settings';
import { desiredCapabilityConstraints } from './desired-caps';
import { validateCaps } from './capabilities';
import B from 'bluebird';
import _ from 'lodash';
import { util } from 'appium-support';
import { ImageElement, makeImageElementCache, getImgElFromArgs } from './image-element';
import { W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY } from '../protocol/protocol';


B.config({
  cancellation: true,
});

const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;

const EVENT_SESSION_INIT = 'newSessionRequested';
const EVENT_SESSION_START = 'newSessionStarted';
const EVENT_SESSION_QUIT_START = 'quitSessionRequested';
const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';

class BaseDriver extends Protocol {

  constructor (opts = {}, shouldValidateCaps = true) {
    super();

    // setup state
    this.sessionId = null;
    this.opts = opts;
    this.caps = null;
    this.helpers = helpers;

    // timeout initialization
    this.newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
    this.implicitWaitMs = 0;

    this._constraints = _.cloneDeep(desiredCapabilityConstraints);
    this.locatorStrategies = [];
    this.webLocatorStrategies = [];

    // use a custom tmp dir to avoid losing data and app when computer is
    // restarted
    this.opts.tmpDir = this.opts.tmpDir ||
                       process.env.APPIUM_TMP_DIR ||
                       os.tmpdir();

    // base-driver internals
    this.curCommand = B.resolve(); // see note in execute
    this.curCommandCancellable = B.resolve(); // see note in execute
    this.shutdownUnexpectedly = false;
    this.noCommandTimer = null;
    this.shouldValidateCaps = shouldValidateCaps;

    // settings should be instantiated by drivers which extend BaseDriver, but
    // we set it to an empty DeviceSettings instance here to make sure that the
    // default settings are applied even if an extending driver doesn't utilize
    // the settings functionality itself
    this.settings = new DeviceSettings({}, _.noop);

    this.resetOnUnexpectedShutdown();

    // keeping track of initial opts
    this.initialOpts = _.cloneDeep(this.opts);

    // allow subclasses to have internal drivers
    this.managedDrivers = [];

    // store event timings
    this._eventHistory = {
      commands: [] // commands get a special place
    };

    // cache the image elements
    this._imgElCache = makeImageElementCache();

    this.protocol = null;
  }

  /**
   * This property is used by AppiumDriver to store the data of the
   * specific driver sessions. This data can be later used to adjust
   * properties for driver instances running in parallel.
   * Override it in inherited driver classes if necessary.
   *
   * @return {object} Driver properties mapping
   */
  get driverData () {
    return {};
  }

  /**
   * This property controls the way {#executeCommand} method
   * handles new driver commands received from the client.
   * Override it for inherited classes only in special cases.
   *
   * @return {boolean} If the returned value is true (default) then all the commands
   *   received by the particular driver instance are going to be put into the queue,
   *   so each following command will not be executed until the previous command
   *   execution is completed. False value disables that queue, so each driver command
   *   is executed independently and does not wait for anything.
   */
  get isCommandsQueueEnabled () {
    return true;
  }

  /*
   * make eventHistory a property and return a cloned object so a consumer can't
   * inadvertently change data outside of logEvent
   */
  get eventHistory () {
    return _.cloneDeep(this._eventHistory);
  }

  /*
   * API method for driver developers to log timings for important events
   */
  logEvent (eventName) {
    if (eventName === 'commands') {
      throw new Error('Cannot log commands directly');
    }
    if (typeof eventName !== 'string') {
      throw new Error(`Invalid eventName ${eventName}`);
    }
    if (!this._eventHistory[eventName]) {
      this._eventHistory[eventName] = [];
    }
    let ts = Date.now();
    let logTime = (new Date(ts)).toTimeString();
    this._eventHistory[eventName].push(ts);
    log.debug(`Event '${eventName}' logged at ${ts} (${logTime})`);
  }

  /*
   * Overridden in appium driver, but here so that individual drivers can be
   * tested with clients that poll
   */
  async getStatus () { // eslint-disable-line require-await
    return {};
  }

  /*
   * Initialize a new onUnexpectedShutdown promise, cancelling existing one.
   */
  resetOnUnexpectedShutdown () {
    if (this.onUnexpectedShutdown && !this.onUnexpectedShutdown.isFulfilled()) {
      this.onUnexpectedShutdown.cancel();
    }
    this.onUnexpectedShutdown = new B((resolve, reject, onCancel) => {
      onCancel(() => reject(new B.CancellationError()));
      this.unexpectedShutdownDeferred = {resolve, reject};
    });
    // noop handler to avoid warning.
    this.onUnexpectedShutdown.catch(() => {});
  }

  // we only want subclasses to ever extend the contraints
  set desiredCapConstraints (constraints) {
    this._constraints = Object.assign(this._constraints, constraints);
    // 'presence' means different things in different versions of the validator,
    // when we say 'true' we mean that it should not be able to be empty
    for (const [, value] of _.toPairs(this._constraints)) {
      if (value && value.presence === true) {
        value.presence = {
          allowEmpty: false,
        };
      }
    }
  }

  get desiredCapConstraints () {
    return this._constraints;
  }

  // method required by MJSONWP in order to determine whether it should
  // respond with an invalid session response
  sessionExists (sessionId) {
    if (!sessionId) return false; // eslint-disable-line curly
    return sessionId === this.sessionId;
  }

  // method required by MJSONWP in order to determine if the command should
  // be proxied directly to the driver
  driverForSession (/*sessionId*/) {
    return this;
  }

  logExtraCaps (caps) {
    let extraCaps = _.difference(_.keys(caps),
                                 _.keys(this._constraints));
    if (extraCaps.length) {
      log.warn(`The following capabilities were provided, but are not ` +
               `recognized by Appium:`);
      for (const cap of extraCaps) {
        log.warn(`  ${cap}`);
      }
    }
  }

  validateDesiredCaps (caps) {
    if (!this.shouldValidateCaps) {
      return true;
    }

    try {
      validateCaps(caps, this._constraints);
{'fraction_non_alphanumeric': 0.10147080671519834, 'fraction_numerical': 0.0014113801812509286, 'mean_word_length': 3.0308383233532936, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2232037', 'n_tokens_mistral': 3768, 'n_tokens_neox': 3695, 'n_words': 1233}