trufflesuite/truffle

View on GitHub
packages/core/lib/debug/mocha.js

Summary

Maintainability
A
2 hrs
Test Coverage
const debugModule = require("debug");
const debug = debugModule("lib:debug:mocha");

const colors = require("colors");
const util = require("util");

const { CLIDebugger } = require("./cli");
const execute = require("@truffle/contract/lib/execute");

class CLIDebugHook {
  constructor(config, compilations, runner) {
    this.config = config;
    this.compilations = compilations;
    this.runner = runner; // mocha runner (**not** lib/test/testrunner)
  }

  async debug(operation) {
    // turn off timeouts for the current runnable
    // HACK we don't turn it back on because it doesn't work...
    // tests that take a long time _after_ debug break just won't timeout
    await this.disableTimeout();

    const { txHash, result, method } = await this.invoke(operation);
    debug("txHash: %o", txHash);

    this.printStartTestHook(method);

    const interpreter = await new CLIDebugger(this.config, {
      compilations: this.compilations,
      txHash
    }).run();
    await interpreter.start();

    this.printStopTestHook();

    return result;
  }

  async start() {}

  async disableTimeout() {
    (await this.runner).currentRunnable.timeout(0);
  }

  async invoke(operation) {
    const method = await this.detectMethod(operation);
    const { action } = method;

    switch (action) {
      case "deploy": {
        const result = await operation;

        return {
          txHash: result.transactionHash, // different name; who knew
          method,
          result
        };
      }
      case "send": {
        const result = await operation;

        return { txHash: result.tx, method, result };
      }
      case "call": {
        // replays as send
        const { contract, fn, abi, args, address } = method;

        // get the result of the call
        const result = await operation;

        // and replay it as a transaction so we can debug
        // bit of a HACK: properly making a call act like a tx requires forking
        const { tx: txHash } = await execute.send.call(
          contract,
          fn,
          abi,
          address
        )(...args);

        return { txHash, method, result };
      }
      default: {
        throw new Error(`Unsupported action for debugging: ${action}`);
      }
    }
  }

  detectMethod(promiEvent) {
    return new Promise(accept => {
      for (let action of ["send", "call", "deploy"]) {
        promiEvent.on(
          `execute:${action}:method`,
          ({ fn, abi, args, contract, address }) => {
            accept({
              fn,
              abi,
              args,
              address,
              contract,
              action
            });
          }
        );
      }
    });
  }

  printStartTestHook(method) {
    const formatOperation = ({
      action,
      contract: { contractName },
      abi,
      args
    }) => {
      switch (action) {
        case "deploy": {
          return colors.bold(
            `${contractName}.new(${args.map(util.inspect).join(", ")})`
          );
        }
        case "call":
        case "send": {
          return colors.bold(
            `${contractName}.${abi.name}(${args.map(util.inspect).join(", ")})`
          );
        }
      }
    };

    this.config.logger.log("");
    this.config.logger.log("  ...");
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("  Test run interrupted.");
    this.config.logger.log(`  Debugging ${formatOperation(method)}`);
    this.config.logger.log(
      colors.cyan(
        ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      )
    );
    this.config.logger.log("");
  }

  printStopTestHook() {
    this.config.logger.log("");
    this.config.logger.log("");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  Debugger stopped. Test resuming");
    this.config.logger.log(
      colors.cyan(
        "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
      )
    );
    this.config.logger.log("  ...");
    this.config.logger.log("");
  }
}

module.exports = {
  CLIDebugHook
};