/*
* Copyright 2007 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.T2.Seq;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import Sequenic.T2.Pool;
import Sequenic.T2.Coverage.BasicPath;
/**
* A Trace is a meta representation of test, which is a sequence of test-steps.
* A test-step is for example updating a field of a target object, or calling
* one of its methods. Being a meta representation means that a Trace in itself
* does not cause the test to execute. To execute it, we have to call a special
* method that will interpret the trace, and turns it to an execution. The
* advantage of having a meta representation of a test is that we can do things
* with it, like saving it, reloading it, changing it, and so on.
* See also the doc of the {@link Sequenic.T2 Sequenic.T2 package}.
*
*
To be more precise, a trace consists of a creation step, that creates a
* target object (the object on which the test will be applied on), and a sequence of subsequent {@link
* Sequenic.T2.Trace.TraceStep TraceStep objects}. The creation step is a
* {@link Sequenic.T2.Trace.MkValStep MkVal object}. A
* MkVal object is also a meta representation, which is ínterpreted can create an
* object, whereas a trace step is
* used to apply side effects on the target object. A trace step may
* also consist of MkVal steps, e.g. when it needs to create objects
* to pass as parameters to a method.
*/
public class Trace implements Serializable{
public Trace()
{
this(null,new ArrayDeque(), -1, false, false, Collections.EMPTY_SET);
}
private Trace(MkValStep c, Deque t, int iOTO, boolean vio, boolean div, Set cov)
{
creation = c;
trace = t;
indexOfTargetObj = iOTO;
violating = vio;
diverging = div;
coverage = cov;
}
/**
* The creation step that starts this execution trace.
*/
transient public MkValStep creation;
/**
* The sequence of steps belonging to this trace.
*/
transient public Deque trace;
/**
* Implicitly there is a pool associated to a trace. The pool
* is used to keep track of object generated when the trace
* is generated. This field gives the index of the target object
* in this pool.
*/
public int indexOfTargetObj;
/**
* Is set to true when this trace is marked a violating.
*/
public boolean violating;
/**
* Is set to true when this trace is marked as diverging.
*/
public boolean diverging;
/**
* We maintain the invariant that coverage is never null.
*/
private Set coverage;
/**
* Used to classify different kind of traces, e.g. to distinguish
* violating and non-violating traces. The purpose of this field is
* mainly to quickly filter traces without having to execute them.
*/
//public String classification = null;
/**
* Currently these are the only classifications :)
*/
//public static final String VIOLATING_TRACE = "VIOLATING TRACE";
//public static final String DIVERGING_TRACE = "POSSIBLY NON TERMINATING TRACE";
/**
* This method will try to execute *all* steps in this trace, even if
* it is marked as DIVERGING. The execution is stopped when a violation
* is detected. An trace execution report will be returned, from which
* it can be inferred if e.g. the execution finds a new violation,
* or if a violation that was found in the execution producing the trace
* now disappears.
*
* Particularly with divergence, this method in itself does not
* implement a time out mechanism or such. So it cannot force a diverging
* step to stop. Such a mechanism is assumed to be implemented in either
* the step execution system, or on the external framework that uses this
* method.
*
*
If no such stopping mechanism is present, then one should take into
* account that this method may thus diverge.
*
* @param pool Pass here the same pool as used to produce this
* execution trace.
*
* @param printIntermediateStepsOption When true (default) report will
* include the report of intermediate steps in a trace. Else only the
* creation of the target object and the last two steps will be reported.
*/
public TraceExecInfo execAllSteps(Class CUT,
Pool pool,
List classinvs,
boolean printIntermediateStepsOption,
ReportersPool reporters) {
boolean produceReport = reporters != null;
pool.reset();
TraceExecInfo info = new TraceExecInfo(this) ;
Object targetObj = null;
int stepNr = 0 ;
boolean ok = true ;
//System.out.println("***"+ Show.show(creation)) ;
if (produceReport) reporters.reportTraceBegin();
ok = info.update_with_step(creation.MkTargetObject(CUT, pool, classinvs, reporters), stepNr) ;
//System.out.println(">>> " + ok + info.new_violating_flag + info.lastStepResult.isAsmOrReqViolating()) ;
stepNr++;
if (ok) { // no violation found
targetObj = pool.get(indexOfTargetObj) ;
assert (targetObj == info.lastStepResult.targetObj) ;
for (TraceStep step : trace) {
//System.out.println(">>> " + trace.size()) ;
if (!printIntermediateStepsOption && stepNr <= trace.size() - 2) {
ok = info.update_with_step(step.exec(CUT, pool, targetObj, stepNr, classinvs, ReportersPool.NULLreporter),
stepNr) ;
} else {
ok = info.update_with_step(step.exec(CUT, pool, targetObj, stepNr, classinvs, reporters),
stepNr) ;
}
stepNr++ ;
if (!ok) break;
}
}
if (produceReport) {
reporters.reportTraceEnd();
}
return info;
}
/**
* As either execAllSteps or execAllStepsButLastDiv, depending on the boolean
* flag given.
*/
public TraceExecInfo exec(Class CUT,
Pool pool,
List classinvs,
boolean assumeLastStepDiverging,
boolean printIntermediateStepsOption,
ReportersPool reporters
) {
if (assumeLastStepDiverging)
return execAllStepsButLastDiv(CUT,pool,classinvs,printIntermediateStepsOption,reporters) ;
else
return execAllSteps (CUT,pool,classinvs,printIntermediateStepsOption,reporters) ;
}
public TraceStep getStep(int index)
{
if(index<0 || index>=trace.size())
throw new IndexOutOfBoundsException();
Iterator iter = trace.iterator();
while(index--!=0)
iter.next();
return iter.next();
}
/**
* This is as execAllSteps. BUT the last step in the trace is ASSUMED
* to be diverging, and will not be executed. If no violation up to
* this last-step, this method will provide a meta-reporting of the
* last-step, and just assume that it is diverging (which will be
* reflected in the info it returns).
*/
public TraceExecInfo execAllStepsButLastDiv(Class CUT,
Pool pool,
List classinvs,
boolean printIntermediateStepsOption,
ReportersPool reporters) {
assert (creation != null) ;
TraceExecInfo info = null ;
int traceLength = this.trace.size() + 1 ;
METARUN laststep = null ;
if (trace.size()>0) {
// remove the last step first, then execute:
laststep = trace.removeLast() ;
info = execAllSteps(CUT, pool,classinvs,printIntermediateStepsOption,reporters) ;
// putting back the last-step:
trace.add((TraceStep) laststep) ;
if (info.found_new_violation()) return info ;
}
else laststep = creation ;
// No new violation found; now only the last step remains.
// We don't execute the last step, we just
// make a meta-report of it, and ASSUMING it would be diverging:
Object targetObj = null ;
if (indexOfTargetObj >= 0) targetObj = pool.get(indexOfTargetObj) ;
info.lastStepResult = new ExecResult(CUT,laststep,targetObj) ;
info.lastStepResult.possiblyDiverging = true ;
info.new_diverging_flag = true ;
info.violation_location = traceLength - 1 ;
if (reporters!=null) reporters.reportStep(info.lastStepResult,0) ;
return info ;
}
public boolean isObsolete() {
if(creation.isObsolete())
return true;
for(TraceStep ts : trace)
{
if(ts.isObsolete())
return true;
}
return false;
}
public Set coverage()
{
return coverage;
}
public void setCoverage(Set coverage)
{
if(coverage==null)
throw new NullPointerException("Coverage can not be null");
this.coverage = coverage;
}
// /**
// * If this trace does not cover any modification-traversing paths
// * then the coverage data is cleared and the method returns true.
// *
// * Otherwise the coverage data is updated and this method returns false.
// *
// * @param selector
// * @return
// */
// public boolean updateCoverageAndSelect(BasicPath.Selector selector)
// {
// HashSet newCoverage = new HashSet(coverage.size());
// BasicPath newBP;
// for(BasicPath bp : coverage)
// {
// newBP = bp.cachedSelectAndUpdate(selector);
// if(newBP==null)
// {
// //bp is modification-traversing: clear coverage, return true
// coverage = Collections.emptySet();
// return true;
// }
// else
// //bp is not modification-traversing, update coverage tentatively, continue
// newCoverage.add(newBP);
// }
// //No bp was modification-traversing, update coverage definitely, return false
// coverage = newCoverage;
// return false;
// }
//
/*=================================================================
*==== SERIALIZATION =======
*=================================================================
*
* There are two ways to serialize Traces.
* Choose which to use in the serialization routines of TraceFile.
*
*/
/* ========= METHOD 1: Conventional Serialization ================
*
* This implements Serializable and only deviates where necessary.
*/
private static final long serialVersionUID = -14045306304279703L;
/** These methods are for Serialization, because TraceSteps and MkValSteps are no longer Serializable.
* Doing this reduces the storage size of a Trace by 50% and allows us to (de)serialize Obsolete traces.
*
*/
private void writeObject(java.io.ObjectOutputStream out)
throws IOException{
out.defaultWriteObject();
Externalize_MkValStep.write(creation,out);
out.writeInt(trace.size());
for(TraceStep ts : trace) {
ts.write(out);
}
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException{
in.defaultReadObject();
creation = Externalize_MkValStep.read(in);
int size = in.readInt();
trace = new LinkedList();
while(size-->0)
trace.add( TraceStep.read(in));
}
/* ///// END METHOD 1
*
* ========= METHOD 2: Manual Serialization ================
*
* A custom, manual serialization routine that is harder to maintain, but uses less storage space.
* It should also be faster.
*/
private static final int maskDiverging = 1;
private static final int maskViolating = 2;
private static final int maskCreationIsNull = 4;
private static final int maskTraceIsNull = 8;
private static final int maskUseInt = 32;
private static final int maxUnsignedShort = 65535;
public static final int endOfStream = 16;
/**
* A custom, manual serialization routine that is harder to maintain, but uses less storage space.
* It should also be faster.
*/
public void alternativeSerializationWrite(ObjectOutput out) throws IOException
{
int mask = 0;
int covSize = -1;
int traceSize = -1;
boolean useInt = false;
if(diverging) mask |= maskDiverging;
if(violating) mask |= maskViolating;
if(creation == null) mask |= maskCreationIsNull;
if(trace == null)
mask |= maskTraceIsNull;
else if(maxUnsignedShort < (traceSize=trace.size()))
useInt = true;
else if(maxUnsignedShort < (covSize=coverage.size()))
useInt = true;
if(useInt || indexOfTargetObj > Short.MAX_VALUE || indexOfTargetObj < Short.MIN_VALUE)
mask |= maskUseInt;
out.write(mask);
if(useInt)
out.writeInt(indexOfTargetObj);
else
out.writeShort(indexOfTargetObj);
if(creation!=null)
Externalize_MkValStep.write(creation, out);
if(covSize>=0) {
if(useInt)
out.writeInt(covSize);
else
out.writeShort(covSize);
for(BasicPath bp : coverage)
out.writeObject(bp);
}
if(traceSize >= 0) {
if(useInt)
out.writeInt(traceSize);
else
out.writeShort(traceSize);
for(TraceStep ts : trace)
ts.write(out);
}
}
/** Dual of the 'write' method.
*/
public static Trace alternativeSerializationRead(ObjectInput in) throws IOException,
ClassNotFoundException
{
final int mask = in.read();
if(mask==endOfStream)
return null;
final boolean useInt = (mask & maskUseInt)!=0;
final int iOTO = useInt ? in.readInt() : in.readShort();
final MkValStep c;
if((mask & maskCreationIsNull) != 0)
c = null;
else
c = Externalize_MkValStep.read(in);
final Set cov;
//coverage
{
int size = useInt ? in.readInt() : in.readUnsignedShort();
if(size>1)
{
cov = new HashSet(2*size);
while(size-->0)
cov.add((BasicPath) in.readObject());
} else if(size==0){
cov = Collections.emptySet();
} else {
cov = Collections.singleton((BasicPath)in.readObject());
}
}
final Deque t;
if((mask & maskTraceIsNull) != 0) {
t = null;
}
else {
int size = useInt ? in.readInt() : in.readShort();
t = new ArrayDeque(size);
while(size-->0)
t.add(TraceStep.read(in));
}
return new Trace(c,t,iOTO,(mask & maskViolating)!=0,(mask & maskDiverging)!=0,cov);
}
}