Source: eval/match_adapters/MatchAdapter.js

import {IdExtractor} from '../../extract/IdExtractor.js';
import assert from 'assert';
import {Logger} from '../../util/Logger.js';
import {ActualMatching} from '../actual/ActualMatching.js';
import {AbstractAdapter} from '../driver/AbstractAdapter.js';
import {AbstractTestResult} from '../result/AbstractTestResult.js';

/**
 * The superclass for all adapters to matching algorithms.
 */
export class MatchAdapter extends AbstractAdapter {
  /**
   * Construct a new MatchAdapter instance.
   * @param {String} path The path to the directory containing the matching
   *     algorithm and the run script
   * @param {String} displayName The name to display for the matching
   *     algorithm this adapter represents.
   */
  constructor(path, displayName) {
    super(path, displayName);
  }

  /**
   * @inheritDoc
   * @param {MatchTestCase} testCase The diff test case to run.
   * @return {MatchTestResult} The result.
   */
  evalCase(testCase) {
    let matching;
    try {
      matching = this.run(testCase.oldTree, testCase.newTree);
    } catch (e) {
      // Check if timeout or runtime error
      if (e.code === 'ETIMEDOUT') {
        Logger.info(this.displayName + ' timed out for ' + testCase.name, this);
        return testCase.complete(
            this.displayName,
            null,
            AbstractTestResult.VERDICTS.TIMEOUT,
        );
      } else {
        Logger.info(
            this.displayName + ' crashed on ' + testCase.name +
            ': ' + e.toString(),
            this,
        );
        return testCase.complete(
            this.displayName,
            null,
            AbstractTestResult.VERDICTS.RUNTIME_ERROR,
        );
      }
    }
    try {
      this.verifyResult(matching, testCase.expected);
    } catch (e) {
      Logger.info(
          this.displayName + ' gave wrong answer for ' +
          testCase.name + ': ' + e.toString(),
          this,
      );
      return testCase.complete(
          this.displayName,
          new ActualMatching(null, matching),
          AbstractTestResult.VERDICTS.WRONG_ANSWER,
      );
    }
    return testCase.complete(
        this.displayName,
        new ActualMatching(null, matching),
        AbstractTestResult.VERDICTS.OK,
    );
  }

  /**
   * Run this matching algorithm.
   * @param {Node} oldTree The root of the old (original) tree.
   * @param {Node} newTree The root of the new (changed) tree.
   * @return {Matching} The produced matching.
   * @abstract
   */
  run(oldTree, newTree) {
    Logger.abstractMethodExecution();
  }

  /**
   * Verify that an actual matching follows the rules of the expected matching.
   * @param {Matching} matching The actual matching produced by the algorithm.
   * @param {ExpectedMatching} expected Rules for the expected matching.
   * @throws {Error} If the actual matching does not conform to the rules of
   *     the expected.
   */
  verifyResult(matching, expected) {
    const oldToNewIdMap = new Map();
    const newToOldIdMap = new Map();

    // Extract IDs of matched nodes
    const idExtractor = new IdExtractor();
    for (const [oldNode, newNode] of matching.oldToNewMap) {
      oldToNewIdMap.set(idExtractor.get(oldNode), idExtractor.get(newNode));
    }
    for (const [newNode, oldNode] of matching.newToOldMap) {
      newToOldIdMap.set(idExtractor.get(newNode), idExtractor.get(oldNode));
    }

    // verify that matching meets the expected results

    for (const matchPair of expected.matches) {
      const oldId = matchPair[0];
      const newId = matchPair[1];
      assert.ok(
          oldToNewIdMap.has(oldId),
          'old node ' + oldId + ' is not matched',
      );
      assert.strictEqual(
          oldToNewIdMap.get(oldId),
          newId,
          'old node ' + oldId + ' is matched with ' +
          oldToNewIdMap.get(oldId) + ' instead of ' + newId,
      );
    }

    for (const notMatchPair of expected.notMatches) {
      const oldId = notMatchPair[0];
      const newId = notMatchPair[1];
      if (oldToNewIdMap.has(oldId)) {
        assert.notStrictEqual(
            oldToNewIdMap.get(oldId),
            newId,
            'old node ' + oldId + ' is wrongfully matched with ' + newId,
        );
      }
    }

    for (const oldId of expected.oldMatched) {
      assert.ok(
          oldToNewIdMap.has(oldId),
          'old node ' + oldId + ' is not matched',
      );
    }

    for (const newId of expected.newMatched) {
      assert.ok(
          newToOldIdMap.has(newId),
          'mew node ' + newId + ' is not matched',
      );
    }

    for (const oldId of expected.notOldMatched) {
      assert.ok(
          !oldToNewIdMap.has(oldId),
          'old node ' + oldId + ' is wrongfully matched',
      );
    }

    for (const newId of expected.notNewMatched) {
      assert.ok(
          !newToOldIdMap.has(newId),
          'mew node ' + newId + ' is wrongfully matched',
      );
    }
  }
}