X-Git-Url: https://mograsim.net/gitweb/?a=blobdiff_plain;f=tests%2Fnet.mograsim.logic.model.am2900.tests%2Fsrc%2Fnet%2Fmograsim%2Flogic%2Fmodel%2Fam2900%2Futil%2FTestEnvironmentHelper.java;fp=tests%2Fnet.mograsim.logic.model.am2900.tests%2Fsrc%2Fnet%2Fmograsim%2Flogic%2Fmodel%2Fam2900%2Futil%2FTestEnvironmentHelper.java;h=4bae8ec5adf890b4c29f9b9376a515af981b03dc;hb=7d05144c25daa53e60fc9ed9fd503546a86567f8;hp=0000000000000000000000000000000000000000;hpb=8bed58cd47f4e53a0a83e066d38864aa6875502f;p=Mograsim.git diff --git a/tests/net.mograsim.logic.model.am2900.tests/src/net/mograsim/logic/model/am2900/util/TestEnvironmentHelper.java b/tests/net.mograsim.logic.model.am2900.tests/src/net/mograsim/logic/model/am2900/util/TestEnvironmentHelper.java new file mode 100644 index 00000000..4bae8ec5 --- /dev/null +++ b/tests/net.mograsim.logic.model.am2900.tests/src/net/mograsim/logic/model/am2900/util/TestEnvironmentHelper.java @@ -0,0 +1,276 @@ +package net.mograsim.logic.model.am2900.util; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.TreeSet; + +import net.mograsim.logic.core.components.CoreBitDisplay; +import net.mograsim.logic.core.components.CoreManualSwitch; +import net.mograsim.logic.core.timeline.Timeline; +import net.mograsim.logic.model.LogicUIStandaloneGUI; +import net.mograsim.logic.model.am2900.Am2900Loader; +import net.mograsim.logic.model.am2900.TestableCircuit; +import net.mograsim.logic.model.am2900.TestableCircuit.Result; +import net.mograsim.logic.model.model.LogicModel; +import net.mograsim.logic.model.model.LogicModelModifiable; +import net.mograsim.logic.model.model.components.ModelComponent; +import net.mograsim.logic.model.model.components.atomic.ModelBitDisplay; +import net.mograsim.logic.model.model.components.atomic.ModelManualSwitch; +import net.mograsim.logic.model.model.components.submodels.SubmodelComponent; +import net.mograsim.logic.model.model.wires.ModelWire; +import net.mograsim.logic.model.model.wires.Pin; +import net.mograsim.logic.model.modeladapter.CoreModelParameters; +import net.mograsim.logic.model.modeladapter.LogicCoreAdapter; +import net.mograsim.logic.model.serializing.IndirectModelComponentCreator; +import net.mograsim.logic.model.util.ModellingTool; + +public class TestEnvironmentHelper +{ + private final TestableCircuit testEnvInstance; + private final Class testEnvClass; + private final String modelId; + private Field componentField; + private Optional timelineField = Optional.empty(); + + private ModelComponent component; + private Timeline timeline; + private LogicModelModifiable logicModel; + private ModellingTool modellingTool; + private HashMap idSwitchMap = new HashMap<>(); + private HashMap idDisplayMap = new HashMap<>(); + + private DebugState debug = DebugState.NO_DEBUG; + private Set wireDebugChangeSet; + private boolean debugWires = false; + public int debugEventThreshold = 10_000; + public int debugEventCount = 500; + private int eventCounter; + + public TestEnvironmentHelper(TestableCircuit testEnv, String modelId) + { + this.testEnvInstance = testEnv; + this.modelId = modelId; + this.testEnvClass = testEnvInstance.getClass(); + for (Field f : testEnvClass.getDeclaredFields()) + { + if (ModelComponent.class.isAssignableFrom(f.getType())) + { + componentField = f; + componentField.setAccessible(true); + } else if (Timeline.class.isAssignableFrom(f.getType())) + { + f.setAccessible(true); + timelineField = Optional.of(f); + } + } + if (componentField == null) + throw new IllegalStateException("No component or timeline field found!"); + } + + public void setup(DebugState debug) + { + this.debug = debug; + // Create logic model + logicModel = new LogicModelModifiable(); + modellingTool = ModellingTool.createFor(logicModel); + Am2900Loader.setup(); + component = IndirectModelComponentCreator.createComponent(logicModel, modelId); + setField(componentField, component); + + component.getPins().values().forEach(this::extendModelPin); + + // Create core model + CoreModelParameters params = new CoreModelParameters(); + params.gateProcessTime = 50; + params.wireTravelTime = 10; + timeline = LogicCoreAdapter.convert(logicModel, params); + timelineField.ifPresent(f -> setField(f, timeline)); + + // Bind switches/displays to this test class + component.getPins().values().forEach(this::bindModelPin); + + if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST) + { + setupDebugging(); + } + timeline.addEventAddedListener(te -> eventCounter++); + } + + private void extendModelPin(Pin p) + { + String javaIdentId = idToJavaIdentifier(p.name); + try + { + Field f = testEnvClass.getDeclaredField(javaIdentId); + Class type = f.getType(); + if (CoreManualSwitch.class.isAssignableFrom(type)) + { + ModelManualSwitch gms = new ModelManualSwitch(logicModel, p.logicWidth); + modellingTool.connect(p, gms.getOutputPin()); + idSwitchMap.put(p.name, gms); + } else if (CoreBitDisplay.class.isAssignableFrom(type)) + { + ModelBitDisplay gbd = new ModelBitDisplay(logicModel, p.logicWidth); + modellingTool.connect(p, gbd.getInputPin()); + idDisplayMap.put(p.name, gbd); + } else if (SwitchWithDisplay.class.isAssignableFrom(type)) + { + SwitchWithDisplay swd = new SwitchWithDisplay(logicModel, p); + setField(f, swd); + } else + { + fail("unkown field type " + type); + } + } + catch (NoSuchFieldException | SecurityException e) + { + fail(e); + } + } + + private void bindModelPin(Pin p) + { + String javaIdentId = idToJavaIdentifier(p.name); + if (idDisplayMap.containsKey(p.name)) + setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay()); + if (idSwitchMap.containsKey(p.name)) + setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch()); + } + + private void setupDebugging() + { + // Debug code + HashSet wiresIncludingSubmodels = new HashSet<>(); + Queue modelsToIterate = new LinkedList<>(); + modelsToIterate.add(logicModel); + while (modelsToIterate.size() > 0) + { + LogicModel model = modelsToIterate.poll(); + wiresIncludingSubmodels.addAll(model.getWiresByName().values()); + for (ModelComponent comp : model.getComponentsByName().values()) + if (comp instanceof SubmodelComponent) + modelsToIterate.offer(((SubmodelComponent) comp).submodel); + } + System.out.println(wiresIncludingSubmodels.size()); + logicModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w -> + { + if (debugWires) + { + wireDebugChangeSet.add(w.toString()); + } + })); + } + + public Result run() + { + // Normal execution until completion or eventLimit + int eventLimit = debugEventThreshold; + eventCounter = 0; + debugWires = false; + while (eventCounter < eventLimit) + { + timeline.executeNext(); + if (!timeline.hasNext()) + return Result.SUCCESS; + } + + // Start debugging if event limit is reached (if debug is active) + if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST) + return debugThisRun(); + + return Result.OUT_OF_TIME; + } + + private Result debugThisRun() + { + int eventLimit = debugEventThreshold; + debugWires = true; + wireDebugChangeSet = new TreeSet<>(); + Set oldChangeSet; + // observe wire changes to detect, if we are really stuck in an endless loop + do + { + eventLimit += debugEventCount; + oldChangeSet = wireDebugChangeSet; + wireDebugChangeSet = new TreeSet<>(); + while (eventCounter < eventLimit) + { + timeline.executeNext(); + if (!timeline.hasNext()) + { + // no endless loop, but more events needed than expected + System.out.println("run() took longer than expected: " + eventCounter); + return Result.SUCCESS; + } + } + } while (!oldChangeSet.equals(wireDebugChangeSet)); + // if stuck, abort execution and print wires + System.err.print("Problematic Wire updates:"); + wireDebugChangeSet.forEach(System.out::println); + System.err.println("run() failed: " + eventCounter); + return Result.OUT_OF_TIME; + } + + private static String idToJavaIdentifier(String s) + { + StringBuilder sb = new StringBuilder(s.length()); + char c = s.charAt(0); + sb.append(Character.isJavaIdentifierStart(c) ? c : '_'); + for (int i = 1; i < s.length(); i++) + sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_'); + return sb.toString(); + } + + private void setField(Field f, S value) + { + try + { + f.setAccessible(true); + f.set(testEnvInstance, Objects.requireNonNull(value)); + } + catch (Exception e) + { + fail(e); + } + } + + private void setField(String name, S value) + { + try + { + Field f = testEnvClass.getDeclaredField(name); + f.setAccessible(true); + f.set(testEnvInstance, Objects.requireNonNull(value)); + } + catch (Exception e) + { + fail(e); + } + } + + public void displayState() + { + try + { + new LogicUIStandaloneGUI(logicModel).run(); + logicModel.setRedrawHandler(null); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public enum DebugState + { + NO_DEBUG, DEBUG_AT_PERFORMANCE_COST; + } +}