/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.jmeter.gui.action; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.swing.JOptionPane; import org.apache.jmeter.JMeter; import org.apache.jmeter.engine.JMeterEngineException; import org.apache.jmeter.engine.StandardJMeterEngine; import org.apache.jmeter.engine.TreeCloner; import org.apache.jmeter.engine.TreeClonerNoTimer; import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.gui.action.validation.TreeClonerForValidation; import org.apache.jmeter.gui.tree.JMeterTreeListener; import org.apache.jmeter.gui.tree.JMeterTreeNode; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestPlan; import org.apache.jmeter.threads.AbstractThreadGroup; import org.apache.jmeter.timers.Timer; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.collections.HashTree; import org.apache.jorphan.collections.ListedHashTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Set of Actions to: * <ul> * <li>Start a Test Plan</li> * <li>Start a Test Plan without sleeping on the timers</li> * <li>Stop a Test Plan</li> * <li>Shutdown a Test plan</li> * <li>Run a set of Thread Groups</li> * <li>Run a set of Thread Groups without sleeping on the timers</li> * <li>Validate a set of Thread Groups with/without sleeping on the timers depending on jmeter properties</li> * </ul> */ public class Start extends AbstractAction { private static final Logger log = LoggerFactory.getLogger(Start.class); private static final Set<String> commands = new HashSet<>(); private static final String VALIDATION_CLONER_CLASS_PROPERTY_NAME = "testplan_validation.tree_cloner_class"; //$NON-NLS-1$ /** * Implementation of {@link TreeCloner} used to clone the tree before running validation */ private static final String CLONER_FOR_VALIDATION_CLASS_NAME = JMeterUtils.getPropDefault(VALIDATION_CLONER_CLASS_PROPERTY_NAME, //$NON-NLS-1$ "org.apache.jmeter.validation.ComponentTreeClonerForValidation"); static { commands.add(ActionNames.ACTION_START); commands.add(ActionNames.ACTION_START_NO_TIMERS); commands.add(ActionNames.ACTION_STOP); commands.add(ActionNames.ACTION_SHUTDOWN); commands.add(ActionNames.RUN_TG); commands.add(ActionNames.RUN_TG_NO_TIMERS); commands.add(ActionNames.VALIDATE_TG); } private StandardJMeterEngine engine; /** * Constructor for the Start object. */ public Start() { } /** * Gets the ActionNames attribute of the Start object. * * @return the ActionNames value */ @Override public Set<String> getActionNames() { return commands; } @Override public void doAction(ActionEvent e) { if (e.getActionCommand().equals(ActionNames.ACTION_START)) { popupShouldSave(e); startEngine(false); } else if (e.getActionCommand().equals(ActionNames.ACTION_START_NO_TIMERS)) { popupShouldSave(e); startEngine(true); } else if (e.getActionCommand().equals(ActionNames.ACTION_STOP)) { if (engine != null) { log.info("Stopping test"); GuiPackage.getInstance().getMainFrame().showStoppingMessage(""); engine.stopTest(); } } else if (e.getActionCommand().equals(ActionNames.ACTION_SHUTDOWN)) { if (engine != null) { log.info("Shutting test down"); GuiPackage.getInstance().getMainFrame().showStoppingMessage(""); engine.askThreadsToStop(); } } else if (e.getActionCommand().equals(ActionNames.RUN_TG) || e.getActionCommand().equals(ActionNames.RUN_TG_NO_TIMERS) || e.getActionCommand().equals(ActionNames.VALIDATE_TG)) { popupShouldSave(e); boolean noTimers = e.getActionCommand().equals(ActionNames.RUN_TG_NO_TIMERS); boolean isValidation = e.getActionCommand().equals(ActionNames.VALIDATE_TG); JMeterTreeListener treeListener = GuiPackage.getInstance().getTreeListener(); JMeterTreeNode[] nodes = treeListener.getSelectedNodes(); nodes = Copy.keepOnlyAncestors(nodes); AbstractThreadGroup[] tg = keepOnlyThreadGroups(nodes); if(nodes.length > 0) { startEngine(noTimers, isValidation, tg); } else { log.warn("No thread group selected the test will not be started"); } } } /** * filter the nodes to keep only the thread group * @param currentNodes jmeter tree nodes * @return the thread groups */ private AbstractThreadGroup[] keepOnlyThreadGroups(JMeterTreeNode[] currentNodes) { List<AbstractThreadGroup> nodes = new ArrayList<>(); for (JMeterTreeNode jMeterTreeNode : currentNodes) { if(jMeterTreeNode.getTestElement() instanceof AbstractThreadGroup) { nodes.add((AbstractThreadGroup) jMeterTreeNode.getTestElement()); } } return nodes.toArray(new AbstractThreadGroup[nodes.size()]); } /** * Start JMeter engine * @param ignoreTimer flag to ignore timers */ private void startEngine(boolean ignoreTimer) { startEngine(ignoreTimer, null); } /** * Start JMeter engine * @param ignoreTimer flag to ignore timers * @param threadGroupsToRun Array of AbstractThreadGroup to run */ private void startEngine(boolean ignoreTimer, AbstractThreadGroup[] threadGroupsToRun) { startEngine(ignoreTimer, false, threadGroupsToRun); } /** * Start JMeter engine * @param ignoreTimer flag to ignore timers * @param isValidationShot * @param threadGroupsToRun Array of AbstractThreadGroup to run */ private void startEngine(boolean ignoreTimer, boolean isValidationShot, AbstractThreadGroup[] threadGroupsToRun) { GuiPackage gui = GuiPackage.getInstance(); HashTree testTree = gui.getTreeModel().getTestPlan(); JMeter.convertSubTree(testTree); if(threadGroupsToRun != null && threadGroupsToRun.length>0) { removeThreadGroupsFromHashTree(testTree, threadGroupsToRun); } testTree.add(testTree.getArray()[0], gui.getMainFrame()); if (log.isDebugEnabled()) { log.debug("test plan before cloning is running version: {}", ((TestPlan) testTree.getArray()[0]).isRunningVersion()); } ListedHashTree clonedTree = null; if(isValidationShot) { TreeCloner cloner = createTreeClonerForValidation(); testTree.traverse(cloner); clonedTree = cloner.getClonedTree(); } else { TreeCloner cloner = cloneTree(testTree, ignoreTimer); clonedTree = cloner.getClonedTree(); } if ( popupCheckExistingFileListener(testTree) ) { engine = new StandardJMeterEngine(); engine.configure(clonedTree); try { engine.runTest(); } catch (JMeterEngineException e) { JOptionPane.showMessageDialog(gui.getMainFrame(), e.getMessage(), JMeterUtils.getResString("error_occurred"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$ } if (log.isDebugEnabled()) { log.debug("test plan after cloning and running test is running version: {}", ((TestPlan) testTree.getArray()[0]).isRunningVersion()); } } } /** * * @return {@link TreeCloner} */ private static TreeCloner createTreeClonerForValidation() { Class<?> clazz; try { clazz = Class.forName(CLONER_FOR_VALIDATION_CLASS_NAME, true, Thread.currentThread().getContextClassLoader()); return (TreeCloner) clazz.newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) { log.error("Error instanciating class:'{}' defined in property:'{}'", CLONER_FOR_VALIDATION_CLASS_NAME, VALIDATION_CLONER_CLASS_PROPERTY_NAME, ex); return new TreeClonerForValidation(); } } /** * Remove thread groups from testTree that are not in threadGroupsToKeep * @param testTree {@link HashTree} * @param threadGroupsToKeep Array of {@link AbstractThreadGroup} to keep */ private void removeThreadGroupsFromHashTree(HashTree testTree, AbstractThreadGroup[] threadGroupsToKeep) { LinkedList<Object> copyList = new LinkedList<>(testTree.list()); for (Object o : copyList) { TestElement item = (TestElement) o; if (o instanceof AbstractThreadGroup) { if (!isInThreadGroups(item, threadGroupsToKeep)) { // hack hack hack // due to the bug of equals / hashcode on AbstractTestElement // where 2 AbstractTestElement can be equals but have different hashcode try { item.setEnabled(false); testTree.remove(item); } finally { item.setEnabled(true); } } else { removeThreadGroupsFromHashTree(testTree.getTree(item), threadGroupsToKeep); } } else { removeThreadGroupsFromHashTree(testTree.getTree(item), threadGroupsToKeep); } } } /** * @param item {@link TestElement} * @param threadGroups Array of {@link AbstractThreadGroup} * @return true if item is in threadGroups array */ private boolean isInThreadGroups(TestElement item, AbstractThreadGroup[] threadGroups) { for (AbstractThreadGroup abstractThreadGroup : threadGroups) { if(item == abstractThreadGroup) { return true; } } return false; } /** * Create a Cloner that ignores {@link Timer} if removeTimers is true * @param testTree {@link HashTree} * @param removeTimers boolean remove timers * @return {@link TreeCloner} */ private TreeCloner cloneTree(HashTree testTree, boolean removeTimers) { TreeCloner cloner = null; if(removeTimers) { cloner = new TreeClonerNoTimer(false); } else { cloner = new TreeCloner(false); } testTree.traverse(cloner); return cloner; } }