Source: eval/merge_adapters/MergeAdapter.js

import {execFileSync} from 'child_process';
import {EvalConfig} from '../../config/EvalConfig.js';
import fs from 'fs';
import {Preprocessor} from '../../io/Preprocessor.js';
import {HashExtractor} from '../../extract/HashExtractor.js';
import {Logger} from '../../util/Logger.js';
import {ActualMerge} from '../actual/ActualMerge.js';
import {AbstractAdapter} from '../driver/AbstractAdapter.js';
import {AbstractTestResult} from '../result/AbstractTestResult.js';

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

  /**
   * @inheritDoc
   * @param {MergeTestCase} testCase The merge test case to run.
   * @return {MergeTestResult} The result.
   */
  evalCase(testCase) {
    let exec;
    try {
      exec = this.run(testCase.base, testCase.branch1, testCase.branch2);
    } 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,
        );
      }
    }
    const parser = new Preprocessor();
    const actual = new ActualMerge(
        exec,
        parser.fromString(exec),
    );
    const verdict = this.verifyResult(actual, testCase.expected);

    if (verdict === AbstractTestResult.VERDICTS.WRONG_ANSWER) {
      Logger.info(
          this.displayName + ' gave wrong answer for ' + testCase.name,
          this,
      );
    }
    return testCase.complete(this.displayName, actual, verdict);
  }

  /**
   * Run this three-way merge algorithm.
   * @param {Node} base The root of the base process tree.
   * @param {Node} branch1 The root of the first branch process tree.
   * @param {Node} branch2 The root of the second branch process tree.
   * @return {String} The merge result as an XML document.
   */
  run(base, branch1, branch2) {
    const baseString = base.toXmlString();
    const branch1String = branch1.toXmlString();
    const branch2String = branch2.toXmlString();

    const baseFilePath = this.path + '/base.xml';
    const branch1Filepath = this.path + '/1.xml';
    const branch2FilePath = this.path + '/2.xml';

    fs.writeFileSync(baseFilePath, baseString);
    fs.writeFileSync(branch1Filepath, branch1String);
    fs.writeFileSync(branch2FilePath, branch2String);

    return execFileSync(
        this.path + '/' + EvalConfig.FILENAMES.RUN_SCRIPT,
        [
          baseFilePath,
          branch1Filepath,
          branch2FilePath,
        ],
        EvalConfig.EXECUTION_OPTIONS,
    ).toString();
  }

  /**
   * Verify that an actual merge result matches the expected result.
   * @param {ActualMerge} actualMerge The actual merge result.
   * @param {ExpectedMerge} expectedMerge The expected merge result.
   * @return {String} The verdict
   */
  verifyResult(actualMerge, expectedMerge) {
    const actualTree = actualMerge.tree;
    const hashExtractor = new HashExtractor();
    if (expectedMerge.expectedTrees.some((tree) =>
        hashExtractor.get(tree) === hashExtractor.get(actualTree))) {
      return AbstractTestResult.VERDICTS.OK;
    } else if (expectedMerge.acceptedTrees.some((tree) =>
        hashExtractor.get(tree) === hashExtractor.get(actualTree))) {
      return AbstractTestResult.VERDICTS.ACCEPTABLE;
    } else {
      return AbstractTestResult.VERDICTS.WRONG_ANSWER;
    }
  }
}