Last sync 2016.04.01
[deliverable/titan.core.git] / titan_executor_api / TITAN_Executor_API / src / org / eclipse / titan / executorapi / JniExecutor.java
CommitLineData
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 ******************************************************************************/
13package org.eclipse.titan.executorapi;
14
15import java.io.BufferedReader;
16import java.io.File;
17import java.io.IOException;
18import java.io.InputStreamReader;
19import java.util.ArrayList;
20import java.util.List;
21import java.util.HashMap;
22import java.util.Map;
23import java.util.regex.Matcher;
24import java.util.regex.Pattern;
25
26import org.eclipse.titan.executor.jni.ComponentStruct;
27import org.eclipse.titan.executor.jni.HostStruct;
28import org.eclipse.titan.executor.jni.IJNICallback;
29import org.eclipse.titan.executor.jni.JNIMiddleWare;
30import org.eclipse.titan.executor.jni.McStateEnum;
31import org.eclipse.titan.executor.jni.Timeval;
32import org.eclipse.titan.executor.jni.VerdictTypeEnum;
33import org.eclipse.titan.executorapi.exception.JniExecutorException;
34import org.eclipse.titan.executorapi.exception.JniExecutorIllegalArgumentException;
35import org.eclipse.titan.executorapi.exception.JniExecutorJniLoadException;
36import org.eclipse.titan.executorapi.exception.JniExecutorStartSessionException;
37import org.eclipse.titan.executorapi.exception.JniExecutorWrongStateException;
38import 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 */
90public 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}
This page took 0.081239 seconds and 5 git commands to generate.