/* * Copyright 2009 Wishnu Prasetya. * * This file is part of T2. * T2 is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License (GPL) as published by the * Free Software Foundation; either version 3 of the License, or any * later version. * * T2 is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * A copy of the GNU General Public License can be found in T2 distribution. * If it is missing, see http://www.gnu.org/licenses. */ package Sequenic.T2ext.CoverageEngine; import java.lang.reflect.Method; import java.util.*; import java.io.*; import java.util.concurrent.*; import Sequenic.T2.*; import Sequenic.T2.Seq.*; import Sequenic.T2ext.Analyzer.*; import Sequenic.T2ext.Instrumenter.*; /** * This class is an implementation of the BaseEngine. * *

* It provides a coverage engine implementing a semi-exhaustive search strategy. * This search algorithm can be shared by new subclasses, but a subclass will * then have an opportunity to e.g. more cleverly define the search domains. * With respect to search domains, this engine will just provide simplistic * approach for constructing them; a subclass should refine it. * *

* The algorithm works as follows. * *

* Let "sigma" be our target path. This is the path to cover. This engine will * first look at the provided coverage report to find a T2-test-trace "tau" that * produces an execution whose coverage distance to sigma is minimum. Let "z" be * the METARUN step in tau, that produces that closest execution meant above. * Note that this z can also be tau's creation step. * *

* Tau is then copied to form a "base-trace"; this base-trace is a template from * which we will generate new traces to try out. The base-trace is first * "prepared". This engine does it by dropping all steps AFTER the best step, * because they are considered to be less important towards covering the target * path. * *

* A subclass can override the method "prepareBaseTrace" to implement a * different preparation strategy. * *

* We define the "parameters" of the base-trace to be its REF and CONST steps. * The general idea of this engine is to variate these parameters to hope to * cover sigma. However, varying all parameters will explode our search space. * So there is a method "setupParamsFrame" that will decide which parameters to * vary; these will be put in a set called "parameters frame". * *

* This engine's selection strategy is as follows. Roughly said, select * k-parameters of the step "z" above, and those of previous steps if "z" has * too few parameters to fill-in k. Parameters of previous steps that do not * change the state of target object will be skipped. * *

* A subclass may override setupParamsFrame to provide a different strategy of * parameters selection. * *

* Next, we define for each parameter in the parameters-frame, what its (finite) * domain is. We will then vary this parameter over this domain. For each * parameter "p" in the parameter frame, we associate a "domain". This is set by * the method "setupDomains". * *

* This engine will constructs the domains of CONST parameters using T2's * BaseDomainSmall. Parameters of REF-type will actually not be varied. * *

* A subclass should override this method "setupDomains" to implement different * domains. * *

* In the main loop we simply vary every parameter over its domain. For every * combination, a new trace is produced, executed, and its coverage is measured. * If sigma is covered, we are done. Else we keep track which trace delivers the * minimum distance to sigma. The main loop is also capped to a certain number * of combinations, so that we don't iterate too long (the search space can * indeed be huge). Furthermore, randomization is added so that the order in * which we draw values from each domain is random; so that we are not biased * towards certain patterns of combinations. * * @author Wishnu Prasetya * */ public class SemiExhaustiveEngine extends BaseEngine { /** * Just the same target path, represented as the list of its node-ids. */ LinkedList targetpathX; /** * A copy of the test-trace that most closely covers the target path. */ Trace orgClosestTrace = null; /** * This is the index of the particular step in orgClosestTrace that most * closely covers the target path. If the index is negative, then the * creation step is meant. */ Integer indexOfBestStep = null; /** * The coverage distance of orgClosestTrace wrt to the targetpath. */ int orgDistance; /** * The best observed execution path (generated by best-step above) that most * closely comes to the target path. */ List bestObservedPath = null; /** * A base-trace forms the template-trace from which this engine produces new * traces. The idea is to produce new traces by varying certain parameters * of the base-trace. * *

* The base-trace is obtained by cloning orgClosestTrace, and dropping all * steps AFTER the best-step in orgClosestTrace (because they presumed to be * less relevant towards covering the target-path). */ Trace basetrace = null; /** * This points to the step in the basetrace that most closely covers the * target path. This should be a copy of the best step pointed to by * indexOfBestStep. */ METARUN bestStep = null; /** * The run-ID of the execution that most closely cover the target path. */ private Long runId; /** * The maximum number of steps in the search. Each step correspond to the * binding of a relevant parameter in the basetrace with a value from its * domain. This is to prevent the search from running too long. * * Default is 50000. */ public int MaxSteps = 50000; boolean debug = false; /** * This defines our "parameters horizon". Suppose that this is K. * * Out of all parameters in the parameters frame, only the first K * parameters will be varied. This is used to limit the search space. */ int maxParamsHorizon = 5; /** * The parameters-frame. This will be sorted in reverse order, such that the * first parameters are those of the last step of the base trace. */ List paramsFrame; /** * Suppose the value of this field is K. This implies that only up-to K last * parameters in paramsFrame will be variated. This is a mechanism to * restrict the search space. The default is 5. */ int paramsFrameScope = 5; /** * This maintains a mapping from each parameter in the parameters-frame to * its finite domain. */ Map domainTable; Random rnd = new Random(); /** * This will provide additional information about the target trace. */ T2TraceInspection traceInspection; public SemiExhaustiveEngine() { super(); } /** * Constructor; note the series of quite complex setups we need to do. */ public SemiExhaustiveEngine(T2TraceFileCoverageResult covReport_, T2TraceFileCoverageAnalyzer t2covAnalyzer_, LinkedList targetpath_) { super(covReport_, t2covAnalyzer_, targetpath_); // Setups; don't change the order: targetpathX = new LinkedList(); for (Node N : targetpath) targetpathX.add(N.getId()); setupRunId(); if (runId != null) { setupT2Trace(); setupOrgDistance(); prepareBaseTrace(); traceInspection = new T2TraceInspection(basetrace, t2covAnalyzer .getC(), t2covAnalyzer.getPool(), t2covAnalyzer .getClassinvs()); setupParamsFrame(); setupDomains(); } } /** * Pre-cond: targetpath is a valid path and is not covered yet! * * This recovers the runId of the execution-path that most closely covers * the target-path. */ private void setupRunId() { assert targetpath != null; /* * System.out.println(">>> Path to cover: ") ; for (Node N : targetpath) * { System.out.print(N.getId() + " --> ") ; } * System.out.print("EXIT, of method " + CFG.getMethod()) ; */ CoverageResult coverage = covReport.totCoverage.get(CFG); // get the runID of the closest directly covering path runId = coverage.directlyClosestPaths.get(targetpath); /* * if (runId==null) { throw new * Error("Cannot find a T2-trace that covers the target path.") ; } */ } /** * This calculates orgClosestTrace, set basetrace, bestStep etc. */ private void setupT2Trace() { // get the trace and step that deliver this runID: orgClosestTrace = (Trace) cloneObj(covReport.getTrace(runId)); bestObservedPath = covReport.observedpaths.get(runId).getNodes(); if (orgClosestTrace == null) { throw new Error( "Cannot find a T2-trace that covers the target path."); } indexOfBestStep = covReport.getStepIndex(runId); basetrace = (Trace) cloneObj(orgClosestTrace); // best step turns out to be the creation step: if (indexOfBestStep == 0) bestStep = basetrace.creation; else bestStep = basetrace.trace.get(indexOfBestStep - 1); } private Serializable cloneObj(Serializable t) { Serializable clone = null; try { clone = (Serializable) Sequenic.T2.Obj.Cloner.clone(t); } catch (Exception e) { throw new Error("Problem with cloning base-trace.", e); } return clone; } /** * This method will prepare the base-trace before we vary it to generate new * traces. This engine will drop all steps that comes AFTER the best step. * The motivation is that we want to focus on improving the best step, and * doing this by varying its parameters, or parameters of the steps that * precede it. The steps after the best steps are therefore considered less * important. * *

* Override this method in a subclass to implement a different preparation * strategy. */ protected void prepareBaseTrace() { if (indexOfBestStep == 0) { basetrace.trace.clear(); } else { // Drop the sufix after the bestStep; they are not relevant anymore. while (!basetrace.trace.isEmpty() && basetrace.trace.getLast() != bestStep) basetrace.trace.removeLast(); } } /** * Calculate the distance of orgClosestTrace to the target path. */ private void setupOrgDistance() { Sensor.ejectObservations(); assert ReportersPool.NULLreporter != null; executeT2Trace(orgClosestTrace); CoverageResult result = t2covAnalyzer.calculateCoverage().totCoverage .get(CFG); // now calculate the distance: Long runid = result.directlyClosestPaths.get(targetpath); ObservedPath execpath = Sensor.getObservedPaths().get(runid); orgDistance = distCalculator.directCoveragePathDistance(execpath .getNodes(), targetpathX); } /** * This will setup the parameters frame over the base-trace. Note this * presumes that basetrace has been made so that its best step is its last * one. * *

* The parameters have to be sorted in reverse order. * *

* REF-type params will be excluded. Currently only integer-like CONST * params are included. * *

* Override this to implement different strategy to select parameters. */ protected void setupParamsFrame() { paramsFrame = new LinkedList(); List params = traceInspection.getParams(0, basetrace.trace .size() + 1, true); // Reverse the order of params, and put them in paramsFrame: int n = params.size(); while (0 < n) { n--; MkValStep P = params.get(n); if (P instanceof CONST && isPrimitiveCONST((CONST) P)) { paramsFrame.add(P); } } } static public Class typeOfCONST(CONST C) { if (C.val != null) return C.val.getClass(); return C.C; } private boolean isIntegerLikeCONST(CONST CONST) { Class C = typeOfCONST(CONST); return (C == Integer.class) || (C == Integer.TYPE) || (C == Short.class) || (C == Short.TYPE) || (C == Byte.class) || (C == Byte.TYPE) || (C == Long.class) || (C == Long.TYPE); } private boolean isPrimitiveCONST(CONST CONST) { Class C = typeOfCONST(CONST); return isIntegerLikeCONST(CONST) || (C == Boolean.class) || (C == Boolean.TYPE) || (C == String.class) || (C == Character.class) || (C == Character.TYPE); } /** * Construct the domain of each parameter in the parameters-frame. We'll * just pull of the domain from the BaseDomainSmall. * *

* Override this to implement different domains. */ protected void setupDomains() { domainTable = new HashMap(); BaseDomainSmall BDS = new BaseDomainSmall(); for (MkValStep P : paramsFrame) { CONST CONST = (CONST) P; Class C = typeOfCONST(CONST); List domain = new LinkedList(); if (isIntegerLikeCONST(CONST)) { for (int i = -3; i < BDS.getRange() - 3; i++) { if (C == Integer.class || C == Integer.TYPE) domain.add(new Integer(i)); if (C == Long.class || C == Long.TYPE) domain.add(new Long(i)); if (C == Short.class || C == Short.TYPE) domain.add(new Short((short) i)); if (C == Byte.class || C == Byte.TYPE) domain.add(new Byte((byte) i)); } } // Just for testing: // if (C == Integer.class) domain.add(new Integer(13)) ; if (C == Boolean.class || C == Boolean.TYPE) { domain.add(new Boolean(true)); domain.add(new Boolean(false)); } if (C == Character.class || C == Character.TYPE) { char[] chars = BDS.getCharsupply(); for (int i = 0; i < chars.length; i++) domain.add(new Character(chars[i])); } if (C == String.class) { String[] strings = BDS.getStringsupply(); for (int i = 0; i < strings.length; i++) domain.add(new String(strings[i])); } domainTable.put(P, domain); } } /** * Just returning the domain of the given parameter. */ protected List getDomain(MkValStep param) { return domainTable.get(param); } /** * The main method; for generating new traces. */ @Override public LinkedList> generateTraces() { if (debug) { if (runId == null) System.out.println(">>> Can't find a base execution path."); else { System.out.println("\n>>> #basetrace: " + basetrace.trace.size()); System.out.println("Best observed execution path: "); for (Integer N : bestObservedPath) { System.out.print(N + " --> "); } System.out.print("EXIT "); System.out.println(">>> beststep: " + bestStep); System.out.println(">>> basetrace:"); simplePrintTrace(basetrace); System.out.println(">>> #paramsFrame: " + paramsFrame.size()); for (MkValStep P : paramsFrame) { System.out.print("Param " + P + " domain: "); List domain = domainTable.get(P); for (Object o : domain) System.out.print(o + ", "); System.out.println(""); } } } newTraces.clear(); // No execution could be found: if (runId == null) return newTraces; // Else search: bestTraceFoundSofar = null; bestDistanceSoFar = orgDistance; search(0, MaxSteps); if (bestTraceFoundSofar != null) newTraces.add(new Pair(bestTraceFoundSofar, bestDistanceSoFar)); return newTraces; } private Trace bestTraceFoundSofar; private int bestDistanceSoFar; private PathDistanceCalculator distCalculator = new PathDistanceCalculator(); /** * The worker of generateTraces. * * This will try all combinations of the constants on each position in * paramsFrame. You can pass a quota to break off the search when the quota * becomes 0. */ private int search(int k, int quotaleft) { // Break off if we have no computation quota left: if (quotaleft == 0) return 0; // base case, then we have generated a full permutation; // perform the trace, and calculate coverage: if (k >= Math.min(maxParamsHorizon, paramsFrame.size())) { Sensor.ejectObservations(); executeT2Trace(basetrace); CFGBunchFile CFGs = t2covAnalyzer.getCOVanalyzer().getCFGs(); CoverageAnalyzer analz = new CoverageAnalyzer(CFGs); CoverageResult result = analz.calculateMethodCoverage(CFG); // result.simplePrint(Sensor.getObservedPaths()) ; // the target path is directly covered! if (result.directlyCoveredPaths.contains(targetpath)) { bestTraceFoundSofar = (Trace) cloneObj(basetrace); bestDistanceSoFar = 0; if (debug) { System.out.println(">>> Target path FOUND!"); } // return 0 to prevent further recursion elsewhere: return 0; } // else calculate the distance: Long runid = result.directlyClosestPaths.get(targetpath); // assert runid != null ; ObservedPath execpath = Sensor.getObservedPaths().get(runid); // assert execpath != null ; int dist = distCalculator.directCoveragePathDistance(execpath .getNodes(), targetpathX); if (dist < bestDistanceSoFar) bestTraceFoundSofar = (Trace) cloneObj(basetrace); return quotaleft - 1; } // else recursion: // Get the next parameters to variate: MkValStep P = paramsFrame.get(k); CONST C = (CONST) P; // Retrieve the domain for this parameter: List domain = getDomain(P); // Now quantify over the domain, and continue recursion: // We also randomize the order with which the values in the domain are // picked: int qleft = quotaleft; int[] choices = generateRandomPermutation(domain.size()); for (int i = 0; i < choices.length; i++) { C.val = domain.get(choices[i]); qleft--; qleft = search(k + 1, qleft); if (qleft <= 0) break; } return qleft; } /** * Will generate a random permutation of numbers in [0..size). */ int[] generateRandomPermutation(int size) { int[] res = new int[size]; ArrayList a = new ArrayList(size); for (int i = 0; i < size; i++) a.add(i); for (int i = 0; i < size; i++) { int k = rnd.nextInt(size - i); res[i] = a.remove(k); } return res; } static public void simplePrintTrace(Trace tr) { System.out.println(" [0] " + tr.creation); int i = 1; for (TraceStep S : tr.trace) { System.out.println(" [" + i + "] " + S); i++ ; } } static public void main(String[] args) { Engine_10 E = new Engine_10(); int[] perm = E.generateRandomPermutation(10); System.out.print(">>> "); for (int i = 0; i < perm.length; i++) System.out.print(perm[i] + " , "); } }