Runners plugin dev guide
Introduction
Bali offers a mechanism to take yours runner with it's runnable items execution orchestration to web (and much more).
It is ProjectAdapter abstract Factory Pattern You need to implement along with some other interfaces defining result behaviour
and then use in projects.xml for your project load.

public interface ProjectAdapter {
public ProjectBuilder getProjectBuilder();
public BaseProject buildEmptyProject();
}

JUnit, TestNG, Java Main, Cucumber run adapters already elaborated. Look into /projects/Bali-RunAdapters/ to see their implementations in details.
RunnableItem interface
Implement RunnableItem interface

Add properties, which fully identify runnable entity from Your's runner point of view.
Set list of required test resources to be locked on resource pools by before runnable execution start.
Then, you can request theese instances using Bali Client API to use in test.

public interface RunnableItem {

/* class of the Runner to execute current item */
public Class < ? instanceof ProjectRunner > getProjectRunner();

/* list of required test resources to be locked on resource pools
by before runnable execution start */

public List < RequiredTestResource > getRequiredTestResources();

}
example for JUnit Runnable
It is enough to know JUnit test class to run test methods inside.

public class JUnitRunnableItem implements RunnableItem {

// JUnit class, containings tests
Class testClass;
ClassLoader classLoader;

private List < RequiredTestResource> requiredTestResources;

public Class < ? instanceof ProjectRunner > getProjectRunner(){
return JUnitRunner.class;
}

... getters/setters...

}
Rexample for java Main Runnable
It is enough to know class to be able to run static main(String[] arg) method inside.

public class MainRunnableItem implements RunnableItem {

// class, containing static main(String[] arg) method
Class testClass;
ClassLoader classLoader;

private List < RequiredTestResource > requiredTestResources;

public Class < ? instanceof ProjectRunner > getProjectRunner(){
return MainRunner.class;
}
... getters/setters...

}
example for Cucumber Runnable
It is enough to know feature file, scenario name and steps directory to be able to run cucumber test.

public class CucumberRunnableItem implements RunnableItem {

String scenarioName = "";
String featureFile = "";
String stepsDir = "";
ClassLoader classLoader;

private List < RequiredTestResource > requiredTestResources;

public Class < ? instanceof ProjectRunner > getProjectRunner(){
return CucumberRunner.class;
}
... getters/setters...

}
Project Runner
Project runner should implement RunnableItem execution kick-off. Usually its just wrapping original framework runner inside with
values taken from Runnable item bean.

public interface ProjectRunner {

public void run(RunnableItem runnableItem) throws Exception;

}
example for JUnitRunner
Implemented using JUnit's original runner. Class, containing JUnit test methods, is provided in JUnitRunnableItem.

public class JUnitProjectRunner implements ProjectRunner {

@Override
public void run(RunnableItem runnableItem) {
JUnitRunnableItem item = (JUnitRunnableItem) runnableItem;
Thread.currentThread().setContextClassLoader(item.getClassLoader());
TestResult testResult = new TestResult();
TestSuite suite = new TestSuite(item.getTestClass());
suite.run(testResult);
}

}
example for Java Main Runner
Implemented invoke of static method main(String[] arg) using java reflection API. Class is provided from MainRunnableItem bean.

public class MainProjectRunner implements ProjectRunner {

protected static final Logger log = LogManager.getLogger(MainProjectRunner.class);

@Override
public void run(RunnableItem runnableItem) throws Exception {
MainRunnableItem item = (MainRunnableItem) runnableItem;
Thread.currentThread().setContextClassLoader(item.getClassLoader());

Class< ? > c = item.getTestClass();
Class[] argTypes = new Class[] { String[].class };
Method main = c.getDeclaredMethod("main", argTypes);
String[] mainArgs = new String[]{};
log.info("invoking %s.main()%n"+ c.getName());
main.invoke(null, (Object)mainArgs);
}

}
example for Cucumber Runner
Implemented using Cucumber's original runner.

