001    package nl.cwi.sen1.tunit;
002    
003    import java.lang.management.ManagementFactory;
004    import java.lang.management.MemoryMXBean;
005    import java.lang.management.ThreadInfo;
006    import java.lang.management.ThreadMXBean;
007    import java.net.InetAddress;
008    import java.util.ArrayList;
009    import java.util.Collections;
010    import java.util.HashMap;
011    import java.util.HashSet;
012    import java.util.List;
013    import java.util.Map;
014    import java.util.Set;
015    
016    import toolbus.ToolBus;
017    import toolbus.adapter.AbstractTool;
018    import toolbus.adapter.ToolBridge;
019    import toolbus.logging.ILogger;
020    import toolbus.logging.IToolBusLoggerConstants;
021    import toolbus.logging.LoggerFactory;
022    import aterm.ATerm;
023    import aterm.ATermAppl;
024    import aterm.ATermList;
025    import aterm.pure.PureFactory;
026    
027    public class TestToolBridge extends ToolBridge{
028            public final static long DEFAULTTIMEOUT = 10000;
029    
030            private final Object completionLock = new Object();
031            private final WaitLock expectLock = new WaitLock();
032            private final WaitLock actionLock = new WaitLock();
033            
034            private final TUnitTestCase testCase;
035            
036            private final PureFactory termFactory;
037            private final AbstractTool tool;
038    
039            private final Map<String, WaitLock> expectedAckEvents;
040            private final Map<ATerm, ATerm> expectedEvals;
041            private final Set<ATerm> expectedDos;
042            
043            private final List<ATerm> actionQueue;
044            private volatile boolean expecting;
045            
046            private final boolean verbose;
047            
048            public TestToolBridge(TUnitTestCase testCase, PureFactory termFactory, AbstractTool tool, String toolName, int toolID, InetAddress host, int port, boolean verbose){
049                    super(termFactory, toolName, toolID, host, port);
050                    this.testCase = testCase;
051                    this.termFactory = termFactory;
052                    this.tool = tool;
053                    
054                    expectedAckEvents = Collections.synchronizedMap(new HashMap<String, WaitLock>());
055                    expectedEvals = Collections.synchronizedMap(new HashMap<ATerm, ATerm>());
056                    expectedDos = Collections.synchronizedSet(new HashSet<ATerm>());
057                    
058                    actionQueue = Collections.synchronizedList(new ArrayList<ATerm>());
059                    expecting = false;
060                    
061                    this.verbose = verbose;
062            }
063            
064            public TestToolBridge(TUnitTestCase testCase, PureFactory termFactory, AbstractTool tool, String toolName, int toolID, ClassLoader classLoader, ToolBus toolbus, boolean verbose){
065                    super(termFactory, toolName, toolID, toolbus);
066                    
067                    this.testCase = testCase;
068                    this.termFactory = termFactory;
069                    this.tool = tool;
070                    
071                    expectedAckEvents = Collections.synchronizedMap(new HashMap<String, WaitLock>());
072                    expectedEvals = Collections.synchronizedMap(new HashMap<ATerm, ATerm>());
073                    expectedDos = Collections.synchronizedSet(new HashSet<ATerm>());
074                    
075                    actionQueue = Collections.synchronizedList(new ArrayList<ATerm>());
076                    expecting = false;
077                    
078                    this.verbose = verbose;
079            }
080            
081            public void receive(byte operation, ATerm aTerm){
082                    if(operation == ACKEVENT){
083                            String eventName = ((ATermAppl)((ATermList) aTerm).getFirst()).getAFun().toString();
084                            WaitLock eventLock = expectedAckEvents.remove(eventName);
085                            
086                            synchronized(completionLock){
087                                    completionLock.notifyAll();
088                            }
089                            
090                            if(eventLock == null) testCase.testFailed("Tool "+getToolName()+": unexpected ack event: "+aTerm);
091                            
092                            synchronized(eventLock){
093                                    eventLock.notified = true;
094                                    eventLock.notify();
095                            }
096                    }
097                    
098                    if(verbose) System.err.println("Tool "+getToolName()+" recieved (" + operation + ") " + aTerm);
099                    
100                    super.receive(operation, aTerm);
101            }
102            
103            public boolean checkSignature(ATerm signature){
104                    return true;
105            }
106            
107            public void doDo(ATerm aTerm){
108                    synchronized(actionLock){
109                            if(expecting){
110                                    if(!expectedDos.contains(aTerm)) testCase.testFailed("Tool "+getToolName()+": unexpected do: "+aTerm);
111                            }else{
112                                    actionQueue.add(aTerm);
113                                    
114                                    long waitToMillis = System.currentTimeMillis() + DEFAULTTIMEOUT;
115                                    while(!actionLock.notified && System.currentTimeMillis() < waitToMillis){
116                                            try{
117                                                    long timeToWait = waitToMillis - System.currentTimeMillis();
118                                                    if(timeToWait > 0) actionLock.wait(timeToWait);
119                                            }catch(InterruptedException irex){/* Ignore this.*/}
120                                    }
121                                    if(!expectedDos.contains(aTerm)) testCase.testFailed("Tool "+getToolName()+": unexpected do: "+aTerm);
122                            }
123                            actionLock.notified = false;
124                    }                       
125    
126                    synchronized(expectLock){
127                            expectLock.notified = true;
128                            expectLock.notify();
129                    }
130            }
131            
132            public ATerm doEval(ATerm aTerm){
133                    ATerm value;
134                    
135                    synchronized(actionLock){
136                            if(expecting){
137                                    value = expectedEvals.get(aTerm);
138                                    
139                                    if(value == null) testCase.testFailed("Tool "+getToolName()+": unexpected eval: "+aTerm);
140                            }else{
141                                    actionQueue.add(aTerm);
142                                    
143                                    long waitToMillis = System.currentTimeMillis() + DEFAULTTIMEOUT;
144                                    while(!actionLock.notified && System.currentTimeMillis() < waitToMillis){
145                                            try{
146                                                    long timeToWait = waitToMillis - System.currentTimeMillis();
147                                                    if(timeToWait > 0) actionLock.wait(timeToWait);
148                                            }catch(InterruptedException irex){/* Ignore this.*/}
149                                    }
150                                    
151                                    value = expectedEvals.get(aTerm);       
152                                    
153                                    if(value == null) testCase.testFailed("Tool "+getToolName()+": unexpected eval: "+aTerm);
154                            }
155                            
156                            actionLock.notified = false;
157                    }
158                    
159                    synchronized(expectLock){
160                            expectLock.notified = true;
161                            expectLock.notify();                            
162                    }
163                    
164                    return value;
165            }
166            
167            public ATerm doGetPerformanceStats(){
168                    return getPerformanceStats();
169            }
170            
171            public void doReceiveAckEvent(ATerm aTerm){
172                    tool.receiveAckEvent(aTerm);
173            }
174            
175            public void doTerminate(ATerm aTerm){
176                    tool.receiveTerminate(aTerm);
177            }
178            
179            public void sendEvent(final ATerm event, final long timeout){
180                    final WaitLock eventLock = new WaitLock();
181                    final String eventName = ((ATermAppl) event).getAFun().toString();
182                    
183                    if(verbose) System.err.println("Tool "+getToolName()+" sends event: "+event);
184    
185                    expectedAckEvents.put(eventName, eventLock);
186                    
187                    postEvent(event);
188                    
189                    new Thread(){
190                            public void run(){
191                                    long waitToMillis = System.currentTimeMillis() + timeout;
192                            
193                                    synchronized(eventLock){
194                                            while(!eventLock.notified){
195                                                    try{
196                                                            long timeToWait = waitToMillis - System.currentTimeMillis();
197                                                            if(timeToWait > 0) eventLock.wait(timeToWait);
198                                                    }catch(InterruptedException irex){/* Ignore this. */}
199                                                    
200                                                    if(System.currentTimeMillis() >= waitToMillis){
201                                                            expectedAckEvents.remove(eventName);
202                                                            synchronized(completionLock){
203                                                                    completionLock.notifyAll();
204                                                            }
205                                                            
206                                                            testCase.testFailed("Tool "+getToolName()+": timed out while waiting for ack event of: "+event);
207                                                    }
208                                            }
209                                    }
210                            }
211                    }.start();
212            }
213            
214            public ATerm sendRequest(final ATerm request, ATerm expectedResponse, long timeout){
215                    final Object requestLock = new Object();
216                    
217                    if(verbose) System.err.println("Tool "+getToolName()+" sends request "+request);
218                    
219                    class RequestPoster extends Thread{
220                            public volatile ATerm response = null;
221                            
222                            public void run(){
223                                    response = postRequest(request);
224                                    synchronized(requestLock){
225                                            requestLock.notify();
226                                    }
227                            }
228                    }
229                    RequestPoster requestPoster = new RequestPoster();
230                    requestPoster.start();
231    
232                    long waitToMillis = System.currentTimeMillis() + timeout;
233                    synchronized(requestLock){
234                            while(requestPoster.response == null){
235                                    try{
236                                            long timeToWait = waitToMillis - System.currentTimeMillis();
237                                            if(timeToWait > 0) requestLock.wait(timeToWait);
238                                    }catch(InterruptedException irex){/* Ignore this. */}
239                                    
240                                    if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for response of: "+request); 
241                            }
242                    }
243                    
244                    ATerm response = requestPoster.response;
245                    
246                    if(response != expectedResponse) testCase.testFailed("Tool "+getToolName()+": received an unexpected response. Expected: "+expectedResponse+", got: "+response);
247                    
248                    return response;
249            }
250            
251            public void registerForDo(ATerm action){
252                    expectedDos.add(action);
253            }
254            
255            public void registerForEval(ATerm action, ATerm expectedResult){
256                    expectedEvals.put(action, expectedResult);
257            }
258            
259            public void expectAction(long timeout){
260                    if(expectedDos.size() + expectedEvals.size() == 0) testCase.testFailed("Can't expect nothing.");
261                    
262                    if(verbose) System.err.println("Tool "+getToolName()+" expects eval's: "+expectedEvals+" and do's: "+expectedDos);
263    
264                    synchronized(actionLock){
265                            expecting = true;
266                            
267                            if(!actionQueue.isEmpty()){
268                                    actionLock.notified = true;
269                                    actionLock.notify();
270                            }
271                    }
272                    
273                    long waitToMillis = System.currentTimeMillis() + timeout;
274                    synchronized(expectLock){
275                            while(!expectLock.notified){
276                                    try{
277                                            long timeToWait = waitToMillis - System.currentTimeMillis();
278                                            if(timeToWait > 0) expectLock.wait(timeToWait);
279                                    }catch(InterruptedException irex){/* Ignore this. */}
280                                    
281                                    if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for evals and/or do's.");
282                            }
283                            expectLock.notified = false;
284                    }
285                    
286                    synchronized(actionLock){
287                            actionQueue.clear();
288                            expecting = false;
289                    }
290                    
291                    expectedDos.clear();
292                    expectedEvals.clear();
293            }
294            
295            public void waitForCompletion(long timeout){
296                    long waitToMillis = System.currentTimeMillis() + timeout;
297                    
298                    synchronized(completionLock){
299                            while(!expectedAckEvents.isEmpty()){
300                                    try{
301                                            long timeToWait = waitToMillis - System.currentTimeMillis();
302                                            if(timeToWait > 0) completionLock.wait(timeToWait);
303                                    }catch(InterruptedException irex){/* Ignore this. */}
304                                    
305                                    if(System.currentTimeMillis() >= waitToMillis) testCase.testFailed("Tool "+getToolName()+": timed out while waiting for ack event(s) for: "+expectedAckEvents);
306                            }
307                    }
308                    
309                    if(!actionQueue.isEmpty()) testCase.testFailed("Tool "+getToolName()+": unexpected queued eval or do detected while waiting for completion."+actionQueue);
310            }
311            
312            private static class WaitLock{
313                    public volatile boolean notified = false;
314            }
315            
316            /**
317             * Gathers performance statistics about this tool, like memory usage and the user-/system-time
318             * spend per thread.
319             * 
320             * @return Performance statictics.
321             */
322            private ATerm getPerformanceStats(){
323                    String type = getType();
324                    
325                    // Type stuff
326                    ATerm remote = termFactory.makeAppl(termFactory.makeAFun(type, 0, true));
327                    ATerm toolType = termFactory.makeAppl(termFactory.makeAFun("type", 1, false), remote);
328                    
329                    ATerm java = termFactory.makeAppl(termFactory.makeAFun("Java", 0, true));
330                    ATerm toolLanguage = termFactory.makeAppl(termFactory.makeAFun("language", 1, false), java);
331                    
332                    ATerm toolData = termFactory.makeAppl(termFactory.makeAFun("tool", 2, false), toolType, toolLanguage);
333                    
334                    // Memory stuff
335                    MemoryMXBean mmxb = ManagementFactory.getMemoryMXBean();
336                    long heapMemoryUsage = mmxb.getHeapMemoryUsage().getUsed();
337                    long nonHeapMemoryUsage = mmxb.getNonHeapMemoryUsage().getUsed();
338                    
339                    ATerm heapUsage = termFactory.makeAppl(termFactory.makeAFun("heap-usage", 1, false), termFactory.makeInt(((int) (heapMemoryUsage / 1024))));
340                    ATerm nonHeapUsage = termFactory.makeAppl(termFactory.makeAFun("non-heap-usage", 1, false), termFactory.makeInt(((int) (nonHeapMemoryUsage / 1024))));
341                    
342                    ATerm memory = termFactory.makeAppl(termFactory.makeAFun("memory-usage", 2, false), heapUsage, nonHeapUsage);
343                    
344                    // Thread stuff
345                    ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
346                    Thread[] relevantThreads = new Thread[currentThreadGroup.activeCount() * 2 + 10]; // Create an array that's more then big enough.
347                    currentThreadGroup.enumerate(relevantThreads);
348                    
349                    Set<Long> relevantThreadIds = new HashSet<Long>();
350                    for(int i = 0; i < relevantThreads.length; i++){
351                            Thread thread = relevantThreads[i];
352                            if(thread != null){
353                                    relevantThreadIds.add(new Long(thread.getId()));
354                            }
355                    }
356                    
357                    ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
358                    
359                    ATerm threads;
360                    
361                    long[] threadIds = tmxb.getAllThreadIds();
362                    int nrOfThreads = threadIds.length;
363                    try{
364                            ATermList threadsList = termFactory.makeList();
365                            for(int i = 0; i < nrOfThreads; i++){
366                                    long threadId = threadIds[i];
367                                    if(relevantThreadIds.contains(new Long(threadId))){ // Only list the info if we're interested in it.
368                                            ThreadInfo ti = tmxb.getThreadInfo(threadId);
369                                            if(ti != null){
370                                                    String threadName = ti.getThreadName();
371                                                    long userTime = tmxb.getThreadUserTime(threadIds[i]);
372                                                    long systemTime = tmxb.getThreadCpuTime(threadIds[i]) - userTime;
373                                                    
374                                                    if((userTime + systemTime) <= 0) continue;
375                                                    
376                                                    ATerm userTimeTerm = termFactory.makeAppl(termFactory.makeAFun("user-time", 1, false), termFactory.makeInt(((int) (userTime / 1000000))));
377                                                    ATerm systemTimeTerm = termFactory.makeAppl(termFactory.makeAFun("system-time", 1, false), termFactory.makeInt(((int) (systemTime / 1000000))));
378                                                    ATerm thread = termFactory.makeAppl(termFactory.makeAFun(threadName, 2, false), userTimeTerm, systemTimeTerm);
379                                                    
380                                                    threadsList = termFactory.makeList(thread, threadsList);
381                                            }
382                                    }
383                            }
384                            
385                            threads = termFactory.makeAppl(termFactory.makeAFun("threads", 1, false), threadsList);
386                    }catch(UnsupportedOperationException uoex){
387                            threads = termFactory.make("threads(unsupported-operation)");
388                            LoggerFactory.log("Thread time profiling is not supported by this JVM.", ILogger.ERROR, IToolBusLoggerConstants.TOOL);
389                    }
390                    
391                    return termFactory.makeAppl(termFactory.makeAFun("performance-stats", 3, false), toolData, memory, threads);
392            }
393    }