Commit 85ebce29 authored by Prasetya, S.W.B. (Wishnu)'s avatar Prasetya, S.W.B. (Wishnu)
Browse files

Merge branch 'new-treatement-for-interface-and-abs' into 'master'

New treatment for interface and abs

See merge request !1
parents 5cf2285c 5be16a2b
package Sequenic.T3.DerivativeSuiteGens.Gen2;
import static Sequenic.T3.Generator.GenCombinators.OneOfVal;
import java.io.Serializable;
import Sequenic.T3.Generator.Generator;
import Sequenic.T3.JavaType.JTypeUtils;
import Sequenic.T3.Sequence.Datatype.PARAM;
public class CustomGenDSL {
static boolean ofOfTheseTypes(PARAM P, Class... cls) {
Class C = JTypeUtils.getTopClass(P.ty) ;
for (Class D : cls) {
if(C == D) return true ;
}
return false ;
}
public static Generator<PARAM,Serializable> OneOfVal_(Float... f) {
Serializable[] f_ = f ;
Generator<PARAM,Serializable> gen = OneOfVal(f_) ;
return gen.If(P -> ofOfTheseTypes(P,Float.class, Float.TYPE)) ;
}
public static Generator<PARAM,Serializable> OneOfVal_(Integer... f) {
Serializable[] f_ = f ;
Generator<PARAM,Serializable> gen = OneOfVal(f_) ;
return gen.If(P -> ofOfTheseTypes(P,Integer.class, Integer.TYPE)) ;
}
public static Generator<PARAM,Serializable> OneOfVal_(String... f) {
Serializable[] f_ = f ;
Generator<PARAM,Serializable> gen = OneOfVal(f_) ;
return gen.If(P -> ofOfTheseTypes(P,String.class)) ;
}
}
......@@ -33,6 +33,13 @@ import Sequenic.T3.Pool;
import Sequenic.T3.SimpleClassLoader;
import Sequenic.T3.Sequence.Datatype.*;
/**
* This provides top-level APIs to invoke G2 generator. Use the static method:
*
* generateSuites(CUTname,config,timebudget) ... the budget is in ms.
*
*/
public class G2 {
/**
......@@ -97,13 +104,14 @@ public class G2 {
static public class TimeBudgetException extends Exception { }
/**
* Create an instance of G2 test suites generator. CUT has to be a concrete class
* (it should not be an interface nor an abstract class).
* @throws Exception
* Create an instance of G2 test suites generator. Typically, the CUT is a concrete
* class (it is not an interface or an abstract class). If an interface or an abstract
* class is given as the CUT, it may still contain concrete static methods, which we
* can test; so in these cases we will target them.
*/
protected G2(String CUTname, G2Config config) throws Exception {
this.config = config ;
g2sg = new G2SuiteGen(CUTname,config.CUTrootDir, config.useCoverageGuidance,config.useStaticInfo,config.dirOfStaticInfo) ;
g2sg = new G2SuiteGen(CUTname,config.CUTrootDir, config.useCoverageGuidance,config.useStaticInfo,config.dirOfStaticInfo,config.customPrimitivesGenerator) ;
g2sg.regressionMode = config.regressionMode ;
g2sg.injectOracles = config.injectOracles ;
g2sg.maxSuffixLength = config.maxSuffixLength ;
......@@ -127,28 +135,38 @@ public class G2 {
else t = new SingleTarget(config,g2sg,m) ;
worklist.addTarget(t);
}
g2sg.scope.configureForADTtesting();
for (Constructor co : g2sg.scope.constructors) {
SingleTarget t ;
if (config.refinementHeuristic.equals("evo")) t = new EvoSingleTarget(config,g2sg,co) ;
else t = new SingleTarget(config,g2sg,co) ;
//worklist.addTarget(t);
}
for (Method m : g2sg.scope.methods) {
if (Modifier.isStatic(m.getModifiers())) continue ;
SingleTarget t ;
if (config.refinementHeuristic.equals("evo")) t = new EvoSingleTarget(config,g2sg,m) ;
else t = new SingleTarget(config,g2sg,m) ;
worklist.addTarget(t);
if (!g2sg.scope.CUT.isInterface() && ! Modifier.isAbstract(g2sg.scope.CUT.getModifiers())) {
// set targets for ADT testing; only when the CUT is concrete:
g2sg.scope.configureForADTtesting();
for (Constructor co : g2sg.scope.constructors) {
SingleTarget t ;
if (config.refinementHeuristic.equals("evo")) t = new EvoSingleTarget(config,g2sg,co) ;
else t = new SingleTarget(config,g2sg,co) ;
//worklist.addTarget(t);
}
for (Method m : g2sg.scope.methods) {
if (Modifier.isStatic(m.getModifiers())) continue ;
SingleTarget t ;
if (config.refinementHeuristic.equals("evo")) t = new EvoSingleTarget(config,g2sg,m) ;
else t = new SingleTarget(config,g2sg,m) ;
worklist.addTarget(t);
}
}
t3log.info("Creating " + printTargets()) ;
}
private String printTargets() {
StringBuilder s = new StringBuilder() ;
s.append("#worklist=" + worklist.waiting.size() + ", targets: ") ;
s.append("G2 generator targeting " + g2sg.scope.CUT.getName()) ;
s.append(", #worklist=" + worklist.waiting.size() + ", targets: ") ;
for (SingleTarget target : worklist.waiting) {
target.maxNumberOfRefinement = config.maxNumberOfRefinements_ofEachTarget ;
target.minimumCovTobeHappy = config.minimumCovTobeHappy_ofEachTarget ;
s.append("\n " + target.getName() + "(" + target.getNumOfParams() + ")") ;
}
t3log.info("Creating G2 targeting " + g2sg.scope.CUT.getName() + ", " + s.toString());
return s.toString() ;
}
public void startTimeBudget(long timebudget) {
......@@ -165,6 +183,11 @@ public class G2 {
* The whole process will keep going, until it runs out time budget.
*/
protected void generateAndRefine() throws TimeBudgetException, InterruptedException {
if (worklist.isEmpty()) {
t3log.info("" + g2sg.scope.CUT.getName() + " induces no target, so NO TEST SUITE is generated.") ;
return ;
}
// DON't do shut down here!! --> JacocoInstrumenter.shutDownJacocoLogger();
double budget = Math.max(1,timebudgetTracker.getBudget()) ;
double remaining = timebudgetTracker.check() ;
......@@ -180,35 +203,51 @@ public class G2 {
int prefixRefinementCount = 0 ;
int prefixStagnationCount = 0 ;
int stagnationLimit = 3 ;
boolean generateMorePrefix = worklist.hasADTtarget() ;
while (!worklist.isEmpty()) {
if (worklist.targets.size() == 0) {
// the first time, this will generate the prefixes, upon the next iterations,
// this will refine the prefixes each time the targetlist is emptied.
if (prefixStagnationCount < 3) {
prefixRefinementCount++ ;
Integer K = config.numberOfPrefixes ;
if (K==null) K = 50 ;
if (g2sg.getPrefixes() != null) K = Math.min(K,10) ;
int numOfAddedPrefixes = g2sg.incrementallyGeneratePrefixes(K,config.maxPrefixLength,config.maxObjectDepth) ;
if (numOfAddedPrefixes == 0) prefixStagnationCount++ ;
if (generateMorePrefix && prefixStagnationCount < stagnationLimit) {
prefixRefinementCount++ ;
Integer K = config.numberOfPrefixes ;
if (K==null) K = 50 ;
if (g2sg.getPrefixes() != null) K = Math.min(K,10) ;
Integer numOfAddedPrefixes = g2sg.incrementallyGeneratePrefixes(K,config.maxPrefixLength,config.maxObjectDepth) ;
// reset this flag to false:
generateMorePrefix = false ;
if (numOfAddedPrefixes==null) {
// cannot generate prefixes because CUT has no constructor/factory,
// we should now remove all ADT targets because we can't solve them anyway
worklist.removeADTtargets();
t3log.info("Since " + g2sg.scope.CUT.getName() + " can't be instantiated, we now DROP all ADT targets.") ;
// go back to the loop-head again:
continue ;
}
t3log.info("=== Generating/refining prefixes of "
if (numOfAddedPrefixes == 0) {
prefixStagnationCount++ ;
if (prefixStagnationCount >= stagnationLimit) {
t3log.warning("Prefix generation for " + g2sg.scope.CUT.getName() + " reaches stagnation. NO further prefixes will be generated.");
}
}
t3log.info("=== Generating/refining prefixes of "
+ g2sg.scope.CUT.getName()
+ ", generation: " + prefixRefinementCount
+ ", size: " + g2sg.getPrefixes().suite.size()
+ ". Generation: " + prefixRefinementCount
+ ", adding: " + numOfAddedPrefixes
+ ", tot-size: " + g2sg.getPrefixes().suite.size()
+ ", cov: " + g2sg.currentPrefixes.coverage
+ ", #tobjs: " + g2sg.currentPrefixes.tobjs.size()
);
);
}
else {
t3log.warning("Prefix generation for " + g2sg.scope.CUT.getName() + " reaches stagnation. NO further prefixes will be generated.");
}
}
SingleTarget target = worklist.getNext(remaining/budget) ;
target.refine() ;
worklist.evaluateAndPutBack(target) ;
boolean targetSolved = worklist.evaluateAndPutBack(target) ;
if (!targetSolved && target.isADT && target.target instanceof Method) {
// a method-ADT target cannot be solved. Signal need to generate more prefixes.
// Note that a constructor-target does not need prefix.
generateMorePrefix = true ;
}
target.saveSuite() ;
// save a minimized version of the test suite; however this seem to drop
......@@ -227,14 +266,28 @@ public class G2 {
/**
* Use this factory method to construct instances of G2 to generate test
* suites for a given CUT.
* If the CUT is not concrete (it is an interface or abstract) then
* the method tries to find a concrete implementation of the CUT and target
* this implementation instead. If there are multiple implementations, one
* will be selected randomly.
* There are three special cases:
*
* (1) CUT is an interface. It may have static methods, which are then concrete.
* G2 will then target these methods.
*
* (2) CUT is an abstract class. Like Interface, it may gace static methods, which
* must be concrete. G2 will in any case target these methods.
*
* An abstract class may also have some concrete constructors and instance methods.
* These methods cannot be tested directly since Java prevents direct creation of
* instances of an abstract class. We will have to test the CUT indirectly through
* a concrete subclass. Ideally, the tester should provide a proxy subclass that
* will simply pass calls to implemented methods of the CUT, but for now G2 will just
* search for a random implementation of the CUT and targets this implementation instead.
*
* If the CUT has no inner class, then only a single instance of G2 will be
* generated. If it has static inner classes, one instance of G2 will be
* recursively constructed for each such inner class.
* This will create a separate instance of G2, targeting the implementation.
*
* TODO: a tool to generate such a proxy class.
*
* (3) If the CUT has inner classes, and if G2 is configured to chase them, it will
* target these inner classes as well. Fresh instances of G2 will be created for each
* inner class.
*/
static public List<G2> mkG2(String CUTname, G2Config config) {
ImplementationMap imap = new ImplementationMap(new String[0]) ;
......@@ -263,49 +316,44 @@ public class G2 {
ImplementationMap imap,
ClassLoader loader)
{
List<G2> g2s = new LinkedList<G2>() ;
try {
Class CUT = loader.loadClass(CUTname) ;
boolean abstractCUT = Modifier.isAbstract(CUT.getModifiers()) ;
if (CUT.isInterface() || abstractCUT) {
// if CUT is an interface or abstract, find an implementation:
Class imp = imap.getRndImp(CUT) ;
if (imp!=null) {
t3log.info(CUT.getName() + " is abstract or interface; trying to target a concrete implementation instead, namely:" + imp.getName());
g2s.add(new G2(imp.getName(),config)) ;
}
else t3log.warning(CUT.getName() + " is abstract or interface; but failed to find an implementation to target!");
if (abstractCUT) {
//Method[] publicmethods = CUT.getMethods() ;
Method[] CUTmethods = CUT.getDeclaredMethods() ;
for (Method m : CUTmethods) {
int m_ = m.getModifiers() ;
if (!Modifier.isAbstract(m_) && Modifier.isStatic(m_)) {
t3log.info(CUT.getName() + " is abstract, but it has at least one concrete and static method; so we target the class too.");
g2s.add(new G2(CUTname,config)) ;
break ;
}
}
}
List<G2> g2s = new LinkedList<G2>() ;
try {
// First create G2 for the CUT itself. Even if it is non-concrete it may contains concrete
// static classes that we should test
g2s.add(new G2(CUTname,config)) ;
// If CUT is abstract, it may have concrete methods. To test these we need to create
// instances of the CUT. However, an abstract class cannot be instantiated, so we need
// to find a concrete implementation, and test the CUT indirectly through this concrete
// implementation:
Class CUT = loader.loadClass(CUTname) ;
if (!CUT.isInterface() && Modifier.isAbstract(CUT.getModifiers())) {
// if CUT is abstract, find an implementation:
Class imp = imap.getRndImp(CUT) ;
if (imp!=null) {
t3log.info(CUT.getName() + " is abstract; we will test indirectly through a concrete implementation, namely:" + imp.getName());
G2 g2indirect = new G2(imp.getName(),config) ;
g2s.add(g2indirect) ;
}
else t3log.warning(CUT.getName() + " is abstract; but failed to find an implementation to target!");
}
else g2s.add(new G2(CUTname,config)) ;
// we additionally constructs instances of G2 for every static inner
// class of the CUT
if (config.targetStaticInnerClassesToo) {
// in some rare case CUT.getDeclaredClasses() may throw java.lang.IllegalAccessError, cannot access superclass...
// can't figure out why; probably classloader issue
for (Class innerC : CUT.getDeclaredClasses()) {
// non static inner classes and inherited inner classes are NOT targeted:
// NOTE: well, include them nonetheless...
// if (! Modifier.isStatic(innerC.getModifiers())) continue ;
// if (innerC.getDeclaringClass() != CUT) continue ;
// t3log.info("Found an inner static class " + innerC.getName());
g2s.addAll(mkG2worker(innerC.getName(),config,imap,loader)) ;
}
// if configured to do so, we additionally constructs instances of G2 for every static inner
// class of the CUT
if (config.targetStaticInnerClassesToo) {
// in some rare case CUT.getDeclaredClasses() may throw java.lang.IllegalAccessError, cannot access superclass...
// can't figure out why; probably classloader issue
for (Class innerC : CUT.getDeclaredClasses()) {
// non static inner classes and inherited inner classes are NOT targeted:
// NOTE: well, include them nonetheless...
// if (! Modifier.isStatic(innerC.getModifiers())) continue ;
// if (innerC.getDeclaringClass() != CUT) continue ;
// t3log.info("Found an inner static class " + innerC.getName());
g2s.addAll(mkG2worker(innerC.getName(),config,imap,loader)) ;
}
}
}
catch (Throwable t) {
......@@ -335,7 +383,7 @@ public class G2 {
return ;
}
if (g2s.length>1) {
String s = "G2 will target CUT and its internal classes: " ;
String s = "G2 will target these classes: " ;
for (int k=0; k<g2s.length; k++) {
if (k>0) s += ", " ;
s += g2s[k].g2sg.scope.CUT.getName() ;
......
package Sequenic.T3.DerivativeSuiteGens.Gen2;
import java.io.Serializable;
import Sequenic.T3.Generator.Generator;
import Sequenic.T3.Sequence.Datatype.PARAM;
public class G2Config {
public String CUTrootDir ;
......@@ -66,6 +71,14 @@ public class G2Config {
// controlling how violating sequences are shown:
public boolean showExcExecution = true ;
public int showLength = 4 ;
public int showDepth = 5 ;
public int showDepth = 5 ;
public Generator<PARAM,Serializable> customPrimitivesGenerator = null ;
public G2Config usePrimitiveGenerator(Generator<PARAM,Serializable> gen) {
customPrimitivesGenerator = gen ;
return this ;
}
}
......@@ -20,6 +20,7 @@ package Sequenic.T3.DerivativeSuiteGens.Gen2;
import static Sequenic.T3.Generator.GenCombinators.SequenceWhile;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
......@@ -82,13 +83,13 @@ public class G2SuiteGen {
public float fieldUpdateProbability = 0.3f ;
private G2SuiteGen() {}
//private G2SuiteGen() {}
public G2SuiteGen(String CUTname,
String CUTrootDir)
throws Exception
{
this(CUTname,CUTrootDir,true,true,null) ;
this(CUTname,CUTrootDir,true,true,null,null) ;
}
/**
......@@ -100,7 +101,9 @@ public class G2SuiteGen {
String CUTrootDir,
boolean useCoverageGuidance,
boolean useStaticInfo,
String staticInfoDir)
String staticInfoDir,
Generator<PARAM,Serializable> customPrimValGenerator
)
throws Exception
{
//t3log.info("** CUT = " + CUT.getName());
......@@ -137,31 +140,17 @@ public class G2SuiteGen {
}
}
configure(CUT,staticInfoDir) ;
configure(CUT,staticInfoDir,customPrimValGenerator) ;
}
// configuring other stuffs ; called from the constructor.
private void configure(Class CUT, String staticInfoDir) {
private void configure(Class CUT, String staticInfoDir, Generator<PARAM,Serializable> customPrimValGenerator) {
// Be careful with the order of setting up!
// setting up implementation-map and testing scope:
imap = new ImplementationMap(new String[0]) ;
scope = new TestingScope(imap,CUT) ;
// Forcefully adding private constructors to the scope... hackish.
scope.configureForADTtesting() ;
if (true /* scope.constructors.isEmpty() && scope.creatorMethods.isEmpty() */) {
for (Constructor co : CUT.getDeclaredConstructors()) {
int mod = co.getModifiers() ;
if (Modifier.isPrivate(mod) && ! Modifier.isAbstract(mod)) {
scope.forceConstructors.add(co) ;
}
}
if (!scope.forceConstructors.isEmpty()) {
t3log.info("Forcefully adding private constructors into the scope of " + CUT.getName()) ;
}
}
// read various static information provided in file(s):
if (staticInfoDir != null) {
try {
......@@ -190,7 +179,7 @@ public class G2SuiteGen {
}
// setup value generator, and custom pool:
gen2vmg = new G2ValueMG(imap) ;
gen2vmg = new G2ValueMG(imap,customPrimValGenerator) ;
resetSeededConstants() ;
pool = gen2vmg.pool ;
valueMG = gen2vmg.valueMG() ;
......@@ -484,10 +473,38 @@ public class G2SuiteGen {
*
* If maximum length is reached and we reach stagnation, we reset the legth
* to 1.
*
* If the testing scope does not contain any constructor/factory to create an instance of
* the SUT the method will however returns null.
*/
private int refine(int N) {
private Integer refine(int N) {
// prepare the scope
scope.configureForADTtesting();
// If we have no constructor nor creation method, forcefully add declared
// non-visible constructors:
if(scope.constructors.isEmpty() && scope.creatorMethods().isEmpty()) {
boolean added = false ;
for (Constructor co : scope.CUT.getDeclaredConstructors()) {
int mod = co.getModifiers() ;
if (! Modifier.isAbstract(mod)) {
added = true ; scope.constructors.add(co) ;
}
}
if(added) {
Logger.getLogger(CONSTANTS.T3loggerName).info("Forcefully adding non-visible constructors into the scope of " + scope.CUT.getName()) ;
}
else {
// if forcing fails, then we simply have no means to create an instance of
// the CUT, hence also no means to produce prefixes ;
Logger.getLogger(CONSTANTS.T3loggerName).info("Cannot find a constructor/factory to instantiate "
+ scope.CUT.getName()
+ " to create prefixes. No prefixes will be generated.") ;
return null ;
}
}
if (staticInfo != null) {
resetSeededConstants() ;
addSeededSPrimitives(staticInfo.getAllConstansts()) ;
......@@ -558,9 +575,12 @@ public class G2SuiteGen {
/**
* This will generate up to N prefixes iteratively. It returns the number
* of prefixes it manages to generate.
*
* If the testing scope does not contain any constructor/factory to create an instance of
* the SUT the method will however returns null.
*/
public int incrementallyGeneratePrefixes(int N, int maxlength, int maxdepth) {
System.err.println(">>> invoking incrementallyGeneratePrefixes...") ;
public Integer incrementallyGeneratePrefixes(int N, int maxlength, int maxdepth) {
// t3log.info(">>> invoking incrementallyGeneratePrefixes...") ;
if (currentPrefixes == null) {
currentPrefixes = new Prefixes(maxlength,maxdepth) ;
}
......@@ -568,14 +588,16 @@ public class G2SuiteGen {
currentPrefixes.maxlength = maxlength ;
currentPrefixes.ObjStructureMaxDepth = maxdepth ;
}
int added = currentPrefixes.refine(N) ;
Integer added = currentPrefixes.refine(N) ;
if(added == null) {
return null ;
}
if(added == 0) {
System.err.println(">>> incrementallyGeneratePrefixes DONE; FAIL to add any.") ;
t3log.info("FAIL to add any new prefix.") ;
}
else {
System.err.println(">>> incrementallyGeneratePrefixes DONE. Adding " + added
+ " prefixes. New size: " + currentPrefixes.size()) ;
t3log.info("Adding " + added + "; #prefixes now =" + currentPrefixes.size()) ;
}
return added ;
}
......
......@@ -27,6 +27,7 @@ import java.util.Map.Entry;
import Sequenic.T3.ImplementationMap;
import Sequenic.T3.Pool;
import Sequenic.T3.T3Random;
import Sequenic.T3.Generator.GenCombinators;
import Sequenic.T3.Generator.Generator;
import Sequenic.T3.Generator.Value.*;
import Sequenic.T3.JavaType.JTfun;
......@@ -43,6 +44,12 @@ public class G2ValueMG {
public G2Pool pool ;
/**
* Two ways to provide custom control on generating primitive values. One is by supplying
* an explicit generator (below). And the other is by providing a list of constants.
*/
public Generator<PARAM,Serializable> customPrimValGenerator = null ;
/**
* primitive values and strings that are seeded (more likely to be needed):
*/
......@@ -122,7 +129,7 @@ public class G2ValueMG {
}
}
public G2ValueMG(ImplementationMap ImpsMap) {
public G2ValueMG(ImplementationMap ImpsMap, Generator<PARAM,Serializable> customPrimValGenerator) {
String chars = "abcdefghijklmnopqrstuvwxyz" ;
chars = chars
+ chars.toUpperCase()
......@@ -137,6 +144,7 @@ public class G2ValueMG {
collectiongens = new CollectionLikeMG(collectionSize,ImpsMap) ;
lambdagens = new LamdaMG(ImpsMap) ;
refgens = new REFMG(pool) ;
this.customPrimValGenerator = customPrimValGenerator ;
}
/**
......@@ -384,7 +392,17 @@ public class G2ValueMG {
}
else {
toss = toss - chanceToPickPreviousPrimitive ;
if (toss < chanceToPickSeededPrimitive) o = getRandomSeededSPrimitive(ty) ;
if (toss < chanceToPickSeededPrimitive) {
o = null ;
if (customPrimValGenerator != null) {
if (rnd.nextFloat() <= 0.5) {
// 50% chance to pick using the custom generator
Maybe<Serializable> o_ = customPrimValGenerator.generate(P) ;
if(o_ != null) o = o_.val ;
}
}
if(o==null) o = getRandomSeededSPrimitive(ty) ;
}
else {
toss = toss - chanceToPickSeededPrimitive ;
if (toss < chanceToPickNeighbor) o = getRandomNeighbor(ty) ;
......@@ -436,7 +454,7 @@ public class G2ValueMG {
* This will construct the meta-generator to generate values.
*/
public Generator<PARAM,STEP> valueMG() {
FUN<Generator<PARAM,STEP>> recGenerator = new FUN<Generator<PARAM,STEP>>() ;
Generator<PARAM,STEP> g =
FirstOf(
......
......@@ -124,13 +124,13 @@ public class SingleTarget {
if (target instanceof Method) {
Method m = (Method) target ;
if (isADT) {
g2sg.scope.configureForADTtesting();
// g2sg.scope.configureForADTtesting(); this will be done in the generate below