51833be825444387563927fa7b61dcf74338b88c
[Mograsim.git] / tests / net.mograsim.logic.model.am2900.tests / src / net / mograsim / logic / model / am2900 / util / TestEnvironmentHelper.java
1 package net.mograsim.logic.model.am2900.util;
2
3 import static org.junit.jupiter.api.Assertions.fail;
4
5 import java.lang.reflect.Field;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.LinkedList;
9 import java.util.Objects;
10 import java.util.Optional;
11 import java.util.Queue;
12 import java.util.Set;
13 import java.util.TreeSet;
14
15 import net.mograsim.logic.core.components.CoreBitDisplay;
16 import net.mograsim.logic.core.components.CoreManualSwitch;
17 import net.mograsim.logic.core.timeline.Timeline;
18 import net.mograsim.logic.model.LogicUIStandaloneGUI;
19 import net.mograsim.logic.model.am2900.Am2900Loader;
20 import net.mograsim.logic.model.am2900.TestableCircuit;
21 import net.mograsim.logic.model.am2900.TestableCircuit.Result;
22 import net.mograsim.logic.model.model.LogicModel;
23 import net.mograsim.logic.model.model.LogicModelModifiable;
24 import net.mograsim.logic.model.model.components.ModelComponent;
25 import net.mograsim.logic.model.model.components.atomic.ModelBitDisplay;
26 import net.mograsim.logic.model.model.components.atomic.ModelManualSwitch;
27 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
28 import net.mograsim.logic.model.model.wires.ModelWire;
29 import net.mograsim.logic.model.model.wires.Pin;
30 import net.mograsim.logic.model.modeladapter.CoreModelParameters;
31 import net.mograsim.logic.model.modeladapter.LogicCoreAdapter;
32 import net.mograsim.logic.model.serializing.IndirectModelComponentCreator;
33 import net.mograsim.logic.model.util.ModellingTool;
34
35 public class TestEnvironmentHelper
36 {
37         private final TestableCircuit testEnvInstance;
38         private final Class<?> testEnvClass;
39         private final String modelId;
40         private Field componentField;
41         private Optional<Field> timelineField = Optional.empty();
42
43         private ModelComponent component;
44         private Timeline timeline;
45         private LogicModelModifiable logicModel;
46         private ModellingTool modellingTool;
47         private HashMap<String, ModelManualSwitch> idSwitchMap = new HashMap<>();
48         private HashMap<String, ModelBitDisplay> idDisplayMap = new HashMap<>();
49
50         private DebugState debug = DebugState.NO_DEBUG;
51         private Set<String> wireDebugChangeSet;
52         private boolean debugWires = false;
53         public int debugEventThreshold = 10_000;
54         public int debugEventCount = 500;
55         private int eventCounter;
56
57         public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
58         {
59                 this.testEnvInstance = testEnv;
60                 this.modelId = modelId;
61                 this.testEnvClass = testEnvInstance.getClass();
62                 for (Field f : testEnvClass.getDeclaredFields())
63                 {
64                         if (ModelComponent.class.isAssignableFrom(f.getType()))
65                         {
66                                 componentField = f;
67                                 componentField.setAccessible(true);
68                         } else if (Timeline.class.isAssignableFrom(f.getType()))
69                         {
70                                 f.setAccessible(true);
71                                 timelineField = Optional.of(f);
72                         }
73                 }
74                 if (componentField == null)
75                         throw new IllegalStateException("No component or timeline field found!");
76         }
77
78         public void setup(DebugState debug)
79         {
80                 this.debug = debug;
81                 // Create logic model
82                 logicModel = new LogicModelModifiable();
83                 modellingTool = ModellingTool.createFor(logicModel);
84                 Am2900Loader.setup();
85                 component = IndirectModelComponentCreator.createComponent(logicModel, modelId);
86                 setField(componentField, component);
87
88                 component.getPins().values().forEach(this::extendModelPin);
89
90                 // Create core model
91                 CoreModelParameters params = new CoreModelParameters();
92                 params.gateProcessTime = 50;
93                 params.hardcodedComponentProcessTime = params.gateProcessTime * 5;
94                 params.wireTravelTime = 10;
95                 timeline = LogicCoreAdapter.convert(logicModel, params);
96                 timelineField.ifPresent(f -> setField(f, timeline));
97
98                 // Bind switches/displays to this test class
99                 component.getPins().values().forEach(this::bindModelPin);
100
101                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
102                 {
103                         setupDebugging();
104                 }
105                 timeline.addEventAddedListener(te -> eventCounter++);
106         }
107
108         private void extendModelPin(Pin p)
109         {
110                 String javaIdentId = idToJavaIdentifier(p.name);
111                 try
112                 {
113                         Field f = testEnvClass.getDeclaredField(javaIdentId);
114                         Class<?> type = f.getType();
115                         if (CoreManualSwitch.class.isAssignableFrom(type))
116                         {
117                                 ModelManualSwitch gms = new ModelManualSwitch(logicModel, p.logicWidth);
118                                 modellingTool.connect(p, gms.getOutputPin());
119                                 idSwitchMap.put(p.name, gms);
120                         } else if (CoreBitDisplay.class.isAssignableFrom(type))
121                         {
122                                 ModelBitDisplay gbd = new ModelBitDisplay(logicModel, p.logicWidth);
123                                 modellingTool.connect(p, gbd.getInputPin());
124                                 idDisplayMap.put(p.name, gbd);
125                         } else if (SwitchWithDisplay.class.isAssignableFrom(type))
126                         {
127                                 SwitchWithDisplay swd = new SwitchWithDisplay(logicModel, p);
128                                 setField(f, swd);
129                         } else
130                         {
131                                 fail("unkown field type " + type);
132                         }
133                 }
134                 catch (NoSuchFieldException | SecurityException e)
135                 {
136                         fail(e);
137                 }
138         }
139
140         private void bindModelPin(Pin p)
141         {
142                 String javaIdentId = idToJavaIdentifier(p.name);
143                 if (idDisplayMap.containsKey(p.name))
144                         setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
145                 if (idSwitchMap.containsKey(p.name))
146                         setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
147         }
148
149         private void setupDebugging()
150         {
151                 // Debug code
152                 HashSet<ModelWire> wiresIncludingSubmodels = new HashSet<>();
153                 Queue<LogicModel> modelsToIterate = new LinkedList<>();
154                 modelsToIterate.add(logicModel);
155                 while (modelsToIterate.size() > 0)
156                 {
157                         LogicModel model = modelsToIterate.poll();
158                         wiresIncludingSubmodels.addAll(model.getWiresByName().values());
159                         for (ModelComponent comp : model.getComponentsByName().values())
160                                 if (comp instanceof SubmodelComponent)
161                                         modelsToIterate.offer(((SubmodelComponent) comp).submodel);
162                 }
163                 System.out.println(wiresIncludingSubmodels.size());
164                 logicModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
165                 {
166                         if (debugWires)
167                         {
168                                 wireDebugChangeSet.add(w.toString());
169                         }
170                 }));
171         }
172
173         public Result run()
174         {
175                 // Normal execution until completion or eventLimit
176                 int eventLimit = debugEventThreshold;
177                 eventCounter = 0;
178                 debugWires = false;
179                 while (eventCounter < eventLimit)
180                 {
181                         timeline.executeNext();
182                         if (!timeline.hasNext())
183                                 return Result.SUCCESS;
184                 }
185
186                 // Start debugging if event limit is reached (if debug is active)
187                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
188                         return debugThisRun();
189
190                 return Result.OUT_OF_TIME;
191         }
192
193         private Result debugThisRun()
194         {
195                 int eventLimit = debugEventThreshold;
196                 debugWires = true;
197                 wireDebugChangeSet = new TreeSet<>();
198                 Set<String> oldChangeSet;
199                 // observe wire changes to detect, if we are really stuck in an endless loop
200                 do
201                 {
202                         eventLimit += debugEventCount;
203                         oldChangeSet = wireDebugChangeSet;
204                         wireDebugChangeSet = new TreeSet<>();
205                         while (eventCounter < eventLimit)
206                         {
207                                 timeline.executeNext();
208                                 if (!timeline.hasNext())
209                                 {
210                                         // no endless loop, but more events needed than expected
211                                         System.out.println("run() took longer than expected: " + eventCounter);
212                                         return Result.SUCCESS;
213                                 }
214                         }
215                 } while (!oldChangeSet.equals(wireDebugChangeSet));
216                 // if stuck, abort execution and print wires
217                 System.err.print("Problematic Wire updates:");
218                 wireDebugChangeSet.forEach(System.out::println);
219                 System.err.println("run() failed: " + eventCounter);
220                 return Result.OUT_OF_TIME;
221         }
222
223         private static String idToJavaIdentifier(String s)
224         {
225                 StringBuilder sb = new StringBuilder(s.length());
226                 char c = s.charAt(0);
227                 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
228                 for (int i = 1; i < s.length(); i++)
229                         sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
230                 return sb.toString();
231         }
232
233         private <S> void setField(Field f, S value)
234         {
235                 try
236                 {
237                         f.setAccessible(true);
238                         f.set(testEnvInstance, Objects.requireNonNull(value));
239                 }
240                 catch (Exception e)
241                 {
242                         fail(e);
243                 }
244         }
245
246         private <S> void setField(String name, S value)
247         {
248                 try
249                 {
250                         Field f = testEnvClass.getDeclaredField(name);
251                         f.setAccessible(true);
252                         f.set(testEnvInstance, Objects.requireNonNull(value));
253                 }
254                 catch (Exception e)
255                 {
256                         fail(e);
257                 }
258         }
259
260         public void displayState()
261         {
262                 try
263                 {
264                         new LogicUIStandaloneGUI(logicModel).run();
265                         logicModel.setRedrawHandler(null);
266                 }
267                 catch (Exception e)
268                 {
269                         e.printStackTrace();
270                 }
271         }
272
273         public enum DebugState
274         {
275                 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;
276         }
277 }