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