package Sequenic.T2ext.Selection; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import Sequenic.T2.Coverage.BasicPath; import Sequenic.T2.Seq.Trace; import Sequenic.T2.StreamTraces.TraceFilter; import Sequenic.T2ext.Instrumenter.CFG; import Sequenic.T2ext.Instrumenter.CFGBunchFile; public class RegressionTestSelector implements ISelector{ public static final int RothermelHarrold = 0; public static final int BallPartial = 1; public static final int BallValid = 2; final Set newMethods; final Map oldMethods; //Legacy constructor public RegressionTestSelector(Iterable old_methods, Iterable new_methods, boolean useBallPartial) { this(old_methods,new_methods,useBallPartial ? BallPartial : RothermelHarrold); } public TraceFilter cachedJustSelect() { return CachedSelector.create(this, true); } public RegressionTestSelector(Iterable old_methods, Iterable new_methods, int algorithm) { HashMap newMethods = new HashMap(); oldMethods = new HashMap(); RothermelHarrold rh = algorithm==RothermelHarrold ? new RothermelHarrold() : null; PathSelector.Factory psFactory = algorithm==BallPartial ? new PathSelector.Factory() : null; for(CFG newCFG : new_methods) newMethods.put(newCFG.getMethodName(), newCFG); for(CFG oldCFG : old_methods) { //Deleted methods have no entry in newMethods, so remove returns null //then analyze returns null, which puts null in oldMethods. oldMethods.put(oldCFG.getMethodName(), analyze(oldCFG, newMethods.remove(oldCFG.getMethodName()),rh, psFactory) ); } for(Map.Entry kv : newMethods.entrySet()) kv.setValue(null);//remove link to CFGs for garbage collection this.newMethods = newMethods.keySet(); } private PathSelector getSelector(String methodName) { PathSelector sel = oldMethods.get(methodName); if(sel==null) { if(!oldMethods.containsKey(methodName)) //We have observed a path in a method for which we don't have the CFG. //This is odd, it shouldn't happen. throw new Error("Warning: path observed in unknown method: "+methodName +".\n Perhaps you forgot to include the CFGs of its class?"); //methodName is a deletedMethod, oldMethods contains mapping {methodName: null} //Substitute null with deletedMethodSelector. return deletedMethodSelector; } return sel; } public boolean isModificationTraversing(BasicPath path) { return getSelector(path.getMethodUname()).select(path.getNodes()); } public BasicPath synthesizeCoverage(BasicPath bp) { try { int[] path = getSelector(bp.getMethodUname()).synthesize(bp.getNodes()); if(path==null) return null; if(path==bp.getNodes()) return bp; return BasicPath.create(bp.getMethodUname(), path); } catch(Throwable t) { t.printStackTrace(); System.err.println(bp); System.exit(0); return null; } } @Override public String toString() {return toString(false);}; public String toString(boolean onlyChanged) { StringBuffer sb = new StringBuffer(); sb.append("New methods: "); sb.append(newMethods.isEmpty() ? "0. " : newMethods.size() +": \n"); for(String m : newMethods) sb.append(" ").append(m).append("\n"); sb.append("Existing methods: "+oldMethods.size()+":\n"); for(String m : oldMethods.keySet()) { if(onlyChanged && oldMethods.get(m)==PathSelector.NEVER) continue; sb.append(" ").append(m); if(oldMethods.get(m)==null) sb.append(": deleted.\n"); else sb.append(": ").append(oldMethods.get(m).toString()).append("\n"); } return sb.toString(); } /* There will almost definitely exists tests/observed paths that cover deleted methods, * so we must handle them separately. * * Some of these tests will be obsolete, e.g. those that directly call the deleted method. * * Assuming obsolete tests are filtered out, we have two options, return: * 1) Always select, since such a test will most likely (surely?) be modification-traversing. * 2) Never select and rely on changes at the call site of the method to identify the method as modification-traversing. * But note that in this case we can't relabel the path even though we should. * * So we choose (1) */ private static final PathSelector deletedMethodSelector = PathSelector.ALWAYS; //if newCFG is null: returns null //if rh is not null, uses Rothermel-Harrold //if rh is null and psFactory is not null, uses Ball Partial Reachability //if rh is null and psFactory is null, uses Ball-Valid Reachability private static final PathSelector analyze(CFG oldCFG, CFG newCFG, RothermelHarrold rh, PathSelector.Factory psFactory) { if(newCFG==null) return null; if(rh!=null) return rh.getDangerousEdges(oldCFG.getStartNode(), newCFG.getStartNode()); InterclassGraph ig = new InterclassGraph(oldCFG.getStartNode(), newCFG.getStartNode()); if(psFactory!=null) return ig.ballPartialDangerousEdges(psFactory); return ig; } public static final TraceFilter fromSelector(final ISelector selector,final boolean alsoUnsynthesizable) { return new TraceFilter() { public boolean add(Trace t) { if(alsoUnsynthesizable) { return SelectiveRunner.synthesizeAll(selector, t.coverage()) < 0; } else { for(BasicPath p : t.coverage()) if(selector.isModificationTraversing(p)) return true; return false; } } }; } public static Callable callableTraceFilter( final String oldDir, final String newDir, final int algorithm, final String...classNames) { return new Callable() { public TraceFilter call() throws Exception { ArrayList oldCfgs = new ArrayList(100); ArrayList newCfgs = new ArrayList(100); for(String c : classNames) { oldCfgs.addAll(CFGBunchFile.load(oldDir+c+".cfgs").cfgs); newCfgs.addAll(CFGBunchFile.load(newDir+c+".cfgs").cfgs); } return new RegressionTestSelector(oldCfgs, newCfgs, algorithm).cachedJustSelect(); } }; } }