public class CucumberProjectRunner implements ProjectRunner {


@Override
public void run(RunnableItem runnableItem) throws Exception {
CucumberRunnableItem item = (CucumberRunnableItem) runnableItem;

String[] arg = new String[]{
"--format",
"org.kkonoplev.bali.runner.cucumber.CukeReporter",
"--name",
item.getScenarioName(),
"--glue",
item.getStepsDir(),
item.getFeatureFile()
};

String envThreadName = ModifyThreadName(Thread.currentThread().getName(),
TestExecContextThreadLocal.getTestExecContext().getSuiteExecContext().getSuiteMdl().getOptions().toLowerCase());
Thread.currentThread().setName(envThreadName);
Thread.currentThread().setContextClassLoader(item.getClassLoader());

byte exitstatus = run(arg, Thread.currentThread().getContextClassLoader());

}

public final static String envPrefix = "_env_";

private String ModifyThreadName(String name, String envName) {
String newName = name;
int indx = newName.indexOf(envPrefix);
if (indx != -1)
newName = newName.substring(0, indx)+envPrefix+envName;
else
newName = newName+envPrefix+envName;
return newName;
}

public static byte run(String[] argv, ClassLoader classLoader) throws IOException {

RuntimeOptions runtimeOptions = new RuntimeOptions(asList(argv));

ResourceLoader resourceLoader = new MultiLoader(classLoader);
ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
cucumber.runtime.Runtime runtime = new cucumber.runtime.Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
runtime.run();
return runtime.exitStatus();

}

private static ArrayList asList(String[] argv) {
ArrayList list = new ArrayList();

for (String s: argv)
list.add(s);

return list;
}
}
Project interface
Project interface along with BaseProject implementing its minimal structure is aimed to be primary bean.

Project bean contains runnable items tree (getRootFolderNode()) and resource pools after building.
Add properties, which fully identify project content and are enough for building runnable items tree and its execution.
Interface's setter/getter defined values. Theese are common for every project structure.
1) String name - uniq project name. Value is taken from projects.xml -> ... -> ...
2) String parent - parent project name. Value is taken from projects.xml -> ... -> ...
3) Date loadDate - time it was initialized, resource pools and tests tree structure created. Value is initialized by builder.
4) TreeNode rootFolderNode - link to tree's root element. Value is initialized in builder.
5) int testCount - count of scenarious in the tree (count of leafs). Value is initialized in builder.
6) ArrayList< Resource Pool > getResourcePools() - list of resource pool structures
public interface Project {

/*in Base Project returns TreeNode element */
public TreeNode getTreeNode(String path);

public String getName();
public void setName(String name);

public Date getLoadDate();
public void setLoadDate(Date loadDate);

public TreeNode getRootFolderNode();
public void setRootFolderNode(TreeNode rootFolderNode);

public int getTestcount();
public void setTestcount(int testcount);

public ArrayList getResourcePools();
public void setResourcePools(ArrayList resourceStorages);

public String getTestmatch();
public void setTestmatch(String testmatch);

}
TreeNode (tree's structure basis)
Tests tree structure you are observing in the front end is rendered from root TreeNode bean.
The structure is created in project builder.

public class TreeNode implements Serializable {

// text You see in the front end as node's main text
// usually it is name of folder or runnable item
protected String label;

// inner uniq name of the node
protected String name;

// color, applied to text
protected String color = "black";

// text is shown on mouse over popup
protected String title = "";
protected String path = "";

// text is shown on suite execution screen, repsenting runnable item
protected String lineViewLabel = "";

// runnable item instance referenced in current node
protected transient RunnableItem runnableItem;
protected List tags = new ArrayList();
protected ArrayList childs = new ArrayList();

public TreeNode(){ }
}
... setters/getters

}

example for JUnit, Java Main
First define Java oriented project. It contains classLoader, which loads our scenarious java code.

public class BaseJavaProject extends BaseProject {

protected URLClassLoader classLoader;
... getter/setter

}

Define JUnit project. We load java classes from bin folder and library jars from libfolder, let add theese properties.
Laters we'll define 'em and take from projects.xml file

