UNPKG

@syntest/search

Version:

The common core of the SynTest Framework

170 lines 6.96 kB
"use strict"; /* * Copyright 2020-2021 Delft University of Technology and SynTest contributors * * This file is part of SynTest Framework - SynTest Core. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ObjectiveManager = void 0; const crypto = require("node:crypto"); const logging_1 = require("@syntest/logging"); const Archive_1 = require("../../Archive"); const diagnostics_1 = require("../../util/diagnostics"); const ExceptionObjectiveFunction_1 = require("../ExceptionObjectiveFunction"); /** * Manager that keeps track of which objectives have been covered and are still to be searched. * * @author Mitchell Olsthoorn */ class ObjectiveManager { /** * Constructor. * * @param runner Encoding runner * @param secondaryObjectives Secondary objectives to use */ constructor(runner, secondaryObjectives) { ObjectiveManager.LOGGER = (0, logging_1.getLogger)("ObjectiveManager"); this._archive = new Archive_1.Archive(); this._currentObjectives = new Set(); this._coveredObjectives = new Set(); this._uncoveredObjectives = new Set(); this._runner = runner; this._secondaryObjectives = secondaryObjectives; } /** * Evaluate multiple encodings on the current objectives. * * @param encodings The encoding to evaluate * @param budgetManager The budget manager to track the remaining budget * @param terminationManager The termination trigger manager */ async evaluateMany(encodings, budgetManager, terminationManager) { for (const encoding of encodings) { // If there is no budget left or a termination trigger has been triggered, stop evaluating if (!budgetManager.hasBudgetLeft() || terminationManager.isTriggered()) { break; } await this.evaluateOne(encoding, budgetManager, terminationManager); } } /** * Evaluate one encoding on the current objectives. * * @param encoding The encoding to evaluate * @param budgetManager The budget manager to track evaluation * @param terminationManager The termination trigger manager */ async evaluateOne(encoding, budgetManager, _terminationManager) { ObjectiveManager.LOGGER.debug(`Evaluating encoding ${encoding.id}`); // Execute the encoding const result = await this._runner.execute(this._subject, encoding); // TODO: Use events for this so we can elimate the dependency on the budget manager budgetManager.evaluation(); // Store the execution result in the encoding encoding.setExecutionResult(result); // For all current objectives for (const objectiveFunction of this._currentObjectives) { this.evaluateObjective(encoding, objectiveFunction); } // Create separate exception objective when an exception occurred in the execution if (result.hasError()) { let stack = result.getError().stack; stack = stack ? stack .split("\n") // only use location lines .filter((line) => line.startsWith(" at")) // only use locations within the source code (i.e. not from the generated tests) .filter((line) => line.includes("/instrumented/")) // stupid hack should be done better somehow, suffices for now .join("\n") : result.getError().message; const hash = crypto.createHash("md5").update(stack).digest("hex"); const numberOfExceptions = this._archive .getObjectives() .filter((objective) => objective instanceof ExceptionObjectiveFunction_1.ExceptionObjectiveFunction) .filter((objective) => objective.getIdentifier() === hash).length; if (numberOfExceptions === 0) { this._archive.update(new ExceptionObjectiveFunction_1.ExceptionObjectiveFunction(this._subject, hash, result.getError()), encoding, false); } } } evaluateObjective(encoding, objectiveFunction) { // Calculate and store the distance const distance = objectiveFunction.calculateDistance(encoding); if (Number.isNaN(distance)) { throw new TypeError((0, diagnostics_1.shouldNeverHappen)("ObjectiveManager")); } encoding.setDistance(objectiveFunction, distance); objectiveFunction.updateDistance(distance); // When the objective is covered, update the objectives and the archive if (distance === 0) { ObjectiveManager.LOGGER.debug(`Objective ${objectiveFunction.getIdentifier()} covered by encoding ${encoding.id}`); const newObjectives = this._handleCoveredObjective(objectiveFunction, encoding); for (const objective of newObjectives) { this.evaluateObjective(encoding, objective); } } else { ObjectiveManager.LOGGER.debug(`Distance from objective ${objectiveFunction.getIdentifier()} is ${distance} for encoding ${encoding.id}`); this._handleUncoveredObjective(objectiveFunction, encoding, distance); } } /** * Reset the objectives. * * This function is used to reset the objectives when the search subject changes. * * TODO: Should the archive be reset as well? */ _reset() { this._archive.clear(); this._currentObjectives.clear(); this._coveredObjectives.clear(); this._uncoveredObjectives.clear(); } /** * Return the uncovered objectives. */ getUncoveredObjectives() { return this._uncoveredObjectives; } /** * Return the current objectives. */ getCurrentObjectives() { return this._currentObjectives; } /** * Return the covered objectives. */ getCoveredObjectives() { return this._coveredObjectives; } /** * Return the archive. */ getArchive() { return this._archive; } /** * Determines if there are objectives left to cover. */ hasObjectives() { return this._currentObjectives.size > 0; } } exports.ObjectiveManager = ObjectiveManager; //# sourceMappingURL=ObjectiveManager.js.map