283 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
package cd;
 | 
						|
 | 
						|
import java.io.File;
 | 
						|
import java.io.FileNotFoundException;
 | 
						|
import java.io.FileReader;
 | 
						|
import java.io.FileWriter;
 | 
						|
import java.io.IOException;
 | 
						|
import java.io.InputStream;
 | 
						|
import java.io.PrintStream;
 | 
						|
import java.util.List;
 | 
						|
import java.util.concurrent.TimeUnit;
 | 
						|
 | 
						|
import org.junit.Assert;
 | 
						|
import org.junit.Rule;
 | 
						|
import org.junit.Test;
 | 
						|
import org.junit.rules.DisableOnDebug;
 | 
						|
import org.junit.rules.TestRule;
 | 
						|
import org.junit.rules.Timeout;
 | 
						|
 | 
						|
import cd.backend.codegen.AssemblyFailedException;
 | 
						|
import cd.frontend.parser.ParseFailure;
 | 
						|
import cd.frontend.semantic.SemanticFailure;
 | 
						|
import cd.ir.Ast.ClassDecl;
 | 
						|
import cd.util.FileUtil;
 | 
						|
import cd.util.debug.AstDump;
 | 
						|
 | 
						|
abstract public class AbstractTestAgainstFrozenReference {
 | 
						|
    
 | 
						|
	public static final String SEMANTIC_OK = "OK";
 | 
						|
    public static final String PARSE_FAILURE = "ParseFailure";
 | 
						|
	
 | 
						|
    public File file, sfile, binfile, infile;
 | 
						|
	public File parserreffile, semanticreffile, execreffile, cfgreffile, rdreffile,
 | 
						|
	        nnreffile, optreffile;
 | 
						|
	public File errfile;
 | 
						|
	public Main main;
 | 
						|
 | 
						|
	public static int counter = 0;
 | 
						|
 | 
						|
	@Rule
 | 
						|
	public TestRule timeout = new DisableOnDebug(new Timeout(15, TimeUnit.SECONDS));
 | 
						|
	
