/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.galaxytools; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.junit.Before; import org.junit.Test; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import fr.ens.biologie.genomique.eoulsan.EoulsanException; import fr.ens.biologie.genomique.eoulsan.EoulsanRuntimeDebug; import fr.ens.biologie.genomique.eoulsan.core.Parameter; import fr.ens.biologie.genomique.eoulsan.galaxytools.elements.ToolElement; import fr.ens.biologie.genomique.eoulsan.util.GuavaCompatibility; /** * The class define unit tests on GalaxyToolStep, it check if the command line * build from tool shed XML and parameters correspond to the expected syntax. It * use an extra file which set data useful to create command line and expected * value of this. Syntax example: _____________________________________________ * test_description=grep python script_________________________________________ * toolshedxml.path=grep.xml___________________________________________________ * param.key1=value1___________________________________________________________ * param.key3=value2___________________________________________________________ * output.key3=value3__________________________________________________________ * command.expected=python grep.py_____________________________________________ * @author Sandrine Perrin * @since 2.0 */ public class GalaxyToolInterpreterTest { public final static Splitter SPLITTER = Splitter.on("=").trimResults().omitEmptyStrings(); public final static Splitter SPLITTER_KEY = Splitter.on(".").trimResults().omitEmptyStrings(); public final static Splitter SPLITTER_SPACE = Splitter.on(" ").trimResults().omitEmptyStrings(); /** Key for value test description, it marks too the start on a new test. */ private static final String NEW_TEST_KEY = "test_description"; /** Directory path which contains all tool shed XML file. */ private static final String SRC_DIR = "/galaxytools"; private static final String SRC_TESTS_SETTING = SRC_DIR + "/testdatatoolshedgalaxy.txt"; @Before public void setUp() throws Exception { EoulsanRuntimeDebug.initDebugEoulsanRuntime(); } /** * Test tool interpreter, it read the extra file. To set a test, it give XML * name file, parameter value, command line expected. * @throws FileNotFoundException the XML file is not found * @throws Exception if an error occurs during setting or execution on a test */ @Test public void testToolInterpreter() throws FileNotFoundException, Exception { // Extract extra file, contains key=value final InputStream srcTestsSetting = this.getClass().getResourceAsStream(SRC_TESTS_SETTING); String line = ""; ToolTest tt = null; // Read file try (final BufferedReader br = new BufferedReader(new InputStreamReader(srcTestsSetting))) { while ((line = br.readLine()) != null) { final String lineTrimmed = line.trim(); // Skip empty line or comment if (lineTrimmed.isEmpty() || lineTrimmed.startsWith("#") || !lineTrimmed.contains("=")) { continue; } final int pos = lineTrimmed.indexOf("="); if (pos < 0) throw new Exception( "Invalid entry key=value in file with " + lineTrimmed); final String key = lineTrimmed.substring(0, pos); final String value = (pos < lineTrimmed.length() ? lineTrimmed.substring(pos + 1) : ""); // Check if it is a new test if (key.equals(NEW_TEST_KEY)) { if (tt != null) { // Execute current test tt.launchTest(); } // Init new test tt = new ToolTest(value); } else { // Update current test tt.addData(key, value); } } // Launch last test setting in file if (tt != null) { tt.launchTest(); } } catch (IOException e) { e.printStackTrace(); } } // // Internal Class // /** * The class define a test corresponding to a tool and parameters associated. * @author Sandrine Perrin * @since 2.0 */ final class ToolTest { /** Keys expected from description file */ private static final String TOOLSHEDXML_PATH_KEY = "toolshedxml"; private static final String EXECUTABLE_PATH_KEY = "executable"; private static final String COMMAND_KEY = "command"; private static final String INPUT_KEY = "input"; private static final String OUTPUT_KEY = "output"; private static final String PARAM_KEY = "param"; private static final String OTHER_KEY = "other"; private final String description; private String toolXMLPath; private String executableToolPath; private String command; private final Set<Parameter> setStepParameters = new HashSet<>(); private final Map<String, String> inputCommandVariables = new HashMap<>(); private final Map<String, String> outputCommandVariables = new HashMap<>(); private final Map<String, String> otherCommandVariables = new HashMap<>(); /** * Adds the data from extra file in test instance. * @param key the key * @param value the value * @throws EoulsanException it occurs if a data is invalid. */ public void addData(final String key, final String value) throws EoulsanException { final String keyPrefix = GuavaCompatibility.splitToList(SPLITTER_KEY, key).get(0); final String nameVariable = key.substring(key.indexOf('.') + 1); switch (keyPrefix) { case TOOLSHEDXML_PATH_KEY: // File save in test directory files this.toolXMLPath = SRC_DIR + "/" + value; break; case EXECUTABLE_PATH_KEY: this.executableToolPath = value; break; case COMMAND_KEY: this.command = value; break; case INPUT_KEY: this.inputCommandVariables.put(nameVariable, value); break; case OUTPUT_KEY: this.outputCommandVariables.put(nameVariable, value); break; case PARAM_KEY: addParam(nameVariable, value); break; case OTHER_KEY: this.otherCommandVariables.put(nameVariable, value); break; default: } } /** * Launch test. * @throws FileNotFoundException the XML file is not found * @throws EoulsanException if an error occurs during setting or execution * on a test */ public void launchTest() throws FileNotFoundException, EoulsanException { // Check if command executed is setting if (this.command == null || this.command.isEmpty()) { throw new EoulsanException( "UnitTest on GalaxyTool: missing command line expected, test can not be launch."); } // Init tool interpreter final InputStream is = this.getClass().getResourceAsStream(toolXMLPath); assertNotNull("Resource not found: " + toolXMLPath, is); final GalaxyToolInterpreter interpreter = new GalaxyToolInterpreter(is); // Configure interpreter with parameters setting in workflow Eoulsan file interpreter.configure(setStepParameters); // Compile variable from parameter workflow and Tool file compileParameters(interpreter); // Extract instance on toolData which contains all data useful from XML // file final ToolData toolData = interpreter.getToolData(); // Check input data names int inputCount = 0; for (Map.Entry<String, ToolElement> e : interpreter.getInputs() .entrySet()) { if (e.getValue().isFile()) { inputCount++; assertTrue(this.inputCommandVariables.containsKey(e.getKey()) || this.inputCommandVariables.containsKey( GalaxyToolInterpreter.removeNamespace(e.getKey()))); } } assertEquals(this.inputCommandVariables.size(), inputCount); // Check output data names int outputCount = 0; for (Map.Entry<String, ToolElement> e : interpreter.getOutputs() .entrySet()) { if (e.getValue().isFile()) { outputCount++; assertTrue(this.outputCommandVariables.containsKey(e.getKey()) || this.outputCommandVariables.containsKey( GalaxyToolInterpreter.removeNamespace(e.getKey()))); } } assertEquals(this.outputCommandVariables.size(), outputCount); final Map<String, String> variables = new HashMap<>(); variables.putAll(this.otherCommandVariables); variables.putAll(this.inputCommandVariables); variables.putAll(this.outputCommandVariables); // Init Tool python interpreter final CheetahInterpreter tpi = new CheetahInterpreter(toolData.getCommandScript(), variables); // Create command line and compare with command expected compareCommandLine(tpi.execute()); } /** * Compile parameters in interpreter instance, replace actions executed by * GalaxyTool from StepContext instance, which is extract from the workflow. * @param interpreter the interpreter instance. */ private void compileParameters(final GalaxyToolInterpreter interpreter) { // Replace actions realize in GalaxyToolInterpreter from StepContext // instance for (final ToolElement ptg : interpreter.getInputs().values()) { if (!ptg.isFile()) { // Update list variables needed to build command line if (!this.otherCommandVariables.containsKey(ptg.getName())) { this.otherCommandVariables.put(ptg.getName(), ptg.getValue()); } } } } /** * Compare command line, word by word from expected version to generate by * Python interpreter. * @param commandLine the command line to compare */ private void compareCommandLine(final String commandLine) { // Compare command final List<String> commandBuildByInterpreter = GuavaCompatibility.splitToList(SPLITTER_SPACE, commandLine); final List<String> commandExpected = GuavaCompatibility.splitToList(SPLITTER_SPACE, this.command); int length = commandExpected.size(); // Compare length assertTrue("Number words requiered is invalid, expected " + length + " obtains by PythonInterpreter " + commandBuildByInterpreter.size() + ": " + commandBuildByInterpreter, length == commandBuildByInterpreter.size()); // Compare word by word assertTrue( "Expected: " + commandExpected + " but got " + commandBuildByInterpreter, Objects.deepEquals(commandExpected, commandBuildByInterpreter)); } /** * Adds the parameter in test, it should prefixed with param in extra file, * corresponding to value extract from workflow XML file. * @param key the key * @param value the value */ private void addParam(final String key, final String value) { Preconditions.checkNotNull(key, "key for parameter"); this.setStepParameters.add(new Parameter(key, value)); } // // Constructor // /** * Constructor * @param description the description */ ToolTest(final String description) { this.description = description; } } }