Author: pmouawad Date: Sat Apr 13 08:25:35 2019 New Revision: 1857453 URL: http://svn.apache.org/viewvc?rev=1857453&view=rev Log: Bug 63219 - New function "__StringToFile" to save a string into a file
Contributed by UbikLoadPack (https://ubikloadpack.com) Bugzilla Id: 63219 Added: jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java (with props) jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java (with props) Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties jmeter/trunk/xdocs/changes.xml jmeter/trunk/xdocs/usermanual/functions.xml Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties?rev=1857453&r1=1857452&r2=1857453&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties (original) +++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties Sat Apr 13 08:25:35 2019 @@ -1204,6 +1204,10 @@ string_from_file_encoding=File encoding string_from_file_file_name=Enter path (absolute or relative) to file string_from_file_seq_final=Final file sequence number (opt) string_from_file_seq_start=Start file sequence number (opt) +string_to_file_pathname=Path to file (absolute) +string_to_file_content=String to write +string_to_file_way_to_write=Append to file (true appends, false overwrites, default true) +string_to_file_encoding=Charset (defaults to UTF-8) summariser_title=Generate Summary Results summary_report=Summary Report switch_controller_label=Switch Value Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties?rev=1857453&r1=1857452&r2=1857453&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties (original) +++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties Sat Apr 13 08:25:35 2019 @@ -1193,6 +1193,10 @@ string_from_file_encoding=Encodage du fi string_from_file_file_name=Entrer le chemin (absolu ou relatif) du fichier string_from_file_seq_final=Nombre final de séquence de fichier string_from_file_seq_start=Démarer le nombre de séquence de fichier +string_to_file_pathname=Chemin du fichier (absolu) +string_to_file_content=Chaîne de caractère à écrire +string_to_file_way_to_write=Ajouter (true ajoute, false écrase, par défaut true) +string_to_file_encoding=Encodage (UTF-8 par défaut) summariser_title=Générer les resultats consolidés summary_report=Rapport consolidé switch_controller_label=Aller vers le numéro d'élément (ou nom) subordonné \: Added: jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java?rev=1857453&view=auto ============================================================================== --- jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java (added) +++ jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java Sat Apr 13 08:25:35 2019 @@ -0,0 +1,159 @@ +/* + * 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.functions; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.util.JMeterUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * FileToString Function to read a complete file into a String. + * + * Parameters: + * - file name + * - append (true/false) + * - file encoding (optional) + * + * Returns: true if ok , false if an error occured + * + * @since 5.2 + */ +public class StringToFile extends AbstractFunction { + private static final Logger log = LoggerFactory.getLogger(StringToFile.class); + private static final List<String> desc = new LinkedList<>(); + private static final String KEY = "__StringToFile";//$NON-NLS-1$ + private static final ConcurrentHashMap<String, Lock> lockMap = new ConcurrentHashMap<>(); + static { + desc.add(JMeterUtils.getResString("string_to_file_pathname")); + desc.add(JMeterUtils.getResString("string_to_file_content"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_to_file_way_to_write"));//$NON-NLS-1$ + desc.add(JMeterUtils.getResString("string_to_file_encoding"));//$NON-NLS-1$ + } + private Object[] values; + + public StringToFile() { + super(); + } + + /** + * Write to file + * @return boolean true if success , false otherwise + * @throws IOException + */ + private boolean writeToFile() throws IOException { + String fileName = ((CompoundVariable) values[0]).execute().trim(); + String content = ((CompoundVariable) values[1]).execute(); + boolean append = true; + if (values.length >= 3) { + append = Boolean.parseBoolean(((CompoundVariable) values[2]).execute().toLowerCase().trim()); + } + Charset charset = StandardCharsets.UTF_8; + if (values.length == 4) { + String charsetParamValue = ((CompoundVariable) values[3]).execute(); + if (StringUtils.isNotEmpty(charsetParamValue)) { + charset = Charset.forName(charsetParamValue); + } + } + + if (fileName.isEmpty()) { + log.error("File name '{}' is empty", fileName); + return false; + } + log.debug("Writing {} to file {} with charset {} and append {}", content, fileName, charset, append); + + Lock localLock = new ReentrantLock(); + Lock lock = lockMap.putIfAbsent(fileName, localLock); + try { + if (lock == null) { + localLock.lock(); + } else { + lock.lock(); + } + File file = new File(fileName); + File fileParent = file.getParentFile(); + if (fileParent == null || (fileParent.exists() && fileParent.isDirectory() && fileParent.canWrite())) { + FileUtils.writeStringToFile(file, content, charset, append); + } else { + log.error("The parent file of {} doesn't exist or is not writable", file); + return false; + } + } finally { + if (lock == null) { + localLock.unlock(); + } else { + lock.unlock(); + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public String execute(SampleResult previousResult, Sampler currentSampler) throws InvalidVariableException { + boolean executionResult; + try { + executionResult = this.writeToFile(); + } catch (UnsupportedCharsetException ue) { + executionResult = false; + log.error("The encoding of file is not supported"); + } catch (IllegalCharsetNameException ie) { + executionResult = false; + log.error("The encoding of file contains illegal characters"); + } catch (IOException e) { + executionResult = false; + } + return String.valueOf(executionResult); + } + + /** {@inheritDoc} */ + @Override + public void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException { + checkParameterCount(parameters, 2, 4); + values = parameters.toArray(); + } + + /** {@inheritDoc} */ + @Override + public String getReferenceKey() { + return KEY; + } + + /** {@inheritDoc} */ + @Override + public List<String> getArgumentDesc() { + return desc; + } +} Propchange: jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/src/functions/org/apache/jmeter/functions/StringToFile.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java?rev=1857453&view=auto ============================================================================== --- jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java (added) +++ jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java Sat Apr 13 08:25:35 2019 @@ -0,0 +1,306 @@ +/* + * 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.functions; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collection; +import java.util.LinkedList; + +import org.apache.commons.io.FileUtils; +import org.apache.jmeter.engine.util.CompoundVariable; +import org.apache.jmeter.junit.JMeterTestCase; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests for {@link StringToFile} + */ +public class TestStringtoFile extends JMeterTestCase { + protected AbstractFunction function; + private SampleResult result; + private Collection<CompoundVariable> params; + private static final Logger log = LoggerFactory.getLogger(TestSimpleFunctions.class); + private static final String DIR_NAME = "dirTest"; + private static final String FILENAME = "test.txt"; + private static final String STRING_TO_WRITE = "test"; + private static final String ENCODING = StandardCharsets.UTF_8.toString(); + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Before + public void setUp() { + function = new StringToFile(); + result = new SampleResult(); + JMeterContext jmctx = JMeterContextService.getContext(); + JMeterVariables vars = new JMeterVariables(); + jmctx.setVariables(vars); + jmctx.setPreviousResult(result); + params = new LinkedList<>(); + } + + @Before + @After + public void deleteFileBeforeAfterTest() { + File file = new File(FILENAME); + try { + Files.deleteIfExists(file.toPath()); + } catch (IOException e) { + Assert.fail("File " + FILENAME + "should not exist"); + } + } + + @Test + public void testParameterCount() throws Exception { + checkInvalidParameterCounts(function, 2, 4); + } + + @Test + public void testWriteToFile() throws Exception { + params.add(new CompoundVariable(FILENAME)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileWhenDirectoryDoesntExist() throws Exception { + String pathDirectory = File.separator + DIR_NAME; + File dir = new File(pathDirectory); + if (dir.exists()) { + deleteDir(dir); + } + String pathname = pathDirectory + File.separator + FILENAME; + params.add(new CompoundVariable(pathname)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertFalse("This method 'Stringtofile' should fail to run since directory does not exist", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileWhenDirectoryExist() throws InvalidVariableException { + File dir = null; + try { + dir = tempFolder.newFolder(DIR_NAME); + } catch (IOException e1) { + Assert.fail("can't create the directory"); + } + String pathname = dir.getAbsolutePath() + File.separator + FILENAME; + params.add(new CompoundVariable(pathname)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run if parent directory already exists", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileOptParamWayToWriteIsNull() throws Exception { + params.add(new CompoundVariable(FILENAME)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run with empty append", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileOptParamEncodingIsNull() throws Exception { + params.add(new CompoundVariable(FILENAME)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run with no charset", + Boolean.parseBoolean(returnValue)); + + } + + @Test + public void testWriteToFileEncodingNotSupported() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable("UTF-20")); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertFalse("This method 'Stringtofile' should have failed to run with wrong charset", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileEncodingNotLegal() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable("UTFéé")); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertFalse("This method 'Stringtofile' should have failed to run with illegal chars in charset", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileIOException() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + file.setWritable(false); + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable("UTF-8")); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertFalse("This method 'Stringtofile' should have failed to run with non writable folder", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileRequiredFilePathIsNull() throws Exception { + params.add(new CompoundVariable(null)); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertFalse("This method 'Stringtofile' should fail to run with null file", Boolean.parseBoolean(returnValue)); + } + + @Test + public void testWriteToFileRequiredStringIsNull() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable("")); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should succeed with empty String to write", + Boolean.parseBoolean(returnValue)); + } + + @Test + public void testOverwrite() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("false")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run", + Boolean.parseBoolean(returnValue)); + String res = FileUtils.readFileToString(file, ENCODING).trim(); + Assert.assertEquals("The string should be 'test'", "test", res); + } + + @Test + public void testAppend() throws Exception { + File file = null; + try { + file = tempFolder.newFile(FILENAME); + } catch (IOException e1) { + Assert.fail("Can't create the file successfully"); + } + params.add(new CompoundVariable(file.getAbsolutePath())); + params.add(new CompoundVariable(STRING_TO_WRITE)); + params.add(new CompoundVariable("true")); + params.add(new CompoundVariable(ENCODING)); + function.setParameters(params); + String returnValue = function.execute(result, null); + returnValue = function.execute(result, null); + Assert.assertTrue("This method 'Stringtofile' should have successfully run", + Boolean.parseBoolean(returnValue)); + String res = FileUtils.readFileToString(file, ENCODING).trim(); + Assert.assertEquals("The string should be 'testtest'", "testtest", res); + } + + @Test + public void testDescription() { + Assert.assertEquals("Function 'stringtofile' should have successfully reading the configuration file 'messages.properties'", + JMeterUtils.getResString("string_to_file_pathname"), + function.getArgumentDesc().get(0)); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + try { + Files.deleteIfExists(dir.toPath()); + return true; + } catch (IOException e) { + return false; + } + } +} Propchange: jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/test/src/org/apache/jmeter/functions/TestStringtoFile.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: jmeter/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1857453&r1=1857452&r2=1857453&view=diff ============================================================================== --- jmeter/trunk/xdocs/changes.xml [utf-8] (original) +++ jmeter/trunk/xdocs/changes.xml [utf-8] Sat Apr 13 08:25:35 2019 @@ -102,6 +102,7 @@ to view the last major behaviors with th <h3>Functions</h3> <ul> + <li><bug>63219</bug>New function <code>__StringToFile</code> to save a string into a file. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li> </ul> <h3>I18N</h3> @@ -173,6 +174,7 @@ to view the last major behaviors with th </p> <ul> <li>Clifford Harms (clifford.harms at gmail.com)</li> + <li><a href="https://ubikloadpack.com">Ubik Load Pack</a></li> </ul> <p>We also thank bug reporters who helped us improve JMeter.</p> <ul> Modified: jmeter/trunk/xdocs/usermanual/functions.xml URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/usermanual/functions.xml?rev=1857453&r1=1857452&r2=1857453&view=diff ============================================================================== --- jmeter/trunk/xdocs/usermanual/functions.xml (original) +++ jmeter/trunk/xdocs/usermanual/functions.xml Sat Apr 13 08:25:35 2019 @@ -120,6 +120,7 @@ Alternatively, just use <code>/</code> i <tr><td>Input</td><td> <a href="#__FileToString">FileToString</a></td><td>read an entire file</td><td>2.4</td></tr> <tr><td>Input</td><td> <a href="#__CSVRead">CSVRead</a></td><td>read from CSV delimited file</td><td>1.9</td></tr> <tr><td>Input</td><td> <a href="#__XPath">XPath</a></td><td>Use an XPath expression to read from a file</td><td>2.0.3</td></tr> + <tr><td>Input</td><td> <a href="#__StringToFile">StringToFile</a></td><td>write a string to a file</td><td>5.2</td></tr> <tr><td>Calculation</td><td> <a href="#__counter">counter</a></td><td>generate an incrementing number</td><td>1.X</td></tr> <tr><td>Formatting</td><td> <a href="#__dateTimeConvert">dateTimeConvert</a></td><td>Convert a date or time from source to target format</td><td>4.0</td></tr> <tr><td>Calculation</td><td> <a href="#__digest">digest</a></td><td>Generate a digest (SHA-1, SHA-256, MD5...)</td><td>4.0</td></tr> @@ -1483,7 +1484,6 @@ A reference name - <code>refName</code> </properties> <p>The file name, encoding and reference name parameters are resolved every time the function is executed.</p> </component> - <component index="§-num;.5.30" name="__samplerName"> <description> @@ -1712,6 +1712,30 @@ returns: </p> </component> +<component index="§-num;.5.39" name="__StringToFile"> +<description> + <p> + The <code>__StringToFile</code> function can be used to write a string to a file. + Each time it is called it writes a string to file appending or overwriting. + </p> + <p>The default return value from the function is the empty string</p> +</description> +<properties> + <property name="Path to file" required="Yes"> + Path to the file name.(The path is absolute) + </property> + <property name="String to write" required="Yes"> + The string to write to the file + </property> + <property name="File encoding if not UTF-8" required="No"> + The encoding to be used to write to the file. If not specified, the default encoding is <code>UTF-8</code>. + </property> + <property name="Append to file?" required="No"> + The way to write the string, <code>true</code> means append, <code>false</code> means overwrite. + </property> +</properties> +</component> + </subsection> <subsection name="§-num;.6 Pre-defined Variables" anchor="predefinedvars">
