/*
ManualTestTI99ModuleDetection.java
(c) 2013 Ed Swartz
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
*/
package v9t9.machine.common.tests;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import v9t9.common.client.ISettingsHandler;
import v9t9.common.events.NotifyException;
import v9t9.common.files.IPathFileLocator;
import v9t9.common.machine.IMachine;
import v9t9.common.memory.IMemoryDomain;
import v9t9.common.memory.MemoryEntryInfo;
import v9t9.common.modules.IModule;
import v9t9.common.modules.IModuleDetector;
import v9t9.common.modules.ModuleDatabase;
import v9t9.common.settings.BasicSettingsHandler;
import v9t9.machine.ti99.machine.StandardTI994AMachineModel;
/**
* @author ejs
*
*/
public class ManualTestTI99ModuleDetection {
private ISettingsHandler settings;
private IMachine machine;
private IPathFileLocator locator;
@Before
public void setup() throws Exception {
settings = new BasicSettingsHandler();
machine = new StandardTI994AMachineModel().createMachine(settings);
locator = machine.getRomPathFileLocator();
}
/**
* Make sure we detect all the stock modules in our library
* @throws Exception
*/
@Test
public void testStockModules1() throws Exception {
IModuleDetector detector = machine.createModuleDetector();
detector.scan(new File("/usr/local/src/v9t9-data/modules/mess"));
detector.scan(new File("/usr/local/src/v9t9-data/modules/tosec"));
detector.scan(new File("/usr/local/src/v9t9-data/modules"));
detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/zip"));
detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/rpk"));
//detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/emulators/cartridges/rpk/converted"));
validateStockModules(detector);
}
/**
* Make sure we detect all the stock modules in our library
* @throws Exception
*/
@Test
public void testStockModules2() throws Exception {
IModuleDetector detector = machine.createModuleDetector();
detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/zip"));
detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/rpk"));
//detector.scan(new File("/usr/local/src/v9t9-data/modules/ftp.whtech.com/emulators/cartridges/rpk/converted"));
validateStockModules(detector);
}
/**
* Make sure we detect all the stock modules in our library
* @throws Exception
*/
@Test
public void testStockModules3() throws Exception {
IModuleDetector detector = machine.createModuleDetector();
detector.scan(new File("/usr/local/src/v9t9-data/modules/mess"));
detector.scan(new File("/usr/local/src/v9t9-data/modules/tosec"));
detector.scan(new File("/usr/local/src/v9t9-data/modules"));
validateStockModules(detector);
}
/**
* @param detector
*/
private void validateStockModules(IModuleDetector detector) {
Map<String, List<IModule>> md5ToModules = detector.gatherDuplicatesByMD5();
Map<String, List<IModule>> nameToModules = detector.gatherDuplicatesByName();
IModule[] stocks = machine.getModuleManager().getStockModules();
StringBuilder sb = new StringBuilder();
for (IModule stock : stocks) {
String md5 = stock.getMD5();
if (!md5ToModules.containsKey(md5)) {
List<IModule> nameMatches = nameToModules.get(stock.getName());
if (nameMatches == null)
continue;
sb.append("did not find ").append(stock.getMD5()).append(" = ").append(stock.getName()).append('\n');
if (nameMatches != null) {
sb.append("\tbut found: ");
for (IModule name : nameMatches)
sb.append(name.getMD5()).append(" = ").append(name.getName()).append("\n\t\t");
sb.append('\n');
}
}
}
if (sb.length() > 0)
fail(sb.toString());
}
@Test
public void testMyModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules",
"(?i).*\\.bin",
"c.bin", "g.bin",
"forthc.bin", "nforthc.bin", "0forth.bin",
"TI-EXTBC.BIN","TI-EXTBD.BIN", "cp01.bin"
);
}
@Test
public void testMessModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules/mess",
"(?i).*\\.bin", "forthc.bin",
"weightc.bin", "weightd.bin",
"phm3021c.bin", "phm3021d.bin",
"taxc.bin", "taxd.bin",
"phm3016c.bin", "phm3016d.bin"
);
}
@Test
public void testToSecModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules/tosec",
"(?i).*\\.bin", "Forthc (19xx)(-)(Unknown).bin",
"Supercart (19xx)(Texas Instruments).bin", // nothing
"Sneggit (1982)(Texas Instruments)(File 1 of 2)(Sneggitc).bin" // no #2
);
}
@Test
public void testToWhtechZipModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/zip",
"(?i).*\\.zip");
}
@Test
public void testToWhtechRpkModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/rpk",
"(?i).*\\.rpk");
}
@Test
public void testToWhtechRpkConvertedModules() throws Exception {
testDirectory("/usr/local/src/v9t9-data/modules/ftp.whtech.com.ed/emulators/cartridges/rpk/converted",
"(?i).*\\.rpk");
}
protected List<IModule> testDirectory(String path, final String pattern, final String... ignore) {
File dir = new File(path);
assertTrue(path, dir.exists());
Set<File> allFiles = getAllFiles(pattern, dir, ignore);
Collection<IModule> modules = machine.createModuleDetector().scan(dir);
List<IModule> matches = new ArrayList<IModule>();
System.out.println("Found " + modules.size() + " modules in " + dir);
for (IModule module : modules) {
Collection<File> files = module.getUsedFiles(locator);
if (allFiles.removeAll(files)) {
System.out.println(module);
for (File file : files) {
System.out.println("\t" + file);
}
matches.add(module);
}
verifySpecificModule(module);
}
StringBuilder sb = new StringBuilder();
for (File file : allFiles) {
sb.append("*** did not detect ").append(file).append('\n');
}
if (sb.length() > 0) {
System.err.print(sb);
fail(sb.toString());
}
return matches;
}
/**
* @param pattern
* @param dir
* @param ignore
* @return
*/
protected Set<File> getAllFiles(final String pattern, File dir,
final String... ignore) {
Set<File> allFiles = new LinkedHashSet<File>(Arrays.asList(dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory() || !pathname.getName().matches(pattern))
return false;
for (String ign : ignore) {
if (pathname.getName().equals(ign))
return false;
if (pathname.getName().indexOf('\uFFFD') >= 0)
return false;
}
return true;
}
})));
return allFiles;
}
/**
* @param module
*/
private void verifySpecificModule(IModule module) {
if (verifyMiniMemory(module))
return;
if (verifyEASuperCart(module))
return;
if (verifyParsec(module))
return;
}
/**
* @param module
* @return
*/
private boolean verifyEASuperCart(IModule module) {
if (module.getName().equalsIgnoreCase("EA/8K Super Cart")) {
boolean foundGraphics = false;
// make sure it has the nonvolatile RAM
MemoryEntryInfo[] infos = module.getMemoryEntryInfos();
for (MemoryEntryInfo info : infos) {
if (info.getDomainName().equals(IMemoryDomain.NAME_GRAPHICS)
&& info.getAddress() == 0x6000) {
foundGraphics = true;
}
}
assertTrue(foundGraphics);
assertEquals(module.toString(), 2, infos.length);
return true;
}
return false;
}
/**
* @param module
* @return
*/
private boolean verifyMiniMemory(IModule module) {
if (module.getName().equalsIgnoreCase("Easy Bug")) {
fail("should be named Mini Memory");
}
else if (module.getName().equalsIgnoreCase("Mini Memory")) {
// make sure it has the nonvolatile RAM
MemoryEntryInfo[] infos = module.getMemoryEntryInfos();
for (MemoryEntryInfo info : infos) {
if (info.getDomainName().equals(IMemoryDomain.NAME_CPU)
&& info.getAddress() == 0x6000) {
assertEquals(0x1000, info.getSize());
assertEquals("9BCF230E42BB280199A04F0E0C4797C1", info.getFileMD5());
}
}
assertEquals(module.toString(), 3, infos.length);
return true;
}
return false;
}
/**
* @param module
* @return
*/
private boolean verifyParsec(IModule module) {
if (module.getName().equalsIgnoreCase("Parsec")) {
// make sure it has banked ROM and GROM
MemoryEntryInfo[] infos = module.getMemoryEntryInfos();
int cpus = 0;
int gpls = 0;
for (MemoryEntryInfo info : infos) {
if (info.getDomainName().equals(IMemoryDomain.NAME_CPU)
&& info.getAddress() == 0x6000) {
cpus++;
}
else if (info.getDomainName().equals(IMemoryDomain.NAME_GRAPHICS)
&& info.getAddress() == 0x6000) {
gpls++;
}
}
assertEquals(1, gpls);
assertEquals(1, cpus);
assertEquals(2, infos.length);
return true;
}
return false;
}
@Test
public void testNonModules() throws Exception {
testNoneDirectory("/usr/local/src/v9t9-data/roms", "(?i).*\\.bin", "ed_basicL.bin");
testNoneDirectory("/usr/local/src/v9t9-data/roms/pcode", "(?i).*\\.bin");
testNoneDirectory("/home/ejs/devel/emul/v9t9/v9t9-java/v9t9-data/data/ti99/dsrs", "(?i).*\\.bin");
}
protected void testNoneDirectory(String path, final String pattern, final String... ignore) {
File dir = new File(path);
assertTrue(dir.exists());
Collection<IModule> modules = machine.createModuleDetector().scan(dir);
System.out.println("Found " + modules.size() + " modules in " + dir);
StringBuilder sb = new StringBuilder();
for (IModule module : modules) {
boolean bad = false;
Collection<File> files = module.getUsedFiles(locator);
for (File file : files) {
if (!bad && !Arrays.asList(ignore).contains(file.getName())) {
bad = true;
System.out.println(module);
sb.append("*** found ").append(module).append('\n');
}
if (bad)
System.out.println("\t" + file);
}
}
if (sb.length() > 0) {
System.err.print(sb);
fail(sb.toString());
}
}
@Test
public void testMilliken() throws Exception {
List<IModule> mods = testDirectory("/usr/local/src/v9t9-data/modules/mess",
"(?i)(phm309.|phm310.)g\\.bin"
);
assertEquals(11, mods.size());
for (IModule mod : mods) {
assertFalse(mod.toString(), mod.getName().isEmpty());
}
boolean anyEqual = false;
for (int i = 0; i < mods.size(); i++) {
IModule mod = mods.get(i);
for (int j = i+1; j < mods.size(); j++) {
IModule amod = mods.get(j);
if (mod.equals(amod)) {
// if equal, must have identical files & contents
System.out.println(mod.getName() + " == " + amod.getName() + "\n");
anyEqual = true;
}
}
}
if (anyEqual)
System.out.println("Some modules are equal");
}
/**
* @param mods
* @param md5Map
*/
private void addModuleHashes(List<IModule> mods,
Map<String, IModule> md5Map,
Map<String, String> nameToMd5Map,
StringBuilder fails) {
for (IModule mod : mods) {
String md5 = mod.getMD5();
System.out.println(mod.getName() + " -> " + md5 + " @ " + mod.getMemoryEntryInfos()[0]);
IModule old = md5Map.put(md5, mod);
if (old != null && !old.equals(mod) && !old.getName().equals(mod.getName())) {
System.out.println("\tconflicts with " + old.getName());
fails.append(mod.getName());
fails.append(" conflicts with ");
fails.append(old.getName());
fails.append("\n");
}
String oldMd5 = nameToMd5Map.put(mod.getName(), md5);
if (oldMd5 != null && !oldMd5.equals(md5)) {
fails.append(mod.getName()).append(" has two MD5s: ").
append(oldMd5).append(" and ").append(md5).append('\n');
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ModuleDatabase.saveModuleListAndClose(
machine.getMemory(), bos, null,
old != null ? Arrays.asList(old, mod) : Arrays.asList(mod));
} catch (NotifyException e) {
e.printStackTrace();
}
fails.append('\t').append(bos.toString()).append('\n');
}
}
}
/**
* Most modules can be detected fine, but some cause problems since their
* headers are all the same or are auto-start. We have a hard-coded database
* to cover these.
* @throws Exception
*/
@Test
public void testModuleDatabase() throws Exception {
List<IModule> mods = testDirectory("/usr/local/src/v9t9-data/modules/mess",
"(?i)(phm.*)\\.bin",
"phm3021c.bin", "phm3021d.bin",
"phm3016c.bin", "phm3016d.bin"
);
assertEquals(121, mods.size());
for (IModule mod : mods) {
assertFalse(mod.toString(), mod.getName().isEmpty());
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mods.size(); i++) {
IModule mod = mods.get(i);
System.out.println(mod);
if (mod.getName().equals("") || mod.getName().matches("phm.*")) {
sb.append(mod.getMD5() + " = " + mod.toString()).append('\n');
}
}
if (sb.length() > 0) {
System.err.println(sb);
fail(sb.toString());
}
}
/**
* The module hashes -- which include titles -- are meant to
* uniquely identify a module by content, excising anything about
* its filename, but retaining the number of files and their
* memory mappings.
* @throws Exception
*/
@Test
public void testHashes() throws Exception {
Map<String, IModule> md5Map = new HashMap<String, IModule>();
Map<String, String> nameToMd5Map = new HashMap<String, String>();
StringBuilder sb = new StringBuilder();
List<IModule> mods;
mods = testDirectory("/usr/local/src/v9t9-data/modules/mess",
"(?i).*\\.bin",
"forthc.bin", "nforthc.bin", "0forth.bin",
"weightc.bin", "weightd.bin",
"phm3021c.bin", "phm3021d.bin",
"taxc.bin", "taxd.bin",
"phm3016c.bin", "phm3016d.bin"
);
addModuleHashes(mods, md5Map, nameToMd5Map, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules/tosec",
"(?i).*\\.bin", "Forthc (19xx)(-)(Unknown).bin",
"Supercart (19xx)(Texas Instruments).bin", // nothing
"Sneggit (1982)(Texas Instruments)(File 1 of 2)(Sneggitc).bin" // no #2
);
addModuleHashes(mods, md5Map, nameToMd5Map, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules",
"(?i).*\\.bin",
"c.bin", "g.bin",
"forthc.bin", "nforthc.bin", "0forth.bin",
"TI-EXTBC.BIN","TI-EXTBD.BIN", "cp01.bin",
"xxxxxxxg.bin"
);
addModuleHashes(mods, md5Map, nameToMd5Map, sb);
//[[[
mods = testDirectory("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/rpk",
"(?i).*\\.rpk");
// These are named according to whim and are mostly wrong,
// so use well-known names for this test
for (IModule mod : mods) {
IModule ex = md5Map.get(mod.getMD5());
if (ex != null)
mod.setName(ex.getName());
}
addModuleHashes(mods, md5Map, nameToMd5Map, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules/ftp.whtech.com/Cartridges/rpk/converted",
"(?i).*\\.rpk");
// These are named according to whim and are mostly wrong,
// so use well-known names for this test
for (IModule mod : mods) {
IModule ex = md5Map.get(mod.getMD5());
if (ex != null)
mod.setName(ex.getName());
}
addModuleHashes(mods, md5Map, nameToMd5Map, sb);
//]]]
if (sb.length() > 0)
fail(sb.toString());
}
@Test
public void testBanks() throws Exception {
StringBuilder sb = new StringBuilder();
List<IModule> mods;
mods = testDirectory("/usr/local/src/v9t9-data/modules/mess",
"(?i).*\\.bin",
"forthc.bin", "nforthc.bin", "0forth.bin",
"weightc.bin", "weightd.bin",
"phm3021c.bin", "phm3021d.bin",
"taxc.bin", "taxd.bin",
"phm3016c.bin", "phm3016d.bin"
);
doTestBanks(mods, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules/tosec",
"(?i).*\\.bin", "Forthc (19xx)(-)(Unknown).bin",
"Supercart (19xx)(Texas Instruments).bin", // nothing
"Sneggit (1982)(Texas Instruments)(File 1 of 2)(Sneggitc).bin" // no #2
);
doTestBanks(mods, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules",
"(?i).*\\.bin",
"c.bin", "g.bin",
"forthc.bin", "nforthc.bin", "0forth.bin",
"TI-EXTBC.BIN","TI-EXTBD.BIN", "cp01.bin",
"xxxxxxxg.bin"
);
doTestBanks(mods, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules/pitfall",
"(?i).*\\.bin");
doTestBanks(mods, sb);
mods = testDirectory("/usr/local/src/v9t9-data/modules/magic_memory_ti_workshop",
"(?i).*\\.bin");
doTestBanks(mods, sb);
if (sb.length() > 0)
fail(sb.toString());
}
/**
* @param mods
*/
private void doTestBanks(List<IModule> mods, StringBuilder sb) {
for (IModule mod : mods) {
for (MemoryEntryInfo info : mod.getMemoryEntryInfos()) {
if (info.isBanked()) {
if (info.getSize() > 0x2000 || info.getSize2() > 0x2000) {
sb.append("Invalid size for banked module: ").append(info).append('\n');
}
}
}
}
}
}