com.artos.framework.infra.Runner Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (C) 2018 Arpit Shah
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
package com.artos.framework.infra;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.logging.log4j.core.LoggerContext;
import org.xml.sax.SAXException;
import com.artos.exception.InvalidDataException;
import com.artos.framework.FWStaticStore;
import com.artos.framework.xml.TestScriptParser;
import com.artos.framework.xml.TestSuite;
import com.artos.interfaces.PrePostRunnable;
import com.artos.interfaces.TestExecutable;
public class Runner {
Class extends PrePostRunnable> cls;
// Default thread count should be 1
int threadCount = 1;
/**
* @param cls Class which contains main() method
* @see TestContext
*/
public Runner(Class extends PrePostRunnable> cls) {
this.cls = cls;
}
/**
* Responsible for executing test cases.
*
*
* - Test script is provided in command line argument then test script will be used to generate test list and execute from that
* - Test script is not provided then test list will be prepared using reflection.
*
*
* @param args command line arguments
* @param loopCount test loop count
*
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
* @throws InvalidDataException if user provides invalid data
* @throws IOException if io operation error occurs
* @throws SAXException If any parse errors occur.
* @throws ParserConfigurationException if a DocumentBuildercannot be created which satisfies the configuration requested.
*/
public void run(String[] args, int loopCount)
throws InterruptedException, ExecutionException, ParserConfigurationException, SAXException, IOException, InvalidDataException {
// pass empty array list so reflection will be used
run(args, new ArrayList<>(), loopCount, null);
}
/**
* Responsible for executing test cases.
*
*
* - Test script is provided in command line argument then test script will be used to generate test list and execute from that
* - Test script is not provided then test list will be prepared using reflection. supplied group list will be applied.
*
*
* @param args command line arguments
* @param loopCount test loop count
* @param groupList group list required to filter test cases
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
* @throws InvalidDataException if user provides invalid data
* @throws IOException if io operation error occurs
* @throws SAXException If any parse errors occur.
* @throws ParserConfigurationException if a DocumentBuildercannot be created which satisfies the configuration requested.
*/
public void run(String[] args, int loopCount, List groupList)
throws InterruptedException, ExecutionException, ParserConfigurationException, SAXException, IOException, InvalidDataException {
// pass empty array list so reflection will be used
run(args, new ArrayList<>(), loopCount, groupList);
}
/**
* Responsible for executing test cases.
*
*
* - Test script is provided in command line argument then test script will be used to generate test list and execute from that
* - In absence of test script, ArrayList() provided by user will be used to generate test list.
* - In absence of test script and ArrayList() is null or empty, reflection will be used to generate test list.
*
*
* @param args command line arguments
* @param testList testList provided by user
* @param loopCount test loop count
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
* @throws InvalidDataException if user provides invalid data
* @throws IOException if io operation error occurs
* @throws SAXException If any parse errors occur.
* @throws ParserConfigurationException if a DocumentBuildercannot be created which satisfies the configuration requested.
*/
public void run(String[] args, List testList, int loopCount)
throws InterruptedException, ExecutionException, ParserConfigurationException, SAXException, IOException, InvalidDataException {
// pass empty array list so reflection will be used
run(args, testList, loopCount, null);
}
/**
* Responsible for executing test cases.
*
*
* - Test script is provided in command line argument then test script will be used to generate test list and execute from that
* - In absence of test script, ArrayList() provided by user will be used to generate test list.
* - In absence of test script and ArrayList() is null or empty, reflection will be used to generate test list.
*
*
* @param args command line arguments
* @param testList testList provided by user
* @param loopCount test loop count
* @param groupList group list required to filter test cases
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
* @throws InvalidDataException if user provides invalid data
* @throws IOException if io operation error occurs
* @throws SAXException If any parse errors occur.
* @throws ParserConfigurationException if a DocumentBuildercannot be created which satisfies the configuration requested.
*/
@SuppressWarnings("unchecked")
public void run(String[] args, List testList, int loopCount, List groupList)
throws InterruptedException, ExecutionException, ParserConfigurationException, SAXException, IOException, InvalidDataException {
if (null == groupList || groupList.isEmpty()) {
// Add a default group if user does not pass a group parameter
groupList = new ArrayList<>();
groupList.add("*");
}
// if loop count is set to 0 or negative then set to at least 1
if (loopCount < 1) {
loopCount = 1;
}
// Process command line arguments
CliProcessor.proessCommandLine(args);
generateRequiredFiles();
// process test suites
List testSuiteList = createTestSuiteList();
if (null != testSuiteList && !testSuiteList.isEmpty()) {
// Thread count should be same as number of test suites
threadCount = testSuiteList.size();
}
// generate logger context
LoggerContext loggerContext = createGlobalLoggerContext(testSuiteList);
// Start Executor service
{
ExecutorService service = Executors.newFixedThreadPool(threadCount + 20);
List> futures = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(threadCount);
// create thread per test suite
for (int i = 0; i < threadCount; i++) {
// Create new context for each thread
TestContext context = new TestContext();
// store main() class object
context.setPrePostRunnableObj(cls);
// store thread latch
context.setThreadLatch(latch);
// Get logger for particular thread and set to context object
LogWrapper logWrapper = new LogWrapper(loggerContext, i);
// store logger
context.setOrganisedLogger(logWrapper);
if (null != testSuiteList && !testSuiteList.isEmpty()) {
// store test suite
context.setTestSuite(testSuiteList.get(i));
// store loopCount from test suite
context.setTotalLoopCount(testSuiteList.get(i).getLoopCount());
} else {
// if testSuite is not provided then take loopCount from
// main() method
context.setTotalLoopCount(loopCount);
}
// Launch a thread with runnable
Future> f = service.submit(new SuiteTask(context, testList, groupList));
futures.add((Future) f);
}
// wait for all tasks to complete before continuing
for (Future f : futures) {
f.get();
}
// shut down the executor service so that this thread can exit
service.shutdownNow();
// Block until all threads complete execution
latch.await();
// Terminate JVM
System.exit(0);
}
}
private void generateRequiredFiles() throws IOException {
if (FWStaticStore.frameworkConfig.isGenerateEclipseTemplate()) {
// only create template file if not present already
File targetFile = new File(FWStaticStore.TEMPLATE_BASE_DIR + File.separator + "template.xml");
if (!targetFile.exists() || !targetFile.isFile()) {
// create dir if not present
File file = new File(FWStaticStore.TEMPLATE_BASE_DIR);
if (!file.exists() || !file.isDirectory()) {
file.mkdirs();
}
InputStream ins = getClass().getResourceAsStream("/com/artos/template/template.xml");
byte[] buffer = new byte[ins.available()];
ins.read(buffer);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);
outStream.flush();
outStream.close();
ins.close();
}
}
if (FWStaticStore.frameworkConfig.isEnableExtentReport()) {
// only create Extent config file if not present already
File targetFile = new File(FWStaticStore.CONFIG_BASE_DIR + File.separator + "extent_configuration.xml");
// TODO This code is temporary and can be removed after couple of releases
{
final File oldfXmlFile = new File(FWStaticStore.CONFIG_BASE_DIR + "Extent_Config.xml");
if (oldfXmlFile.exists() && oldfXmlFile.isFile()) {
try {
File newFile = new File(targetFile.getParent(), targetFile.getName());
Files.move(oldfXmlFile.toPath(), newFile.toPath());
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
if (!targetFile.exists() || !targetFile.isFile()) {
// create dir if not present
File file = new File(FWStaticStore.CONFIG_BASE_DIR);
if (!file.exists() || !file.isDirectory()) {
file.mkdirs();
}
InputStream ins = getClass().getResourceAsStream("/com/artos/template/extent_configuration.xml");
byte[] buffer = new byte[ins.available()];
ins.read(buffer);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);
outStream.flush();
outStream.close();
ins.close();
}
}
if (FWStaticStore.frameworkConfig.isEnableEmailClient()) {
String emailAuthSettingsFilePath = FWStaticStore.frameworkConfig.getEmailAuthSettingsFilePath();
// only create auth settings file if not present already
File targetFile = new File(emailAuthSettingsFilePath);
// if provided file is not named correctly then fail here
if (!targetFile.getName().equals("user_auth_settings.xml")) {
System.err.println("Invalid File Name : " + targetFile.getAbsolutePath());
}
if (!targetFile.exists() || !targetFile.isFile()) {
// create dir if not present
File file = new File(FWStaticStore.CONFIG_BASE_DIR);
if (!file.exists() || !file.isDirectory()) {
file.mkdirs();
}
InputStream ins = getClass().getResourceAsStream("/com/artos/template/user_auth_settings.xml");
byte[] buffer = new byte[ins.available()];
ins.read(buffer);
OutputStream outStream = new FileOutputStream(targetFile);
outStream.write(buffer);
outStream.flush();
outStream.close();
ins.close();
}
}
}
/**
* If test script is provided via command line then parse test script and generate list of test suites
*
* @return list of test suites
*
* @throws InvalidDataException if user provides invalid data
* @throws IOException if io operation error occurs
* @throws SAXException If any parse errors occur.
* @throws ParserConfigurationException if a DocumentBuildercannot be created which satisfies the configuration requested.
*/
private List createTestSuiteList() throws ParserConfigurationException, SAXException, IOException, InvalidDataException {
if (null == CliProcessor.testScriptFile) {
return null;
}
TestScriptParser xml = new TestScriptParser();
List testSuiteList = xml.readTestScript(CliProcessor.testScriptFile);
return testSuiteList;
}
/**
* Creates appenders for number of suites provided incase parallel execution is required, if test script is not provided then appenders are
* created for one thread
*
* @param testSuiteList list of testSuites
* @return LoggetContext
*/
private LoggerContext createGlobalLoggerContext(List testSuiteList) {
// Create loggerContext with all possible thread appenders
/**
* Package name can not be used for log sub-directory name in case where test cases are launched from project root directory, thus log will
* come out in logging base directory.
*/
String logSubDir = "";
if (null != cls.getPackage()) {
logSubDir = cls.getPackage().getName();
}
// Get Framework configuration set by user
String logDirPath = FWStaticStore.frameworkConfig.getLogRootDir();
boolean enableLogDecoration = FWStaticStore.frameworkConfig.isEnableLogDecoration();
boolean enableTextLog = FWStaticStore.frameworkConfig.isEnableTextLog();
boolean enableHTMLLog = FWStaticStore.frameworkConfig.isEnableHTMLLog();
// Create loggerContext
OrganisedLog organisedLog = new OrganisedLog(logDirPath, logSubDir, enableLogDecoration, enableTextLog, enableHTMLLog, testSuiteList);
return organisedLog.getLoggerContext();
}
}
/**
* Runnable class which will be used in each thread created for test suite
*/
class SuiteTask implements Runnable {
TestContext context;
List testList;
List groupList;
CountDownLatch latch;
/**
* Constructor for Runnable
*
* @param context test context
* @param testList list of TestExecutable (provided by user)
*/
public SuiteTask(TestContext context, List testList, List groupList) {
this.context = context;
this.testList = testList;
this.groupList = groupList;
}
@Override
public void run() {
try {
// Create ArtosRunner per thread
ArtosRunner artos = new ArtosRunner(context);
artos.run(testList, groupList);
} catch (Exception e) {
e.printStackTrace();
context.getLogger().error(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy