@syntest/search
Version:
The common core of the SynTest Framework
170 lines • 6.96 kB
JavaScript
;
/*
* 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