public class JUnitProject extends BaseJavaProject {
private String binfolder;
private String libfolder;
private String prepath;

... getters/setters
}
example for Cucumber
Beside class loading from bin and lib folder, Cucumber runner requires steps dir.

public class CucumberProject extends BaseJavaProject {

private String binfolder;
private String libfolder;
private String prepath;
private String stepsDir;

... getters/setters
}
Project builder
Implement ProjectBuilder(builder pattern) interface.

It is responsible for
1) creating runnable items tree structure (TreeNode model), which You will observe and orchestrate from front end
2) initializing Resource Pools according to factory classes and load commands defined in xml config.

public interface ProjectBuilder {

public void init(BaseProject project) throws Exception;
public void setProjectService(ProjectService projectService);
public void updateInfo(String content);

}
example for JUnit, JavaMain
Runnable items tree is built from traversing of bin folder, containing test codes. Classes, ending with "test.class" are assumed to be JUnit's one.

public class JUnitProjectBuilder implements ProjectBuilder {

protected ProjectService projectSvc;
protected JUnitProject project;

public void init(BaseProject proj) throws Exception {

this.project = (JUnitProject) proj;
log.info("init project:"+project.getName());

// initialize classloader from bin and lib folder
initClassLoader();

// traverse bin directory and search for java classes ending with "test.class"
// a assumed to JUnit tests classes.
// Create tree node structure and set rootFolderNode(TreeNode rootNode) in project bean.
initRunnableTree();

// resource pools properties like initializing its factory classes names and list of commands to 'em are parsed and ready.
// Here we need to create instance of the factory in each pool, post commands to it getting TestExecResources list on out
// and collecting em in resource's pool list.
initResourcePools();
initResultsExporter(project.getPluggableProcessorStore(), project.getClassLoader());

project.setLoadDate(new Date());

}
....
@Override public void setProjectService(ProjectService projectService) { this.projectSvc = projectService; }
@Override public void updateInfo(String content) { }
example for Cucumber
Runnable items tree is built from traversing of scenarious folder (bin folder), containing gerklin language files.
Gerklin scenario is parsed and inner runnable scenarious are identified.

public class CucumberProjectBuilder implements ProjectBuilder {

private static final Logger log = Logger.getLogger(ProjectBuilder.class);
protected ProjectService projectSvc;
protected CucumberProject project;

public CucumberProjectBuilder(){ }

public CucumberProjectBuilder(ProjectService projectSvc_){ }

public void init(BaseProject project) throws Exception {

CucumberProject proj = (CucumberProject) project;
log.info("init project:"+proj.getName());

// init classloader ....
URLClassLoader classLoader = createClassLoader(proj.getBinfolder(), proj.getLibfolder(), parentLoader); proj.setClassLoader(classLoader); project = proj;

// parse and init tests tree structure
TreeNode rootNode = getFolderStructure(new File(proj.getBinfolder()), new ExtFileFilter(proj.getTestmatch()), proj.getBinfolder());

// get first tree node, having more than 1 child
TreeNode shortRootNode = getShortRootNode(rootNode);

// define pre-path to the element above
String prepath = getPrePath(shortRootNode, rootNode);

if (prepath.indexOf(".") > 0){

prepath = prepath.substring(prepath.indexOf(".")+1); }
proj.setPrepath(prepath);
updateLeafsPath(shortRootNode);
proj.setRootFolderNode(shortRootNode);

//init ResourceStorages
for (ResourcePool storage: proj.getResourceStorages()){
initResourceStorage(storage, classLoader);
}

addPredefinedTagsResourceStorage(proj);
initResultsExporter(proj.getPluggableProcessorStore(), classLoader);

// set load date proj.setLoadDate(new Date()); }
@Override public void setProjectService(ProjectService projectService) { this.projectSvc = projectService; }

@Override public void updateInfo(String content) { log.info("Update project "+project.getName()+" content ");
log.info(content); project.setLibfolder(content);
project.setLoadDate(new Date());
log.info("Libs path updated for project "+project.getName());
log.info("ready for rebuild");
}

}
Made on
Tilda