001/**
002 * ////////////////////////////////////////////////////////////////////////////////
003 * //
004 * //  D SOFTWARE INCORPORATED
005 * //  Copyright 2007-2014 D Software Incorporated
006 * //  All Rights Reserved.
007 * //
008 * //  NOTICE: D Software permits you to use, modify, and distribute this file
009 * //  in accordance with the terms of the license agreement accompanying it.
010 * //
011 * //  Unless required by applicable law or agreed to in writing, software
012 * //  distributed under the License is distributed on an "AS IS" BASIS,
013 * //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * //
015 * ////////////////////////////////////////////////////////////////////////////////
016 */
017package com.thed.launcher;
018
019import com.thed.model.Agent;
020import com.thed.model.AutomationScriptJobDTO;
021import com.thed.model.TestcaseBatchExecution;
022import com.thed.util.ScriptUtil;
023import com.thed.util.ZeeConstants;
024import com.thed.zblast.util.RestUtil;
025import org.apache.commons.configuration.PropertiesConfiguration;
026import org.apache.commons.httpclient.HttpClient;
027
028import java.io.*;
029import java.util.logging.Level;
030import java.util.logging.Logger;
031
032/**
033 * This class executes batch script in separate thread.
034 * It uses an {@link IZBotScriptLauncher} instance for starting/stopping the batch process and
035 * finally sends the overall batch status along with TestcaseBatchExecution details to server.
036 *
037 * @author ZephyrDev
038 *
039 */
040public class ScriptProcessor implements Runnable {
041
042        /*----------------------------------------------------------
043         * ATTRIBUTES
044         *----------------------------------------------------------*/
045
046        private Logger logger = Logger.getLogger(ScriptProcessor.class.getName());
047
048        private Agent agent ;
049        private TestcaseBatchExecution testcaseBatchExecution ;
050        private String url ;
051        private PropertiesConfiguration properties;
052
053        /* indicates overall execution is completed. */
054        private boolean completed = false ;
055
056        private HttpClient client ;
057
058        private String status = "";
059
060        /*----------------------------------------------------------
061         * END OF ATTRIBUTES
062         *----------------------------------------------------------*/
063
064
065        /*----------------------------------------------------------
066         * CONSTRUCTOR
067         *----------------------------------------------------------*/
068
069        public ScriptProcessor(Agent agent, TestcaseBatchExecution testcaseBatchExecution, String url, PropertiesConfiguration properties) {
070                this.agent = agent ;
071                this.testcaseBatchExecution = testcaseBatchExecution;
072                this.url = url ;
073                this.properties = properties;
074                this.client = new HttpClient();
075        }
076
077        /*----------------------------------------------------------
078         * END OF CONSTRUCTOR
079         *----------------------------------------------------------*/
080
081        /**
082         * This method loops through the entire batch of test cases, executes them and communicates the
083         * real-time status of each testcase back to the server.
084         * <br/>
085         * The order of LifeCycle methods is given below :-<br/>
086         * <br/>
087         * <{@link IZBotScriptLauncher#batchStart()}> - Invoked once before start of batch.<br/>
088         * &nbsp;&nbsp;<{@link IZBotScriptLauncher#testcaseExecutionStart()}> - invoked once before start of each testcase.<br/>
089         * &nbsp;&nbsp;&nbsp;&nbsp;<{@link IZBotScriptLauncher#testcaseExecutionRun()}> - This method executes testcase.<br/>
090         * <br/>
091         * &nbsp;&nbsp;&nbsp;&nbsp;In sequential mode, ZBot will wait for each script to finish, gather the process status and update test status in Zephyr Server via a webservice.<br/>
092         * &nbsp;&nbsp;&nbsp;&nbsp;Exit Status 0 is translated as pass. Any Non Zero values is treated as fail.<br/>
093         * &nbsp;&nbsp;&nbsp;&nbsp;In parallel mode, it will launch them all without waiting for it to finish. It also, doesnt update the test execution status.<br/>
094         * <br/>
095         * &nbsp;&nbsp;&nbsp;&nbsp;<{@link IZBotScriptLauncher#testcaseExecutionResult()}> - process testcase result.<br/>
096         * &nbsp;&nbsp;<{@link IZBotScriptLauncher#testcaseExecutionEnd()}> - invoked once after each testcase is done executing.<br/>
097         * <{@link IZBotScriptLauncher#batchEnd()}> - invoked once after end of batch.<br/>
098         *
099         */
100        public void run() {
101
102                logger.info("START: execute batch script: " + testcaseBatchExecution);
103
104                try {
105                        status = "Batch execution starting...";
106                        /* update realtime status */
107                        updateAgentRealTimeStatus();
108
109                        /* execute all testcases */
110                        if (testcaseBatchExecution.getTestcaseExecutionList() != null) {
111
112                //handling new script jobs
113                                boolean status = executeScript(testcaseBatchExecution);
114                                updateAgentRealTimeStatus();
115                AutomationScriptJobDTO automationScriptJobDTO = RestUtil.getScriptJobDetail(testcaseBatchExecution.getJobId());
116                                File file = new File(automationScriptJobDTO.getResultPath());
117                                if(!file.exists()){
118                                        throw new IOException("""
119                                                        Cannot run program %s: No such file or directory""".formatted(file));
120                                }
121                if(automationScriptJobDTO.getIsParseResultFile()!=null && !automationScriptJobDTO.getIsParseResultFile()){
122                        if(testcaseBatchExecution.getUpdateTestcaseStatusFromScript()) {
123                                                ScriptUtil.processTestCaseExecution(testcaseBatchExecution, status ? "1" : "2", agent);
124                                        }
125                }
126                else{
127                    logger.log(Level.INFO, "Script job Parsing started");
128                    ScriptUtil.updateParseTestcaseExecutionResult(testcaseBatchExecution, agent);
129                    logger.log(Level.INFO, "Script job Parsing Ends");
130                }
131                        }
132
133                        /* update realtime status */
134                        updateAgentRealTimeStatus();
135                        logger.info("END: execute batch script: " + testcaseBatchExecution);
136
137                } catch (Throwable t) {
138                        /* TODO: signal error */
139                        logger.log(Level.WARNING, "FAILURE: batch script execution " + t.getMessage());
140                    t.printStackTrace();
141                }
142
143                try {
144                        ScriptUtil.sendResponse(client, url, agent.getToken(), ZeeConstants.RESULT_SUCCESS, testcaseBatchExecution);
145                } catch (Exception e) {
146                        logger.log(Level.WARNING, "exception in sending response to server. " + e.getMessage());
147                    e.printStackTrace();
148                }
149                completed = true ;
150        }
151
152        /**
153         *  This method is used by zephyrAgent class to fetch realtime status.
154         */
155        public String getRealtimeStatus() {
156                return status;
157        }
158        /**
159         * Returns the boolean flag to check the overall execution is completed.
160         */
161        public boolean isCompleted() {
162                return completed;
163        }
164
165        private void updateAgentRealTimeStatus() {
166                System.out.println(status);
167                logger.log(Level.INFO, this::getRealtimeStatus);
168                ScriptUtil.updateAgentRealTimeStatus(client, url, agent.getToken(), getRealtimeStatus());
169        }
170
171        private boolean executeScript(TestcaseBatchExecution testcaseBatchExecution) throws IOException {
172                try {
173                        status = "STATUS: Script Executing";
174                        updateAgentRealTimeStatus();
175                        logger.info("""
176                                        about to run script with path: %s %s""".formatted(testcaseBatchExecution.getScriptPath(), testcaseBatchExecution.getParameter()));
177                        /* logic to execute testcases sequentially. i.e. next testcase waits till first testcase is finished */
178                        String[] cmds = {testcaseBatchExecution.getScriptPath(), testcaseBatchExecution.getParameter()};
179                        Process process = Runtime.getRuntime().exec(cmds);
180                        status = "STATUS: Waiting for execution completion";
181                        updateAgentRealTimeStatus();
182                        new ProcessStreamReader(process.getErrorStream()).start();
183                        new ProcessStreamReader(process.getInputStream()).start();
184                        int processStatus = process.waitFor() ;
185                        status = "STATUS: Script Successfully Executed";
186                        /* real value: by convention 0 indicates successful completion of script */
187                        if (processStatus == 0) {
188                                return true; /* 1 = pass */
189                        } else {
190                                return false; /* 2 = fail */
191                        }
192                } catch (Exception e) {
193                        status = "STATUS: Execution Error " + e.getMessage();
194                        logger.log(Level.WARNING, "exception in script execution. " + e.getMessage());
195                        e.printStackTrace();
196                        return false;
197                }
198        }
199
200        static class ProcessStreamReader extends Thread {
201                BufferedReader br;
202                private final Logger logger = Logger.getLogger(this.getClass().getName());
203                public ProcessStreamReader(InputStream in){
204                        super();
205                        br = new BufferedReader(new InputStreamReader(in));
206                }
207
208                @Override
209                public void run() {
210                        String line;
211                        try {
212                                while ((line = br.readLine()) != null) {
213                                        logger.info(Thread.currentThread().getName() + " | Read output..." + line);
214                                }
215                        } catch (IOException e) {
216                                logger.log(Level.SEVERE, "Error in reading process steams \n", e);
217                        }finally{
218                                if(br != null){
219                                        try { br.close(); } catch (IOException e) {}
220                                }
221                        }
222                        logger.info(Thread.currentThread().getName() + " | Done reading process log input stream");
223                }
224        }
225
226}