 | 
						|
	@Test
 | 
						|
	public void test() throws Throwable {
 | 
						|
		System.err.println("[" + counter++ + " = " + file + "]");
 | 
						|
		
 | 
						|
		try {
 | 
						|
			// Delete intermediate files from previous runs:
 | 
						|
			if (sfile.exists())
 | 
						|
				sfile.delete();
 | 
						|
			if (binfile.exists())
 | 
						|
				binfile.delete();
 | 
						|
 | 
						|
			runReference();
 | 
						|
			
 | 
						|
			List<ClassDecl> astRoots = testParser();
 | 
						|
			if (astRoots != null) {
 | 
						|
				{
 | 
						|
					boolean passedSemanticAnalysis = testSemanticAnalyzer(astRoots);
 | 
						|
					
 | 
						|
					{
 | 
						|
						if (passedSemanticAnalysis) {
 | 
						|
							testCodeGenerator(astRoots);
 | 
						|
							
 | 
						|
							{
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} catch (org.junit.ComparisonFailure cf) {
 | 
						|
			throw cf;
 | 
						|
		} catch (Throwable e) {
 | 
						|
			PrintStream err = new PrintStream(errfile);
 | 
						|
			err.println("Debug information for file: " + this.file);
 | 
						|
			err.println(this.main.debug.toString());
 | 
						|
			err.println("Test failed because an exception was thrown:");
 | 
						|
			err.println("    " + e.getLocalizedMessage());
 | 
						|
			err.println("Stack trace:");
 | 
						|
			e.printStackTrace(err);
 | 
						|
			System.err.println(FileUtil.read(errfile));
 | 
						|
			throw e;
 | 
						|
		}
 | 
						|
 | 
						|
		// if we get here, then the test passed, so delete the errfile:
 | 
						|
		// (which has been accumulating debug output etc)
 | 
						|
		if (errfile.exists())
 | 
						|
			errfile.delete();
 | 
						|
	}
 | 
						|
 | 
						|
	private void runReference() throws IOException, InterruptedException {
 | 
						|
        String slash = File.separator;
 | 
						|
        String colon = File.pathSeparator;
 | 
						|
        String javaExe = System.getProperty("java.home") + slash + "bin" + slash + Config.JAVA_EXE;
 | 
						|
        
 | 
						|
        ProcessBuilder pb = new ProcessBuilder(
 | 
						|
                javaExe, "-Dcd.meta_hidden.Version=" + referenceVersion(),
 | 
						|
                "-cp", "lib/frozenReferenceObf.jar" + colon + " lib/junit-4.12.jar" + colon + "lib/antlr-4.7.1-complete.jar",
 | 
						|
                "cd.FrozenReferenceMain", file.getAbsolutePath());
 | 
						|
	        
 | 
						|
        Process proc = pb.start();
 | 
						|
        proc.waitFor();
 | 
						|
        try (InputStream err = proc.getErrorStream()) {
 | 
						|
            if (err.available() > 0) {
 | 
						|
                byte b[] = new byte[err.available()];
 | 
						|
                err.read(b, 0, b.length);
 | 
						|
                System.err.println(new String(b));
 | 
						|
            }
 | 
						|
        }
 | 
						|
	}
 | 
						|
	
 | 
						|
	private static String referenceVersion() {
 | 
						|
        {
 | 
						|
            return "BENCH";
 | 
						|
        }
 | 
						|
	}
 | 
						|
 | 
						|
	private String tryReadRefFile(File fileToFind) {
 | 
						|
		if (fileToFind.exists() && fileToFind.lastModified() >= file.lastModified()) {
 | 
						|
			try {
 | 
						|
				return FileUtil.read(fileToFind);
 | 
						|
			} catch (IOException e) {
 | 
						|
				throw new RuntimeException("ERROR: could not read file " + fileToFind.getPath());
 | 
						|
			}
 | 
						|
		}
 | 
						|
		throw new RuntimeException("ERROR: could not find file " + fileToFind.getPath());
 | 
						|
	}
 | 
						|
	
 | 
						|
	/** Run the parser and compare the output against the reference results */
 | 
						|
	private List<ClassDecl> testParser() throws Exception {
 | 
						|
		String parserRef = tryReadRefFile(parserreffile);
 | 
						|
		List<ClassDecl> astRoots = null;
 | 
						|
		String parserOut;
 | 
						|
 | 
						|
		try {
 | 
						|
			astRoots = main.parse(new FileReader(this.file));
 | 
						|
 | 
						|
			parserOut = AstDump.toString(astRoots);
 | 
						|
		} catch (ParseFailure pf) {
 | 
						|
			{
 | 
						|
				// Parse errors are ok too.
 | 
						|
				main.debug("");
 | 
						|
				main.debug("");
 | 
						|
				main.debug("%s", pf.toString());
 | 
						|
				parserOut = PARSE_FAILURE;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		{
 | 
						|
			// Now that the 2nd assignment is over, we don't
 | 
						|
			// do a detailed comparison of the AST, just check
 | 
						|
			// whether the parse succeeded or failed.
 | 
						|
			if (parserOut.equals(PARSE_FAILURE)	|| parserRef.equals(PARSE_FAILURE))
 | 
						|
				assertEquals("parser", parserRef, parserOut);
 | 
						|
		}
 | 
						|
		return astRoots;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	private boolean testSemanticAnalyzer(List<ClassDecl> astRoots)
 | 
						|
			throws IOException {
 | 
						|
		String semanticRef = findSemanticRef();
 | 
						|
 | 
						|
		boolean passed;
 | 
						|
		String result;
 | 
						|
		try {
 | 
						|
			main.semanticCheck(astRoots);
 | 
						|
			result = SEMANTIC_OK;
 | 
						|
			passed = true;
 | 
						|
		} catch (SemanticFailure sf) {
 | 
						|
			result = sf.cause.name();
 | 
						|
			main.debug("Error message: %s", sf.getLocalizedMessage());
 | 
						|
			passed = false;
 | 
						|
		}
 | 
						|
 | 
						|
		assertEquals("semantic", semanticRef, result);
 | 
						|
		return passed;
 | 
						|
	}
 | 
						|
	
 | 
						|
	private String findSemanticRef() throws IOException {
 | 
						|
		// Semantic ref file is a little different. It consists
 | 
						|
		// of 2 lines, but only the first line is significant.
 | 
						|
		// The second one contains additional information that we log
 | 
						|
		// to the debug file.
 | 
						|
	
 | 
						|
		String res = tryReadRefFile(semanticreffile);
 | 
						|
	
 | 
						|
		// Extract the first line: there should always be multiple lines,
 | 
						|
		// but someone may have tinkered with the file or something
 | 
						|
		if (res.contains("\n")) {
 | 
						|
			int newline = res.indexOf("\n");
 | 
						|
			String info = res.substring(newline + 1);
 | 
						|
			if (!info.equals("") && !info.equals("\n"))
 | 
						|
				main.debug("Error message from reference is: %s", info);
 | 
						|
			return res.substring(0, newline); // 1st line
 | 
						|
		} else {
 | 
						|
			return res;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	
 | 
						|
	/**
 | 
						|
	 * Run the code generator, assemble the resulting .s file, and (if the output
 | 
						|
	 * is well-defined) compare against the expected output.
 | 
						|
	 */
 | 
						|
	private void testCodeGenerator(List<ClassDecl> astRoots)
 | 
						|
			throws IOException {
 | 
						|
		// Determine the input and expected output.
 | 
						|
		String inFile = (infile.exists() ? FileUtil.read(infile) : "");
 | 
						|
		String execRef = tryReadRefFile(execreffile);
 | 
						|
 | 
						|
		// Run the code generator:
 | 
						|
		try (FileWriter fw = new FileWriter(this.sfile)) {
 | 
						|
			main.generateCode(astRoots, fw);
 | 
						|
		}
 | 
						|
 | 
						|
		// At this point, we have generated a .s file and we have to compile
 | 
						|
		// it to a binary file. We need to call out to GCC or something
 | 
						|
		// to do this.
 | 
						|
		String asmOutput = FileUtil.runCommand(
 | 
						|
				Config.ASM_DIR,
 | 
						|
				Config.ASM,
 | 
						|
				new String[] { binfile.getAbsolutePath(),
 | 
						|
						sfile.getAbsolutePath() }, null, false);
 | 
						|
 | 
						|
		// To check if gcc succeeded, check if the binary file exists.
 | 
						|
		// We could use the return code instead, but this seems more
 | 
						|
		// portable to other compilers / make systems.
 | 
						|
		if (!binfile.exists())
 | 
						|
			throw new AssemblyFailedException(asmOutput);
 | 
						|
 | 
						|
		// Execute the binary file, providing input if relevant, and
 | 
						|
		// capturing the output. Check the error code so see if the
 | 
						|
		// code signaled dynamic errors.
 | 
						|
		String execOut = FileUtil.runCommand(new File("."),
 | 
						|
				new String[] { binfile.getAbsolutePath() }, new String[] {},
 | 
						|
				inFile, true);
 | 
						|
 | 
						|
		// Compute the output to what we expected to see.
 | 
						|
		if (!execRef.equals(execOut))
 | 
						|
		    assertEqualOutput("exec", execRef, execOut);
 | 
						|
	}
 | 
						|
 | 
						|
	private void assertEquals(String phase, String exp, String act_) {
 | 
						|
		String act = act_.replace("\r\n", "\n"); // for windows machines
 | 
						|
		if (!exp.equals(act)) {
 | 
						|
			warnAboutDiff(phase, exp, act);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	
 | 
						|
	/**
 | 
						|
	 * Compare the output of two executions
 | 
						|
	 */
 | 
						|
	private void assertEqualOutput(String phase, String exp, String act_) {
 | 
						|
		String act = act_.replace("\r\n", "\n"); // for windows machines
 | 
						|
		if (!exp.equals(act)) {
 | 
						|
			warnAboutDiff(phase, exp, act);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	private void warnAboutDiff(String phase, String exp, String act) {
 | 
						|
		try {
 | 
						|
			try (PrintStream err = new PrintStream(errfile)) {
 | 
						|
				err.println("Debug information for file: " + this.file);
 | 
						|
				err.println(this.main.debug.toString());
 | 
						|
				err.println(String.format(
 | 
						|
						"Phase %s failed because we expected to see:", phase));
 | 
						|
				err.println(exp);
 | 
						|
				err.println("But we actually saw:");
 | 
						|
				err.println(act);
 | 
						|
				err.println("The difference is:");
 | 
						|
				err.println(Diff.computeDiff(exp, act));
 | 
						|
			}
 | 
						|
		} catch (FileNotFoundException exc) {
 | 
						|
			System.err.println("Unable to write debug output to " + errfile
 | 
						|
					+ ":");
 | 
						|
			exc.printStackTrace();
 | 
						|
		}
 | 
						|
		Assert.assertEquals(
 | 
						|
				String.format("Phase %s for %s failed!", phase,
 | 
						|
						file.getPath()), exp, act);
 | 
						|
	}
 | 
						|
	
 | 
						|
}
 |