/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp 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. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.autotests; import static jpcsp.graphics.VideoEngine.readLittleEndianInt; import static jpcsp.graphics.VideoEngine.readLittleEndianShort; import java.awt.DisplayMode; import java.awt.Rectangle; import java.awt.Window; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.TimeoutException; import javax.imageio.ImageIO; import jpcsp.util.FileUtil; import jpcsp.util.LWJGLFixer; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator; import jpcsp.Emulator; import jpcsp.Allegrex.compiler.RuntimeContext; import jpcsp.GUI.IMainGUI; import jpcsp.HLE.HLEModuleManager; import jpcsp.HLE.Modules; import jpcsp.HLE.VFS.emulator.EmulatorVirtualFileSystem; import jpcsp.filesystems.umdiso.UmdIsoReader; import jpcsp.hardware.Screen; import jpcsp.log.LoggingOutputStream; public class AutoTestsRunner { private static final Logger log = Logger.getLogger("pspautotests"); private static final int FAIL_TIMEOUT = 10; // in seconds static { LWJGLFixer.fixOnce(); log.addAppender(new ConsoleAppender()); } static public void main(String[] args) { new AutoTestsRunner().run(); } Emulator emulator; static private void debug(String str) { //log.info(str); System.err.println(str); } static private void info(String str) { //log.info(str); System.out.println(str); } static private void error(String str) { //log.error(str); System.err.println(str); } class DummyGUI implements IMainGUI { @Override public void setMainTitle(String title) { } @Override public void RefreshButtons() { } @Override public void setLocation() { } @Override public DisplayMode getDisplayMode() { return new DisplayMode(480, 272, 32, 60); } @Override public void endWindowDialog() { } @Override public boolean isFullScreen() { return false; } @Override public boolean isVisible() { return false; } @Override public void pack() { } @Override public void setFullScreenDisplaySize() { } @Override public void startWindowDialog(Window window) { } @Override public void startBackgroundWindowDialog(Window window) { } @Override public Rectangle getCaptureRectangle() { return null; } @Override public void onUmdChange() { } @Override public void onMemoryStickChange() { } @Override public void setDisplayMinimumSize(int width, int height) { } @Override public void setDisplaySize(int width, int height) { } @Override public void run() { } @Override public void pause() { } @Override public void reset() { } } public AutoTestsRunner() { emulator = new Emulator(new DummyGUI()); emulator.setFirmwareVersion(630); } public void run() { DOMConfigurator.configure("LogSettings.xml"); System.setOut(new PrintStream(new LoggingOutputStream(Logger.getLogger("emu"), Level.INFO))); Screen.setHasScreen(false); //IoFileMgrForUser.defaultTimings.get(IoFileMgrForUser.IoOperation.iodevctl).setDelayMillis(0); Modules.sceDisplayModule.setCalledFromCommandLine(); try { runImpl(); } catch (Throwable o) { o.printStackTrace(); } System.exit(0); } static private File rootDirectory = FileUtil.findFolderNameInAncestors(new File("."), "pspautotests"); protected void runImpl() throws Throwable { File folder = rootDirectory; if (folder != null) { runTestFolder(new File(folder, "/tests")); } else { error("Can't find pspautotests folder"); } // runTest(rootDirectory + "/tests/cpu/vfpu/vector"); // runTestFolder(rootDirectory + "/tests/cpu"); } protected void runTestFolder(File folder) throws Throwable { File[] files = folder.listFiles(); if (files != null) { for (File file : files) { if (file.getName().charAt(0) == '.') { continue; } if (file.isDirectory()) { runTestFolder(file); } else if (file.isFile()) { String name = file.getPath(); if (name.substring(name.length() - 9).equals(".expected")) { runTest(name.substring(0, name.length() - 9)); } } } } } protected void runTest(String baseFileName) throws Throwable { new File(EmulatorVirtualFileSystem.getScreenshotFileName()).delete(); boolean timeout = false; try { runFile(baseFileName + ".prx"); } catch (TimeoutException toe) { timeout = true; } checkOutput(baseFileName, baseFileName + ".expected", timeout); } protected BufferedImage readBmp(File imageFile) throws IOException { InputStream is = new BufferedInputStream(new FileInputStream(imageFile)); // Reading file header int magic = readLittleEndianShort(is); int fileSize = readLittleEndianInt(is); is.skip(4); int dataOffset = readLittleEndianInt(is); // Reading DIB header int dibHeaderLength = readLittleEndianInt(is); int imageWidth = readLittleEndianInt(is); int imageHeight = readLittleEndianInt(is); int numberPlanes = readLittleEndianShort(is); int bitsPerPixel = readLittleEndianShort(is); // Skip rest of DIB header until data start is.skip(dataOffset - 14 - 16); BufferedImage img = null; if (magic == (('M' << 8) | 'B') && dibHeaderLength >= 16 && fileSize >= dataOffset && numberPlanes == 1 && bitsPerPixel == 32) { img = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); for (int y = imageHeight - 1; y >= 0; y--) { for (int x = 0; x < imageWidth; x++) { int argb = readLittleEndianInt(is); img.setRGB(x, y, argb); } } } is.close(); return img; } protected boolean areColorsEqual(int color1, int color2) { return (color1 & 0x00FFFFFF) == (color2 & 0x00FFFFFF); } protected boolean compareScreenshots(File expected, File result, File compare) { boolean equals = false; try { BufferedImage expectedImg = ImageIO.read(expected); BufferedImage resultImg; try { resultImg = ImageIO.read(result); } catch (RuntimeException e) { // java.lang.RuntimeException: New BMP version not implemented yet. resultImg = readBmp(result); } int width = Math.min(expectedImg.getWidth(), resultImg.getWidth()); int height = Math.min(expectedImg.getHeight(), resultImg.getHeight()); BufferedImage compareImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); equals = true; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int expectedColor = expectedImg.getRGB(x, y); int resultColor = resultImg.getRGB(x, y); if (areColorsEqual(expectedColor, resultColor)) { compareImg.setRGB(x, y, 0x000000); } else { compareImg.setRGB(x, y, 0xFF0000); equals = false; } } } ImageIO.write(compareImg, "bmp", compare); } catch (IOException e) { error(String.format("comparing screenshots %s", e)); } return equals; } protected void checkOutput(String baseFileName, String fileName, boolean timeout) throws IOException { String actualOutput = AutoTestsOutput.getOutput().trim(); String expectedOutput = readFileAsString(fileName).trim(); if (actualOutput.equals(expectedOutput)) { info(String.format("%s: OK", baseFileName)); } else { if (timeout) { error(String.format("%s: FAIL, TIMEOUT", baseFileName)); } else { error(String.format("%s: FAIL", baseFileName)); } diff(expectedOutput, actualOutput); } File screenshotExpected = new File(fileName + ".bmp"); if (screenshotExpected.canRead()) { File screenshotResult = new File(EmulatorVirtualFileSystem.getScreenshotFileName()); if (screenshotResult.canRead()) { File savedScreenshotResult = new File(baseFileName + ".result.bmp"); savedScreenshotResult.delete(); if (screenshotResult.renameTo(savedScreenshotResult)) { info(String.format("%s: saved screenshot under '%s'", baseFileName, savedScreenshotResult)); File compareScreenshot = new File(baseFileName + ".compare.bmp"); if (compareScreenshots(screenshotExpected, savedScreenshotResult, compareScreenshot)) { info(String.format("%s: screenshots are identical", baseFileName)); } else { error(String.format("%s: screenshots differ, see '%s'", baseFileName, compareScreenshot)); } } else { error(String.format("%s: cannot save screenshot from '%s' to '%s'", baseFileName, screenshotResult, savedScreenshotResult)); } } else { error(String.format("%s: FAIL, no result screenshot found", baseFileName)); } } } public static void diff(String x, String y) { diff(x.split("\\n"), y.split("\\n")); } public static void diff(String[] x, String[] y) { // number of lines of each file int M = x.length; int N = y.length; // opt[i][j] = length of LCS of x[i..M] and y[j..N] int[][] opt = new int[M+1][N+1]; // compute length of LCS and all subproblems via dynamic programming for (int i = M-1; i >= 0; i--) { for (int j = N-1; j >= 0; j--) { if (x[i].equals(y[j])) opt[i][j] = opt[i+1][j+1] + 1; else opt[i][j] = Math.max(opt[i+1][j], opt[i][j+1]); } } // recover LCS itself and print out non-matching lines to standard output int i = 0, j = 0; while (i < M && j < N) { if (x[i].equals(y[j])) { debug(" " + x[i]); i++; j++; } else if (opt[i+1][j] >= opt[i][j+1]) { info("- " + x[i++]); } else { info("+ " + y[j++]); } } // dump out one remainder of one string if the other is exhausted while (i < M || j < N) { if (i == M) { info("+ " + y[j++]); } else if (j == N) { info("- " + x[i++]); } } } protected void reset() { AutoTestsOutput.clearOutput(); Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_PAUSE); Emulator.getInstance().initNewPsp(false); } protected void runFile(String fileName) throws Throwable { File file = new File(fileName); reset(); try { RandomAccessFile raf = new RandomAccessFile(file, "r"); try { FileChannel roChannel = raf.getChannel(); try { ByteBuffer readbuffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int) roChannel.size()); { //module = emulator.load(file.getPath(), readbuffer); } } finally { roChannel.close(); } } finally { raf.close(); } } catch (FileNotFoundException fileNotFoundException) { } RuntimeContext.setIsHomebrew(true); UmdIsoReader umdIsoReader = new UmdIsoReader(rootDirectory + "/input/cube.cso"); Modules.IoFileMgrForUserModule.setIsoReader(umdIsoReader); jpcsp.HLE.Modules.sceUmdUserModule.setIsoReader(umdIsoReader); Modules.IoFileMgrForUserModule.setfilepath(file.getParent()); debug(String.format("Running: %s...", fileName)); { RuntimeContext.setIsHomebrew(false); HLEModuleManager.getInstance().startModules(false); Modules.sceDisplayModule.setUseSoftwareRenderer(true); { emulator.RunEmu(); long startTime = System.currentTimeMillis(); while (!Emulator.pause) { Modules.sceDisplayModule.step(); if (System.currentTimeMillis() - startTime > FAIL_TIMEOUT * 1000) { throw(new TimeoutException()); } Thread.sleep(1); } } HLEModuleManager.getInstance().stopModules(); } } private static String readFileAsString(String filePath) throws IOException { StringBuilder s = new StringBuilder(); BufferedReader f = null; try { f = new BufferedReader(new FileReader(filePath)); // Read line by line to exclude all carriage returns ('\r') while (true) { String line = f.readLine(); if (line == null) { break; } s.append(line); s.append('\n'); } } finally { if (f != null) { try { f.close(); } catch (IOException ignored) { } } } return s.toString(); } }