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