Commit | Line | Data |
---|---|---|
970ed795 | 1 | /****************************************************************************** |
d44e3c4f | 2 | * Copyright (c) 2000-2016 Ericsson Telecom AB |
970ed795 EL |
3 | * All rights reserved. This program and the accompanying materials |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
d44e3c4f | 7 | * |
8 | * Contributors: | |
9 | * Balasko, Jeno | |
10 | * Lovassy, Arpad | |
11 | * | |
970ed795 EL |
12 | ******************************************************************************/ |
13 | package org.eclipse.titan.executorapi; | |
14 | ||
15 | import java.io.BufferedReader; | |
16 | import java.io.File; | |
17 | import java.io.IOException; | |
18 | import java.io.InputStreamReader; | |
19 | import java.util.ArrayList; | |
20 | import java.util.List; | |
21 | import java.util.HashMap; | |
22 | import java.util.Map; | |
23 | import java.util.regex.Matcher; | |
24 | import java.util.regex.Pattern; | |
25 | ||
26 | import org.eclipse.titan.executor.jni.ComponentStruct; | |
27 | import org.eclipse.titan.executor.jni.HostStruct; | |
28 | import org.eclipse.titan.executor.jni.IJNICallback; | |
29 | import org.eclipse.titan.executor.jni.JNIMiddleWare; | |
30 | import org.eclipse.titan.executor.jni.McStateEnum; | |
31 | import org.eclipse.titan.executor.jni.Timeval; | |
32 | import org.eclipse.titan.executor.jni.VerdictTypeEnum; | |
33 | import org.eclipse.titan.executorapi.exception.JniExecutorException; | |
34 | import org.eclipse.titan.executorapi.exception.JniExecutorIllegalArgumentException; | |
35 | import org.eclipse.titan.executorapi.exception.JniExecutorJniLoadException; | |
36 | import org.eclipse.titan.executorapi.exception.JniExecutorStartSessionException; | |
37 | import org.eclipse.titan.executorapi.exception.JniExecutorWrongStateException; | |
38 | import org.eclipse.titan.executorapi.util.Log; | |
39 | ||
40 | ||
41 | /** | |
42 | * SINGLETON | |
43 | * <p> | |
44 | * This executor handles the execution of tests compiled in a parallel mode, via directly connecting to the MainController (MC) written in C++. | |
d44e3c4f | 45 | * It controls MC with commands, most of methods in this class represents one or more MC commands. |
970ed795 EL |
46 | * These methods can be called in certain states. |
47 | * They can be synchronous or asynchronous. | |
48 | * <p> | |
49 | * Recommended order of method calls during a normal test execution: | |
50 | * <ol> | |
51 | * <li> {@link #init()} | |
52 | * <li> {@link #addHostController(HostController)} | |
53 | * <li> {@link #setConfigFileName(String)} | |
54 | * <li> {@link #setObserver(IJniExecutorObserver)} | |
55 | * <li> {@link #startSession()} | |
56 | * <li> {@link #startHostControllers()} (asynchronous) | |
57 | * <li> {@link #configure()} (asynchronous) | |
58 | * <li> {@link #createMTC()} (asynchronous) | |
59 | * <li> One or more of the following methods: | |
60 | * <ul> | |
61 | * <li> {@link #executeTestcase(String, String)} (asynchronous) | |
62 | * <li> {@link #executeControl(String)} (asynchronous) | |
63 | * <li> {@link #getExecuteCfgLen()} and then {@link #executeCfg(int)} (asynchronous) | |
64 | * </ul> | |
65 | * <li> {@link #exitMTC()} (asynchronous) | |
66 | * <li> {@link #shutdownSession()} (asynchronous) | |
67 | * </ol> | |
68 | * <p> | |
69 | * This is singleton, because there is only one MainController instance. | |
70 | * <p> | |
71 | * When an asynchronous operation is finished, status changed (intermediate state, but operation is still running) or error happens or notification arrives from MC, | |
72 | * callback methods of {@link IJniExecutorObserver} will be called. | |
73 | * This observer is set by calling {@link #setObserver(IJniExecutorObserver)}. | |
74 | * <p> | |
75 | * If problem occurs synchronously (in synchronous functions or in asynchronous functions before request is sent to MC), | |
76 | * a {@link JniExecutorException} is thrown. | |
77 | * <br> | |
78 | * These problems can be one of the following cases: | |
79 | * <ul> | |
80 | * <li> method is called in invalid state | |
81 | * <li> one of the method parameters are invalid | |
82 | * </ul> | |
83 | * <p> | |
84 | * Otherwise (problem occurs during asynchronous operations in MC) {@link IJniExecutorObserver#error(int, String)} is called. | |
85 | * <p> | |
86 | * Simplified state diagram: | |
87 | * <p> | |
88 | * <img src="../../../../../doc/uml/TITAN_Executor_API_state_diagram_simple.png"/> | |
89 | */ | |
90 | public class JniExecutor implements IJNICallback { | |
91 | ||
92 | // Exception texts | |
93 | ||
94 | /** Used by checkConnection() */ | |
95 | private static final String EXCEPTION_TEXT_WRONG_STATE_CONNECTED = "Executor is already initialized."; | |
96 | /** Used by checkConnection() */ | |
97 | private static final String EXCEPTION_TEXT_WRONG_STATE_NOT_CONNECTED = "Executor is NOT initialized, call init() first."; | |
98 | /** Used by buildWrongStateMessage() */ | |
99 | private static final String EXCEPTION_TEXT_WRONG_STATE_PART_1 = "Method cannot be called in this state. Current state: "; | |
100 | /** Used by buildWrongStateMessage() */ | |
101 | private static final String EXCEPTION_TEXT_WRONG_STATE_PART_2 = ", expected state(s): "; | |
102 | ||
103 | /** Used by setConfigFileName() */ | |
104 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_CFG_FILENAME_NULL = "Configuration file name is null."; | |
105 | /** Used by setConfigFileName() */ | |
106 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_CFG_FILE_NOT_EXIST = "Configuration file does NOT exists."; | |
107 | /** Used by addHostController() */ | |
108 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_ADD_HC_NULL = "Host Controller is null."; | |
109 | /** Used by executeControl() and executeTestcase() */ | |
110 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CONTROL_NAME_NULL_OR_EMPTY = "Test control name is null or empty."; | |
111 | /** Used by executeTestcase() */ | |
112 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CASE_NAME_NULL_OR_EMPTY = "Test case name is null or empty."; | |
113 | /** Used by executeCfg() */ | |
114 | private static final String EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CFG_INDEX_OUT_OF_BOUND = "Test index is out of bound."; | |
115 | ||
116 | /** Used by startSession() */ | |
117 | private static final String EXCEPTION_TEXT_START_SESSION = "Start session failed. Error code: "; | |
118 | ||
119 | // Error texts | |
120 | ||
121 | /** Used by runCommand() */ | |
122 | private static final String ERROR_TEXT_RUN_COMMAND_EXIT_CODE_NOT_0_PART_1 = "Command: \""; | |
123 | /** Used by runCommand() */ | |
124 | private static final String ERROR_TEXT_RUN_COMMAND_EXIT_CODE_NOT_0_PART_2 = "\" exited with the following exit code: "; | |
125 | /** Used by runCommand() */ | |
126 | private static final String ERROR_TEXT_RUN_COMMAND_FAILED = "Error running command: "; | |
127 | /** Used by runCommand() */ | |
128 | private static final String ERROR_TEXT_RUN_COMMAND_INTERRUPTED = "The following command is interrupted: "; | |
129 | ||
130 | ||
131 | /** | |
132 | * Default config string for JNIMiddleWare.configure(). This is used, if {@link #setConfigFileName(String)} is not called. | |
133 | */ | |
134 | private static final String DEFAULT_CONFIG_STRING = "//This part was added by the TITAN Executor API.\n" + | |
135 | "[LOGGING]\n" + | |
136 | "LogFile := \"./../log//%e.%h-part%i-%r.%s\"\n"; | |
137 | ||
138 | /** | |
139 | * Default TCP listen port. TCP listen port is needed as an input parameter to start a new session. | |
140 | */ | |
141 | private static final int DEFAULT_TCP_LISTEN_PORT = 0; | |
142 | ||
143 | /** | |
144 | * Default local host address. | |
145 | * NULL is translated to 0.0.0.0 when it is sent to MainController | |
146 | */ | |
147 | private static final String DEFAULT_LOCAL_HOST_ADDRESS = "NULL"; | |
148 | ||
149 | /** | |
150 | * JNI middleware instance. OWNED | |
151 | */ | |
152 | private final JNIMiddleWare mJniMw; | |
153 | ||
154 | /** | |
155 | * API observer for notifications and callbacks. NOT OWNED | |
156 | */ | |
157 | private IJniExecutorObserver mObserver = null; | |
158 | ||
159 | /** | |
160 | * TCP port which is returned by start_session(), comes from MainController. | |
161 | * It is needed as an input parameter to start a new HC. | |
162 | * -1 means uninitialized/invalid value | |
163 | */ | |
164 | private int mMcPort = -1; | |
165 | ||
166 | /** | |
167 | * Local host address. It is needed as an input parameter to start a new HC. | |
168 | * Default value: NULL, which is translated to 0.0.0.0 when it is sent to MainController | |
169 | */ | |
170 | private String mMcHost = DEFAULT_LOCAL_HOST_ADDRESS; | |
171 | ||
172 | /** | |
173 | * List of host controllers, which are started and connected with MainController by {@link #startHostControllers()}. OWNED | |
174 | * @see #startHostControllers() | |
175 | */ | |
176 | private List<HostController> mHostControllers = null; | |
177 | ||
178 | /** | |
179 | * true, if {@link #setConfigFileName(String)} is called, so config file is pre-processed by MainController, | |
180 | * and the result config data is stored in MC. | |
181 | * In this case {@link #startSession()} and {@link #configure()} will use the config data stored in MC. | |
182 | * <p> | |
183 | * false otherwise. | |
184 | * In this case {@link #startSession()} and {@link #configure()} will use the default values which are defined | |
185 | * in this class (private static final ... DEFAULT_*) | |
186 | */ | |
187 | private boolean mCfgFilePreprocessed = false; | |
188 | ||
189 | /** | |
190 | * true, if {@link #shutdownSession()} is called, false otherwise. | |
191 | * <p> | |
192 | * The reason that we need this flag is that {@link #shutdownSession()} can be called in many states, | |
193 | * so through more asynchronous requests when a request is finished we need to call another, | |
194 | * and we need to remember that during the whole process. | |
195 | */ | |
196 | private boolean mShutdownRequested = false; | |
197 | ||
198 | /** | |
199 | * Lock object for waitForCompletion() | |
200 | */ | |
201 | private final Object mLockCompletion = new Object(); | |
202 | ||
203 | /** | |
204 | * Pattern for verdict, that comes as a notification after execution of a testcase. | |
205 | * If a test control is executed, this notification is sent multiple times after each testcase. | |
206 | * <p> | |
207 | * Example: | |
208 | * <br> | |
209 | * Test case HelloW finished. Verdict: pass | |
210 | * <br> | |
211 | * Test case HelloW2 finished. Verdict: inconc | |
212 | */ | |
213 | private static final Pattern PATTERN_VERDICT = | |
214 | Pattern.compile("Test case (.*) finished\\. Verdict: (none|pass|inconc|fail|error)"); | |
215 | ||
216 | // Group indexes for this pattern | |
217 | // It is stored here, because group indexes must be synchronized with the pattern. | |
218 | private static final int PATTERN_VERDICT_GROUP_INDEX_TESTCASE = 1; | |
219 | private static final int PATTERN_VERDICT_GROUP_INDEX_VERDICTTYPENAME = 2; | |
220 | ||
221 | /** | |
222 | * Pattern for dynamic testcase error, that comes as a notification after unsuccessful execution of a testcase. | |
223 | * If a test control is executed, this notification is sent multiple times after each testcase. | |
224 | * <p> | |
225 | * Example: | |
226 | * <br> | |
227 | * Dynamic test case error: Test case tc_HelloW does not exist in module MyExample. | |
228 | */ | |
229 | private static final Pattern PATTERN_DYNAMIC_TESTCASE_ERROR = | |
230 | Pattern.compile("Dynamic test case error: (.*)"); | |
231 | ||
232 | // Group indexes for this pattern | |
233 | // It is stored here, because group indexes must be synchronized with the pattern. | |
234 | private static final int PATTERN_DYNAMIC_TESTCASE_ERROR_GROUP_INDEX_ERROR_TEXT = 1; | |
235 | ||
236 | ||
237 | /** | |
238 | * Pattern for verdict statistics, that comes as a notification after executing all the testcases after exit MTC. | |
239 | * This notification is sent only once for a MTC session. | |
240 | * <p> | |
241 | * Example: | |
242 | * <br> | |
243 | * Verdict statistics: 0 none (0.00 %), 1 pass (50.00 %), 1 inconc (50.00 %), 0 fail (0.00 %), 0 error (0.00 %). | |
244 | * <br> | |
245 | * Verdict statistics: 0 none, 0 pass, 0 inconc, 0 fail, 0 error. | |
246 | */ | |
247 | private static final Pattern PATTERN_VERDICT_STATS = | |
248 | Pattern.compile("Verdict statistics: " | |
249 | + "(\\d+) none[^,]*, " | |
250 | + "(\\d+) pass[^,]*, " | |
251 | + "(\\d+) inconc[^,]*, " | |
252 | + "(\\d+) fail[^,]*, " | |
253 | + "(\\d+) error.*"); | |
254 | ||
255 | // Group indexes for this pattern | |
256 | // It is stored here, because group indexes must be synchronized with the pattern. | |
257 | private static final int PATTERN_VERDICT_STATS_GROUP_INDEX_NONE = 1; | |
258 | private static final int PATTERN_VERDICT_STATS_GROUP_INDEX_PASS = 2; | |
259 | private static final int PATTERN_VERDICT_STATS_GROUP_INDEX_INCONC = 3; | |
260 | private static final int PATTERN_VERDICT_STATS_GROUP_INDEX_FAIL = 4; | |
261 | private static final int PATTERN_VERDICT_STATS_GROUP_INDEX_ERROR = 5; | |
262 | ||
263 | /** | |
264 | * Pattern for error outside of test cases, that comes as a notification after executing all the testcases after exit MTC. | |
265 | * This notification is sent only once for a MTC session after PATTERN_VERDICT_STATS. | |
266 | * <p> | |
267 | * OPTIONAL | |
268 | * <p> | |
269 | * Example: | |
270 | * <br> | |
271 | * Number of errors outside test cases: 2 | |
272 | */ | |
273 | private static final Pattern PATTERN_ERRORS_OUTSIDE_OF_TESTCASES = | |
274 | Pattern.compile("Number of errors outside test cases: (\\d+)"); | |
275 | ||
276 | // Group indexes for this pattern | |
277 | // It is stored here, because group indexes must be synchronized with the pattern. | |
278 | private static final int PATTERN_ERRORS_OUTSIDE_OF_TESTCASES_GROUP_INDEX_ERRORS = 1; | |
279 | ||
280 | /** | |
281 | * Pattern for overall verdict, that comes as a notification after executing all the testcases after exit MTC. | |
282 | * This notification is sent only once for a MTC session after PATTERN_VERDICT_STATS and PATTERN_ERRORS_OUTSIDE_OF_TESTCASES (optional). | |
283 | * <p> | |
284 | * Example: | |
285 | * <br> | |
286 | * Test execution summary: 0 test case was executed. Overall verdict: error | |
287 | */ | |
288 | private static final Pattern PATTERN_OVERALL_VERDICT = | |
289 | Pattern.compile("Test case (.*) finished\\. Verdict: (none|pass|inconc|fail|error)"); | |
290 | ||
291 | // Group indexes for this pattern | |
292 | // It is stored here, because group indexes must be synchronized with the pattern. | |
293 | private static final int PATTERN_OVERALL_VERDICT_GROUP_INDEX_NUMBER_OF_TESTCASE = 1; | |
294 | private static final int PATTERN_OVERALL_VERDICT_GROUP_INDEX_VERDICTTYPENAME = 2; | |
295 | ||
296 | /** | |
297 | * Private constructor, because it is a singleton. | |
298 | */ | |
299 | private JniExecutor() { | |
300 | mJniMw = new JNIMiddleWare(this); | |
301 | } | |
302 | ||
303 | /** | |
304 | * Lazy holder for the singleton (Bill Pugh solution) | |
305 | * Until we need an instance, the holder class will not be initialized until required and you can still use other static members of the singleton class. | |
306 | * @see <a href="http://en.wikipedia.org/wiki/Singleton_pattern#Initialization_On_Demand_Holder_Idiom">Wikipedia</a> | |
307 | */ | |
308 | private static class SingletonHolder { | |
309 | /** Singleton instance */ | |
310 | private static final JniExecutor mInstance = new JniExecutor(); | |
311 | } | |
312 | ||
313 | /** | |
314 | * @return the singleton instance | |
315 | */ | |
316 | public static JniExecutor getInstance() { | |
317 | return SingletonHolder.mInstance; | |
318 | } | |
319 | ||
320 | @Override | |
321 | public void statusChangeCallback( final McStateEnum aState ) { | |
322 | Log.fi( aState ); | |
323 | if ( mShutdownRequested ) { | |
324 | continueShutdown( aState ); | |
325 | } | |
326 | ||
327 | observerStatusChanged( aState ); | |
328 | Log.fo(); | |
329 | } | |
330 | ||
331 | @Override | |
332 | public void errorCallback( final int aSeverity, final String aMsg ) { | |
333 | Log.fi( aSeverity, aMsg ); | |
334 | observerError( aSeverity, aMsg ); | |
335 | Log.fo(); | |
336 | } | |
337 | ||
338 | @Override | |
339 | public void batchedInsertNotify( final List<String[]> aNotifications ) { | |
340 | Log.fi( aNotifications ); | |
341 | for (String[] n : aNotifications) { | |
342 | Timeval timestamp; | |
343 | try { | |
344 | timestamp = new Timeval(Integer.parseInt(n[0]), Integer.parseInt(n[1])); | |
345 | } catch (Exception e) { | |
346 | timestamp = new Timeval(); | |
347 | } | |
348 | ||
349 | final String source = n[2]; | |
350 | int severity; | |
351 | try { | |
352 | severity = Integer.parseInt(n[3]); | |
353 | } catch (Exception e) { | |
354 | severity = 0; | |
355 | } | |
356 | ||
357 | final String message = n[4]; | |
358 | notifyCallback(timestamp, source, severity, message ); | |
359 | } | |
360 | Log.fo(); | |
361 | } | |
362 | ||
363 | @Override | |
364 | public void notifyCallback( final Timeval aTime, final String aSource, final int aSeverity, final String aMsg ) { | |
365 | Log.fi( aTime, aSource, aSeverity, aMsg ); | |
366 | if ( !processSpecialNotifications( aMsg ) ) { | |
367 | observerNotify( aTime, aSource, aSeverity, aMsg ); | |
368 | } | |
369 | Log.fo(); | |
370 | } | |
371 | ||
372 | /** | |
373 | * Gets lock object for synchronizing MC related operations. | |
374 | * @return lock object | |
375 | */ | |
376 | public final Object getLock() { | |
377 | return mJniMw.getLock(); | |
378 | } | |
379 | ||
380 | /** | |
381 | * Gets connection state. SYNCHRONOUS | |
382 | * @return true, if connected (connection to MC is initialized (after init() is called) and not yet terminated (before asynchronous shutdownSession() is completed)) | |
383 | * <br> | |
384 | * false otherwise (disconnected: not initialized or terminated) | |
385 | */ | |
386 | public boolean isConnected() { | |
387 | synchronized (getLock()) { | |
388 | return mJniMw.isConnected(); | |
389 | } | |
390 | } | |
391 | ||
392 | /** | |
393 | * Connects client to MainController. SYNCHRONOUS | |
394 | * <p> | |
395 | * It can be called in disconnected state, | |
396 | * it moves MC to MC_INACTIVE state. | |
397 | * <p> | |
398 | * Do NOT call {@link #setObserver(IJniExecutorObserver)} or {@link #addHostController(HostController)} before {@link #init()}, because {@link #init()} will delete them. | |
399 | * @throws JniExecutorWrongStateException | |
400 | * @throws JniExecutorJniLoadException | |
401 | */ | |
402 | public void init() throws JniExecutorWrongStateException, JniExecutorJniLoadException { | |
403 | synchronized (getLock()) { | |
404 | // make sure, that we are in uninitialized state, otherwise calling reset() is NOT allowed | |
405 | checkConnection( false ); | |
406 | JniExecutorJniLoadException e = JNIMiddleWare.getException(); | |
407 | if ( e != null ) { | |
408 | throw e; | |
409 | } | |
410 | reset(); | |
411 | mJniMw.initialize(1500); | |
412 | } | |
413 | } | |
414 | ||
415 | /** | |
416 | * Adds a new Host Controller. SYNCHRONOUS | |
417 | * <p> | |
418 | * It doesn't communicate with MainController, just stores the HCs, | |
419 | * {@link #startHostControllers()} will start the HCs according to this information | |
420 | * <p> | |
421 | * It can be called in MC_INACTIVE, MC_LISTENING or MC_LISTENING_CONFIGURED state, the operation will not change the state. | |
422 | * @param aHc the new HC | |
423 | * @throws JniExecutorIllegalArgumentException | |
424 | * @throws JniExecutorWrongStateException | |
425 | * @see #startHostControllers() | |
426 | */ | |
427 | public void addHostController( final HostController aHc ) throws JniExecutorIllegalArgumentException, JniExecutorWrongStateException { | |
428 | synchronized (getLock()) { | |
429 | checkState( McStateEnum.MC_INACTIVE, McStateEnum.MC_LISTENING, McStateEnum.MC_LISTENING_CONFIGURED ); | |
430 | if ( aHc == null ) { | |
431 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_ADD_HC_NULL ); | |
432 | } | |
433 | ||
434 | if ( mHostControllers == null ) { | |
435 | mHostControllers = new ArrayList<HostController>(); | |
436 | } | |
437 | mHostControllers.add( aHc ); | |
438 | } | |
439 | } | |
440 | ||
441 | /** | |
442 | * @return added host controllers. It can be null | |
443 | */ | |
444 | public List<HostController> getHostControllers() { | |
445 | return mHostControllers; | |
446 | } | |
447 | ||
448 | /** | |
449 | * Sets the configuration file of the test. SYNCHRONOUS | |
450 | * <p> | |
451 | * If setConfigFileName() is called before {@link #startSession()} and {@link #configure()} (HIGHLY RECOMMENDED), | |
452 | * it extracts the config string from the config file, and the result config string is stored in MainController, | |
453 | * which will be used by configure() and no config data needed to be passed to MC. | |
454 | * <p> | |
455 | * If setConfigFileName() is NOT called before {@link #startSession()} and {@link #configure()}, | |
456 | * a default config string will be passed to MainController by {@link #configure()}, | |
457 | * a default TCP listen port and default MC host address (localhost) will be used in {@link #startSession()}, | |
458 | * and 1 HC will be started by {@link #startHostControllers()}. | |
459 | * <p> | |
460 | * It can be called in MC_INACTIVE state, the operation will not change the state. | |
461 | * | |
462 | * @param aConfigFileName the configuration file of the test with full path | |
463 | * @throws JniExecutorWrongStateException | |
464 | * @throws JniExecutorIllegalArgumentException | |
465 | * @see #configure() | |
466 | */ | |
467 | public void setConfigFileName( final String aConfigFileName ) throws JniExecutorWrongStateException, JniExecutorIllegalArgumentException { | |
468 | Log.fi(); | |
469 | synchronized (getLock()) { | |
470 | checkState( McStateEnum.MC_INACTIVE ); | |
471 | if ( aConfigFileName == null ) { | |
472 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_CFG_FILENAME_NULL ); | |
473 | } | |
474 | // config file cannot be a remote file only local, see config_preproc_p.tab.cc preproc_parse_file() | |
475 | final File cfgFile = new File(aConfigFileName); | |
476 | if ( !cfgFile.exists() || !cfgFile.isFile() ) { | |
477 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_CFG_FILE_NOT_EXIST ); | |
478 | } | |
479 | mJniMw.do_set_cfg_file(aConfigFileName); | |
480 | mCfgFilePreprocessed = true; | |
481 | } | |
482 | Log.fo(); | |
483 | } | |
484 | ||
485 | /** | |
486 | * Sets the API observer for notifications and callbacks. SYNCHRONOUS | |
487 | * <p> | |
488 | * It can be called in connected state, the operation will not change the state. | |
489 | * @param aObserver the observer, it can be null, but not recommended. | |
490 | * @throws JniExecutorWrongStateException | |
491 | */ | |
492 | public void setObserver( final IJniExecutorObserver aObserver ) throws JniExecutorWrongStateException { | |
493 | checkConnection(true); | |
494 | mObserver = aObserver; | |
495 | } | |
496 | ||
497 | /** | |
498 | * Starts MC session. SYNCHRONOUS | |
499 | * <p> | |
500 | * It can be called in MC_INACTIVE state. | |
501 | * <br> | |
502 | * MC goes to MC_LISTENING state when operation is finished. | |
503 | * <p> | |
504 | * NOTE: The operation is synchronous, but {@link IJniExecutorObserver#statusChanged(McStateEnum)} with parameter MC_LISTENING is also called. | |
505 | * @throws JniExecutorWrongStateException | |
506 | * @throws JniExecutorStartSessionException | |
507 | */ | |
508 | public void startSession() throws JniExecutorWrongStateException, JniExecutorStartSessionException { | |
509 | Log.fi(); | |
510 | synchronized (getLock()) { | |
511 | checkState( McStateEnum.MC_INACTIVE ); | |
512 | ||
513 | // DEFAULT_LOCAL_HOST_ADDRESS is used by default if config file is not provided ( setConfigFileName() is not called ) | |
514 | String localAddress = ( mCfgFilePreprocessed ? mJniMw.do_get_mc_host() : DEFAULT_LOCAL_HOST_ADDRESS ); | |
515 | ||
516 | // TCP listening port | |
517 | // DEFAULT_TCP_LISTEN_PORT is used by default if config file is not provided ( setConfigFileName() is not called ) | |
518 | int tcpport = ( mCfgFilePreprocessed ? mJniMw.do_get_port() : DEFAULT_TCP_LISTEN_PORT ); | |
519 | ||
520 | mMcHost = localAddress; | |
521 | ||
522 | // 3rd parameter should be true on Linux, false on Windows | |
523 | int port = mJniMw.do_start_session(localAddress, tcpport, !JNIMiddleWare.isWin() ); | |
524 | if (port <= 0) { | |
525 | // there were some errors starting the session | |
526 | // error message is sent by MC, so errorCallBack() -> IJniExecutorObserver.error() is called. | |
527 | throw new JniExecutorStartSessionException( EXCEPTION_TEXT_START_SESSION + port ); | |
528 | } | |
529 | else { | |
530 | mMcPort = port; | |
531 | } | |
532 | } | |
533 | Log.fo(); | |
534 | } | |
535 | ||
536 | /** | |
537 | * Start the Host Controllers. ASYNCHRONOUS | |
538 | * <p> | |
539 | * Host Controllers are started with a system command, each in a separate thread, | |
540 | * because all the commands block its thread until shutdown_session() is called which moves MC to MC_INACTIVE state. | |
541 | * <p> | |
542 | * It can be called in MC_LISTENING or MC_LISTENING_CONFIGURED state. | |
543 | * <br> | |
544 | * MC goes from MC_LISTENING to MC_HC_CONNECTED state when all the HCs are started. | |
545 | * <br> | |
546 | * MC goes from MC_LISTENING_CONFIGURED to MC_CONFIGURING state when all the HCs are started. | |
547 | * @throws JniExecutorWrongStateException | |
548 | */ | |
549 | public void startHostControllers() throws JniExecutorWrongStateException { | |
550 | Log.fi(); | |
551 | synchronized (getLock()) { | |
552 | checkState( McStateEnum.MC_LISTENING, McStateEnum.MC_LISTENING_CONFIGURED ); | |
553 | if ( mHostControllers != null ) { | |
554 | for ( final HostController hc : mHostControllers ) { | |
555 | startHostController( hc ); | |
556 | } | |
557 | } | |
558 | } | |
559 | Log.fo(); | |
560 | } | |
561 | ||
562 | /** | |
563 | * Starts the given Host Controller. ASYNCHRONOUS | |
564 | * <p> | |
565 | * A host controller is started with a system command. | |
566 | * @param aHc the HC to start | |
567 | */ | |
568 | private void startHostController( final HostController aHc ) { | |
569 | final String command = aHc.getCommand( mMcHost, mMcPort ); | |
570 | ||
571 | /* | |
572 | * The command runs in a separate thread, because this command will move | |
573 | * the MainController to MC_HC_CONNECTED state and it blocks the thread until | |
574 | * shutdown_session() is called which moves MC to MC_INACTIVE state. | |
575 | * So runCommand() function ends right after shutdown_session() is called. | |
576 | */ | |
577 | new Thread() { | |
578 | public void run() { | |
579 | runCommand( command ); | |
580 | } | |
581 | }.start(); | |
582 | } | |
583 | ||
584 | /** | |
585 | * Runs a system command. SYNCHRONOUS | |
586 | * <p> | |
587 | * Tested on Linux, NOT tested on CygWin | |
588 | * @param aCommand The system command. sh -c is added at the beginning of the command. | |
589 | */ | |
590 | private void runCommand( final String aCommand ) { | |
591 | Log.fi(aCommand); | |
592 | Runtime run = Runtime.getRuntime(); | |
593 | Process p = null; | |
594 | try { | |
595 | String[] cmd = { "sh", "-c", aCommand }; | |
596 | p = run.exec(cmd); | |
597 | ||
598 | p.getErrorStream(); | |
599 | BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); | |
600 | String s; | |
601 | while ((s = br.readLine()) != null) { | |
602 | Log.f("line: " + s); | |
603 | //send output to observer with notify() | |
604 | notifyCallback( new Timeval(), mMcHost, 0, s); | |
605 | } | |
606 | p.waitFor(); | |
607 | final int exitValue = p.exitValue(); | |
608 | Log.f("exit: " + exitValue); | |
609 | if ( exitValue != 0 ) { | |
610 | errorCallback(0, ERROR_TEXT_RUN_COMMAND_EXIT_CODE_NOT_0_PART_1 + aCommand + | |
611 | ERROR_TEXT_RUN_COMMAND_EXIT_CODE_NOT_0_PART_2 + exitValue); | |
612 | } | |
613 | } | |
614 | catch (IOException e) { | |
615 | Log.f(e.toString()); | |
616 | errorCallback(0, ERROR_TEXT_RUN_COMMAND_FAILED + aCommand); | |
617 | } catch (InterruptedException e) { | |
618 | Log.f(e.toString()); | |
619 | errorCallback(0, ERROR_TEXT_RUN_COMMAND_INTERRUPTED + aCommand); | |
620 | } | |
621 | finally { | |
622 | p.destroy(); | |
623 | } | |
624 | Log.fo(); | |
625 | } | |
626 | ||
627 | /** | |
628 | * Set parameters of the execution, which was pre-processed by setConfigFileName(). ASYNCHRONOUS | |
629 | * <p> | |
630 | * If setConfigFileName() is called before startSession() and configure() (HIGHLY RECOMMENDED), | |
631 | * it extracts the config string from the config file, and the result config string is stored in MainController, | |
632 | * which will be used by configure() and no config data needed to be passed to MC. | |
633 | * <p> | |
634 | * If setConfigFileName() is NOT called before startSession() and configure(), | |
635 | * a default config string will be passed to MainController by configure(), | |
636 | * a default TCP listen port and default MC host address (localhost) will be used in startSession(), | |
637 | * and 1 HC will be started by startHostControllers(). | |
638 | * <p> | |
639 | * It can be called in MC_LISTENING, MC_LISTENING_CONFIGURED or MC_HC_CONNECTED state. | |
640 | * <br> | |
641 | * MC goes from MC_LISTENING or MC_LISTENING_CONFIGURED to MC_LISTENING_CONFIGURED when operation is finished | |
642 | * (but config string is NOT yet downloaded to the HCs, because HCs are not started yet). | |
643 | * <br> | |
644 | * MC goes from MC_HC_CONNECTED to MC_CONFIGURING, and then to MC_ACTIVE when operation is finished (config string is downloaded to all HCs). | |
645 | * @throws JniExecutorWrongStateException | |
646 | * @see JniExecutor#setConfigFileName(String) | |
647 | */ | |
648 | public void configure() throws JniExecutorWrongStateException { | |
649 | synchronized (getLock()) { | |
650 | checkState( McStateEnum.MC_HC_CONNECTED, McStateEnum.MC_LISTENING, McStateEnum.MC_LISTENING_CONFIGURED ); | |
651 | ||
652 | // DEFAULT_CONFIG_STRING is used by default if config file is not provided ( setConfigFileName() is not called ) | |
653 | // otherwise empty string is sent to MC, which means MC will used its stored config data | |
654 | mJniMw.do_configure( mCfgFilePreprocessed ? null : DEFAULT_CONFIG_STRING ); | |
655 | } | |
656 | } | |
657 | ||
658 | /** | |
659 | * Creates Main Test Component (MTC), which is the last step before we can execute the tests. ASYNCHRONOUS | |
660 | * <p> | |
661 | * It can be called in MC_ACTIVE state, | |
662 | * it moves MC to MC_CREATING_MTC, | |
663 | * and then to MC_READY state. | |
664 | * @throws JniExecutorWrongStateException | |
665 | */ | |
666 | public void createMTC() throws JniExecutorWrongStateException { | |
667 | synchronized (getLock()) { | |
668 | checkState( McStateEnum.MC_ACTIVE ); | |
669 | mJniMw.do_create_mtc(0); | |
670 | } | |
671 | } | |
672 | ||
673 | /** | |
674 | * Executes a test control by module name. ASYNCHRONOUS | |
675 | * <p> | |
676 | * It can be called in MC_READY state, | |
677 | * it moves MC to MC_EXECUTING_CONTROL, | |
678 | * and then to MC_READY state when test control execution is finished. | |
679 | * @param aModule module name | |
680 | * @throws JniExecutorWrongStateException if MC state != MC_READY | |
681 | * @throws JniExecutorIllegalArgumentException if ( aModule == null || aModule.length() == 0 ) | |
682 | */ | |
683 | public void executeControl( final String aModule ) throws JniExecutorWrongStateException, JniExecutorIllegalArgumentException { | |
684 | synchronized (getLock()) { | |
685 | checkState( McStateEnum.MC_READY ); | |
686 | if ( aModule == null || aModule.length() == 0 ) { | |
687 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CONTROL_NAME_NULL_OR_EMPTY ); | |
688 | } | |
689 | mJniMw.do_execute_control( aModule ); | |
690 | } | |
691 | } | |
692 | ||
693 | /** | |
694 | * Executes a testcase by name. ASYNCHRONOUS | |
695 | * <p> | |
696 | * It can be called in MC_READY state, | |
697 | * it moves MC to MC_EXECUTING_TESTCASE, | |
698 | * and then to MC_READY state when testcase execution is finished. | |
699 | * @param aModule module name | |
700 | * @param aTestcase testcase name | |
701 | * @throws JniExecutorWrongStateException if MC state != MC_READY | |
702 | * @throws JniExecutorIllegalArgumentException if aModule or aTestcase is null or empty | |
703 | */ | |
704 | public void executeTestcase( final String aModule, final String aTestcase ) throws JniExecutorWrongStateException, JniExecutorIllegalArgumentException { | |
705 | synchronized (getLock()) { | |
706 | checkState( McStateEnum.MC_READY ); | |
707 | if ( aModule == null || aModule.length() == 0 ) { | |
708 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CONTROL_NAME_NULL_OR_EMPTY ); | |
709 | } | |
710 | if ( aTestcase == null || aTestcase.length() == 0 ) { | |
711 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CASE_NAME_NULL_OR_EMPTY ); | |
712 | } | |
713 | mJniMw.do_execute_testcase( aModule, aTestcase ); | |
714 | } | |
715 | } | |
716 | ||
717 | /** | |
718 | * Gets the length of the execute list. SYNCHRONOUS | |
719 | * <p> | |
720 | * Execute list is defined in the [EXECUTE] section in the configuration file. | |
721 | * It must be called after setConfigFileName() to have valid data, | |
722 | * otherwise it will return 0, because this value is not filled. | |
723 | * <p> | |
724 | * It can be called in MC_READY state, | |
725 | * <p> | |
726 | * @return The length of the execute list, or 0, if there is no [EXECUTE] section in the configuration file. | |
727 | * @throws JniExecutorWrongStateException | |
728 | */ | |
729 | public int getExecuteCfgLen() throws JniExecutorWrongStateException { | |
730 | synchronized (getLock()) { | |
731 | // for safety reasons, it could be read in any state, but in some cases data is uninitialized before calling set_cfg_file | |
732 | checkState( McStateEnum.MC_READY ); | |
733 | return mJniMw.do_get_execute_cfg_len(); | |
734 | } | |
735 | } | |
736 | ||
737 | /** | |
738 | * Executes the index-th element of the execute list. ASYNCHRONOUS | |
739 | * <p> | |
740 | * Execute list is defined in the [EXECUTE] section in the configuration file. | |
741 | * <p> | |
742 | * It can be called in MC_READY state, | |
743 | * it moves MC to MC_EXECUTING_TESTCASE or MC_EXECUTING_CONTROL, | |
744 | * and then to MC_READY state when testcase or test control execution is finished. | |
745 | * @param aIndex The test index from the execute list | |
746 | * @throws JniExecutorWrongStateException | |
747 | * @throws JniExecutorIllegalArgumentException | |
748 | */ | |
749 | public void executeCfg( final int aIndex ) throws JniExecutorWrongStateException, JniExecutorIllegalArgumentException { | |
750 | synchronized (getLock()) { | |
751 | checkState( McStateEnum.MC_READY ); | |
752 | final int executeCfgLen = getExecuteCfgLen(); | |
753 | if ( aIndex < 0 || aIndex >= executeCfgLen ) { | |
754 | throw new JniExecutorIllegalArgumentException( EXCEPTION_TEXT_ILLEGAL_ARG_TEST_CFG_INDEX_OUT_OF_BOUND ); | |
755 | } | |
756 | mJniMw.do_execute_cfg( aIndex ); | |
757 | } | |
758 | } | |
759 | ||
760 | /** | |
761 | * Switches the "pause after testcase" flag on or off. By default it is off. SYNCHRONOUS | |
762 | * <p> | |
763 | * It makes sense only in case of test control. | |
764 | * If it is on, the test execution is paused after every testcase, | |
765 | * and state will be switched to MC_PAUSED. | |
766 | * <p> | |
767 | * It will switch to MC_PAUSED even after the last testcase. | |
768 | * In this case continueExecution() will change the state to MC_EXECUTING_CONTROL and immediately to MC_READY if there is no more testcase to run. | |
769 | * Otherwise continueExecution() will change the state to MC_EXECUTING_CONTROL, a the next testcase is executed. | |
770 | * If we switch from on to off in MC_PAUSED state, execution of the next testcase will be automatically continued. | |
771 | * <p> | |
772 | * This function can be called in connected state. | |
773 | * @param aNewState the new "pause after testcase" flag state. true: on, false: off | |
774 | * @throws JniExecutorWrongStateException | |
775 | */ | |
776 | public void pauseExecution( final boolean aNewState ) throws JniExecutorWrongStateException { | |
777 | synchronized (getLock()) { | |
778 | checkConnection(true); | |
779 | mJniMw.do_stop_after_testcase( aNewState ); | |
780 | } | |
781 | } | |
782 | ||
783 | /** | |
784 | * Gets the "pause after testcase" flag. SYNCHRONOUS | |
785 | * <p> | |
786 | * This function can be called in connected state. | |
787 | * @return true, if test execution will stop after a testcase, false otherwise | |
788 | * @see #pauseExecution(boolean) | |
789 | * @throws JniExecutorWrongStateException | |
790 | */ | |
791 | public boolean isPaused() throws JniExecutorWrongStateException { | |
792 | synchronized (getLock()) { | |
793 | checkConnection(true); | |
794 | return mJniMw.do_get_stop_after_testcase(); | |
795 | } | |
796 | } | |
797 | ||
798 | /** | |
799 | * Continues execution if test control is paused, it executes the next testcase from the control. ASYNCHRONOUS | |
800 | * <p> | |
801 | * It can be called in MC_PAUSED state, | |
802 | * it moves MC to MC_TERMINATING_TESTCASE, and then to MC_READY state. | |
803 | * @throws JniExecutorWrongStateException | |
804 | */ | |
805 | public void continueExecution() throws JniExecutorWrongStateException { | |
806 | synchronized (getLock()) { | |
807 | checkState( McStateEnum.MC_PAUSED ); | |
808 | mJniMw.do_continue_testcase(); | |
809 | } | |
810 | } | |
811 | ||
812 | /** | |
813 | * Stops execution of a running or paused test. ASYNCHRONOUS | |
814 | * <p> | |
815 | * It can be called in MC_READY, MC_EXECUTING_TESTCASE, MC_EXECUTING_CONTROL or MC_PAUSED state, | |
816 | * it moves MC to MC_TERMINATING_TESTCASE, and then to MC_READY state. | |
817 | * @throws JniExecutorWrongStateException | |
818 | */ | |
819 | public void stopExecution() throws JniExecutorWrongStateException { | |
820 | synchronized (getLock()) { | |
821 | checkState( McStateEnum.MC_READY, McStateEnum.MC_EXECUTING_CONTROL, McStateEnum.MC_EXECUTING_TESTCASE, McStateEnum.MC_PAUSED ); | |
822 | if ( McStateEnum.MC_READY != getState() ) { | |
823 | mJniMw.do_stop_execution(); | |
824 | } | |
825 | // otherwise do nothing, MC would do the same, but there is no point to call it in this state | |
826 | } | |
827 | } | |
828 | ||
829 | /** | |
830 | * Stops MTC. ASYNCHRONOUS | |
831 | * <p> | |
832 | * It can be called when no more tests to run. | |
833 | * It can be called in MC_READY state, | |
834 | * it moves MC to MC_TERMINATING_MTC state, and then to MC_ACTIVE state. | |
835 | * @throws JniExecutorWrongStateException | |
836 | */ | |
837 | public void exitMTC() throws JniExecutorWrongStateException { | |
838 | synchronized (getLock()) { | |
839 | checkState( McStateEnum.MC_READY ); | |
840 | mJniMw.do_exit_mtc(); | |
841 | } | |
842 | } | |
843 | ||
844 | /** | |
845 | * Shuts down MC session. ASYNCHRONOUS | |
846 | * <p> | |
847 | * Shutdown can be called in any state, it goes through more states if needed. | |
848 | * It goes to MC_INACTIVE state, and then to disconnected. | |
849 | * <br> | |
850 | * For example if a testcase is executed (MC_EXECUTING_CONTROL, MC_EXECUTING_TESTCASE or MC_PAUSED state), | |
851 | * stop execution request is sent and we get to MC_TERMINATING_TESTCASE state, then to MC_READY state. | |
852 | * Then exit mtc request is sent, and we get to MC_TERMINATING_MTC and then to MC_ACTIVE state. | |
853 | * Then shutdown session request is sent, and we get to MC_SHUTDOWN, then to MC_INACTIVE state. | |
854 | * Then synchronous terminate request is sent, which closes the connection. | |
855 | * <p> | |
856 | * When it is called in MC_LISTENING, MC_LISTENING_CONFIGURED, MC_HC_CONNECTED or MC_ACTIVE state, | |
857 | * it goes to MC_INACTIVE in one step via MC_SHUTDOWN. | |
858 | */ | |
859 | public void shutdownSession() { | |
860 | Log.fi(); | |
861 | synchronized (getLock()) { | |
862 | if ( !mShutdownRequested && isConnected() ) { | |
863 | mShutdownRequested = true; | |
864 | final McStateEnum state = getState(); | |
865 | continueShutdown( state ); | |
866 | } | |
867 | // otherwise shutdown is already requested or not initialized, we don't need to do anything | |
868 | } | |
869 | Log.fo(); | |
870 | } | |
871 | ||
872 | /** | |
873 | * Continues next asynchronous (or final synchronous) step of shutdown. | |
874 | * <p> | |
875 | * Prerequisite: mShutdownRequested == true | |
876 | * @param aState current MC state | |
877 | */ | |
878 | private void continueShutdown( final McStateEnum aState ) { | |
879 | Log.fi(aState); | |
880 | synchronized (getLock()) { | |
881 | switch ( aState ) { | |
882 | case MC_INACTIVE: | |
883 | mShutdownRequested = false; | |
884 | // session shutdown is finished (requested by jnimw.do_shutdown_session()) | |
885 | mJniMw.terminate_internal(); | |
886 | // NOTE: Do NOT call reset here, because it will set the observer to null, | |
887 | // and client will NOT be called back! | |
888 | mHostControllers = null; | |
889 | synchronized (mLockCompletion) { | |
890 | mLockCompletion.notifyAll(); | |
891 | } | |
892 | break; | |
893 | case MC_LISTENING: | |
894 | case MC_LISTENING_CONFIGURED: | |
895 | case MC_HC_CONNECTED: | |
896 | case MC_ACTIVE: | |
897 | mJniMw.do_shutdown_session(); | |
898 | // mJniMw.do_terminate_internal() must be also called when shutdown is finished, see statusChangeCallback() case MC_INACTIVE | |
899 | break; | |
900 | case MC_READY: | |
901 | mJniMw.do_exit_mtc(); | |
902 | break; | |
903 | case MC_EXECUTING_CONTROL: | |
904 | case MC_EXECUTING_TESTCASE: | |
905 | case MC_PAUSED: | |
906 | mJniMw.do_stop_execution(); | |
907 | break; | |
908 | ||
909 | default: | |
910 | // We are in some unstable/intermediate state, we don't have to do anything, because we will get to some stable state | |
911 | // NOTE: MC_EXECUTING_CONTROL and MC_EXECUTING_TESTCASE are also intermediate states, but they can be interrupted by calling do_stop_execution() | |
912 | break; | |
913 | } | |
914 | } | |
915 | Log.fo(); | |
916 | } | |
917 | ||
918 | /** | |
919 | * Gets number of hosts. SYNCHRONOUS | |
920 | * <p> | |
921 | * It must be called in McStateEnum.MC_HC_CONNECTED, McStateEnum.MC_ACTIVE or MC_READY state. | |
922 | * @return number of hosts | |
923 | * @throws JniExecutorWrongStateException | |
924 | */ | |
925 | public int getNumberOfHosts() throws JniExecutorWrongStateException { | |
926 | synchronized (getLock()) { | |
927 | checkState( McStateEnum.MC_HC_CONNECTED, McStateEnum.MC_ACTIVE, McStateEnum.MC_READY ); | |
928 | return mJniMw.do_get_nof_hosts(); | |
929 | } | |
930 | } | |
931 | ||
932 | /** | |
933 | * Gets host data. SYNCHRONOUS | |
934 | * <p> | |
935 | * It must be called in McStateEnum.MC_HC_CONNECTED, McStateEnum.MC_ACTIVE or MC_READY state. | |
936 | * @param aIndex host index | |
937 | * @return host data | |
938 | * @throws JniExecutorWrongStateException | |
939 | */ | |
940 | public HostStruct getHostData( final int aIndex ) throws JniExecutorWrongStateException { | |
941 | Log.fi(); | |
942 | synchronized (getLock()) { | |
943 | checkState( McStateEnum.MC_HC_CONNECTED, McStateEnum.MC_ACTIVE, McStateEnum.MC_READY ); | |
944 | HostStruct copy = null; | |
945 | try { | |
946 | final HostStruct host = mJniMw.do_get_host_data( aIndex ); | |
947 | copy = (host != null ? new HostStruct(host) : null); | |
948 | } catch (Throwable t) { | |
949 | //do nothing copy is already null | |
950 | Log.f( t.toString() ); | |
951 | } finally { | |
952 | mJniMw.do_release_data(); | |
953 | } | |
954 | Log.fo( copy ); | |
955 | return copy; | |
956 | } | |
957 | } | |
958 | ||
959 | /** | |
960 | * Gets component data. SYNCHRONOUS | |
961 | * <p> | |
962 | * It must be called in MC_READY state | |
963 | * @param aCompRef component reference, this info comes from HostData.components | |
964 | * @return component data | |
965 | * @throws JniExecutorWrongStateException | |
966 | */ | |
967 | public ComponentStruct getComponentData( final int aCompRef ) throws JniExecutorWrongStateException { | |
968 | Log.fi(); | |
969 | synchronized (getLock()) { | |
970 | checkState( McStateEnum.MC_READY ); | |
971 | ComponentStruct copy = null; | |
972 | try { | |
973 | final ComponentStruct component = mJniMw.do_get_component_data( aCompRef ); | |
974 | copy = (component != null ? new ComponentStruct(component) : null); | |
975 | } catch (Throwable t) { | |
976 | //do nothing copy is already null | |
977 | Log.f( t.toString() ); | |
978 | } finally { | |
979 | mJniMw.do_release_data(); | |
980 | } | |
981 | Log.fo( copy ); | |
982 | return copy; | |
983 | } | |
984 | } | |
985 | ||
986 | /** | |
987 | * Gets MC state from MainController. SYNCHRONOUS | |
988 | * <p> | |
989 | * It can be called in any state, but do not call it when there is something to read on the pipe. | |
990 | * @return MC state | |
991 | */ | |
992 | public McStateEnum getState() { | |
993 | synchronized (getLock()) { | |
994 | return mJniMw.do_get_state(); | |
995 | } | |
996 | } | |
997 | ||
998 | /** | |
999 | * Checks if connected state. | |
1000 | * We are connected between init() and shutdownSession(). | |
1001 | * So before init() we expect false, in any other case we expect true | |
1002 | * @param aExpected expected connected state | |
1003 | * @throws JniExecutorWrongStateException if current connected state is not the expected | |
1004 | */ | |
1005 | private void checkConnection( final boolean aExpected ) throws JniExecutorWrongStateException { | |
1006 | final boolean connected = isConnected(); | |
1007 | if ( connected != aExpected ) { | |
1008 | throw new JniExecutorWrongStateException( connected ? EXCEPTION_TEXT_WRONG_STATE_CONNECTED : EXCEPTION_TEXT_WRONG_STATE_NOT_CONNECTED ); | |
1009 | } | |
1010 | } | |
1011 | ||
1012 | /** | |
1013 | * Checks if we are in any of the expected states | |
1014 | * @param aExpectedStates array of the expected states | |
1015 | * @throws JniExecutorWrongStateException if current state is not one of the expected | |
1016 | */ | |
1017 | private void checkState( final McStateEnum... aExpectedStates ) throws JniExecutorWrongStateException { | |
1018 | checkConnection(true); | |
1019 | final McStateEnum currentState = getState(); | |
1020 | boolean found = false; | |
1021 | for ( final McStateEnum state : aExpectedStates) { | |
1022 | if ( currentState == state ) { | |
1023 | found = true; | |
1024 | break; | |
1025 | } | |
1026 | } | |
1027 | if ( !found ) { | |
1028 | handleWrongState(currentState, aExpectedStates); | |
1029 | } | |
1030 | } | |
1031 | ||
1032 | /** | |
1033 | * Builds error message for incorrect state | |
1034 | * @param aActualState the actual state | |
1035 | * @param aExpectedStates array of the expected states | |
1036 | * @return error message | |
1037 | */ | |
1038 | private static String buildWrongStateMessage( final McStateEnum aActualState, final McStateEnum[] aExpectedStates ) { | |
1039 | StringBuilder sb = new StringBuilder(); | |
1040 | sb.append( EXCEPTION_TEXT_WRONG_STATE_PART_1 ); | |
1041 | sb.append(aActualState); | |
1042 | sb.append( EXCEPTION_TEXT_WRONG_STATE_PART_2 ); | |
1043 | for (int i = 0; i < aExpectedStates.length; i++ ) { | |
1044 | if ( i > 0 ) { | |
1045 | sb.append(", "); | |
1046 | } | |
1047 | sb.append( aExpectedStates[ i ] ); | |
1048 | } | |
1049 | ||
1050 | return sb.toString(); | |
1051 | } | |
1052 | ||
1053 | /** | |
1054 | * Error handling for incorrect state | |
1055 | * @param aActualState the actual state | |
1056 | * @param aExpectedStates array of the expected states | |
1057 | * @throws JniExecutorWrongStateException | |
1058 | */ | |
1059 | private static void handleWrongState( final McStateEnum aActualState, final McStateEnum[] aExpectedStates ) throws JniExecutorWrongStateException { | |
1060 | final String msg = buildWrongStateMessage( aActualState, aExpectedStates ); | |
1061 | throw new JniExecutorWrongStateException( msg ); | |
1062 | } | |
1063 | ||
1064 | /** | |
1065 | * Waits until shutdown and terminate. SYNCHRONOUS | |
1066 | * <p> | |
1067 | * Recommended use: | |
1068 | * <ol> | |
1069 | * <li> Call it right after {@link #startHostControllers()}, which is asynchronous, and it will wait until the whole | |
1070 | * process will be finished automatically or manually, successfully or unsuccessfully. | |
1071 | * <li> Call it right after {@link #shutdownSession()} in any state to make sure, that session is shut down. | |
1072 | * </ol> | |
1073 | * <p> | |
1074 | * Finished means that shutdown session is requested and the state gets back to MC_INACTIVE. | |
1075 | * <br> | |
1076 | * If it is called when the executor is disconnected, it will return immediately. | |
1077 | * <br> | |
1078 | * If it is called in MC_INACTIVE state, the executor is terminated and returns immediately. | |
1079 | */ | |
1080 | public void waitForCompletion() { | |
1081 | Log.fi(); | |
1082 | //NOTE: check must be changed to ( getState() == McStateEnum.MC_INACTIVE ) if | |
1083 | // shutdownSession() does NOT call terminate() when it gets to MC_INACTIVE state | |
1084 | if ( !isConnected() ) { | |
1085 | // The request is already finished, so request was called in disconnected/terminated state | |
1086 | Log.f("The request is already finished, so request was called in disconnected/terminated state"); | |
1087 | } | |
1088 | else { | |
1089 | try { | |
1090 | synchronized (mLockCompletion) { | |
1091 | mLockCompletion.wait(); | |
1092 | } | |
1093 | } catch (InterruptedException e) { | |
1094 | Log.f(e.toString()); | |
1095 | } | |
1096 | } | |
1097 | Log.fo(); | |
1098 | } | |
1099 | ||
1100 | /** | |
1101 | * Reset member variables to initial state | |
1102 | */ | |
1103 | private void reset() { | |
1104 | mObserver = null; | |
1105 | mMcPort = -1; | |
1106 | mMcHost = DEFAULT_LOCAL_HOST_ADDRESS; | |
1107 | mHostControllers = null; | |
1108 | mCfgFilePreprocessed = false; | |
1109 | mShutdownRequested = false; | |
1110 | synchronized (mLockCompletion) { | |
1111 | mLockCompletion.notifyAll(); | |
1112 | } | |
1113 | } | |
1114 | ||
1115 | /** | |
1116 | * If notification from MC is special (it must be handled differently) | |
1117 | * @param aMsg notification from MC, which is a possible special notification. | |
1118 | * @return true, if notification is handled, no need to send general notification | |
1119 | * false otherwise | |
1120 | */ | |
1121 | private boolean processSpecialNotifications( final String aMsg ) { | |
1122 | return processVerdict(aMsg) | |
1123 | //|| processDynamicTestcaseError(aMsg) | |
1124 | || processVerdictStats(aMsg) | |
1125 | //|| processErrorsOutsideOfTestcases(aMsg) | |
1126 | //|| processOverallVerdict(aMsg) | |
1127 | || false; | |
1128 | } | |
1129 | ||
1130 | /** | |
1131 | * If notification from MC matches to the verdict pattern, | |
1132 | * verdict notification is sent instead of general notification. | |
1133 | * @param aMsg notification from MC, which is a possible verdict. | |
1134 | * @return true, if notification is handled (verdict notification is sent), | |
1135 | * no need to send general notification | |
1136 | * false otherwise | |
1137 | */ | |
1138 | private boolean processVerdict( final String aMsg ) { | |
1139 | try { | |
1140 | Matcher m = PATTERN_VERDICT.matcher(aMsg); | |
1141 | if ( m.matches() ) { | |
1142 | final String testcase = m.group(PATTERN_VERDICT_GROUP_INDEX_TESTCASE); | |
1143 | final String verdictTypeName = m.group(PATTERN_VERDICT_GROUP_INDEX_VERDICTTYPENAME); | |
1144 | VerdictTypeEnum verdictType = toVerdictType(verdictTypeName); | |
1145 | if ( verdictType == null ) { | |
1146 | return false; | |
1147 | } | |
1148 | observerVerdict(testcase, verdictType); | |
1149 | return true; | |
1150 | } | |
1151 | } catch (Exception e) { | |
1152 | Log.i(e.toString()); | |
1153 | } | |
1154 | return false; | |
1155 | } | |
1156 | ||
1157 | /** | |
1158 | * If notification from MC matches to the dynamic testcase error, | |
1159 | * error notification is sent instead of general notification. | |
1160 | * @param aMsg notification from MC, which is a possible dynamic testcase error. | |
1161 | * @return true, if notification is handled (error notification is sent), | |
1162 | * no need to send general notification | |
1163 | * false otherwise | |
1164 | */ | |
1165 | private boolean processDynamicTestcaseError( final String aMsg ) { | |
1166 | try { | |
1167 | Matcher m = PATTERN_DYNAMIC_TESTCASE_ERROR.matcher(aMsg); | |
1168 | if ( m.matches() ) { | |
1169 | final String errorMsg = m.group(PATTERN_DYNAMIC_TESTCASE_ERROR_GROUP_INDEX_ERROR_TEXT); | |
1170 | //TODO: send error notification | |
1171 | return true; | |
1172 | } | |
1173 | } catch (Exception e) { | |
1174 | Log.i(e.toString()); | |
1175 | } | |
1176 | return false; | |
1177 | } | |
1178 | ||
1179 | /** | |
1180 | * If notification from MC matches to the verdict statistics pattern, | |
1181 | * verdict statistics notification is sent instead of general notification. | |
1182 | * @param aMsg notification from MC, which is a possible verdict statistics. | |
1183 | * @return true, if notification is handled (verdict statistics notification is sent), | |
1184 | * no need to send general notification | |
1185 | * false otherwise | |
1186 | */ | |
1187 | private boolean processVerdictStats( final String aMsg ) { | |
1188 | try { | |
1189 | Matcher m = PATTERN_VERDICT_STATS.matcher(aMsg); | |
1190 | if ( m.matches() ) { | |
1191 | final int none = Integer.parseInt(m.group(PATTERN_VERDICT_STATS_GROUP_INDEX_NONE)); | |
1192 | final int pass = Integer.parseInt(m.group(PATTERN_VERDICT_STATS_GROUP_INDEX_PASS)); | |
1193 | final int inconc = Integer.parseInt(m.group(PATTERN_VERDICT_STATS_GROUP_INDEX_INCONC)); | |
1194 | final int fail = Integer.parseInt(m.group(PATTERN_VERDICT_STATS_GROUP_INDEX_FAIL)); | |
1195 | final int error = Integer.parseInt(m.group(PATTERN_VERDICT_STATS_GROUP_INDEX_ERROR)); | |
1196 | final Map<VerdictTypeEnum, Integer> verdictStats = new HashMap<>(); | |
1197 | verdictStats.put(VerdictTypeEnum.NONE, none); | |
1198 | verdictStats.put(VerdictTypeEnum.PASS, pass); | |
1199 | verdictStats.put(VerdictTypeEnum.INCONC, inconc); | |
1200 | verdictStats.put(VerdictTypeEnum.FAIL, fail); | |
1201 | verdictStats.put(VerdictTypeEnum.ERROR, error); | |
1202 | observerVerdictStats(verdictStats); | |
1203 | return true; | |
1204 | } | |
1205 | } catch (Exception e) { | |
1206 | Log.i(e.toString()); | |
1207 | } | |
1208 | return false; | |
1209 | } | |
1210 | ||
1211 | /** | |
1212 | * If notification from MC matches to the PATTERN_ERRORS_OUTSIDE_OF_TESTCASES pattern, | |
1213 | * special notification is sent instead of general notification. | |
1214 | * @param aMsg notification from MC, which is a possible verdict statistics. | |
1215 | * @return true, if notification is handled (special notification is sent), | |
1216 | * no need to send general notification | |
1217 | * false otherwise | |
1218 | */ | |
1219 | private boolean processErrorsOutsideOfTestcases( final String aMsg ) { | |
1220 | try { | |
1221 | Matcher m = PATTERN_ERRORS_OUTSIDE_OF_TESTCASES.matcher(aMsg); | |
1222 | if ( m.matches() ) { | |
1223 | final int errors = Integer.parseInt(m.group(PATTERN_ERRORS_OUTSIDE_OF_TESTCASES_GROUP_INDEX_ERRORS)); | |
1224 | //TODO | |
1225 | return true; | |
1226 | } | |
1227 | } catch (Exception e) { | |
1228 | Log.i(e.toString()); | |
1229 | } | |
1230 | return false; | |
1231 | } | |
1232 | ||
1233 | /** | |
1234 | * If notification from MC matches to the overall verdict pattern, | |
1235 | * overall verdict notification is sent instead of general notification. | |
1236 | * @param aMsg notification from MC, which is a possible verdict statistics. | |
1237 | * @return true, if notification is handled (verdict statistics notification is sent), | |
1238 | * no need to send general notification | |
1239 | * false otherwise | |
1240 | */ | |
1241 | private boolean processOverallVerdict( final String aMsg ) { | |
1242 | try { | |
1243 | Matcher m = PATTERN_OVERALL_VERDICT.matcher(aMsg); | |
1244 | if ( m.matches() ) { | |
1245 | final int executed = Integer.parseInt(m.group(PATTERN_OVERALL_VERDICT_GROUP_INDEX_NUMBER_OF_TESTCASE)); | |
1246 | final String verdictTypeName = m.group(PATTERN_OVERALL_VERDICT_GROUP_INDEX_VERDICTTYPENAME); | |
1247 | final VerdictTypeEnum verdictType = toVerdictType(verdictTypeName); | |
1248 | //TODO | |
1249 | return true; | |
1250 | } | |
1251 | } catch (Exception e) { | |
1252 | Log.i(e.toString()); | |
1253 | } | |
1254 | return false; | |
1255 | } | |
1256 | ||
1257 | /** | |
1258 | * Converts verdict type name to verdict type | |
1259 | * @param aVerdictTypeName verdict type name candidate | |
1260 | * @return converted verdict type or null if name is invalid | |
1261 | */ | |
1262 | private static VerdictTypeEnum toVerdictType( final String aVerdictTypeName ) { | |
1263 | VerdictTypeEnum verdictType = null; | |
1264 | switch ( aVerdictTypeName ) { | |
1265 | case "none": | |
1266 | verdictType = VerdictTypeEnum.NONE; | |
1267 | break; | |
1268 | case "pass": | |
1269 | verdictType = VerdictTypeEnum.PASS; | |
1270 | break; | |
1271 | case "inconc": | |
1272 | verdictType = VerdictTypeEnum.INCONC; | |
1273 | break; | |
1274 | case "fail": | |
1275 | verdictType = VerdictTypeEnum.FAIL; | |
1276 | break; | |
1277 | case "error": | |
1278 | verdictType = VerdictTypeEnum.ERROR; | |
1279 | break; | |
1280 | default: | |
1281 | Log.i("Unknown verdict type: " + aVerdictTypeName); | |
1282 | } | |
1283 | return verdictType; | |
1284 | } | |
1285 | ||
1286 | // observer callbacks. all the callbacks go through these methods to make sure, that | |
1287 | // - observer is not called if it's null | |
1288 | // - exception/error, which is thrown by the observer is ignored, | |
1289 | // this class provides a service, it has no idea how to handle client's exception/error | |
1290 | ||
1291 | /** | |
1292 | * Notification about status change. It also means, that the asynchronous request is finished successfully, if aNewState is the final state. | |
1293 | * @param aNewState the new MC state | |
1294 | */ | |
1295 | private void observerStatusChanged( final McStateEnum aNewState ) { | |
1296 | Log.fi(aNewState); | |
1297 | if ( mObserver != null ) { | |
1298 | try { | |
1299 | mObserver.statusChanged(aNewState); | |
1300 | } catch (Throwable e) { | |
1301 | Log.i(e.toString()); | |
1302 | // ignore client's exception/error, we have no idea how to handle | |
1303 | } | |
1304 | } | |
1305 | Log.fo(); | |
1306 | } | |
1307 | ||
1308 | /** | |
1309 | * Error from MC. It also means, that the asynchronous request is finished unsuccessfully. | |
1310 | * @param aSeverity error severity | |
1311 | * @param aMsg error message | |
1312 | */ | |
1313 | private void observerError( final int aSeverity, final String aMsg ) { | |
1314 | Log.fi(aSeverity, aMsg); | |
1315 | if ( mObserver != null ) { | |
1316 | try { | |
1317 | mObserver.error(aSeverity, aMsg); | |
1318 | } catch (Throwable e) { | |
1319 | Log.i(e.toString()); | |
1320 | // ignore client's exception/error, we have no idea how to handle | |
1321 | } | |
1322 | } | |
1323 | Log.fo(); | |
1324 | } | |
1325 | ||
1326 | /** | |
1327 | * Notification callback, information comes from MC | |
1328 | * @param aTime timestamp | |
1329 | * @param aSource source, the machine identifier of MC | |
1330 | * @param aSeverity message severity | |
1331 | * @param aMsg message text | |
1332 | */ | |
1333 | private void observerNotify(final Timeval aTime, final String aSource, final int aSeverity, final String aMsg) { | |
1334 | Log.fi(aTime, aSource, aSeverity, aMsg); | |
1335 | if ( mObserver != null ) { | |
1336 | try { | |
1337 | mObserver.notify(aTime, aSource, aSeverity, aMsg); | |
1338 | } catch (Throwable e) { | |
1339 | Log.i(e.toString()); | |
1340 | // ignore client's exception/error, we have no idea how to handle | |
1341 | } | |
1342 | } | |
1343 | Log.fo(); | |
1344 | } | |
1345 | ||
1346 | /** | |
1347 | * Verdict notification, that comes after execution of a testcase. | |
1348 | * If a test control is executed, this notification is sent multiple times after each testcase. | |
1349 | * @param aTestcase name of the testcase | |
1350 | * @param aVerdictType verdict type | |
1351 | */ | |
1352 | private void observerVerdict( final String aTestcase, final VerdictTypeEnum aVerdictType ) { | |
1353 | Log.fi(aTestcase, aVerdictType); | |
1354 | if ( mObserver != null ) { | |
1355 | try { | |
1356 | mObserver.verdict(aTestcase, aVerdictType); | |
1357 | } catch (Throwable e) { | |
1358 | Log.i(e.toString()); | |
1359 | // ignore client's exception/error, we have no idea how to handle | |
1360 | } | |
1361 | } | |
1362 | Log.fo(); | |
1363 | } | |
1364 | ||
1365 | /** | |
1366 | * Verdict statistics notification, that comes after executing all the testcases after exit MTC. | |
1367 | * This notification is sent only once for a MTC session. | |
1368 | * @param aVerdictStats verdict statistics | |
1369 | */ | |
1370 | private void observerVerdictStats( final Map<VerdictTypeEnum, Integer> aVerdictStats ) { | |
1371 | Log.fi(aVerdictStats); | |
1372 | if ( mObserver != null ) { | |
1373 | try { | |
1374 | mObserver.verdictStats(aVerdictStats); | |
1375 | } catch (Throwable e) { | |
1376 | Log.i(e.toString()); | |
1377 | // ignore client's exception/error, we have no idea how to handle | |
1378 | } | |
1379 | } | |
1380 | Log.fo(); | |
1381 | } | |
1382 | } |