package tangentz;
import java.util.HashMap;
import java.util.Map;
import com.topcoder.client.contestant.ProblemComponentModel;
import com.topcoder.shared.language.Language;
import com.topcoder.shared.problem.*;
// TZTester plugin
// http://www.topcoder.com/contest/classes/TZTester/TZTester.html
// customized by cafelier and naoya_t
/**
* @author TangentZ
*
* This tester class is for C++ only. It is based on PopsProcessor which is written for Java.
* It reads in all the given examples for a problem and generates the equivalent C++ code
* to test all the cases. The accumulated running time is 8 seconds, but it is easy to
* selectively run a specific case only.
*
* This tester will define three tags that can be embedded within PopsEdit/FileEdit code template:
* $WRITERCODE$ - place holder for writer code - will be blank if none found
* $PROBLEM$ - place holder for problem description as plain text
* // $RUNTEST$ - place holder for where to put the code that starts the test
* $TESTCODE$ - place holder for where to put the test code
*/
public class TZTester
{
// Map used to store my tags
private HashMap<String,String> m_Tags = new HashMap<String,String>();
// Constants
private static final String k_WRITERCODE = "$WRITERCODE$";
private static final String k_PROBLEM = "$PROBLEM$";
private static final String k_RUNTEST = "$RUNTEST$";
private static final String k_TESTCODE = "$TESTCODE$";
private static final String k_VERSION = "\n// Powered by TZTester 1.01 [25-Feb-2003] : <cafelier&naoya_t>-custom";
// Cut tags
private static final String k_BEGINCUT = "// BEGIN CUT HERE";
private static final String k_ENDCUT = "// END CUT HERE";
// Problem-related variables
private ProblemComponentModel m_Problem = null;
private Language m_Language = null;
/**
* PreProcess the source code
*
* First determines if it is saved code, writer code, or nothing and stores it in $WRITERCODE$ tag
* Secondly builds a main method with default test cases
*/
public String preProcess(String Source, ProblemComponentModel Problem, Language Lang, Renderer Render)
{
// Set defaults for the tags in case we exit out early
m_Tags.put(k_WRITERCODE, "");
m_Tags.put(k_PROBLEM, "");
m_Tags.put(k_RUNTEST, "// *** WARNING *** $RUNTEST$ is not supported by this customized TZTester.");
m_Tags.put(k_TESTCODE, "");
// If there is source and the source is NOT equal to the default solution, return it
if( Source.length()>0 && !Source.equals(Problem.getDefaultSolution()) )
return Source;
// Check to see if the component has any signature
if( !Problem.hasSignature() )
{
m_Tags.put(k_TESTCODE, "// *** WARNING *** Problem has no signature defined for it");
return "";
}
// Get the test cases
TestCase[] TestCases = Problem.getTestCases();
// Check to see if test cases are defined
if( TestCases==null || TestCases.length==0 )
{
m_Tags.put(k_TESTCODE, "// *** WARNING *** No test cases defined for this problem");
return "";
}
// Re-initialize the tags
m_Tags.clear();
m_Tags.put(k_WRITERCODE, Problem.getDefaultSolution());
try { m_Tags.put(k_PROBLEM, Render.toHTML(m_Language)); } catch (Exception Ex) { }
m_Problem = Problem;
m_Language = Lang;
// Generate the test cases
generate_test_code();
return "";
}
/**
* This method will cut the test methods above out
*/
public String postProcess(String Source, Language Lang)
{
// Insert a version string
return Source + k_VERSION;
}
/**
* This method will return my tags. This method is ALWAYS called after preProcess()
*
* @return a map of my tags
*/
public Map getUserDefinedTags()
{
return m_Tags;
}
/**
* This method will generate the code for the test cases.
*/
private void generate_test_code()
{
DataType[] ParamTypes = m_Problem.getParamTypes();
DataType ReturnType = m_Problem.getReturnType();
TestCase[] Cases = m_Problem.getTestCases();
StringBuffer Code = new StringBuffer();
// <<thanks to naoya_t>> Generate the timer function
Code.append("#include <ctime>\n");
Code.append("double start_time; string timer()\n");
Code.append(" { ostringstream os; os << \" (\" << int((clock()-start_time)/CLOCKS_PER_SEC*1000) << \" msec)\"; return os.str(); }\n");
// Generate the vector output function
Code.append("template<typename T> ostream& operator<<(ostream& os, const vector<T>& v)\n");
Code.append(" { os << \"{ \";\n");
Code.append(" for(typename vector<T>::const_iterator it=v.begin(); it!=v.end(); ++it)\n");
Code.append(" os << \'\\\"\' << *it << \'\\\"\' << (it+1==v.end() ? \"\" : \", \"); os << \" }\"; return os; }\n");
// Generate the verification function
generate_verification_code(Code, ReturnType);
// <<modified by cafelier>> : new test code template
Code.append("#define CASE(N) {cerr << \"Test Case #\" << N << \"...\" << flush; start_time=clock();\n");
Code.append("#define END ");
generate_verifier_call(Code, ParamTypes, ReturnType, Cases[0]);
Code.append("}\n");
Code.append("int main(){\n\n");
// Generate the individual test cases
for(int i=0; i<Cases.length; ++i)
generate_test_case(i, Code, ParamTypes, ReturnType, Cases[i]);
for(int i=0; i<2; ++i)
generate_blank_test_case(Cases.length+i, Code, ParamTypes, ReturnType, Cases[0]);
Code.append("\n}\n");
// Insert the cut tags
Code.insert(0, k_BEGINCUT+"\n");
Code.append(k_ENDCUT);
// Replace $TESTCODE$
m_Tags.put(k_TESTCODE, Code.toString());
}
/**
* This method will generate the code for verifying test cases.
*/
private void generate_verification_code(StringBuffer Code, DataType ReturnType)
{
String TypeString = ReturnType.getDescriptor(m_Language);
// <<modified by cafelier>> : new test code template
Code.append("void verify_case(const " + TypeString + "& Expected, const " + TypeString + "& Received) {\n");
// Print "PASSED" or "FAILED" based on the result
// <<modified by naoya_t>> : double precision
if (TypeString.equals("double")) {
Code.append(" bool ok = (abs(Expected - Received) < 1e-9);\n");
} else if (TypeString.equals("vector <double>")) {
// <<modified by cafelier>> : vector<double> !!!
Code.append(" bool ok = true;\n");
Code.append(" for(int i=0; i<Expected.size(); ++i)\n");
Code.append(" if( abs(Expected[i]-Received[i]) >= 1e-9 )\n");
Code.append(" ok = false;\n");
} else {
Code.append(" bool ok = (Expected == Received);\n");
}
Code.append(" if(ok) cerr << \"PASSED\" << timer() << endl; ");
Code.append(" else { cerr << \"FAILED\" << timer() << endl;\n ");
if (ReturnType.getDimension() == 0)
{
Code.append("cerr << \"\\to: \\\"\" << Expected << \'\\\"\' << endl ");
Code.append("<< \"\\tx: \\\"\" << Received << \'\\\"\' << endl; }");
}
else
{
Code.append("cerr << \"\\to: \" << Expected << endl ");
Code.append("<< \"\\tx: \" << Received << endl; }");
}
Code.append(" }\n");
}
/**
* This method will generate the code for one test case.
*/
private void generate_test_case(int Index, StringBuffer Code, DataType[] ParamTypes, DataType ReturnType, TestCase Case)
{
String[] Inputs = Case.getInput();
String Output = Case.getOutput();
String Desc = ReturnType.getDescription();
/*
* Generate code for setting up individual test cases
* and calling the method with these parameters.
*/
// <<modified by cafelier>> : new test code template
Code.append("CASE("+Index+")\n");
// Generate each input variable separately
for (int I = 0; I < Inputs.length; ++I) {
Code.append("\t");
generate_parameter(I, Code, ParamTypes[I], Inputs[I]);
Code.append("\n");
}
// Generate the output variable as the last variable
Code.append("\t");
generate_parameter(-1, Code, ReturnType, Output);
Code.append("\n");
Code.append("END\n");
}
private void generate_blank_test_case(int Index, StringBuffer Code, DataType[] ParamTypes, DataType ReturnType, TestCase Case)
{
String[] Inputs = Case.getInput();
String Output = Case.getOutput();
String Desc = ReturnType.getDescription();
Code.append("CASE("+Index+")\n");
for (int I = 0; I < Inputs.length; ++I) {
Code.append("\t");
generate_parameter(I, Code, ParamTypes[I], "");
Code.append("\n");
}
Code.append("\t");
generate_parameter(-1, Code, ReturnType, "");
Code.append("\n");
Code.append("END\n");
}
private void generate_verifier_call(StringBuffer Code, DataType[] ParamTypes, DataType ReturnType, TestCase Case)
{
String[] Inputs = Case.getInput();
String Output = Case.getOutput();
String Desc = ReturnType.getDescription();
Code.append("verify_case(_, "
+ m_Problem.getClassName() + "()." + m_Problem.getMethodName() + "(");
for(int i=0; i<Inputs.length; ++i)
{
Code.append( m_Problem.getParamNames()[i] );
if( i < Inputs.length-1 )
Code.append(", ");
}
Code.append("));");
}
/**
* This method will generate the required parameter as a unique variable.
*/
private void generate_parameter(int Index, StringBuffer Code, DataType ParamType, String Input)
{
// <<modified by cafelier>> : named parameters
String Name = (Index==-1 ? "_" : m_Problem.getParamNames()[Index]);
String Desc = ParamType.getBaseName();
if (Desc.equals("long") || Desc.equals("Long"))
Desc = "long long";
if (ParamType.getDimension() == 0)
{
// Just a scalar value, simply initialize it at declaration (long integers need an 'L' tagged on)
if (Desc.equals("long long"))
Code.append(ParamType.getDescriptor(m_Language) + " " + Name + " = " + Input + "LL; ");
else
Code.append(ParamType.getDescriptor(m_Language) + " " + Name + " = " + Input + "; ");
}
else
{
// <<modified by cafelier>> : empty array
if( Input.matches("^[\\s\\{\\}]+$") )
{
Code.append(ParamType.getDescriptor(m_Language) + " " + Name + "; ");
}
else
{
// Arrays need to be converted to vector<type> before passing
if (Desc.equals("long long"))
Input = Input.replaceAll("¥¥d+","$1LL");
Code.append(Desc.toLowerCase() + " " + Name + "_[] = " + Input + ";\n\t");
Code.append(" "+ParamType.getDescriptor(m_Language) + " " + Name + "(" + Name + "_, " + Name + "_+sizeof("+Name+"_)/sizeof(*"+Name+"_)); ");
}
}
}
}