X-Git-Url: https://mograsim.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=net.mograsim.logic.model.am2900%2Ftest%2Fnet%2Fmograsim%2Flogic%2Fmodel%2Fam2900%2FTestEnvironmentHelper.java;fp=net.mograsim.logic.model.am2900%2Ftest%2Fnet%2Fmograsim%2Flogic%2Fmodel%2Fam2900%2FTestEnvironmentHelper.java;h=0a4838f3f3f8ef9dab579fc189b6a98695b0671c;hb=ccc97ca46668196a77da02acb2bde450e6d20922;hp=0000000000000000000000000000000000000000;hpb=466a1f49e8a4f231edecf4c6bc726ea68766f1be;p=Mograsim.git diff --git a/net.mograsim.logic.model.am2900/test/net/mograsim/logic/model/am2900/TestEnvironmentHelper.java b/net.mograsim.logic.model.am2900/test/net/mograsim/logic/model/am2900/TestEnvironmentHelper.java new file mode 100644 index 00000000..0a4838f3 --- /dev/null +++ b/net.mograsim.logic.model.am2900/test/net/mograsim/logic/model/am2900/TestEnvironmentHelper.java @@ -0,0 +1,253 @@ +package net.mograsim.logic.model.am2900; + +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.Queue; +import java.util.Set; +import java.util.TreeSet; + +import net.mograsim.logic.core.components.BitDisplay; +import net.mograsim.logic.core.components.ManualSwitch; +import net.mograsim.logic.core.timeline.Timeline; +import net.mograsim.logic.model.am2900.TestableCircuit.Result; +import net.mograsim.logic.model.model.ViewModel; +import net.mograsim.logic.model.model.ViewModelModifiable; +import net.mograsim.logic.model.model.components.GUIComponent; +import net.mograsim.logic.model.model.components.atomic.GUIBitDisplay; +import net.mograsim.logic.model.model.components.atomic.GUIManualSwitch; +import net.mograsim.logic.model.model.components.submodels.SubmodelComponent; +import net.mograsim.logic.model.model.wires.GUIWire; +import net.mograsim.logic.model.model.wires.Pin; +import net.mograsim.logic.model.modeladapter.LogicModelParameters; +import net.mograsim.logic.model.modeladapter.ViewLogicModelAdapter; +import net.mograsim.logic.model.serializing.IndirectGUIComponentCreator; +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 Field timelineField; + + private GUIComponent component; + private Timeline timeline; + private ViewModelModifiable viewModel; + 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 (GUIComponent.class.isAssignableFrom(f.getType())) + { + componentField = f; + componentField.setAccessible(true); + } else if (Timeline.class.isAssignableFrom(f.getType())) + { + timelineField = f; + timelineField.setAccessible(true); + } + } + if (componentField == null || timelineField == null) + throw new IllegalStateException("No component or timeline field found!"); + } + + public void setup(DebugState debug) + { + this.debug = debug; + // Create view model + viewModel = new ViewModelModifiable(); + modellingTool = ModellingTool.createFor(viewModel); + component = IndirectGUIComponentCreator.createComponent(viewModel, modelId); + setField(componentField, component); + + component.getPins().values().forEach(this::extendModelPin); + + // Create logic model + LogicModelParameters params = new LogicModelParameters(); + params.gateProcessTime = 50; + params.wireTravelTime = 10; + timeline = ViewLogicModelAdapter.convert(viewModel, params); + setField(timelineField, 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 (ManualSwitch.class.isAssignableFrom(type)) + { + GUIManualSwitch gms = new GUIManualSwitch(viewModel, p.logicWidth); + modellingTool.connect(p, gms.getOutputPin()); + idSwitchMap.put(p.name, gms); + } else if (BitDisplay.class.isAssignableFrom(type)) + { + GUIBitDisplay gbd = new GUIBitDisplay(viewModel, p.logicWidth); + modellingTool.connect(p, gbd.getInputPin()); + idDisplayMap.put(p.name, gbd); + } 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(viewModel); + while (modelsToIterate.size() > 0) + { + ViewModel model = modelsToIterate.poll(); + wiresIncludingSubmodels.addAll(model.getWiresByName().values()); + for (GUIComponent comp : model.getComponentsByName().values()) + if (comp instanceof SubmodelComponent) + modelsToIterate.offer(((SubmodelComponent) comp).submodel); + } + System.out.println(wiresIncludingSubmodels.size()); + viewModel.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.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 enum DebugState + { + NO_DEBUG, DEBUG_AT_PERFORMANCE_COST; + } +}